changes from H.G. Muller; version 4.3.7
[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 < 2)  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         j = PieceToNumber(piece);\r
1728         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1729         if(j < 0) continue;               /* should not happen */\r
1730         piece = (ChessSquare) ( j + (int)lowestPiece );\r
1731         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1732         board[holdingsStartRow+j*direction][countsColumn]++;\r
1733     }\r
1734 \r
1735 }\r
1736 \r
1737 static int loggedOn = FALSE;\r
1738 \r
1739 /*-- Game start info cache: --*/\r
1740 int gs_gamenum;\r
1741 char gs_kind[MSG_SIZ];\r
1742 static char player1Name[128] = "";\r
1743 static char player2Name[128] = "";\r
1744 static int player1Rating = -1;\r
1745 static int player2Rating = -1;\r
1746 /*----------------------------*/\r
1747 \r
1748 ColorClass curColor = ColorNormal;\r
1749 \r
1750 void\r
1751 read_from_ics(isr, closure, data, count, error)\r
1752      InputSourceRef isr;\r
1753      VOIDSTAR closure;\r
1754      char *data;\r
1755      int count;\r
1756      int error;\r
1757 {\r
1758 #define BUF_SIZE 8192\r
1759 #define STARTED_NONE 0\r
1760 #define STARTED_MOVES 1\r
1761 #define STARTED_BOARD 2\r
1762 #define STARTED_OBSERVE 3\r
1763 #define STARTED_HOLDINGS 4\r
1764 #define STARTED_CHATTER 5\r
1765 #define STARTED_COMMENT 6\r
1766 #define STARTED_MOVES_NOHIDE 7\r
1767     \r
1768     static int started = STARTED_NONE;\r
1769     static char parse[20000];\r
1770     static int parse_pos = 0;\r
1771     static char buf[BUF_SIZE + 1];\r
1772     static int firstTime = TRUE, intfSet = FALSE;\r
1773     static ColorClass prevColor = ColorNormal;\r
1774     static int savingComment = FALSE;\r
1775     char str[500];\r
1776     int i, oldi;\r
1777     int buf_len;\r
1778     int next_out;\r
1779     int tkind;\r
1780     char *p;\r
1781 \r
1782 #ifdef WIN32\r
1783     if (appData.debugMode) {\r
1784       if (!error) {\r
1785         fprintf(debugFP, "<ICS: ");\r
1786         show_bytes(debugFP, data, count);\r
1787         fprintf(debugFP, "\n");\r
1788       }\r
1789     }\r
1790 #endif\r
1791 \r
1792     if (count > 0) {\r
1793         /* If last read ended with a partial line that we couldn't parse,\r
1794            prepend it to the new read and try again. */\r
1795         if (leftover_len > 0) {\r
1796             for (i=0; i<leftover_len; i++)\r
1797               buf[i] = buf[leftover_start + i];\r
1798         }\r
1799 \r
1800         /* Copy in new characters, removing nulls and \r's */\r
1801         buf_len = leftover_len;\r
1802         for (i = 0; i < count; i++) {\r
1803             if (data[i] != NULLCHAR && data[i] != '\r')\r
1804               buf[buf_len++] = data[i];\r
1805         }\r
1806 \r
1807         buf[buf_len] = NULLCHAR;\r
1808         next_out = leftover_len;\r
1809         leftover_start = 0;\r
1810         \r
1811         i = 0;\r
1812         while (i < buf_len) {\r
1813             /* Deal with part of the TELNET option negotiation\r
1814                protocol.  We refuse to do anything beyond the\r
1815                defaults, except that we allow the WILL ECHO option,\r
1816                which ICS uses to turn off password echoing when we are\r
1817                directly connected to it.  We reject this option\r
1818                if localLineEditing mode is on (always on in xboard)\r
1819                and we are talking to port 23, which might be a real\r
1820                telnet server that will try to keep WILL ECHO on permanently.\r
1821              */\r
1822             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
1823                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
1824                 unsigned char option;\r
1825                 oldi = i;\r
1826                 switch ((unsigned char) buf[++i]) {\r
1827                   case TN_WILL:\r
1828                     if (appData.debugMode)\r
1829                       fprintf(debugFP, "\n<WILL ");\r
1830                     switch (option = (unsigned char) buf[++i]) {\r
1831                       case TN_ECHO:\r
1832                         if (appData.debugMode)\r
1833                           fprintf(debugFP, "ECHO ");\r
1834                         /* Reply only if this is a change, according\r
1835                            to the protocol rules. */\r
1836                         if (remoteEchoOption) break;\r
1837                         if (appData.localLineEditing &&\r
1838                             atoi(appData.icsPort) == TN_PORT) {\r
1839                             TelnetRequest(TN_DONT, TN_ECHO);\r
1840                         } else {\r
1841                             EchoOff();\r
1842                             TelnetRequest(TN_DO, TN_ECHO);\r
1843                             remoteEchoOption = TRUE;\r
1844                         }\r
1845                         break;\r
1846                       default:\r
1847                         if (appData.debugMode)\r
1848                           fprintf(debugFP, "%d ", option);\r
1849                         /* Whatever this is, we don't want it. */\r
1850                         TelnetRequest(TN_DONT, option);\r
1851                         break;\r
1852                     }\r
1853                     break;\r
1854                   case TN_WONT:\r
1855                     if (appData.debugMode)\r
1856                       fprintf(debugFP, "\n<WONT ");\r
1857                     switch (option = (unsigned char) buf[++i]) {\r
1858                       case TN_ECHO:\r
1859                         if (appData.debugMode)\r
1860                           fprintf(debugFP, "ECHO ");\r
1861                         /* Reply only if this is a change, according\r
1862                            to the protocol rules. */\r
1863                         if (!remoteEchoOption) break;\r
1864                         EchoOn();\r
1865                         TelnetRequest(TN_DONT, TN_ECHO);\r
1866                         remoteEchoOption = FALSE;\r
1867                         break;\r
1868                       default:\r
1869                         if (appData.debugMode)\r
1870                           fprintf(debugFP, "%d ", (unsigned char) option);\r
1871                         /* Whatever this is, it must already be turned\r
1872                            off, because we never agree to turn on\r
1873                            anything non-default, so according to the\r
1874                            protocol rules, we don't reply. */\r
1875                         break;\r
1876                     }\r
1877                     break;\r
1878                   case TN_DO:\r
1879                     if (appData.debugMode)\r
1880                       fprintf(debugFP, "\n<DO ");\r
1881                     switch (option = (unsigned char) buf[++i]) {\r
1882                       default:\r
1883                         /* Whatever this is, we refuse to do it. */\r
1884                         if (appData.debugMode)\r
1885                           fprintf(debugFP, "%d ", option);\r
1886                         TelnetRequest(TN_WONT, option);\r
1887                         break;\r
1888                     }\r
1889                     break;\r
1890                   case TN_DONT:\r
1891                     if (appData.debugMode)\r
1892                       fprintf(debugFP, "\n<DONT ");\r
1893                     switch (option = (unsigned char) buf[++i]) {\r
1894                       default:\r
1895                         if (appData.debugMode)\r
1896                           fprintf(debugFP, "%d ", option);\r
1897                         /* Whatever this is, we are already not doing\r
1898                            it, because we never agree to do anything\r
1899                            non-default, so according to the protocol\r
1900                            rules, we don't reply. */\r
1901                         break;\r
1902                     }\r
1903                     break;\r
1904                   case TN_IAC:\r
1905                     if (appData.debugMode)\r
1906                       fprintf(debugFP, "\n<IAC ");\r
1907                     /* Doubled IAC; pass it through */\r
1908                     i--;\r
1909                     break;\r
1910                   default:\r
1911                     if (appData.debugMode)\r
1912                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
1913                     /* Drop all other telnet commands on the floor */\r
1914                     break;\r
1915                 }\r
1916                 if (oldi > next_out)\r
1917                   SendToPlayer(&buf[next_out], oldi - next_out);\r
1918                 if (++i > next_out)\r
1919                   next_out = i;\r
1920                 continue;\r
1921             }\r
1922                 \r
1923             /* OK, this at least will *usually* work */\r
1924             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
1925                 loggedOn = TRUE;\r
1926             }\r
1927             \r
1928             if (loggedOn && !intfSet) {\r
1929                 if (ics_type == ICS_ICC) {\r
1930                   sprintf(str,\r
1931                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
1932                           programVersion);\r
1933 \r
1934                 } else if (ics_type == ICS_CHESSNET) {\r
1935                   sprintf(str, "/style 12\n");\r
1936                 } else {\r
1937                   strcpy(str, "alias $ @\n$set interface ");\r
1938                   strcat(str, programVersion);\r
1939                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
1940 #ifdef WIN32\r
1941                   strcat(str, "$iset nohighlight 1\n");\r
1942 #endif\r
1943                   strcat(str, "$iset lock 1\n$style 12\n");\r
1944                 }\r
1945                 SendToICS(str);\r
1946                 intfSet = TRUE;\r
1947             }\r
1948 \r
1949             if (started == STARTED_COMMENT) {\r
1950                 /* Accumulate characters in comment */\r
1951                 parse[parse_pos++] = buf[i];\r
1952                 if (buf[i] == '\n') {\r
1953                     parse[parse_pos] = NULLCHAR;\r
1954                     AppendComment(forwardMostMove, StripHighlight(parse));\r
1955                     started = STARTED_NONE;\r
1956                 } else {\r
1957                     /* Don't match patterns against characters in chatter */\r
1958                     i++;\r
1959                     continue;\r
1960                 }\r
1961             }\r
1962             if (started == STARTED_CHATTER) {\r
1963                 if (buf[i] != '\n') {\r
1964                     /* Don't match patterns against characters in chatter */\r
1965                     i++;\r
1966                     continue;\r
1967                 }\r
1968                 started = STARTED_NONE;\r
1969             }\r
1970 \r
1971             /* Kludge to deal with rcmd protocol */\r
1972             if (firstTime && looking_at(buf, &i, "\001*")) {\r
1973                 DisplayFatalError(&buf[1], 0, 1);\r
1974                 continue;\r
1975             } else {\r
1976                 firstTime = FALSE;\r
1977             }\r
1978 \r
1979             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
1980                 ics_type = ICS_ICC;\r
1981                 ics_prefix = "/";\r
1982                 if (appData.debugMode)\r
1983                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
1984                 continue;\r
1985             }\r
1986             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
1987                 ics_type = ICS_FICS;\r
1988                 ics_prefix = "$";\r
1989                 if (appData.debugMode)\r
1990                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
1991                 continue;\r
1992             }\r
1993             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
1994                 ics_type = ICS_CHESSNET;\r
1995                 ics_prefix = "/";\r
1996                 if (appData.debugMode)\r
1997                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
1998                 continue;\r
1999             }\r
2000 \r
2001             if (!loggedOn &&\r
2002                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2003                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2004                  looking_at(buf, &i, "will be \"*\""))) {\r
2005               strcpy(ics_handle, star_match[0]);\r
2006               continue;\r
2007             }\r
2008 \r
2009             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2010               char buf[MSG_SIZ];\r
2011               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2012               DisplayIcsInteractionTitle(buf);\r
2013               have_set_title = TRUE;\r
2014             }\r
2015 \r
2016             /* skip finger notes */\r
2017             if (started == STARTED_NONE &&\r
2018                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2019                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2020                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2021               started = STARTED_CHATTER;\r
2022               i += 3;\r
2023               continue;\r
2024             }\r
2025 \r
2026             /* skip formula vars */\r
2027             if (started == STARTED_NONE &&\r
2028                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2029               started = STARTED_CHATTER;\r
2030               i += 3;\r
2031               continue;\r
2032             }\r
2033 \r
2034             oldi = i;\r
2035             if (appData.zippyTalk || appData.zippyPlay) {\r
2036 #if ZIPPY\r
2037                 if (ZippyControl(buf, &i) ||\r
2038                     ZippyConverse(buf, &i) ||\r
2039                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2040                     loggedOn = TRUE;\r
2041                     continue;\r
2042                 }\r
2043 #endif\r
2044             } else {\r
2045                 if (/* Don't color "message" or "messages" output */\r
2046                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2047                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2048                     looking_at(buf, &i, "--* (*:*): ") ||\r
2049                     /* Regular tells and says */\r
2050                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2051                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2052                     looking_at(buf, &i, "* says: ") ||\r
2053                     /* Message notifications (same color as tells) */\r
2054                     looking_at(buf, &i, "* has left a message ") ||\r
2055                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2056                     /* Whispers and kibitzes */\r
2057                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2058                     looking_at(buf, &i, "* kibitzes: ") ||\r
2059                     /* Channel tells */\r
2060                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2061 \r
2062                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2063                       /* Avoid "tells you:" spoofs in channels */\r
2064                      tkind = 3;\r
2065                   }\r
2066                   if (star_match[0][0] == NULLCHAR ||\r
2067                       strchr(star_match[0], ' ') ||\r
2068                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2069                     /* Reject bogus matches */\r
2070                     i = oldi;\r
2071                   } else {\r
2072                     if (appData.colorize) {\r
2073                       if (oldi > next_out) {\r
2074                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2075                         next_out = oldi;\r
2076                       }\r
2077                       switch (tkind) {\r
2078                       case 1:\r
2079                         Colorize(ColorTell, FALSE);\r
2080                         curColor = ColorTell;\r
2081                         break;\r
2082                       case 2:\r
2083                         Colorize(ColorKibitz, FALSE);\r
2084                         curColor = ColorKibitz;\r
2085                         break;\r
2086                       case 3:\r
2087                         p = strrchr(star_match[1], '(');\r
2088                         if (p == NULL) {\r
2089                           p = star_match[1];\r
2090                         } else {\r
2091                           p++;\r
2092                         }\r
2093                         if (atoi(p) == 1) {\r
2094                           Colorize(ColorChannel1, FALSE);\r
2095                           curColor = ColorChannel1;\r
2096                         } else {\r
2097                           Colorize(ColorChannel, FALSE);\r
2098                           curColor = ColorChannel;\r
2099                         }\r
2100                         break;\r
2101                       case 5:\r
2102                         curColor = ColorNormal;\r
2103                         break;\r
2104                       }\r
2105                     }\r
2106                     if (started == STARTED_NONE && appData.autoComment &&\r
2107                         (gameMode == IcsObserving ||\r
2108                          gameMode == IcsPlayingWhite ||\r
2109                          gameMode == IcsPlayingBlack)) {\r
2110                       parse_pos = i - oldi;\r
2111                       memcpy(parse, &buf[oldi], parse_pos);\r
2112                       parse[parse_pos] = NULLCHAR;\r
2113                       started = STARTED_COMMENT;\r
2114                       savingComment = TRUE;\r
2115                     } else {\r
2116                       started = STARTED_CHATTER;\r
2117                       savingComment = FALSE;\r
2118                     }\r
2119                     loggedOn = TRUE;\r
2120                     continue;\r
2121                   }\r
2122                 }\r
2123 \r
2124                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2125                     looking_at(buf, &i, "* c-shouts: ")) {\r
2126                     if (appData.colorize) {\r
2127                         if (oldi > next_out) {\r
2128                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2129                             next_out = oldi;\r
2130                         }\r
2131                         Colorize(ColorSShout, FALSE);\r
2132                         curColor = ColorSShout;\r
2133                     }\r
2134                     loggedOn = TRUE;\r
2135                     started = STARTED_CHATTER;\r
2136                     continue;\r
2137                 }\r
2138 \r
2139                 if (looking_at(buf, &i, "--->")) {\r
2140                     loggedOn = TRUE;\r
2141                     continue;\r
2142                 }\r
2143 \r
2144                 if (looking_at(buf, &i, "* shouts: ") ||\r
2145                     looking_at(buf, &i, "--> ")) {\r
2146                     if (appData.colorize) {\r
2147                         if (oldi > next_out) {\r
2148                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2149                             next_out = oldi;\r
2150                         }\r
2151                         Colorize(ColorShout, FALSE);\r
2152                         curColor = ColorShout;\r
2153                     }\r
2154                     loggedOn = TRUE;\r
2155                     started = STARTED_CHATTER;\r
2156                     continue;\r
2157                 }\r
2158 \r
2159                 if (looking_at( buf, &i, "Challenge:")) {\r
2160                     if (appData.colorize) {\r
2161                         if (oldi > next_out) {\r
2162                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2163                             next_out = oldi;\r
2164                         }\r
2165                         Colorize(ColorChallenge, FALSE);\r
2166                         curColor = ColorChallenge;\r
2167                     }\r
2168                     loggedOn = TRUE;\r
2169                     continue;\r
2170                 }\r
2171 \r
2172                 if (looking_at(buf, &i, "* offers you") ||\r
2173                     looking_at(buf, &i, "* offers to be") ||\r
2174                     looking_at(buf, &i, "* would like to") ||\r
2175                     looking_at(buf, &i, "* requests to") ||\r
2176                     looking_at(buf, &i, "Your opponent offers") ||\r
2177                     looking_at(buf, &i, "Your opponent requests")) {\r
2178 \r
2179                     if (appData.colorize) {\r
2180                         if (oldi > next_out) {\r
2181                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2182                             next_out = oldi;\r
2183                         }\r
2184                         Colorize(ColorRequest, FALSE);\r
2185                         curColor = ColorRequest;\r
2186                     }\r
2187                     continue;\r
2188                 }\r
2189 \r
2190                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2191                     if (appData.colorize) {\r
2192                         if (oldi > next_out) {\r
2193                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2194                             next_out = oldi;\r
2195                         }\r
2196                         Colorize(ColorSeek, FALSE);\r
2197                         curColor = ColorSeek;\r
2198                     }\r
2199                     continue;\r
2200                 }\r
2201             }\r
2202 \r
2203             if (looking_at(buf, &i, "\\   ")) {\r
2204                 if (prevColor != ColorNormal) {\r
2205                     if (oldi > next_out) {\r
2206                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2207                         next_out = oldi;\r
2208                     }\r
2209                     Colorize(prevColor, TRUE);\r
2210                     curColor = prevColor;\r
2211                 }\r
2212                 if (savingComment) {\r
2213                     parse_pos = i - oldi;\r
2214                     memcpy(parse, &buf[oldi], parse_pos);\r
2215                     parse[parse_pos] = NULLCHAR;\r
2216                     started = STARTED_COMMENT;\r
2217                 } else {\r
2218                     started = STARTED_CHATTER;\r
2219                 }\r
2220                 continue;\r
2221             }\r
2222 \r
2223             if (looking_at(buf, &i, "Black Strength :") ||\r
2224                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2225                 looking_at(buf, &i, "<10>") ||\r
2226                 looking_at(buf, &i, "#@#")) {\r
2227                 /* Wrong board style */\r
2228                 loggedOn = TRUE;\r
2229                 SendToICS(ics_prefix);\r
2230                 SendToICS("set style 12\n");\r
2231                 SendToICS(ics_prefix);\r
2232                 SendToICS("refresh\n");\r
2233                 continue;\r
2234             }\r
2235             \r
2236             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2237                 ICSInitScript();\r
2238                 have_sent_ICS_logon = 1;\r
2239                 continue;\r
2240             }\r
2241               \r
2242             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2243                 (looking_at(buf, &i, "\n<12> ") ||\r
2244                  looking_at(buf, &i, "<12> "))) {\r
2245                 loggedOn = TRUE;\r
2246                 if (oldi > next_out) {\r
2247                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2248                 }\r
2249                 next_out = i;\r
2250                 started = STARTED_BOARD;\r
2251                 parse_pos = 0;\r
2252                 continue;\r
2253             }\r
2254 \r
2255             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2256                 looking_at(buf, &i, "<b1> ")) {\r
2257                 if (oldi > next_out) {\r
2258                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2259                 }\r
2260                 next_out = i;\r
2261                 started = STARTED_HOLDINGS;\r
2262                 parse_pos = 0;\r
2263                 continue;\r
2264             }\r
2265 \r
2266             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2267                 loggedOn = TRUE;\r
2268                 /* Header for a move list -- first line */\r
2269 \r
2270                 switch (ics_getting_history) {\r
2271                   case H_FALSE:\r
2272                     switch (gameMode) {\r
2273                       case IcsIdle:\r
2274                       case BeginningOfGame:\r
2275                         /* User typed "moves" or "oldmoves" while we\r
2276                            were idle.  Pretend we asked for these\r
2277                            moves and soak them up so user can step\r
2278                            through them and/or save them.\r
2279                            */\r
2280                         Reset(FALSE, TRUE);\r
2281                         gameMode = IcsObserving;\r
2282                         ModeHighlight();\r
2283                         ics_gamenum = -1;\r
2284                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2285                         break;\r
2286                       case EditGame: /*?*/\r
2287                       case EditPosition: /*?*/\r
2288                         /* Should above feature work in these modes too? */\r
2289                         /* For now it doesn't */\r
2290                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2291                         break;\r
2292                       default:\r
2293                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2294                         break;\r
2295                     }\r
2296                     break;\r
2297                   case H_REQUESTED:\r
2298                     /* Is this the right one? */\r
2299                     if (gameInfo.white && gameInfo.black &&\r
2300                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2301                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2302                         /* All is well */\r
2303                         ics_getting_history = H_GOT_REQ_HEADER;\r
2304                     }\r
2305                     break;\r
2306                   case H_GOT_REQ_HEADER:\r
2307                   case H_GOT_UNREQ_HEADER:\r
2308                   case H_GOT_UNWANTED_HEADER:\r
2309                   case H_GETTING_MOVES:\r
2310                     /* Should not happen */\r
2311                     DisplayError("Error gathering move list: two headers", 0);\r
2312                     ics_getting_history = H_FALSE;\r
2313                     break;\r
2314                 }\r
2315 \r
2316                 /* Save player ratings into gameInfo if needed */\r
2317                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2318                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2319                     (gameInfo.whiteRating == -1 ||\r
2320                      gameInfo.blackRating == -1)) {\r
2321 \r
2322                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2323                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2324                     if (appData.debugMode)\r
2325                       fprintf(debugFP, "Ratings from header: W %d, B %d\n", \r
2326                               gameInfo.whiteRating, gameInfo.blackRating);\r
2327                 }\r
2328                 continue;\r
2329             }\r
2330 \r
2331             if (looking_at(buf, &i,\r
2332               "* * match, initial time: * minute*, increment: * second")) {\r
2333                 /* Header for a move list -- second line */\r
2334                 /* Initial board will follow if this is a wild game */\r
2335 \r
2336                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2337                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2338                 gameInfo.event = StrSave(str);\r
2339                 gameInfo.variant = StringToVariant(gameInfo.event);\r
2340                 Reset(TRUE,TRUE); /* [HGM] possibly change board or holdings size */\r
2341                 continue;\r
2342             }\r
2343 \r
2344             if (looking_at(buf, &i, "Move  ")) {\r
2345                 /* Beginning of a move list */\r
2346                 switch (ics_getting_history) {\r
2347                   case H_FALSE:\r
2348                     /* Normally should not happen */\r
2349                     /* Maybe user hit reset while we were parsing */\r
2350                     break;\r
2351                   case H_REQUESTED:\r
2352                     /* Happens if we are ignoring a move list that is not\r
2353                      * the one we just requested.  Common if the user\r
2354                      * tries to observe two games without turning off\r
2355                      * getMoveList */\r
2356                     break;\r
2357                   case H_GETTING_MOVES:\r
2358                     /* Should not happen */\r
2359                     DisplayError("Error gathering move list: nested", 0);\r
2360                     ics_getting_history = H_FALSE;\r
2361                     break;\r
2362                   case H_GOT_REQ_HEADER:\r
2363                     ics_getting_history = H_GETTING_MOVES;\r
2364                     started = STARTED_MOVES;\r
2365                     parse_pos = 0;\r
2366                     if (oldi > next_out) {\r
2367                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2368                     }\r
2369                     break;\r
2370                   case H_GOT_UNREQ_HEADER:\r
2371                     ics_getting_history = H_GETTING_MOVES;\r
2372                     started = STARTED_MOVES_NOHIDE;\r
2373                     parse_pos = 0;\r
2374                     break;\r
2375                   case H_GOT_UNWANTED_HEADER:\r
2376                     ics_getting_history = H_FALSE;\r
2377                     break;\r
2378                 }\r
2379                 continue;\r
2380             }                           \r
2381             \r
2382             if (looking_at(buf, &i, "% ") ||\r
2383                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2384                  && looking_at(buf, &i, "}*"))) {\r
2385                 savingComment = FALSE;\r
2386                 switch (started) {\r
2387                   case STARTED_MOVES:\r
2388                   case STARTED_MOVES_NOHIDE:\r
2389                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2390                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2391                     ParseGameHistory(parse);\r
2392 #if ZIPPY\r
2393                     if (appData.zippyPlay && first.initDone) {\r
2394                         FeedMovesToProgram(&first, forwardMostMove);\r
2395                         if (gameMode == IcsPlayingWhite) {\r
2396                             if (WhiteOnMove(forwardMostMove)) {\r
2397                                 if (first.sendTime) {\r
2398                                   if (first.useColors) {\r
2399                                     SendToProgram("black\n", &first); \r
2400                                   }\r
2401                                   SendTimeRemaining(&first, TRUE);\r
2402                                 }\r
2403                                 if (first.useColors) {\r
2404                                   SendToProgram("white\ngo\n", &first);\r
2405                                 } else {\r
2406                                   SendToProgram("go\n", &first);\r
2407                                 }\r
2408                                 first.maybeThinking = TRUE;\r
2409                             } else {\r
2410                                 if (first.usePlayother) {\r
2411                                   if (first.sendTime) {\r
2412                                     SendTimeRemaining(&first, TRUE);\r
2413                                   }\r
2414                                   SendToProgram("playother\n", &first);\r
2415                                   firstMove = FALSE;\r
2416                                 } else {\r
2417                                   firstMove = TRUE;\r
2418                                 }\r
2419                             }\r
2420                         } else if (gameMode == IcsPlayingBlack) {\r
2421                             if (!WhiteOnMove(forwardMostMove)) {\r
2422                                 if (first.sendTime) {\r
2423                                   if (first.useColors) {\r
2424                                     SendToProgram("white\n", &first);\r
2425                                   }\r
2426                                   SendTimeRemaining(&first, FALSE);\r
2427                                 }\r
2428                                 if (first.useColors) {\r
2429                                   SendToProgram("black\ngo\n", &first);\r
2430                                 } else {\r
2431                                   SendToProgram("go\n", &first);\r
2432                                 }\r
2433                                 first.maybeThinking = TRUE;\r
2434                             } else {\r
2435                                 if (first.usePlayother) {\r
2436                                   if (first.sendTime) {\r
2437                                     SendTimeRemaining(&first, FALSE);\r
2438                                   }\r
2439                                   SendToProgram("playother\n", &first);\r
2440                                   firstMove = FALSE;\r
2441                                 } else {\r
2442                                   firstMove = TRUE;\r
2443                                 }\r
2444                             }\r
2445                         }                       \r
2446                     }\r
2447 #endif\r
2448                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2449                         /* Moves came from oldmoves or moves command\r
2450                            while we weren't doing anything else.\r
2451                            */\r
2452                         currentMove = forwardMostMove;\r
2453                         ClearHighlights();/*!!could figure this out*/\r
2454                         flipView = appData.flipView;\r
2455                         DrawPosition(FALSE, boards[currentMove]);\r
2456                         DisplayBothClocks();\r
2457                         sprintf(str, "%s vs. %s",\r
2458                                 gameInfo.white, gameInfo.black);\r
2459                         DisplayTitle(str);\r
2460                         gameMode = IcsIdle;\r
2461                     } else {\r
2462                         /* Moves were history of an active game */\r
2463                         if (gameInfo.resultDetails != NULL) {\r
2464                             free(gameInfo.resultDetails);\r
2465                             gameInfo.resultDetails = NULL;\r
2466                         }\r
2467                     }\r
2468                     HistorySet(parseList, backwardMostMove,\r
2469                                forwardMostMove, currentMove-1);\r
2470                     DisplayMove(currentMove - 1);\r
2471                     if (started == STARTED_MOVES) next_out = i;\r
2472                     started = STARTED_NONE;\r
2473                     ics_getting_history = H_FALSE;\r
2474                     break;\r
2475 \r
2476                   case STARTED_OBSERVE:\r
2477                     started = STARTED_NONE;\r
2478                     SendToICS(ics_prefix);\r
2479                     SendToICS("refresh\n");\r
2480                     break;\r
2481 \r
2482                   default:\r
2483                     break;\r
2484                 }\r
2485                 continue;\r
2486             }\r
2487             \r
2488             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2489                  started == STARTED_HOLDINGS ||\r
2490                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2491                 /* Accumulate characters in move list or board */\r
2492                 parse[parse_pos++] = buf[i];\r
2493             }\r
2494             \r
2495             /* Start of game messages.  Mostly we detect start of game\r
2496                when the first board image arrives.  On some versions\r
2497                of the ICS, though, we need to do a "refresh" after starting\r
2498                to observe in order to get the current board right away. */\r
2499             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2500                 started = STARTED_OBSERVE;\r
2501                 continue;\r
2502             }\r
2503 \r
2504             /* Handle auto-observe */\r
2505             if (appData.autoObserve &&\r
2506                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2507                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2508                 char *player;\r
2509                 /* Choose the player that was highlighted, if any. */\r
2510                 if (star_match[0][0] == '\033' ||\r
2511                     star_match[1][0] != '\033') {\r
2512                     player = star_match[0];\r
2513                 } else {\r
2514                     player = star_match[2];\r
2515                 }\r
2516                 sprintf(str, "%sobserve %s\n",\r
2517                         ics_prefix, StripHighlightAndTitle(player));\r
2518                 SendToICS(str);\r
2519 \r
2520                 /* Save ratings from notify string */\r
2521                 strcpy(player1Name, star_match[0]);\r
2522                 player1Rating = string_to_rating(star_match[1]);\r
2523                 strcpy(player2Name, star_match[2]);\r
2524                 player2Rating = string_to_rating(star_match[3]);\r
2525 \r
2526                 if (appData.debugMode)\r
2527                   fprintf(debugFP, \r
2528                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2529                           player1Name, player1Rating,\r
2530                           player2Name, player2Rating);\r
2531 \r
2532                 continue;\r
2533             }\r
2534 \r
2535             /* Deal with automatic examine mode after a game,\r
2536                and with IcsObserving -> IcsExamining transition */\r
2537             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2538                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2539 \r
2540                 int gamenum = atoi(star_match[0]);\r
2541                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2542                     gamenum == ics_gamenum) {\r
2543                     /* We were already playing or observing this game;\r
2544                        no need to refetch history */\r
2545                     gameMode = IcsExamining;\r
2546                     if (pausing) {\r
2547                         pauseExamForwardMostMove = forwardMostMove;\r
2548                     } else if (currentMove < forwardMostMove) {\r
2549                         ForwardInner(forwardMostMove);\r
2550                     }\r
2551                 } else {\r
2552                     /* I don't think this case really can happen */\r
2553                     SendToICS(ics_prefix);\r
2554                     SendToICS("refresh\n");\r
2555                 }\r
2556                 continue;\r
2557             }    \r
2558             \r
2559             /* Error messages */\r
2560             if (ics_user_moved) {\r
2561                 if (looking_at(buf, &i, "Illegal move") ||\r
2562                     looking_at(buf, &i, "Not a legal move") ||\r
2563                     looking_at(buf, &i, "Your king is in check") ||\r
2564                     looking_at(buf, &i, "It isn't your turn") ||\r
2565                     looking_at(buf, &i, "It is not your move")) {\r
2566                     /* Illegal move */\r
2567                     ics_user_moved = 0;\r
2568                     if (forwardMostMove > backwardMostMove) {\r
2569                         currentMove = --forwardMostMove;\r
2570                         DisplayMove(currentMove - 1); /* before DMError */\r
2571                         DisplayMoveError("Illegal move (rejected by ICS)");\r
2572                         DrawPosition(FALSE, boards[currentMove]);\r
2573                         SwitchClocks();\r
2574                         DisplayBothClocks();\r
2575                     }\r
2576                     continue;\r
2577                 }\r
2578             }\r
2579 \r
2580             if (looking_at(buf, &i, "still have time") ||\r
2581                 looking_at(buf, &i, "not out of time") ||\r
2582                 looking_at(buf, &i, "either player is out of time") ||\r
2583                 looking_at(buf, &i, "has timeseal; checking")) {\r
2584                 /* We must have called his flag a little too soon */\r
2585                 whiteFlag = blackFlag = FALSE;\r
2586                 continue;\r
2587             }\r
2588 \r
2589             if (looking_at(buf, &i, "added * seconds to") ||\r
2590                 looking_at(buf, &i, "seconds were added to")) {\r
2591                 /* Update the clocks */\r
2592                 SendToICS(ics_prefix);\r
2593                 SendToICS("refresh\n");\r
2594                 continue;\r
2595             }\r
2596 \r
2597             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2598                 ics_clock_paused = TRUE;\r
2599                 StopClocks();\r
2600                 continue;\r
2601             }\r
2602 \r
2603             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2604                 ics_clock_paused = FALSE;\r
2605                 StartClocks();\r
2606                 continue;\r
2607             }\r
2608 \r
2609             /* Grab player ratings from the Creating: message.\r
2610                Note we have to check for the special case when\r
2611                the ICS inserts things like [white] or [black]. */\r
2612             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
2613                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
2614                 /* star_matches:\r
2615                    0    player 1 name (not necessarily white)\r
2616                    1    player 1 rating\r
2617                    2    empty, white, or black (IGNORED)\r
2618                    3    player 2 name (not necessarily black)\r
2619                    4    player 2 rating\r
2620                    \r
2621                    The names/ratings are sorted out when the game\r
2622                    actually starts (below).\r
2623                 */\r
2624                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
2625                 player1Rating = string_to_rating(star_match[1]);\r
2626                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
2627                 player2Rating = string_to_rating(star_match[4]);\r
2628 \r
2629                 if (appData.debugMode)\r
2630                   fprintf(debugFP, \r
2631                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
2632                           player1Name, player1Rating,\r
2633                           player2Name, player2Rating);\r
2634 \r
2635                 continue;\r
2636             }\r
2637             \r
2638             /* Improved generic start/end-of-game messages */\r
2639             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
2640                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
2641                 /* If tkind == 0: */\r
2642                 /* star_match[0] is the game number */\r
2643                 /*           [1] is the white player's name */\r
2644                 /*           [2] is the black player's name */\r
2645                 /* For end-of-game: */\r
2646                 /*           [3] is the reason for the game end */\r
2647                 /*           [4] is a PGN end game-token, preceded by " " */\r
2648                 /* For start-of-game: */\r
2649                 /*           [3] begins with "Creating" or "Continuing" */\r
2650                 /*           [4] is " *" or empty (don't care). */\r
2651                 int gamenum = atoi(star_match[0]);\r
2652                 char *whitename, *blackname, *why, *endtoken;\r
2653                 ChessMove endtype = (ChessMove) 0;\r
2654 \r
2655                 if (tkind == 0) {\r
2656                   whitename = star_match[1];\r
2657                   blackname = star_match[2];\r
2658                   why = star_match[3];\r
2659                   endtoken = star_match[4];\r
2660                 } else {\r
2661                   whitename = star_match[1];\r
2662                   blackname = star_match[3];\r
2663                   why = star_match[5];\r
2664                   endtoken = star_match[6];\r
2665                 }\r
2666 \r
2667                 /* Game start messages */\r
2668                 if (strncmp(why, "Creating ", 9) == 0 ||\r
2669                     strncmp(why, "Continuing ", 11) == 0) {\r
2670                     gs_gamenum = gamenum;\r
2671                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
2672 #if ZIPPY\r
2673                     if (appData.zippyPlay) {\r
2674                         ZippyGameStart(whitename, blackname);\r
2675                     }\r
2676 #endif /*ZIPPY*/\r
2677                     continue;\r
2678                 }\r
2679 \r
2680                 /* Game end messages */\r
2681                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
2682                     ics_gamenum != gamenum) {\r
2683                     continue;\r
2684                 }\r
2685                 while (endtoken[0] == ' ') endtoken++;\r
2686                 switch (endtoken[0]) {\r
2687                   case '*':\r
2688                   default:\r
2689                     endtype = GameUnfinished;\r
2690                     break;\r
2691                   case '0':\r
2692                     endtype = BlackWins;\r
2693                     break;\r
2694                   case '1':\r
2695                     if (endtoken[1] == '/')\r
2696                       endtype = GameIsDrawn;\r
2697                     else\r
2698                       endtype = WhiteWins;\r
2699                     break;\r
2700                 }\r
2701                 GameEnds(endtype, why, GE_ICS);\r
2702 #if ZIPPY\r
2703                 if (appData.zippyPlay && first.initDone) {\r
2704                     ZippyGameEnd(endtype, why);\r
2705                     if (first.pr == NULL) {\r
2706                       /* Start the next process early so that we'll\r
2707                          be ready for the next challenge */\r
2708                       StartChessProgram(&first);\r
2709                     }\r
2710                     /* Send "new" early, in case this command takes\r
2711                        a long time to finish, so that we'll be ready\r
2712                        for the next challenge. */\r
2713                     Reset(TRUE, TRUE);\r
2714                 }\r
2715 #endif /*ZIPPY*/\r
2716                 continue;\r
2717             }\r
2718 \r
2719             if (looking_at(buf, &i, "Removing game * from observation") ||\r
2720                 looking_at(buf, &i, "no longer observing game *") ||\r
2721                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
2722                 if (gameMode == IcsObserving &&\r
2723                     atoi(star_match[0]) == ics_gamenum)\r
2724                   {\r
2725                       StopClocks();\r
2726                       gameMode = IcsIdle;\r
2727                       ics_gamenum = -1;\r
2728                       ics_user_moved = FALSE;\r
2729                   }\r
2730                 continue;\r
2731             }\r
2732 \r
2733             if (looking_at(buf, &i, "no longer examining game *")) {\r
2734                 if (gameMode == IcsExamining &&\r
2735                     atoi(star_match[0]) == ics_gamenum)\r
2736                   {\r
2737                       gameMode = IcsIdle;\r
2738                       ics_gamenum = -1;\r
2739                       ics_user_moved = FALSE;\r
2740                   }\r
2741                 continue;\r
2742             }\r
2743 \r
2744             /* Advance leftover_start past any newlines we find,\r
2745                so only partial lines can get reparsed */\r
2746             if (looking_at(buf, &i, "\n")) {\r
2747                 prevColor = curColor;\r
2748                 if (curColor != ColorNormal) {\r
2749                     if (oldi > next_out) {\r
2750                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2751                         next_out = oldi;\r
2752                     }\r
2753                     Colorize(ColorNormal, FALSE);\r
2754                     curColor = ColorNormal;\r
2755                 }\r
2756                 if (started == STARTED_BOARD) {\r
2757                     started = STARTED_NONE;\r
2758                     parse[parse_pos] = NULLCHAR;\r
2759                     ParseBoard12(parse);\r
2760                     ics_user_moved = 0;\r
2761 \r
2762                     /* Send premove here */\r
2763                     if (appData.premove) {\r
2764                       char str[MSG_SIZ];\r
2765                       if (currentMove == 0 &&\r
2766                           gameMode == IcsPlayingWhite &&\r
2767                           appData.premoveWhite) {\r
2768                         sprintf(str, "%s%s\n", ics_prefix,\r
2769                                 appData.premoveWhiteText);\r
2770                         if (appData.debugMode)\r
2771                           fprintf(debugFP, "Sending premove:\n");\r
2772                         SendToICS(str);\r
2773                       } else if (currentMove == 1 &&\r
2774                                  gameMode == IcsPlayingBlack &&\r
2775                                  appData.premoveBlack) {\r
2776                         sprintf(str, "%s%s\n", ics_prefix,\r
2777                                 appData.premoveBlackText);\r
2778                         if (appData.debugMode)\r
2779                           fprintf(debugFP, "Sending premove:\n");\r
2780                         SendToICS(str);\r
2781                       } else if (gotPremove) {\r
2782                         gotPremove = 0;\r
2783                         ClearPremoveHighlights();\r
2784                         if (appData.debugMode)\r
2785                           fprintf(debugFP, "Sending premove:\n");\r
2786                           UserMoveEvent(premoveFromX, premoveFromY, \r
2787                                         premoveToX, premoveToY, \r
2788                                         premovePromoChar);\r
2789                       }\r
2790                     }\r
2791 \r
2792                     /* Usually suppress following prompt */\r
2793                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
2794                         if (looking_at(buf, &i, "*% ")) {\r
2795                             savingComment = FALSE;\r
2796                         }\r
2797                     }\r
2798                     next_out = i;\r
2799                 } else if (started == STARTED_HOLDINGS) {\r
2800                     int gamenum;\r
2801                     char new_piece[MSG_SIZ];\r
2802                     started = STARTED_NONE;\r
2803                     parse[parse_pos] = NULLCHAR;\r
2804                     if (appData.debugMode)\r
2805                       fprintf(debugFP, "Parsing holdings: %s\n", parse);\r
2806                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
2807                         gamenum == ics_gamenum) {\r
2808                         if (gameInfo.variant == VariantNormal) {\r
2809                           /* [HGM] We seem to switch variant during a game!\r
2810                            * Presumably no holdings were displayed, so we have\r
2811                            * to move the position two files to the right to\r
2812                            * create room for them!\r
2813                            */\r
2814                           int i, j;\r
2815                           if(gameInfo.holdingsWidth == 0) /* to be sure */\r
2816                           for(i=0; i<BOARD_HEIGHT; i++)\r
2817                             for(j=BOARD_RGHT-1; j>=0; j--)\r
2818                               boards[currentMove][i][j+2] = boards[currentMove][i][j];\r
2819 \r
2820   if (appData.debugMode) {\r
2821     fprintf(debugFP, "Switch board to Crazy\n");\r
2822     setbuf(debugFP, NULL);\r
2823   }\r
2824                           gameInfo.variant = VariantCrazyhouse; /*temp guess*/\r
2825                           gameInfo.boardWidth  = 8;  /* [HGM] guess board size as well */\r
2826                           gameInfo.boardHeight = 8;\r
2827                           gameInfo.holdingsSize = 5;\r
2828                           gameInfo.holdingsWidth = 2;\r
2829                           InitDrawingSizes(-2, 0);\r
2830                           /* Get a move list just to see the header, which\r
2831                              will tell us whether this is really bug or zh */\r
2832                           if (ics_getting_history == H_FALSE) {\r
2833                             ics_getting_history = H_REQUESTED;\r
2834                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
2835                             SendToICS(str);\r
2836                           }\r
2837                         }\r
2838                         new_piece[0] = NULLCHAR;\r
2839                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
2840                                &gamenum, white_holding, black_holding,\r
2841                                new_piece);\r
2842                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
2843                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
2844                         /* [HGM] copy holdings to board holdings area */\r
2845                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
2846                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
2847 #if ZIPPY\r
2848                         if (appData.zippyPlay && first.initDone) {\r
2849                             ZippyHoldings(white_holding, black_holding,\r
2850                                           new_piece);\r
2851                         }\r
2852 #endif /*ZIPPY*/\r
2853                         if (tinyLayout || smallLayout) {\r
2854                             char wh[16], bh[16];\r
2855                             PackHolding(wh, white_holding);\r
2856                             PackHolding(bh, black_holding);\r
2857                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
2858                                     gameInfo.white, gameInfo.black);\r
2859                         } else {\r
2860                             sprintf(str, "%s [%s] vs. %s [%s]",\r
2861                                     gameInfo.white, white_holding,\r
2862                                     gameInfo.black, black_holding);\r
2863                         }\r
2864 \r
2865                         DrawPosition(FALSE, NULL);\r
2866                         DisplayTitle(str);\r
2867                     }\r
2868                     /* Suppress following prompt */\r
2869                     if (looking_at(buf, &i, "*% ")) {\r
2870                         savingComment = FALSE;\r
2871                     }\r
2872                     next_out = i;\r
2873                 }\r
2874                 continue;\r
2875             }\r
2876 \r
2877             i++;                /* skip unparsed character and loop back */\r
2878         }\r
2879         \r
2880         if (started != STARTED_MOVES && started != STARTED_BOARD &&\r
2881             started != STARTED_HOLDINGS && i > next_out) {\r
2882             SendToPlayer(&buf[next_out], i - next_out);\r
2883             next_out = i;\r
2884         }\r
2885         \r
2886         leftover_len = buf_len - leftover_start;\r
2887         /* if buffer ends with something we couldn't parse,\r
2888            reparse it after appending the next read */\r
2889         \r
2890     } else if (count == 0) {\r
2891         RemoveInputSource(isr);\r
2892         DisplayFatalError("Connection closed by ICS", 0, 0);\r
2893     } else {\r
2894         DisplayFatalError("Error reading from ICS", error, 1);\r
2895     }\r
2896 }\r
2897 \r
2898 \r
2899 /* Board style 12 looks like this:\r
2900    \r
2901    <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
2902    \r
2903  * The "<12> " is stripped before it gets to this routine.  The two\r
2904  * trailing 0's (flip state and clock ticking) are later addition, and\r
2905  * some chess servers may not have them, or may have only the first.\r
2906  * Additional trailing fields may be added in the future.  \r
2907  */\r
2908 \r
2909 #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
2910 \r
2911 #define RELATION_OBSERVING_PLAYED    0\r
2912 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
2913 #define RELATION_PLAYING_MYMOVE      1\r
2914 #define RELATION_PLAYING_NOTMYMOVE  -1\r
2915 #define RELATION_EXAMINING           2\r
2916 #define RELATION_ISOLATED_BOARD     -3\r
2917 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
2918 \r
2919 void\r
2920 ParseBoard12(string)\r
2921      char *string;\r
2922\r
2923     GameMode newGameMode;\r
2924     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;\r
2925     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;\r
2926     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
2927     char to_play, board_chars[72];\r
2928     char move_str[500], str[500], elapsed_time[500];\r
2929     char black[32], white[32];\r
2930     Board board;\r
2931     int prevMove = currentMove;\r
2932     int ticking = 2;\r
2933     ChessMove moveType;\r
2934     int fromX, fromY, toX, toY;\r
2935     char promoChar;\r
2936 \r
2937     fromX = fromY = toX = toY = -1;\r
2938     \r
2939     newGame = FALSE;\r
2940 \r
2941     if (appData.debugMode)\r
2942       fprintf(debugFP, "Parsing board: %s\n", string);\r
2943 \r
2944     move_str[0] = NULLCHAR;\r
2945     elapsed_time[0] = NULLCHAR;\r
2946     n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,\r
2947                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
2948                &gamenum, white, black, &relation, &basetime, &increment,\r
2949                &white_stren, &black_stren, &white_time, &black_time,\r
2950                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
2951                &ticking);\r
2952 \r
2953     if (n < 22) {\r
2954         sprintf(str, "Failed to parse board string:\n\"%s\"", string);\r
2955         DisplayError(str, 0);\r
2956         return;\r
2957     }\r
2958 \r
2959     /* Convert the move number to internal form */\r
2960     moveNum = (moveNum - 1) * 2;\r
2961     if (to_play == 'B') moveNum++;\r
2962     if (moveNum >= MAX_MOVES) {\r
2963       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
2964                         0, 1);\r
2965       return;\r
2966     }\r
2967     \r
2968     switch (relation) {\r
2969       case RELATION_OBSERVING_PLAYED:\r
2970       case RELATION_OBSERVING_STATIC:\r
2971         if (gamenum == -1) {\r
2972             /* Old ICC buglet */\r
2973             relation = RELATION_OBSERVING_STATIC;\r
2974         }\r
2975         newGameMode = IcsObserving;\r
2976         break;\r
2977       case RELATION_PLAYING_MYMOVE:\r
2978       case RELATION_PLAYING_NOTMYMOVE:\r
2979         newGameMode =\r
2980           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
2981             IcsPlayingWhite : IcsPlayingBlack;\r
2982         break;\r
2983       case RELATION_EXAMINING:\r
2984         newGameMode = IcsExamining;\r
2985         break;\r
2986       case RELATION_ISOLATED_BOARD:\r
2987       default:\r
2988         /* Just display this board.  If user was doing something else,\r
2989            we will forget about it until the next board comes. */ \r
2990         newGameMode = IcsIdle;\r
2991         break;\r
2992       case RELATION_STARTING_POSITION:\r
2993         newGameMode = gameMode;\r
2994         break;\r
2995     }\r
2996     \r
2997     /* Modify behavior for initial board display on move listing\r
2998        of wild games.\r
2999        */\r
3000     switch (ics_getting_history) {\r
3001       case H_FALSE:\r
3002       case H_REQUESTED:\r
3003         break;\r
3004       case H_GOT_REQ_HEADER:\r
3005       case H_GOT_UNREQ_HEADER:\r
3006         /* This is the initial position of the current game */\r
3007         gamenum = ics_gamenum;\r
3008         moveNum = 0;            /* old ICS bug workaround */\r
3009         if (to_play == 'B') {\r
3010           startedFromSetupPosition = TRUE;\r
3011           blackPlaysFirst = TRUE;\r
3012           moveNum = 1;\r
3013           if (forwardMostMove == 0) forwardMostMove = 1;\r
3014           if (backwardMostMove == 0) backwardMostMove = 1;\r
3015           if (currentMove == 0) currentMove = 1;\r
3016         }\r
3017         newGameMode = gameMode;\r
3018         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3019         break;\r
3020       case H_GOT_UNWANTED_HEADER:\r
3021         /* This is an initial board that we don't want */\r
3022         return;\r
3023       case H_GETTING_MOVES:\r
3024         /* Should not happen */\r
3025         DisplayError("Error gathering move list: extra board", 0);\r
3026         ics_getting_history = H_FALSE;\r
3027         return;\r
3028     }\r
3029     \r
3030     /* Take action if this is the first board of a new game, or of a\r
3031        different game than is currently being displayed.  */\r
3032     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3033         relation == RELATION_ISOLATED_BOARD) {\r
3034         \r
3035         /* Forget the old game and get the history (if any) of the new one */\r
3036         if (gameMode != BeginningOfGame) {\r
3037           Reset(FALSE, TRUE);\r
3038         }\r
3039         newGame = TRUE;\r
3040         if (appData.autoRaiseBoard) BoardToTop();\r
3041         prevMove = -3;\r
3042         if (gamenum == -1) {\r
3043             newGameMode = IcsIdle;\r
3044         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3045                    appData.getMoveList) {\r
3046             /* Need to get game history */\r
3047             ics_getting_history = H_REQUESTED;\r
3048             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3049             SendToICS(str);\r
3050         }\r
3051         \r
3052         /* Initially flip the board to have black on the bottom if playing\r
3053            black or if the ICS flip flag is set, but let the user change\r
3054            it with the Flip View button. */\r
3055         flipView = appData.autoFlipView ? \r
3056           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3057           appData.flipView;\r
3058         \r
3059         /* Done with values from previous mode; copy in new ones */\r
3060         gameMode = newGameMode;\r
3061         ModeHighlight();\r
3062         ics_gamenum = gamenum;\r
3063         if (gamenum == gs_gamenum) {\r
3064             int klen = strlen(gs_kind);\r
3065             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3066             sprintf(str, "ICS %s", gs_kind);\r
3067             gameInfo.event = StrSave(str);\r
3068         } else {\r
3069             gameInfo.event = StrSave("ICS game");\r
3070         }\r
3071         gameInfo.site = StrSave(appData.icsHost);\r
3072         gameInfo.date = PGNDate();\r
3073         gameInfo.round = StrSave("-");\r
3074         gameInfo.white = StrSave(white);\r
3075         gameInfo.black = StrSave(black);\r
3076         timeControl = basetime * 60 * 1000;\r
3077         timeControl_2 = 0;\r
3078         timeIncrement = increment * 1000;\r
3079         movesPerSession = 0;\r
3080         gameInfo.timeControl = TimeControlTagValue();\r
3081         gameInfo.variant = StringToVariant(gameInfo.event);\r
3082   if (appData.debugMode) {\r
3083     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3084     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3085     setbuf(debugFP, NULL);\r
3086   }\r
3087 \r
3088         gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
3089         gameInfo.boardWidth = gameInfo.boardHeight = 8;\r
3090         switch(gameInfo.variant) {\r
3091             case VariantShogi:\r
3092             case VariantShowgi:\r
3093               gameInfo.boardWidth = 9;  gameInfo.boardHeight = 9;\r
3094               gameInfo.holdingsSize = 7;\r
3095             case VariantBughouse:\r
3096             case VariantCrazyhouse:\r
3097               gameInfo.holdingsWidth = 2; break;\r
3098             default:\r
3099               gameInfo.holdingsWidth = gameInfo.holdingsSize = 0;\r
3100         }\r
3101         InitDrawingSizes(-2, 0);\r
3102         gameInfo.outOfBook = NULL;\r
3103         \r
3104         /* Do we have the ratings? */\r
3105         if (strcmp(player1Name, white) == 0 &&\r
3106             strcmp(player2Name, black) == 0) {\r
3107             if (appData.debugMode)\r
3108               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3109                       player1Rating, player2Rating);\r
3110             gameInfo.whiteRating = player1Rating;\r
3111             gameInfo.blackRating = player2Rating;\r
3112         } else if (strcmp(player2Name, white) == 0 &&\r
3113                    strcmp(player1Name, black) == 0) {\r
3114             if (appData.debugMode)\r
3115               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3116                       player2Rating, player1Rating);\r
3117             gameInfo.whiteRating = player2Rating;\r
3118             gameInfo.blackRating = player1Rating;\r
3119         }\r
3120         player1Name[0] = player2Name[0] = NULLCHAR;\r
3121 \r
3122         /* Silence shouts if requested */\r
3123         if (appData.quietPlay &&\r
3124             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3125             SendToICS(ics_prefix);\r
3126             SendToICS("set shout 0\n");\r
3127         }\r
3128     }\r
3129     \r
3130     /* Deal with midgame name changes */\r
3131     if (!newGame) {\r
3132         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3133             if (gameInfo.white) free(gameInfo.white);\r
3134             gameInfo.white = StrSave(white);\r
3135         }\r
3136         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3137             if (gameInfo.black) free(gameInfo.black);\r
3138             gameInfo.black = StrSave(black);\r
3139         }\r
3140     }\r
3141     \r
3142     /* Throw away game result if anything actually changes in examine mode */\r
3143     if (gameMode == IcsExamining && !newGame) {\r
3144         gameInfo.result = GameUnfinished;\r
3145         if (gameInfo.resultDetails != NULL) {\r
3146             free(gameInfo.resultDetails);\r
3147             gameInfo.resultDetails = NULL;\r
3148         }\r
3149     }\r
3150     \r
3151     /* In pausing && IcsExamining mode, we ignore boards coming\r
3152        in if they are in a different variation than we are. */\r
3153     if (pauseExamInvalid) return;\r
3154     if (pausing && gameMode == IcsExamining) {\r
3155         if (moveNum <= pauseExamForwardMostMove) {\r
3156             pauseExamInvalid = TRUE;\r
3157             forwardMostMove = pauseExamForwardMostMove;\r
3158             return;\r
3159         }\r
3160     }\r
3161     \r
3162     /* Parse the board */\r
3163     for (k = 0; k < 8; k++) {\r
3164       for (j = 0; j < 8; j++)\r
3165         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(7-k)*9 + j]);\r
3166       if(gameInfo.holdingsWidth > 1) {\r
3167            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3168            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3169       }\r
3170     }\r
3171     CopyBoard(boards[moveNum], board);\r
3172     if (moveNum == 0) {\r
3173         startedFromSetupPosition =\r
3174           !CompareBoards(board, initialPosition);\r
3175     }\r
3176     \r
3177     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3178         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3179         /* This was an initial position from a move list, not\r
3180            the current position */\r
3181         return;\r
3182     }\r
3183     \r
3184     /* Update currentMove and known move number limits */\r
3185     newMove = newGame || moveNum > forwardMostMove;\r
3186     if (newGame) {\r
3187         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3188         if (gameMode == IcsExamining && moveNum == 0) {\r
3189           /* Workaround for ICS limitation: we are not told the wild\r
3190              type when starting to examine a game.  But if we ask for\r
3191              the move list, the move list header will tell us */\r
3192             ics_getting_history = H_REQUESTED;\r
3193             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3194             SendToICS(str);\r
3195         }\r
3196     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3197                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3198         forwardMostMove = moveNum;\r
3199         if (!pausing || currentMove > forwardMostMove)\r
3200           currentMove = forwardMostMove;\r
3201     } else {\r
3202         /* New part of history that is not contiguous with old part */ \r
3203         if (pausing && gameMode == IcsExamining) {\r
3204             pauseExamInvalid = TRUE;\r
3205             forwardMostMove = pauseExamForwardMostMove;\r
3206             return;\r
3207         }\r
3208         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3209         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3210             ics_getting_history = H_REQUESTED;\r
3211             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3212             SendToICS(str);\r
3213         }\r
3214     }\r
3215     \r
3216     /* Update the clocks */\r
3217     if (strchr(elapsed_time, '.')) {\r
3218       /* Time is in ms */\r
3219       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3220       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3221     } else {\r
3222       /* Time is in seconds */\r
3223       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3224       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3225     }\r
3226       \r
3227 \r
3228 #if ZIPPY\r
3229     if (appData.zippyPlay && newGame &&\r
3230         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3231         gameMode != IcsExamining)\r
3232       ZippyFirstBoard(moveNum, basetime, increment);\r
3233 #endif\r
3234     \r
3235     /* Put the move on the move list, first converting\r
3236        to canonical algebraic form. */\r
3237     if (moveNum > 0) {\r
3238   if (appData.debugMode) {\r
3239     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3240     fprintf(debugFP, "board = %d-d x%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3241     setbuf(debugFP, NULL);\r
3242   }\r
3243         if (moveNum <= backwardMostMove) {\r
3244             /* We don't know what the board looked like before\r
3245                this move.  Punt. */\r
3246             strcpy(parseList[moveNum - 1], move_str);\r
3247             strcat(parseList[moveNum - 1], " ");\r
3248             strcat(parseList[moveNum - 1], elapsed_time);\r
3249             moveList[moveNum - 1][0] = NULLCHAR;\r
3250         } else if (ParseOneMove(move_str, moveNum - 1, &moveType,\r
3251                                 &fromX, &fromY, &toX, &toY, &promoChar)) {\r
3252             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3253                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3254                                      fromY, fromX, toY, toX, promoChar,\r
3255                                      parseList[moveNum-1]);\r
3256             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3257                              castlingRights[moveNum]) ) {\r
3258               case MT_NONE:\r
3259               case MT_STALEMATE:\r
3260               default:\r
3261                 break;\r
3262               case MT_CHECK:\r
3263                 if(gameInfo.variant != VariantShogi)\r
3264                     strcat(parseList[moveNum - 1], "+");\r
3265                 break;\r
3266               case MT_CHECKMATE:\r
3267                 strcat(parseList[moveNum - 1], "#");\r
3268                 break;\r
3269             }\r
3270             strcat(parseList[moveNum - 1], " ");\r
3271             strcat(parseList[moveNum - 1], elapsed_time);\r
3272             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3273             strcpy(moveList[moveNum - 1], currentMoveString);\r
3274             strcat(moveList[moveNum - 1], "\n");\r
3275         } else if (strcmp(move_str, "none") == 0) {\r
3276             /* Again, we don't know what the board looked like;\r
3277                this is really the start of the game. */\r
3278             parseList[moveNum - 1][0] = NULLCHAR;\r
3279             moveList[moveNum - 1][0] = NULLCHAR;\r
3280             backwardMostMove = moveNum;\r
3281             startedFromSetupPosition = TRUE;\r
3282             fromX = fromY = toX = toY = -1;\r
3283         } else {\r
3284             /* Move from ICS was illegal!?  Punt. */\r
3285 #if 0\r
3286             if (appData.testLegality && appData.debugMode) {\r
3287                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3288                 DisplayError(str, 0);\r
3289             }\r
3290 #endif\r
3291             strcpy(parseList[moveNum - 1], move_str);\r
3292             strcat(parseList[moveNum - 1], " ");\r
3293             strcat(parseList[moveNum - 1], elapsed_time);\r
3294             moveList[moveNum - 1][0] = NULLCHAR;\r
3295             fromX = fromY = toX = toY = -1;\r
3296         }\r
3297   if (appData.debugMode) {\r
3298     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3299     setbuf(debugFP, NULL);\r
3300   }\r
3301 \r
3302 #if ZIPPY\r
3303         /* Send move to chess program (BEFORE animating it). */\r
3304         if (appData.zippyPlay && !newGame && newMove && \r
3305            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3306 \r
3307             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3308                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3309                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3310                     sprintf(str, "Couldn't parse move \"%s\" from ICS",\r
3311                             move_str);\r
3312                     DisplayError(str, 0);\r
3313                 } else {\r
3314                     if (first.sendTime) {\r
3315                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3316                     }\r
3317                     SendMoveToProgram(moveNum - 1, &first);\r
3318                     if (firstMove) {\r
3319                         firstMove = FALSE;\r
3320                         if (first.useColors) {\r
3321                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3322                                         "white\ngo\n" :\r
3323                                         "black\ngo\n", &first);\r
3324                         } else {\r
3325                           SendToProgram("go\n", &first);\r
3326                         }\r
3327                         first.maybeThinking = TRUE;\r
3328                     }\r
3329                 }\r
3330             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3331               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3332                 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);\r
3333                 DisplayError(str, 0);\r
3334               } else {\r
3335                 SendMoveToProgram(moveNum - 1, &first);\r
3336               }\r
3337             }\r
3338         }\r
3339 #endif\r
3340     }\r
3341 \r
3342     if (moveNum > 0 && !gotPremove) {\r
3343         /* If move comes from a remote source, animate it.  If it\r
3344            isn't remote, it will have already been animated. */\r
3345         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3346             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3347         }\r
3348         if (!pausing && appData.highlightLastMove) {\r
3349             SetHighlights(fromX, fromY, toX, toY);\r
3350         }\r
3351     }\r
3352     \r
3353     /* Start the clocks */\r
3354     whiteFlag = blackFlag = FALSE;\r
3355     appData.clockMode = !(basetime == 0 && increment == 0);\r
3356     if (ticking == 0) {\r
3357       ics_clock_paused = TRUE;\r
3358       StopClocks();\r
3359     } else if (ticking == 1) {\r
3360       ics_clock_paused = FALSE;\r
3361     }\r
3362     if (gameMode == IcsIdle ||\r
3363         relation == RELATION_OBSERVING_STATIC ||\r
3364         relation == RELATION_EXAMINING ||\r
3365         ics_clock_paused)\r
3366       DisplayBothClocks();\r
3367     else\r
3368       StartClocks();\r
3369     \r
3370     /* Display opponents and material strengths */\r
3371     if (gameInfo.variant != VariantBughouse &&\r
3372         gameInfo.variant != VariantCrazyhouse) {\r
3373         if (tinyLayout || smallLayout) {\r
3374             sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3375                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3376                     basetime, increment);\r
3377         } else {\r
3378             sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3379                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3380                     basetime, increment);\r
3381         }\r
3382         DisplayTitle(str);\r
3383     }\r
3384 \r
3385    \r
3386     /* Display the board */\r
3387     if (!pausing) {\r
3388       \r
3389       if (appData.premove)\r
3390           if (!gotPremove || \r
3391              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3392              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3393               ClearPremoveHighlights();\r
3394 \r
3395       DrawPosition(FALSE, boards[currentMove]);\r
3396       DisplayMove(moveNum - 1);\r
3397       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3398         RingBell();\r
3399     }\r
3400 \r
3401     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3402 }\r
3403 \r
3404 void\r
3405 GetMoveListEvent()\r
3406 {\r
3407     char buf[MSG_SIZ];\r
3408     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3409         ics_getting_history = H_REQUESTED;\r
3410         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3411         SendToICS(buf);\r
3412     }\r
3413 }\r
3414 \r
3415 void\r
3416 AnalysisPeriodicEvent(force)\r
3417      int force;\r
3418 {\r
3419     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3420          && !force) || !appData.periodicUpdates)\r
3421       return;\r
3422 \r
3423     /* Send . command to Crafty to collect stats */\r
3424     SendToProgram(".\n", &first);\r
3425 \r
3426     /* Don't send another until we get a response (this makes\r
3427        us stop sending to old Crafty's which don't understand\r
3428        the "." command (sending illegal cmds resets node count & time,\r
3429        which looks bad)) */\r
3430     programStats.ok_to_send = 0;\r
3431 }\r
3432 \r
3433 void\r
3434 SendMoveToProgram(moveNum, cps)\r
3435      int moveNum;\r
3436      ChessProgramState *cps;\r
3437 {\r
3438     char buf[MSG_SIZ];\r
3439     if (cps->useUsermove) {\r
3440       SendToProgram("usermove ", cps);\r
3441     }\r
3442     if (cps->useSAN) {\r
3443       char *space;\r
3444       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3445         int len = space - parseList[moveNum];\r
3446         memcpy(buf, parseList[moveNum], len);\r
3447         buf[len++] = '\n';\r
3448         buf[len] = NULLCHAR;\r
3449       } else {\r
3450         sprintf(buf, "%s\n", parseList[moveNum]);\r
3451       }\r
3452       /* [HGM] decrement all digits to code ranks starting from 0 */\r
3453       if(BOARD_HEIGHT>9) {\r
3454           char *p = buf;\r
3455           while(*p) { if(*p < 'A') (*p)--; p++; }\r
3456       }\r
3457       SendToProgram(buf, cps);\r
3458     } else {\r
3459       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3460        * the engine. It would be nice to have a better way to identify castle \r
3461        * moves here. */\r
3462       if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {\r
3463         int fromX = moveList[moveNum][0] - AAA; \r
3464         int fromY = moveList[moveNum][1] - ONE;\r
3465         int toX = moveList[moveNum][2] - AAA; \r
3466         int toY = moveList[moveNum][3] - ONE;\r
3467         if((boards[currentMove][fromY][fromX] == WhiteKing \r
3468             && boards[currentMove][toY][toX] == WhiteRook)\r
3469            || (boards[currentMove][fromY][fromX] == BlackKing \r
3470                && boards[currentMove][toY][toX] == BlackRook)) {\r
3471           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3472           else SendToProgram("O-O-O\n", cps);\r
3473         }\r
3474         else SendToProgram(moveList[moveNum], cps);\r
3475       }\r
3476       else SendToProgram(moveList[moveNum], cps);\r
3477       /* End of additions by Tord */\r
3478     }\r
3479 }\r
3480 \r
3481 void\r
3482 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
3483      ChessMove moveType;\r
3484      int fromX, fromY, toX, toY;\r
3485 {\r
3486     char user_move[MSG_SIZ];\r
3487 \r
3488     switch (moveType) {\r
3489       default:\r
3490         sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",\r
3491                 (int)moveType, fromX, fromY, toX, toY);\r
3492         DisplayError(user_move + strlen("say "), 0);\r
3493         break;\r
3494       case WhiteKingSideCastle:\r
3495       case BlackKingSideCastle:\r
3496       case WhiteQueenSideCastleWild:\r
3497       case BlackQueenSideCastleWild:\r
3498       /* PUSH Fabien */\r
3499       case WhiteHSideCastleFR:\r
3500       case BlackHSideCastleFR:\r
3501       /* POP Fabien */\r
3502         sprintf(user_move, "o-o\n");\r
3503         break;\r
3504       case WhiteQueenSideCastle:\r
3505       case BlackQueenSideCastle:\r
3506       case WhiteKingSideCastleWild:\r
3507       case BlackKingSideCastleWild:\r
3508       /* PUSH Fabien */\r
3509       case WhiteASideCastleFR:\r
3510       case BlackASideCastleFR:\r
3511       /* POP Fabien */\r
3512         sprintf(user_move, "o-o-o\n");\r
3513         break;\r
3514       case WhitePromotionQueen:\r
3515       case BlackPromotionQueen:\r
3516       case WhitePromotionRook:\r
3517       case BlackPromotionRook:\r
3518       case WhitePromotionBishop:\r
3519       case BlackPromotionBishop:\r
3520       case WhitePromotionKnight:\r
3521       case BlackPromotionKnight:\r
3522       case WhitePromotionKing:\r
3523       case BlackPromotionKing:\r
3524 #ifdef FAIRY\r
3525       case WhitePromotionChancellor:\r
3526       case BlackPromotionChancellor:\r
3527       case WhitePromotionArchbishop:\r
3528       case BlackPromotionArchbishop:\r
3529 #endif\r
3530         sprintf(user_move, "%c%c%c%c=%c\n",\r
3531                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
3532                 PieceToChar(PromoPiece(moveType)));\r
3533         break;\r
3534       case WhiteDrop:\r
3535       case BlackDrop:\r
3536         sprintf(user_move, "%c@%c%c\n",\r
3537                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
3538                 AAA + toX, ONE + toY);\r
3539         break;\r
3540       case NormalMove:\r
3541       case WhiteCapturesEnPassant:\r
3542       case BlackCapturesEnPassant:\r
3543       case IllegalMove:  /* could be a variant we don't quite understand */\r
3544         sprintf(user_move, "%c%c%c%c\n",\r
3545                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
3546         break;\r
3547     }\r
3548     SendToICS(user_move);\r
3549 }\r
3550 \r
3551 void\r
3552 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
3553      int rf, ff, rt, ft;\r
3554      char promoChar;\r
3555      char move[7];\r
3556 {\r
3557     if (rf == DROP_RANK) {\r
3558         sprintf(move, "%c@%c%c\n",\r
3559                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
3560     } else {\r
3561         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
3562             sprintf(move, "%c%c%c%c\n",\r
3563                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
3564         } else {\r
3565             sprintf(move, "%c%c%c%c%c\n",\r
3566                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
3567         }\r
3568     }\r
3569     AlphaRank(move, 4);\r
3570 }\r
3571 \r
3572 void\r
3573 ProcessICSInitScript(f)\r
3574      FILE *f;\r
3575 {\r
3576     char buf[MSG_SIZ];\r
3577 \r
3578     while (fgets(buf, MSG_SIZ, f)) {\r
3579         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
3580     }\r
3581 \r
3582     fclose(f);\r
3583 }\r
3584 \r
3585 \r
3586 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
3587 void\r
3588 AlphaRank(char *move, int n)\r
3589 {\r
3590     char *p = move, c;\r
3591 \r
3592     if( !appData.alphaRank ) return;\r
3593 \r
3594     while(c = *p) {\r
3595         if(c>='0' && c<='9') *p += 'a'-'0'; else\r
3596         if(c>='a' && c<='z') *p -= 'a'-'0';\r
3597         p++;\r
3598         if(--n < 1) break;\r
3599     }\r
3600 }\r
3601 \r
3602 /* Parser for moves from gnuchess, ICS, or user typein box */\r
3603 Boolean\r
3604 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
3605      char *move;\r
3606      int moveNum;\r
3607      ChessMove *moveType;\r
3608      int *fromX, *fromY, *toX, *toY;\r
3609      char *promoChar;\r
3610 {       \r
3611     if (appData.debugMode) {\r
3612         fprintf(debugFP, "move to parse: %s\n", move);\r
3613     }\r
3614     AlphaRank(move, 10);\r
3615     *moveType = yylexstr(moveNum, move);\r
3616 \r
3617     switch (*moveType) {\r
3618 #ifdef FAIRY\r
3619       case WhitePromotionChancellor:\r
3620       case BlackPromotionChancellor:\r
3621       case WhitePromotionArchbishop:\r
3622       case BlackPromotionArchbishop:\r
3623 #endif\r
3624       case WhitePromotionQueen:\r
3625       case BlackPromotionQueen:\r
3626       case WhitePromotionRook:\r
3627       case BlackPromotionRook:\r
3628       case WhitePromotionBishop:\r
3629       case BlackPromotionBishop:\r
3630       case WhitePromotionKnight:\r
3631       case BlackPromotionKnight:\r
3632       case WhitePromotionKing:\r
3633       case BlackPromotionKing:\r
3634       case NormalMove:\r
3635       case WhiteCapturesEnPassant:\r
3636       case BlackCapturesEnPassant:\r
3637       case WhiteKingSideCastle:\r
3638       case WhiteQueenSideCastle:\r
3639       case BlackKingSideCastle:\r
3640       case BlackQueenSideCastle:\r
3641       case WhiteKingSideCastleWild:\r
3642       case WhiteQueenSideCastleWild:\r
3643       case BlackKingSideCastleWild:\r
3644       case BlackQueenSideCastleWild:\r
3645       /* Code added by Tord: */\r
3646       case WhiteHSideCastleFR:\r
3647       case WhiteASideCastleFR:\r
3648       case BlackHSideCastleFR:\r
3649       case BlackASideCastleFR:\r
3650       /* End of code added by Tord */\r
3651       case IllegalMove:         /* bug or odd chess variant */\r
3652         *fromX = currentMoveString[0] - AAA;\r
3653         *fromY = currentMoveString[1] - ONE;\r
3654         *toX = currentMoveString[2] - AAA;\r
3655         *toY = currentMoveString[3] - ONE;\r
3656         *promoChar = currentMoveString[4];\r
3657         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
3658             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
3659             *fromX = *fromY = *toX = *toY = 0;\r
3660             return FALSE;\r
3661         }\r
3662         if (appData.testLegality) {\r
3663           return (*moveType != IllegalMove);\r
3664         } else {\r
3665           return !(fromX == fromY && toX == toY);\r
3666         }\r
3667 \r
3668       case WhiteDrop:\r
3669       case BlackDrop:\r
3670         *fromX = *moveType == WhiteDrop ?\r
3671           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
3672         (int) CharToPiece(ToLower(currentMoveString[0]));\r
3673         *fromY = DROP_RANK;\r
3674         *toX = currentMoveString[2] - AAA;\r
3675         *toY = currentMoveString[3] - ONE;\r
3676         *promoChar = NULLCHAR;\r
3677         return TRUE;\r
3678 \r
3679       case AmbiguousMove:\r
3680       case ImpossibleMove:\r
3681       case (ChessMove) 0:       /* end of file */\r
3682       case ElapsedTime:\r
3683       case Comment:\r
3684       case PGNTag:\r
3685       case NAG:\r
3686       case WhiteWins:\r
3687       case BlackWins:\r
3688       case GameIsDrawn:\r
3689       default:\r
3690         /* bug? */\r
3691         *fromX = *fromY = *toX = *toY = 0;\r
3692         *promoChar = NULLCHAR;\r
3693         return FALSE;\r
3694     }\r
3695 }\r
3696 \r
3697 /* [AS] FRC game initialization */\r
3698 static int FindEmptySquare( Board board, int n )\r
3699 {\r
3700     int i = 0;\r
3701 \r
3702     while( 1 ) {\r
3703         while( board[0][i] != EmptySquare ) i++;\r
3704         if( n == 0 )\r
3705             break;\r
3706         n--;\r
3707         i++;\r
3708     }\r
3709 \r
3710     return i;\r
3711 }\r
3712 \r
3713 static void ShuffleFRC( Board board )\r
3714 {\r
3715     int i;\r
3716 \r
3717     srand( time(0) );\r
3718     \r
3719     for( i=0; i<8; i++ ) {\r
3720         board[0][i] = EmptySquare;\r
3721     }\r
3722 \r
3723     board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */\r
3724     board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */\r
3725     board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;\r
3726     board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;\r
3727     board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;\r
3728     board[0][FindEmptySquare(board, 0)] = WhiteRook;\r
3729     board[0][FindEmptySquare(board, 0)] = WhiteKing;\r
3730     board[0][FindEmptySquare(board, 0)] = WhiteRook;\r
3731 \r
3732     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
3733         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
3734     }\r
3735 }\r
3736 \r
3737 static unsigned char FRC_KnightTable[10] = {\r
3738     0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33\r
3739 };\r
3740 \r
3741 static void SetupFRC( Board board, int pos_index )\r
3742 {\r
3743     int i;\r
3744     unsigned char knights;\r
3745 \r
3746     /* Bring the position index into a safe range (just in case...) */\r
3747     if( pos_index < 0 ) pos_index = 0;\r
3748 \r
3749     pos_index %= 960;\r
3750 \r
3751     /* Clear the board */\r
3752     for( i=0; i<8; i++ ) {\r
3753         board[0][i] = EmptySquare;\r
3754     }\r
3755 \r
3756     /* Place bishops and queen */\r
3757     board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */\r
3758     pos_index /= 4;\r
3759     \r
3760     board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */\r
3761     pos_index /= 4;\r
3762 \r
3763     board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;\r
3764     pos_index /= 6;\r
3765 \r
3766     /* Place knigths */\r
3767     knights = FRC_KnightTable[ pos_index ];\r
3768 \r
3769     board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;\r
3770     board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;\r
3771 \r
3772     /* Place rooks and king */\r
3773     board[0][ FindEmptySquare(board, 0) ] = WhiteRook;\r
3774     board[0][ FindEmptySquare(board, 0) ] = WhiteKing;\r
3775     board[0][ FindEmptySquare(board, 0) ] = WhiteRook;\r
3776 \r
3777     /* Mirror piece placement for black */\r
3778     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
3779         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
3780     }\r
3781 }\r
3782 \r
3783 BOOL SetCharTable( char *table, const char * map )\r
3784 /* [HGM] moved here from winboard.c because of its general usefulness */\r
3785 /*       Basically a safe strcpy that uses the last character as King */\r
3786 {\r
3787     BOOL result = FALSE; int NrPieces;\r
3788 \r
3789     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
3790                     && NrPieces >= 12 && !(NrPieces&1)) {\r
3791         int i; /* [HGM] Accept even length from 12 to 34 */\r
3792 \r
3793         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
3794         for( i=0; i<NrPieces/2-1; i++ ) {\r
3795             table[i] = map[i];\r
3796             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
3797         }\r
3798         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
3799         table[(int) BlackKing]  = map[NrPieces-1];\r
3800 \r
3801         result = TRUE;\r
3802     }\r
3803 \r
3804     return result;\r
3805 }\r
3806 \r
3807 void\r
3808 InitPosition(redraw)\r
3809      int redraw;\r
3810 {\r
3811     ChessSquare (* pieces)[BOARD_SIZE];\r
3812     int i, j, pawnRow, overrule,\r
3813     oldx = gameInfo.boardWidth,\r
3814     oldy = gameInfo.boardHeight,\r
3815     oldh = gameInfo.holdingsWidth,\r
3816     oldv = gameInfo.variant;\r
3817 \r
3818     currentMove = forwardMostMove = backwardMostMove = 0;\r
3819 \r
3820     /* [AS] Initialize pv info list [HGM] and game status */\r
3821     {\r
3822         for( i=0; i<MAX_MOVES; i++ ) {\r
3823             pvInfoList[i].depth = 0;\r
3824             epStatus[i]=EP_NONE;\r
3825             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
3826         }\r
3827 \r
3828         initialRulePlies = 0; /* 50-move counter start */\r
3829     }\r
3830 \r
3831     \r
3832     /* [HGM] logic here is completely changed. In stead of full positions */\r
3833     /* the initialized data only consist of the two backranks. The switch */\r
3834     /* selects which one we will use, which is than copied to the Board   */\r
3835     /* initialPosition, which for the rest is initialized by Pawns and    */\r
3836     /* empty squares. This initial position is then copied to boards[0],  */\r
3837     /* possibly after shuffling, so that it remains available.            */\r
3838 \r
3839     gameInfo.holdingsWidth = 0; /* default board sizes */\r
3840     gameInfo.boardWidth    = 8;\r
3841     gameInfo.boardHeight   = 8;\r
3842     gameInfo.holdingsSize  = 0;\r
3843     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
3844     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
3845     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
3846 \r
3847     switch (gameInfo.variant) {\r
3848     default:\r
3849       pieces = FIDEArray;\r
3850       break;\r
3851     case VariantShatranj:\r
3852       pieces = ShatranjArray;\r
3853       nrCastlingRights = 0;\r
3854       break;\r
3855     case VariantTwoKings:\r
3856       pieces = twoKingsArray;\r
3857       nrCastlingRights = 8;                 /* add rights for second King */\r
3858       castlingRights[0][6] = initialRights[2] = 5;\r
3859       castlingRights[0][7] = initialRights[5] = 5;\r
3860       castlingRank[6] = 0;\r
3861       castlingRank[6] = BOARD_HEIGHT-1;\r
3862       startedFromSetupPosition = TRUE;\r
3863       break;\r
3864     case VariantCapablanca:\r
3865       pieces = CapablancaArray;\r
3866       gameInfo.boardWidth = 10;\r
3867       SetCharTable(pieceToChar, "PNBRQ.......AC..Kpnbrq.......ac..k"); \r
3868       break;\r
3869     case VariantGothic:\r
3870       pieces = GothicArray;\r
3871       gameInfo.boardWidth = 10;\r
3872       SetCharTable(pieceToChar, "PNBRQ.......AC..Kpnbrq.......ac..k"); \r
3873       break;\r
3874     case VariantXiangqi:\r
3875       pieces = XiangqiArray;\r
3876       gameInfo.boardWidth  = 9;\r
3877       gameInfo.boardHeight = 10;\r
3878       nrCastlingRights = 0;\r
3879       SetCharTable(pieceToChar, "PH.R.AKE.C.......ph.r.ake.c......."); \r
3880       break;\r
3881     case VariantShogi:\r
3882       pieces = ShogiArray;\r
3883       gameInfo.boardWidth  = 9;\r
3884       gameInfo.boardHeight = 9;\r
3885       gameInfo.holdingsSize = 7;\r
3886       nrCastlingRights = 0;\r
3887       SetCharTable(pieceToChar, "PNBRLSG.........Kpnbrlsg.........k"); \r
3888       break;\r
3889     case VariantShowgi:\r
3890       pieces = ShogiArray;\r
3891       gameInfo.boardWidth  = 9;\r
3892       gameInfo.boardHeight = 9;\r
3893       gameInfo.holdingsSize = 7;\r
3894       nrCastlingRights = 0;\r
3895       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
3896       SetCharTable(pieceToChar, "PNBRQFWEMOUHACG.Kpnbrlsgpnbrls...k"); \r
3897       break;\r
3898     case VariantCourier:\r
3899       pieces = CourierArray;\r
3900       gameInfo.boardWidth  = 12;\r
3901       nrCastlingRights = 0;\r
3902       SetCharTable(pieceToChar, "PNBR.FWEM.......Kpnbr.fwem.......k"); \r
3903       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
3904       break;\r
3905     case VariantKnightmate:\r
3906       pieces = KnightmateArray;\r
3907       strcpy(pieceToChar, "P.BRQ...M.K......p.brq...m.k......"); \r
3908       break;\r
3909     case VariantFairy:\r
3910       pieces = fairyArray;\r
3911       SetCharTable(pieceToChar, "PNBRQFWEMOUHACGSKpnbrqfwemouhacgsk"); \r
3912       startedFromSetupPosition = TRUE;\r
3913       break;\r
3914     case VariantCrazyhouse:\r
3915     case VariantBughouse:\r
3916       pieces = FIDEArray;\r
3917       gameInfo.holdingsSize = 5;\r
3918       break;\r
3919     case VariantWildCastle:\r
3920       pieces = FIDEArray;\r
3921       /* !!?shuffle with kings guaranteed to be on d or e file */\r
3922       break;\r
3923     case VariantNoCastle:\r
3924       pieces = FIDEArray;\r
3925       nrCastlingRights = 0;\r
3926       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
3927       /* !!?unconstrained back-rank shuffle */\r
3928       break;\r
3929     }\r
3930 \r
3931     overrule = 0;\r
3932     if(appData.NrFiles >= 0) {\r
3933         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
3934         gameInfo.boardWidth = appData.NrFiles;\r
3935     }\r
3936     if(appData.NrRanks >= 0) {\r
3937         gameInfo.boardHeight = appData.NrRanks;\r
3938     }\r
3939     if(appData.holdingsSize >= 0) {\r
3940         i = appData.holdingsSize;\r
3941         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
3942         gameInfo.holdingsSize = i;\r
3943     }\r
3944     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
3945     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
3946         DisplayFatalError("Recompile to support this BOARD_SIZE!", 0, 2);\r
3947 \r
3948     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
3949     if(pawnRow < 1) pawnRow = 1;\r
3950 \r
3951     /* User pieceToChar list overrules defaults */\r
3952     if(appData.pieceToCharTable != NULL)\r
3953         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
3954 \r
3955     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
3956 \r
3957         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
3958             s = (ChessSquare) 0; /* account holding counts in guard band */\r
3959         for( i=0; i<BOARD_HEIGHT; i++ )\r
3960             initialPosition[i][j] = s;\r
3961 \r
3962         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
3963         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
3964         initialPosition[pawnRow][j] = WhitePawn;\r
3965         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
3966         if(gameInfo.variant == VariantXiangqi) {\r
3967             if(j&1) {\r
3968                 initialPosition[pawnRow][j] = \r
3969                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
3970                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
3971                    initialPosition[2][j] = WhiteCannon;\r
3972                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
3973                 }\r
3974             }\r
3975         }\r
3976         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
3977     }\r
3978     if( (gameInfo.variant == VariantShogi\r
3979        ||gameInfo.variant == VariantShowgi\r
3980                                          ) && !overrule ) {\r
3981             j=BOARD_LEFT+1;\r
3982             initialPosition[1][j] = WhiteBishop;\r
3983             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
3984             j=BOARD_RGHT-2;\r
3985             initialPosition[1][j] = WhiteRook;\r
3986             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
3987     }\r
3988 \r
3989     if( nrCastlingRights == -1) {\r
3990         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
3991         /*       This sets default castling rights from none to normal corners   */\r
3992         /* Variants with other castling rights must set them themselves above    */\r
3993         nrCastlingRights = 6;\r
3994        \r
3995         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
3996         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
3997         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
3998         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
3999         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4000         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4001 \r
4002         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4003         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4004      }\r
4005 \r
4006     if(gameInfo.variant == VariantFischeRandom) {\r
4007       if( appData.defaultFrcPosition < 0 ) {\r
4008         ShuffleFRC( initialPosition );\r
4009       }\r
4010       else {\r
4011         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4012       }\r
4013     }\r
4014 \r
4015     CopyBoard(boards[0], initialPosition);\r
4016 \r
4017     if(oldx != gameInfo.boardWidth ||\r
4018        oldy != gameInfo.boardHeight ||\r
4019        oldh != gameInfo.holdingsWidth\r
4020 #ifdef GOTHIC\r
4021        || oldv == VariantGothic ||\r
4022        gameInfo.variant == VariantGothic\r
4023 #endif\r
4024                                          )\r
4025             InitDrawingSizes(-2 ,0);\r
4026 \r
4027     if (redraw)\r
4028       DrawPosition(TRUE, boards[currentMove]);\r
4029 }\r
4030 \r
4031 void\r
4032 SendBoard(cps, moveNum)\r
4033      ChessProgramState *cps;\r
4034      int moveNum;\r
4035 {\r
4036     char message[MSG_SIZ];\r
4037     \r
4038     if (cps->useSetboard) {\r
4039       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
4040       sprintf(message, "setboard %s\n", fen);\r
4041       SendToProgram(message, cps);\r
4042       free(fen);\r
4043 \r
4044     } else {\r
4045       ChessSquare *bp;\r
4046       int i, j;\r
4047       /* Kludge to set black to move, avoiding the troublesome and now\r
4048        * deprecated "black" command.\r
4049        */\r
4050       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4051 \r
4052       SendToProgram("edit\n", cps);\r
4053       SendToProgram("#\n", cps);\r
4054       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4055         bp = &boards[moveNum][i][0];\r
4056         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4057           if ((int) *bp < (int) BlackPawn) {\r
4058             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4059                     AAA + j, ONE + i);\r
4060             SendToProgram(message, cps);\r
4061           }\r
4062         }\r
4063       }\r
4064     \r
4065       SendToProgram("c\n", cps);\r
4066       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4067         bp = &boards[moveNum][i][0];\r
4068         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4069           if (((int) *bp != (int) EmptySquare)\r
4070               && ((int) *bp >= (int) BlackPawn)) {\r
4071             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4072                     AAA + j, ONE + i);\r
4073             SendToProgram(message, cps);\r
4074           }\r
4075         }\r
4076       }\r
4077     \r
4078       SendToProgram(".\n", cps);\r
4079     }\r
4080 }\r
4081 \r
4082 int\r
4083 IsPromotion(fromX, fromY, toX, toY)\r
4084      int fromX, fromY, toX, toY;\r
4085 {\r
4086     /* [HGM] add Shogi promotions */\r
4087     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4088     ChessSquare piece;\r
4089 \r
4090     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4091       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4092    /* [HGM] Note to self: line above also weeds out drops */\r
4093     piece = boards[currentMove][fromY][fromX];\r
4094     if(gameInfo.variant == VariantShogi) {\r
4095         promotionZoneSize = 3;\r
4096         highestPromotingPiece = (int)WhiteKing;\r
4097         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4098            and if in normal chess we then allow promotion to King, why not\r
4099            allow promotion of other piece in Shogi?                         */\r
4100     }\r
4101     if((int)piece >= BlackPawn) {\r
4102         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4103              return FALSE;\r
4104         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4105     } else {\r
4106         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4107            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4108     }\r
4109     return ( (int)piece <= highestPromotingPiece );\r
4110 }\r
4111 \r
4112 int\r
4113 InPalace(row, column)\r
4114      int row, column;\r
4115 {   /* [HGM] for Xiangqi */\r
4116     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4117          column < (BOARD_WIDTH + 4)/2 &&\r
4118          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4119     return FALSE;\r
4120 }\r
4121 \r
4122 int\r
4123 PieceForSquare (x, y)\r
4124      int x;\r
4125      int y;\r
4126 {\r
4127   if (x < BOARD_LEFT || x >= BOARD_RGHT || y < 0 || y >= BOARD_HEIGHT)\r
4128      return -1;\r
4129   else\r
4130      return boards[currentMove][y][x];\r
4131 }\r
4132 \r
4133 int\r
4134 OKToStartUserMove(x, y)\r
4135      int x, y;\r
4136 {\r
4137     ChessSquare from_piece;\r
4138     int white_piece;\r
4139 \r
4140     if (matchMode) return FALSE;\r
4141     if (gameMode == EditPosition) return TRUE;\r
4142 \r
4143     if (x >= 0 && y >= 0)\r
4144       from_piece = boards[currentMove][y][x];\r
4145     else\r
4146       from_piece = EmptySquare;\r
4147 \r
4148     if (from_piece == EmptySquare) return FALSE;\r
4149 \r
4150     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4151       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4152 \r
4153     switch (gameMode) {\r
4154       case PlayFromGameFile:\r
4155       case AnalyzeFile:\r
4156       case TwoMachinesPlay:\r
4157       case EndOfGame:\r
4158         return FALSE;\r
4159 \r
4160       case IcsObserving:\r
4161       case IcsIdle:\r
4162         return FALSE;\r
4163 \r
4164       case MachinePlaysWhite:\r
4165       case IcsPlayingBlack:\r
4166         if (appData.zippyPlay) return FALSE;\r
4167         if (white_piece) {\r
4168             DisplayMoveError("You are playing Black");\r
4169             return FALSE;\r
4170         }\r
4171         break;\r
4172 \r
4173       case MachinePlaysBlack:\r
4174       case IcsPlayingWhite:\r
4175         if (appData.zippyPlay) return FALSE;\r
4176         if (!white_piece) {\r
4177             DisplayMoveError("You are playing White");\r
4178             return FALSE;\r
4179         }\r
4180         break;\r
4181 \r
4182       case EditGame:\r
4183         if (!white_piece && WhiteOnMove(currentMove)) {\r
4184             DisplayMoveError("It is White's turn");\r
4185             return FALSE;\r
4186         }           \r
4187         if (white_piece && !WhiteOnMove(currentMove)) {\r
4188             DisplayMoveError("It is Black's turn");\r
4189             return FALSE;\r
4190         }           \r
4191         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
4192             /* Editing correspondence game history */\r
4193             /* Could disallow this or prompt for confirmation */\r
4194             cmailOldMove = -1;\r
4195         }\r
4196         if (currentMove < forwardMostMove) {\r
4197             /* Discarding moves */\r
4198             /* Could prompt for confirmation here,\r
4199                but I don't think that's such a good idea */\r
4200             forwardMostMove = currentMove;\r
4201         }\r
4202         break;\r
4203 \r
4204       case BeginningOfGame:\r
4205         if (appData.icsActive) return FALSE;\r
4206         if (!appData.noChessProgram) {\r
4207             if (!white_piece) {\r
4208                 DisplayMoveError("You are playing White");\r
4209                 return FALSE;\r
4210             }\r
4211         }\r
4212         break;\r
4213         \r
4214       case Training:\r
4215         if (!white_piece && WhiteOnMove(currentMove)) {\r
4216             DisplayMoveError("It is White's turn");\r
4217             return FALSE;\r
4218         }           \r
4219         if (white_piece && !WhiteOnMove(currentMove)) {\r
4220             DisplayMoveError("It is Black's turn");\r
4221             return FALSE;\r
4222         }           \r
4223         break;\r
4224 \r
4225       default:\r
4226       case IcsExamining:\r
4227         break;\r
4228     }\r
4229     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
4230         && gameMode != AnalyzeFile && gameMode != Training) {\r
4231         DisplayMoveError("Displayed position is not current");\r
4232         return FALSE;\r
4233     }\r
4234     return TRUE;\r
4235 }\r
4236 \r
4237 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
4238 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
4239 int lastLoadGameUseList = FALSE;\r
4240 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
4241 ChessMove lastLoadGameStart = (ChessMove) 0;\r
4242 \r
4243 \r
4244 ChessMove\r
4245 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
4246      int fromX, fromY, toX, toY;\r
4247      int promoChar;\r
4248 {\r
4249     ChessMove moveType;\r
4250     ChessSquare pdown, pup;\r
4251 \r
4252     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
4253     if ((fromX == toX) && (fromY == toY)) {\r
4254         return ImpossibleMove;\r
4255     }\r
4256 \r
4257     /* Check if the user is playing in turn.  This is complicated because we\r
4258        let the user "pick up" a piece before it is his turn.  So the piece he\r
4259        tried to pick up may have been captured by the time he puts it down!\r
4260        Therefore we use the color the user is supposed to be playing in this\r
4261        test, not the color of the piece that is currently on the starting\r
4262        square---except in EditGame mode, where the user is playing both\r
4263        sides; fortunately there the capture race can't happen.  (It can\r
4264        now happen in IcsExamining mode, but that's just too bad.  The user\r
4265        will get a somewhat confusing message in that case.)\r
4266        */\r
4267 \r
4268     switch (gameMode) {\r
4269       case PlayFromGameFile:\r
4270       case AnalyzeFile:\r
4271       case TwoMachinesPlay:\r
4272       case EndOfGame:\r
4273       case IcsObserving:\r
4274       case IcsIdle:\r
4275         /* We switched into a game mode where moves are not accepted,\r
4276            perhaps while the mouse button was down. */\r
4277         return ImpossibleMove;\r
4278 \r
4279       case MachinePlaysWhite:\r
4280         /* User is moving for Black */\r
4281         if (WhiteOnMove(currentMove)) {\r
4282             DisplayMoveError("It is White's turn");\r
4283             return ImpossibleMove;\r
4284         }\r
4285         break;\r
4286 \r
4287       case MachinePlaysBlack:\r
4288         /* User is moving for White */\r
4289         if (!WhiteOnMove(currentMove)) {\r
4290             DisplayMoveError("It is Black's turn");\r
4291             return ImpossibleMove;\r
4292         }\r
4293         break;\r
4294 \r
4295       case EditGame:\r
4296       case IcsExamining:\r
4297       case BeginningOfGame:\r
4298       case AnalyzeMode:\r
4299       case Training:\r
4300         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
4301             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
4302             /* User is moving for Black */\r
4303             if (WhiteOnMove(currentMove)) {\r
4304                 DisplayMoveError("It is White's turn");\r
4305                 return ImpossibleMove;\r
4306             }\r
4307         } else {\r
4308             /* User is moving for White */\r
4309             if (!WhiteOnMove(currentMove)) {\r
4310                 DisplayMoveError("It is Black's turn");\r
4311                 return ImpossibleMove;\r
4312             }\r
4313         }\r
4314         break;\r
4315 \r
4316       case IcsPlayingBlack:\r
4317         /* User is moving for Black */\r
4318         if (WhiteOnMove(currentMove)) {\r
4319             if (!appData.premove) {\r
4320                 DisplayMoveError("It is White's turn");\r
4321             } else if (toX >= 0 && toY >= 0) {\r
4322                 premoveToX = toX;\r
4323                 premoveToY = toY;\r
4324                 premoveFromX = fromX;\r
4325                 premoveFromY = fromY;\r
4326                 premovePromoChar = promoChar;\r
4327                 gotPremove = 1;\r
4328                 if (appData.debugMode) \r
4329                     fprintf(debugFP, "Got premove: fromX %d,"\r
4330                             "fromY %d, toX %d, toY %d\n",\r
4331                             fromX, fromY, toX, toY);\r
4332             }\r
4333             return ImpossibleMove;\r
4334         }\r
4335         break;\r
4336 \r
4337       case IcsPlayingWhite:\r
4338         /* User is moving for White */\r
4339         if (!WhiteOnMove(currentMove)) {\r
4340             if (!appData.premove) {\r
4341                 DisplayMoveError("It is Black's turn");\r
4342             } else if (toX >= 0 && toY >= 0) {\r
4343                 premoveToX = toX;\r
4344                 premoveToY = toY;\r
4345                 premoveFromX = fromX;\r
4346                 premoveFromY = fromY;\r
4347                 premovePromoChar = promoChar;\r
4348                 gotPremove = 1;\r
4349                 if (appData.debugMode) \r
4350                     fprintf(debugFP, "Got premove: fromX %d,"\r
4351                             "fromY %d, toX %d, toY %d\n",\r
4352                             fromX, fromY, toX, toY);\r
4353             }\r
4354             return ImpossibleMove;\r
4355         }\r
4356         break;\r
4357 \r
4358       default:\r
4359         break;\r
4360 \r
4361       case EditPosition:\r
4362         /* EditPosition, empty square, or different color piece;\r
4363            click-click move is possible */\r
4364         if (toX == -2 || toY == -2) {\r
4365             boards[0][fromY][fromX] = EmptySquare;\r
4366             DrawPosition(FALSE, boards[currentMove]);\r
4367         } else if (toX >= 0 && toY >= 0) {\r
4368             boards[0][toY][toX] = boards[0][fromY][fromX];\r
4369             boards[0][fromY][fromX] = EmptySquare;\r
4370             DrawPosition(FALSE, boards[currentMove]);\r
4371         }\r
4372         return ImpossibleMove;\r
4373     }\r
4374 \r
4375     /* [HGM] suppress all moves into holdings area and guard band */\r
4376     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
4377             return ImpossibleMove;\r
4378 \r
4379     /* [HGM] <sameColor> moved to here from winboard.c */\r
4380     /* note: EditPosition already filtered out and performed! */\r
4381     pdown = boards[currentMove][fromY][fromX];\r
4382     pup = boards[currentMove][toY][toX];\r
4383     if ( \r
4384             (WhitePawn <= pdown && pdown < BlackPawn &&\r
4385              WhitePawn <= pup && pup < BlackPawn) ||\r
4386             (BlackPawn <= pdown && pdown < EmptySquare &&\r
4387              BlackPawn <= pup && pup < EmptySquare)      )\r
4388          return ImpossibleMove;\r
4389 \r
4390     /* [HGM] If move started in holdings, it means a drop */\r
4391     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
4392          if( pup != EmptySquare ) return ImpossibleMove;\r
4393          if(appData.testLegality) {\r
4394              /* it would be more logical if LegalityTest() also figured out\r
4395               * which drops are legal. For now we forbid pawns on back rank.\r
4396               * Shogi is on its own here...\r
4397               */\r
4398              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
4399                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
4400                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
4401          }\r
4402          return WhiteDrop; /* Not needed to specify white or black yet */\r
4403     }\r
4404 \r
4405     userOfferedDraw = FALSE;\r
4406         \r
4407     /* [HGM] always test for legality, to get promotion info */\r
4408     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
4409                           epStatus[currentMove], castlingRights[currentMove],\r
4410                                          fromY, fromX, toY, toX, promoChar);\r
4411 \r
4412     /* [HGM] but possibly ignore an IllegalMove result */\r
4413     if (appData.testLegality) {\r
4414         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
4415             DisplayMoveError("Illegal move");\r
4416             return ImpossibleMove;\r
4417         }\r
4418     }\r
4419 \r
4420     return moveType;\r
4421     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
4422        function is made into one that returns an OK move type if FinishMove\r
4423        should be called. This to give the calling driver routine the\r
4424        opportunity to finish the userMove input with a promotion popup,\r
4425        without bothering the user with this for invalid or illegal moves */\r
4426 \r
4427 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
4428 }\r
4429 \r
4430 /* Common tail of UserMoveEvent and DropMenuEvent */\r
4431 void\r
4432 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
4433      ChessMove moveType;\r
4434      int fromX, fromY, toX, toY;\r
4435      /*char*/int promoChar;\r
4436 {\r
4437     /* [HGM] <popupFix> kludge to avoid having know the exact promotion\r
4438        move type in caller when we know the move is a legal promotion */\r
4439     if(moveType == NormalMove)\r
4440         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
4441 \r
4442     /* [HGM] convert drag-and-drop piece drops to standard form */\r
4443     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
4444          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
4445          fromX = boards[currentMove][fromY][fromX];\r
4446          fromY = DROP_RANK;\r
4447     }\r
4448 \r
4449     /* [HGM] <popupFix> The following if has been moved here from\r
4450        UserMoveEnevt(). Because it seemed to belon here (why not allow\r
4451        piece drops in training games?), and because it can only be\r
4452        performed after it is known to what we promote. */\r
4453     if (gameMode == Training) {\r
4454       /* compare the move played on the board to the next move in the\r
4455        * game. If they match, display the move and the opponent's response. \r
4456        * If they don't match, display an error message.\r
4457        */\r
4458       int saveAnimate;\r
4459       Board testBoard;\r
4460       CopyBoard(testBoard, boards[currentMove]);\r
4461       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
4462 \r
4463       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
4464         ForwardInner(currentMove+1);\r
4465 \r
4466         /* Autoplay the opponent's response.\r
4467          * if appData.animate was TRUE when Training mode was entered,\r
4468          * the response will be animated.\r
4469          */\r
4470         saveAnimate = appData.animate;\r
4471         appData.animate = animateTraining;\r
4472         ForwardInner(currentMove+1);\r
4473         appData.animate = saveAnimate;\r
4474 \r
4475         /* check for the end of the game */\r
4476         if (currentMove >= forwardMostMove) {\r
4477           gameMode = PlayFromGameFile;\r
4478           ModeHighlight();\r
4479           SetTrainingModeOff();\r
4480           DisplayInformation("End of game");\r
4481         }\r
4482       } else {\r
4483         DisplayError("Incorrect move", 0);\r
4484       }\r
4485       return;\r
4486     }\r
4487 \r
4488   /* Ok, now we know that the move is good, so we can kill\r
4489      the previous line in Analysis Mode */\r
4490   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
4491     forwardMostMove = currentMove;\r
4492   }\r
4493 \r
4494   /* If we need the chess program but it's dead, restart it */\r
4495   ResurrectChessProgram();\r
4496 \r
4497   /* A user move restarts a paused game*/\r
4498   if (pausing)\r
4499     PauseEvent();\r
4500 \r
4501   thinkOutput[0] = NULLCHAR;\r
4502 \r
4503   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
4504 \r
4505   if (gameMode == BeginningOfGame) {\r
4506     if (appData.noChessProgram) {\r
4507       gameMode = EditGame;\r
4508       SetGameInfo();\r
4509     } else {\r
4510       char buf[MSG_SIZ];\r
4511       gameMode = MachinePlaysBlack;\r
4512       SetGameInfo();\r
4513       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
4514       DisplayTitle(buf);\r
4515       if (first.sendName) {\r
4516         sprintf(buf, "name %s\n", gameInfo.white);\r
4517         SendToProgram(buf, &first);\r
4518       }\r
4519     }\r
4520     ModeHighlight();\r
4521   }\r
4522 \r
4523   /* Relay move to ICS or chess engine */\r
4524   if (appData.icsActive) {\r
4525     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
4526         gameMode == IcsExamining) {\r
4527       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
4528       ics_user_moved = 1;\r
4529     }\r
4530   } else {\r
4531     if (first.sendTime && (gameMode == BeginningOfGame ||\r
4532                            gameMode == MachinePlaysWhite ||\r
4533                            gameMode == MachinePlaysBlack)) {\r
4534       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
4535     }\r
4536     SendMoveToProgram(forwardMostMove-1, &first);\r
4537     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
4538       first.maybeThinking = TRUE;\r
4539     }\r
4540     if (currentMove == cmailOldMove + 1) {\r
4541       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
4542     }\r
4543   }\r
4544 \r
4545   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4546 \r
4547   switch (gameMode) {\r
4548   case EditGame:\r
4549     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
4550                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
4551     case MT_NONE:\r
4552     case MT_CHECK:\r
4553       break;\r
4554     case MT_CHECKMATE:\r
4555       if (WhiteOnMove(currentMove)) {\r
4556         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
4557       } else {\r
4558         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
4559       }\r
4560       break;\r
4561     case MT_STALEMATE:\r
4562       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
4563       break;\r
4564     }\r
4565     break;\r
4566     \r
4567   case MachinePlaysBlack:\r
4568   case MachinePlaysWhite:\r
4569     /* disable certain menu options while machine is thinking */\r
4570     SetMachineThinkingEnables();\r
4571     break;\r
4572 \r
4573   default:\r
4574     break;\r
4575   }\r
4576 }\r
4577 \r
4578 void\r
4579 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
4580      int fromX, fromY, toX, toY;\r
4581      int promoChar;\r
4582 {\r
4583     /* [HGM] This routine was added to allow calling of its two logical\r
4584        parts from other modules in the old way. Before, UserMoveEvent()\r
4585        automatically called FinishMove() if the move was OK, and returned\r
4586        otherwise. I separated the two, in order to make it possible to\r
4587        slip a promotion popup in between. But that it always needs two\r
4588        calls, to the first part, (now called UserMoveTest() ), and to\r
4589        FinishMove if the first part succeeded. Calls that do not need\r
4590        to do anything in between, can call this routine the old way. \r
4591     */\r
4592     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
4593 \r
4594     if(moveType != ImpossibleMove)\r
4595         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
4596 }\r
4597 \r
4598 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
4599 {\r
4600     char * hint = lastHint;\r
4601     FrontEndProgramStats stats;\r
4602 \r
4603     stats.which = cps == &first ? 0 : 1;\r
4604     stats.depth = cpstats->depth;\r
4605     stats.nodes = cpstats->nodes;\r
4606     stats.score = cpstats->score;\r
4607     stats.time = cpstats->time;\r
4608     stats.pv = cpstats->movelist;\r
4609     stats.hint = lastHint;\r
4610     stats.an_move_index = 0;\r
4611     stats.an_move_count = 0;\r
4612 \r
4613     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
4614         stats.hint = cpstats->move_name;\r
4615         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
4616         stats.an_move_count = cpstats->nr_moves;\r
4617     }\r
4618 \r
4619     SetProgramStats( &stats );\r
4620 }\r
4621 \r
4622 void\r
4623 HandleMachineMove(message, cps)\r
4624      char *message;\r
4625      ChessProgramState *cps;\r
4626 {\r
4627     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
4628     char realname[MSG_SIZ];\r
4629     int fromX, fromY, toX, toY;\r
4630     ChessMove moveType;\r
4631     char promoChar;\r
4632     char *p;\r
4633     int machineWhite;\r
4634 \r
4635     /*\r
4636      * Kludge to ignore BEL characters\r
4637      */\r
4638     while (*message == '\007') message++;\r
4639 \r
4640     /*\r
4641      * Look for book output\r
4642      */\r
4643     if (cps == &first && bookRequested) {\r
4644         if (message[0] == '\t' || message[0] == ' ') {\r
4645             /* Part of the book output is here; append it */\r
4646             strcat(bookOutput, message);\r
4647             strcat(bookOutput, "  \n");\r
4648             return;\r
4649         } else if (bookOutput[0] != NULLCHAR) {\r
4650             /* All of book output has arrived; display it */\r
4651             char *p = bookOutput;\r
4652             while (*p != NULLCHAR) {\r
4653                 if (*p == '\t') *p = ' ';\r
4654                 p++;\r
4655             }\r
4656             DisplayInformation(bookOutput);\r
4657             bookRequested = FALSE;\r
4658             /* Fall through to parse the current output */\r
4659         }\r
4660     }\r
4661 \r
4662     /*\r
4663      * Look for machine move.\r
4664      */\r
4665     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
4666         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
4667     {\r
4668         /* This method is only useful on engines that support ping */\r
4669         if (cps->lastPing != cps->lastPong) {\r
4670           if (gameMode == BeginningOfGame) {\r
4671             /* Extra move from before last new; ignore */\r
4672             if (appData.debugMode) {\r
4673                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
4674             }\r
4675           } else {\r
4676             if (appData.debugMode) {\r
4677                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
4678                         cps->which, gameMode);\r
4679             }\r
4680 \r
4681             SendToProgram("undo\n", cps);\r
4682           }\r
4683           return;\r
4684         }\r
4685 \r
4686         switch (gameMode) {\r
4687           case BeginningOfGame:\r
4688             /* Extra move from before last reset; ignore */\r
4689             if (appData.debugMode) {\r
4690                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
4691             }\r
4692             return;\r
4693 \r
4694           case EndOfGame:\r
4695           case IcsIdle:\r
4696           default:\r
4697             /* Extra move after we tried to stop.  The mode test is\r
4698                not a reliable way of detecting this problem, but it's\r
4699                the best we can do on engines that don't support ping.\r
4700             */\r
4701             if (appData.debugMode) {\r
4702                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
4703                         cps->which, gameMode);\r
4704             }\r
4705             SendToProgram("undo\n", cps);\r
4706             return;\r
4707 \r
4708           case MachinePlaysWhite:\r
4709           case IcsPlayingWhite:\r
4710             machineWhite = TRUE;\r
4711             break;\r
4712 \r
4713           case MachinePlaysBlack:\r
4714           case IcsPlayingBlack:\r
4715             machineWhite = FALSE;\r
4716             break;\r
4717 \r
4718           case TwoMachinesPlay:\r
4719             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
4720             break;\r
4721         }\r
4722         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
4723             if (appData.debugMode) {\r
4724                 fprintf(debugFP,\r
4725                         "Ignoring move out of turn by %s, gameMode %d"\r
4726                         ", forwardMost %d\n",\r
4727                         cps->which, gameMode, forwardMostMove);\r
4728             }\r
4729             return;\r
4730         }\r
4731 \r
4732         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
4733                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
4734             /* Machine move could not be parsed; ignore it. */\r
4735             sprintf(buf1, "Illegal move \"%s\" from %s machine",\r
4736                     machineMove, cps->which);\r
4737             DisplayError(buf1, 0);\r
4738             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",\r
4739                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
4740             if (gameMode == TwoMachinesPlay) {\r
4741               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
4742                        buf1, GE_XBOARD);\r
4743             }\r
4744             return;\r
4745         }\r
4746 \r
4747         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
4748         /* So we have to redo legality test with true e.p. status here,  */\r
4749         /* to make sure an illegal e.p. capture does not slip through,   */\r
4750         /* to cause a forfeit on a justified illegal-move complaint      */\r
4751         /* of the opponent.                                              */\r
4752         if( gameMode==TwoMachinesPlay && appData.testLegality\r
4753             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
4754                                                               ) {\r
4755            ChessMove moveType;\r
4756            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
4757                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
4758                              fromY, fromX, toY, toX, promoChar);\r
4759             if (appData.debugMode) {\r
4760                 int i;\r
4761                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
4762                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
4763                 fprintf(debugFP, "castling rights\n");\r
4764             }\r
4765             if(moveType == IllegalMove) {\r
4766                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
4767                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
4768                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
4769                            buf1, GE_XBOARD);\r
4770            } else if(gameInfo.variant != VariantFischeRandom)\r
4771            /* [HGM] Kludge to handle engines that send FRC-style castling\r
4772               when they shouldn't (like TSCP-Gothic) */\r
4773            switch(moveType) {\r
4774              case WhiteASideCastleFR:\r
4775              case BlackASideCastleFR:\r
4776                toY++;\r
4777                currentMoveString[2]++;\r
4778                break;\r
4779              case WhiteHSideCastleFR:\r
4780              case BlackHSideCastleFR:\r
4781                toY--;\r
4782                currentMoveString[2]--;\r
4783                break;\r
4784            }\r
4785         }\r
4786         hintRequested = FALSE;\r
4787         lastHint[0] = NULLCHAR;\r
4788         bookRequested = FALSE;\r
4789         /* Program may be pondering now */\r
4790         cps->maybeThinking = TRUE;\r
4791         if (cps->sendTime == 2) cps->sendTime = 1;\r
4792         if (cps->offeredDraw) cps->offeredDraw--;\r
4793 \r
4794 #if ZIPPY\r
4795         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
4796             first.initDone) {\r
4797           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
4798           ics_user_moved = 1;\r
4799         }\r
4800 #endif\r
4801         /* currentMoveString is set as a side-effect of ParseOneMove */\r
4802         strcpy(machineMove, currentMoveString);\r
4803         strcat(machineMove, "\n");\r
4804         strcpy(moveList[forwardMostMove], machineMove);\r
4805 \r
4806         /* [AS] Save move info and clear stats for next move */\r
4807         pvInfoList[ forwardMostMove ].score = programStats.score;\r
4808         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
4809         pvInfoList[ forwardMostMove ].time = -1;\r
4810         ClearProgramStats();\r
4811         thinkOutput[0] = NULLCHAR;\r
4812         hiddenThinkOutputState = 0;\r
4813 \r
4814         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
4815 \r
4816         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
4817         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
4818             int count = 0;\r
4819 \r
4820             while( count < adjudicateLossPlies ) {\r
4821                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
4822 \r
4823                 if( count & 1 ) {\r
4824                     score = -score; /* Flip score for winning side */\r
4825                 }\r
4826 \r
4827                 if( score > adjudicateLossThreshold ) {\r
4828                     break;\r
4829                 }\r
4830 \r
4831                 count++;\r
4832             }\r
4833 \r
4834             if( count >= adjudicateLossPlies ) {\r
4835                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4836 \r
4837                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
4838                     "Xboard adjudication", \r
4839                     GE_XBOARD );\r
4840 \r
4841                 return;\r
4842             }\r
4843         }\r
4844 \r
4845 #ifdef ADJUDICATE // [HGM] some adjudications useful with buggy engines\r
4846 \r
4847         if( gameMode == TwoMachinesPlay && gameInfo.holdingsSize == 0) {\r
4848             int count = 0, epFile = epStatus[forwardMostMove];\r
4849 \r
4850             if(appData.testLegality && appData.checkMates) \r
4851             // don't wait for engine to announce game end if we can judge ourselves\r
4852             switch (MateTest(boards[forwardMostMove],\r
4853                                  PosFlags(forwardMostMove), epFile,\r
4854                                        castlingRights[forwardMostMove]) ) {\r
4855               case MT_NONE:\r
4856               case MT_CHECK:\r
4857               default:\r
4858                 break;\r
4859               case MT_STALEMATE:\r
4860                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4861                 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
4862                     GE_XBOARD );\r
4863                 break;\r
4864               case MT_CHECKMATE:\r
4865                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4866                 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
4867                     "Xboard adjudication: Checkmate", \r
4868                     GE_XBOARD );\r
4869                 break;\r
4870             }\r
4871 \r
4872             if( appData.testLegality )\r
4873             {   /* [HGM] Some more adjudications for obstinate engines */\r
4874                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
4875                     NrWQ=0, NrBQ=0,\r
4876                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;\r
4877                 static int moveCount;\r
4878 \r
4879                 /* First absolutely insufficient mating material. Count what is on board. */\r
4880                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
4881                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
4882                     int m=i;\r
4883 \r
4884                     switch((int) p)\r
4885                     {   /* count B,N,R and other of each side */\r
4886                         case WhiteKnight:\r
4887                              NrWN++; break;\r
4888                         case WhiteBishop:\r
4889                              NrWB++; break;\r
4890                         case BlackKnight:\r
4891                              NrWN++; break;\r
4892                         case BlackBishop:\r
4893                              NrBB++; break;\r
4894                         case WhiteRook:\r
4895                              NrWR++; break;\r
4896                         case BlackRook:\r
4897                              NrBR++; break;\r
4898                         case WhiteQueen:\r
4899                              NrWR++; break;\r
4900                         case BlackQueen:\r
4901                              NrBR++; break;\r
4902                         case EmptySquare: \r
4903                              break;\r
4904                         case BlackPawn:\r
4905                              m = 7-i;\r
4906                         case WhitePawn:\r
4907                              PawnAdvance += m; NrPawns++;\r
4908                     }\r
4909                     NrPieces += (p != EmptySquare);\r
4910                 }\r
4911 \r
4912                 if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2 )\r
4913                 {    /* KBK, KNK or KK */\r
4914 \r
4915                      /* always flag draws, for judging claims */\r
4916                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
4917 \r
4918                      if(appData.materialDraws) {\r
4919                          /* but only adjudicate them if adjudication enabled */\r
4920                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4921                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
4922                          return;\r
4923                      }\r
4924                 }\r
4925 \r
4926                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
4927                 if(NrPieces == 4 && \r
4928                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
4929                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
4930                    || NrWN==2 || NrBN==2     /* KNNK */\r
4931                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
4932                   ) ) {\r
4933                      if(--moveCount < 0 && appData.trivialDraws)\r
4934                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
4935                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4936                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
4937                           return;\r
4938                      }\r
4939                 } else moveCount = 6;\r
4940 \r
4941     if (appData.debugMode) { int i;\r
4942       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
4943               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
4944               appData.drawRepeats);\r
4945       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
4946            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
4947 \r
4948     }\r
4949                 /* Check for rep-draws */\r
4950                 count = 0;\r
4951                 for(k = forwardMostMove-2;\r
4952                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
4953                         epStatus[k] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
4954                     k-=2)\r
4955                 {   int rights=0;\r
4956     if (appData.debugMode) {\r
4957       fprintf(debugFP, " loop\n");\r
4958     }\r
4959                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
4960     if (appData.debugMode) {\r
4961       fprintf(debugFP, "match\n");\r
4962     }\r
4963                         /* compare castling rights */\r
4964                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
4965                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
4966                                 rights++; /* King lost rights, while rook still had them */\r
4967                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
4968                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
4969                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
4970                                    rights++; /* but at least one rook lost them */\r
4971                         }\r
4972                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
4973                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
4974                                 rights++; \r
4975                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
4976                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
4977                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
4978                                    rights++;\r
4979                         }\r
4980     if (appData.debugMode) {\r
4981       for(i=0; i<nrCastlingRights; i++)\r
4982       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
4983     }\r
4984 \r
4985     if (appData.debugMode) {\r
4986       fprintf(debugFP, " %d %d\n", rights, k);\r
4987     }\r
4988                         if( rights == 0 && ++count > appData.drawRepeats-2\r
4989                             && appData.drawRepeats > 1) {\r
4990                              /* adjudicate after user-specified nr of repeats */\r
4991                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4992                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
4993                              return;\r
4994                         }\r
4995                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
4996                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
4997                     }\r
4998                 }\r
4999 \r
5000                 /* Now we test for 50-move draws. Determine ply count */\r
5001                 count = forwardMostMove;\r
5002                 /* look for last irreversble move */\r
5003                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
5004                     count--;\r
5005                 /* if we hit starting position, add initial plies */\r
5006                 if( count == backwardMostMove )\r
5007                     count -= initialRulePlies;\r
5008                 count = forwardMostMove - count; \r
5009                 if( count >= 100)\r
5010                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
5011                          /* this is used to judge if draw claims are legal */\r
5012                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
5013                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5014                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
5015                          return;\r
5016                  }\r
5017             }\r
5018 \r
5019 \r
5020         }\r
5021 #endif\r
5022         if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
5023             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5024 \r
5025             GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
5026 \r
5027             return;\r
5028         }\r
5029 \r
5030         if (gameMode == TwoMachinesPlay) {\r
5031             if (cps->other->sendTime) {\r
5032                 SendTimeRemaining(cps->other,\r
5033                                   cps->other->twoMachinesColor[0] == 'w');\r
5034             }\r
5035             SendMoveToProgram(forwardMostMove-1, cps->other);\r
5036             if (firstMove) {\r
5037                 firstMove = FALSE;\r
5038                 if (cps->other->useColors) {\r
5039                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
5040                 }\r
5041                 SendToProgram("go\n", cps->other);\r
5042             }\r
5043             cps->other->maybeThinking = TRUE;\r
5044         }\r
5045 \r
5046         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5047         \r
5048         if (!pausing && appData.ringBellAfterMoves) {\r
5049             RingBell();\r
5050         }\r
5051 \r
5052         /* \r
5053          * Reenable menu items that were disabled while\r
5054          * machine was thinking\r
5055          */\r
5056         if (gameMode != TwoMachinesPlay)\r
5057             SetUserThinkingEnables();\r
5058 \r
5059         return;\r
5060     }\r
5061 \r
5062     /* Set special modes for chess engines.  Later something general\r
5063      *  could be added here; for now there is just one kludge feature,\r
5064      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
5065      *  when "xboard" is given as an interactive command.\r
5066      */\r
5067     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
5068         cps->useSigint = FALSE;\r
5069         cps->useSigterm = FALSE;\r
5070     }\r
5071 \r
5072     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
5073      * want this, I was asked to put it in, and obliged.\r
5074      */\r
5075     if (!strncmp(message, "setboard ", 9)) {\r
5076         Board initial_position; int i;\r
5077 \r
5078         GameEnds(GameIsDrawn, "Engine aborts game", GE_XBOARD);\r
5079 \r
5080         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
5081             DisplayError("Bad FEN received from engine", 0);\r
5082             return ;\r
5083         } else {\r
5084            Reset(FALSE, FALSE);\r
5085            CopyBoard(boards[0], initial_position);\r
5086            initialRulePlies = FENrulePlies;\r
5087            epStatus[0] = FENepStatus;\r
5088            for( i=0; i<nrCastlingRights; i++ )\r
5089                 castlingRights[0][i] = FENcastlingRights[i];\r
5090            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
5091            else gameMode = MachinePlaysBlack;                 \r
5092            DrawPosition(FALSE, boards[currentMove]);\r
5093         }\r
5094         return;\r
5095     }\r
5096 \r
5097     /*\r
5098      * Look for communication commands\r
5099      */\r
5100     if (!strncmp(message, "telluser ", 9)) {\r
5101         DisplayNote(message + 9);\r
5102         return;\r
5103     }\r
5104     if (!strncmp(message, "tellusererror ", 14)) {\r
5105         DisplayError(message + 14, 0);\r
5106         return;\r
5107     }\r
5108     if (!strncmp(message, "tellopponent ", 13)) {\r
5109       if (appData.icsActive) {\r
5110         if (loggedOn) {\r
5111           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
5112           SendToICS(buf1);\r
5113         }\r
5114       } else {\r
5115         DisplayNote(message + 13);\r
5116       }\r
5117       return;\r
5118     }\r
5119     if (!strncmp(message, "tellothers ", 11)) {\r
5120       if (appData.icsActive) {\r
5121         if (loggedOn) {\r
5122           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
5123           SendToICS(buf1);\r
5124         }\r
5125       }\r
5126       return;\r
5127     }\r
5128     if (!strncmp(message, "tellall ", 8)) {\r
5129       if (appData.icsActive) {\r
5130         if (loggedOn) {\r
5131           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
5132           SendToICS(buf1);\r
5133         }\r
5134       } else {\r
5135         DisplayNote(message + 8);\r
5136       }\r
5137       return;\r
5138     }\r
5139     if (strncmp(message, "warning", 7) == 0) {\r
5140         /* Undocumented feature, use tellusererror in new code */\r
5141         DisplayError(message, 0);\r
5142         return;\r
5143     }\r
5144     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
5145         strcpy(realname, cps->tidy);\r
5146         strcat(realname, " query");\r
5147         AskQuestion(realname, buf2, buf1, cps->pr);\r
5148         return;\r
5149     }\r
5150     /* Commands from the engine directly to ICS.  We don't allow these to be \r
5151      *  sent until we are logged on. Crafty kibitzes have been known to \r
5152      *  interfere with the login process.\r
5153      */\r
5154     if (loggedOn) {\r
5155         if (!strncmp(message, "tellics ", 8)) {\r
5156             SendToICS(message + 8);\r
5157             SendToICS("\n");\r
5158             return;\r
5159         }\r
5160         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
5161             SendToICS(ics_prefix);\r
5162             SendToICS(message + 15);\r
5163             SendToICS("\n");\r
5164             return;\r
5165         }\r
5166         /* The following are for backward compatibility only */\r
5167         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
5168             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
5169             SendToICS(ics_prefix);\r
5170             SendToICS(message);\r
5171             SendToICS("\n");\r
5172             return;\r
5173         }\r
5174     }\r
5175     if (strncmp(message, "feature ", 8) == 0) {\r
5176       ParseFeatures(message+8, cps);\r
5177     }\r
5178     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
5179       return;\r
5180     }\r
5181     /*\r
5182      * If the move is illegal, cancel it and redraw the board.\r
5183      * Also deal with other error cases.  Matching is rather loose\r
5184      * here to accommodate engines written before the spec.\r
5185      */\r
5186     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
5187         strncmp(message, "Error", 5) == 0) {\r
5188         if (StrStr(message, "name") || \r
5189             StrStr(message, "rating") || StrStr(message, "?") ||\r
5190             StrStr(message, "result") || StrStr(message, "board") ||\r
5191             StrStr(message, "bk") || StrStr(message, "computer") ||\r
5192             StrStr(message, "variant") || StrStr(message, "hint") ||\r
5193             StrStr(message, "random") || StrStr(message, "depth") ||\r
5194             StrStr(message, "accepted")) {\r
5195             return;\r
5196         }\r
5197         if (StrStr(message, "protover")) {\r
5198           /* Program is responding to input, so it's apparently done\r
5199              initializing, and this error message indicates it is\r
5200              protocol version 1.  So we don't need to wait any longer\r
5201              for it to initialize and send feature commands. */\r
5202           FeatureDone(cps, 1);\r
5203           cps->protocolVersion = 1;\r
5204           return;\r
5205         }\r
5206         cps->maybeThinking = FALSE;\r
5207 \r
5208         if (StrStr(message, "draw")) {\r
5209             /* Program doesn't have "draw" command */\r
5210             cps->sendDrawOffers = 0;\r
5211             return;\r
5212         }\r
5213         if (cps->sendTime != 1 &&\r
5214             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
5215           /* Program apparently doesn't have "time" or "otim" command */\r
5216           cps->sendTime = 0;\r
5217           return;\r
5218         }\r
5219         if (StrStr(message, "analyze")) {\r
5220             cps->analysisSupport = FALSE;\r
5221             cps->analyzing = FALSE;\r
5222             Reset(FALSE, TRUE);\r
5223             sprintf(buf2, "%s does not support analysis", cps->tidy);\r
5224             DisplayError(buf2, 0);\r
5225             return;\r
5226         }\r
5227         if (StrStr(message, "(no matching move)st")) {\r
5228           /* Special kludge for GNU Chess 4 only */\r
5229           cps->stKludge = TRUE;\r
5230           SendTimeControl(cps, movesPerSession, timeControl,\r
5231                           timeIncrement, appData.searchDepth,\r
5232                           searchTime);\r
5233           return;\r
5234         }\r
5235         if (StrStr(message, "(no matching move)sd")) {\r
5236           /* Special kludge for GNU Chess 4 only */\r
5237           cps->sdKludge = TRUE;\r
5238           SendTimeControl(cps, movesPerSession, timeControl,\r
5239                           timeIncrement, appData.searchDepth,\r
5240                           searchTime);\r
5241           return;\r
5242         }\r
5243         if (!StrStr(message, "llegal")) {\r
5244             return;\r
5245         }\r
5246         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
5247             gameMode == IcsIdle) return;\r
5248         if (forwardMostMove <= backwardMostMove) return;\r
5249 #if 0\r
5250         /* Following removed: it caused a bug where a real illegal move\r
5251            message in analyze mored would be ignored. */\r
5252         if (cps == &first && programStats.ok_to_send == 0) {\r
5253             /* Bogus message from Crafty responding to "."  This filtering\r
5254                can miss some of the bad messages, but fortunately the bug \r
5255                is fixed in current Crafty versions, so it doesn't matter. */\r
5256             return;\r
5257         }\r
5258 #endif\r
5259         if (pausing) PauseEvent();\r
5260         if (gameMode == PlayFromGameFile) {\r
5261             /* Stop reading this game file */\r
5262             gameMode = EditGame;\r
5263             ModeHighlight();\r
5264         }\r
5265         currentMove = --forwardMostMove;\r
5266         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
5267         SwitchClocks();\r
5268         DisplayBothClocks();\r
5269         sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",\r
5270                 parseList[currentMove], cps->which);\r
5271         DisplayMoveError(buf1);\r
5272         DrawPosition(FALSE, boards[currentMove]);\r
5273 \r
5274         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
5275         /* only passes fully legal moves                            */\r
5276         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
5277             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
5278                                 "False illegal-move claim", GE_XBOARD );\r
5279         }\r
5280         return;\r
5281     }\r
5282     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
5283         /* Program has a broken "time" command that\r
5284            outputs a string not ending in newline.\r
5285            Don't use it. */\r
5286         cps->sendTime = 0;\r
5287     }\r
5288     \r
5289     /*\r
5290      * If chess program startup fails, exit with an error message.\r
5291      * Attempts to recover here are futile.\r
5292      */\r
5293     if ((StrStr(message, "unknown host") != NULL)\r
5294         || (StrStr(message, "No remote directory") != NULL)\r
5295         || (StrStr(message, "not found") != NULL)\r
5296         || (StrStr(message, "No such file") != NULL)\r
5297         || (StrStr(message, "can't alloc") != NULL)\r
5298         || (StrStr(message, "Permission denied") != NULL)) {\r
5299 \r
5300         cps->maybeThinking = FALSE;\r
5301         sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",\r
5302                 cps->which, cps->program, cps->host, message);\r
5303         RemoveInputSource(cps->isr);\r
5304         DisplayFatalError(buf1, 0, 1);\r
5305         return;\r
5306     }\r
5307     \r
5308     /* \r
5309      * Look for hint output\r
5310      */\r
5311     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
5312         if (cps == &first && hintRequested) {\r
5313             hintRequested = FALSE;\r
5314             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
5315                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5316                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
5317                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
5318                                     fromY, fromX, toY, toX, promoChar, buf1);\r
5319                 sprintf(buf2, "Hint: %s", buf1);\r
5320                 DisplayInformation(buf2);\r
5321             } else {\r
5322                 /* Hint move could not be parsed!? */\r
5323                 sprintf(buf2,\r
5324                         "Illegal hint move \"%s\"\nfrom %s chess program",\r
5325                         buf1, cps->which);\r
5326                 DisplayError(buf2, 0);\r
5327             }\r
5328         } else {\r
5329             strcpy(lastHint, buf1);\r
5330         }\r
5331         return;\r
5332     }\r
5333 \r
5334     /*\r
5335      * Ignore other messages if game is not in progress\r
5336      */\r
5337     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
5338         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
5339 \r
5340     /*\r
5341      * look for win, lose, draw, or draw offer\r
5342      */\r
5343     if (strncmp(message, "1-0", 3) == 0) {\r
5344         char *p, *q, *r = "";\r
5345         p = strchr(message, '{');\r
5346         if (p) {\r
5347             q = strchr(p, '}');\r
5348             if (q) {\r
5349                 *q = NULLCHAR;\r
5350                 r = p + 1;\r
5351             }\r
5352         }\r
5353         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
5354         return;\r
5355     } else if (strncmp(message, "0-1", 3) == 0) {\r
5356         char *p, *q, *r = "";\r
5357         p = strchr(message, '{');\r
5358         if (p) {\r
5359             q = strchr(p, '}');\r
5360             if (q) {\r
5361                 *q = NULLCHAR;\r
5362                 r = p + 1;\r
5363             }\r
5364         }\r
5365         /* Kludge for Arasan 4.1 bug */\r
5366         if (strcmp(r, "Black resigns") == 0) {\r
5367             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
5368             return;\r
5369         }\r
5370         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
5371         return;\r
5372     } else if (strncmp(message, "1/2", 3) == 0) {\r
5373         char *p, *q, *r = "";\r
5374         p = strchr(message, '{');\r
5375         if (p) {\r
5376             q = strchr(p, '}');\r
5377             if (q) {\r
5378                 *q = NULLCHAR;\r
5379                 r = p + 1;\r
5380             }\r
5381         }\r
5382             \r
5383         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
5384         return;\r
5385 \r
5386     } else if (strncmp(message, "White resign", 12) == 0) {\r
5387         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
5388         return;\r
5389     } else if (strncmp(message, "Black resign", 12) == 0) {\r
5390         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
5391         return;\r
5392     } else if (strncmp(message, "White", 5) == 0 &&\r
5393                message[5] != '(' &&\r
5394                StrStr(message, "Black") == NULL) {\r
5395         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5396         return;\r
5397     } else if (strncmp(message, "Black", 5) == 0 &&\r
5398                message[5] != '(') {\r
5399         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5400         return;\r
5401     } else if (strcmp(message, "resign") == 0 ||\r
5402                strcmp(message, "computer resigns") == 0) {\r
5403         switch (gameMode) {\r
5404           case MachinePlaysBlack:\r
5405           case IcsPlayingBlack:\r
5406             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
5407             break;\r
5408           case MachinePlaysWhite:\r
5409           case IcsPlayingWhite:\r
5410             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
5411             break;\r
5412           case TwoMachinesPlay:\r
5413             if (cps->twoMachinesColor[0] == 'w')\r
5414               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
5415             else\r
5416               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
5417             break;\r
5418           default:\r
5419             /* can't happen */\r
5420             break;\r
5421         }\r
5422         return;\r
5423     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
5424         switch (gameMode) {\r
5425           case MachinePlaysBlack:\r
5426           case IcsPlayingBlack:\r
5427             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
5428             break;\r
5429           case MachinePlaysWhite:\r
5430           case IcsPlayingWhite:\r
5431             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
5432             break;\r
5433           case TwoMachinesPlay:\r
5434             if (cps->twoMachinesColor[0] == 'w')\r
5435               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5436             else\r
5437               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5438             break;\r
5439           default:\r
5440             /* can't happen */\r
5441             break;\r
5442         }\r
5443         return;\r
5444     } else if (strncmp(message, "computer mates", 14) == 0) {\r
5445         switch (gameMode) {\r
5446           case MachinePlaysBlack:\r
5447           case IcsPlayingBlack:\r
5448             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
5449             break;\r
5450           case MachinePlaysWhite:\r
5451           case IcsPlayingWhite:\r
5452             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
5453             break;\r
5454           case TwoMachinesPlay:\r
5455             if (cps->twoMachinesColor[0] == 'w')\r
5456               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5457             else\r
5458               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5459             break;\r
5460           default:\r
5461             /* can't happen */\r
5462             break;\r
5463         }\r
5464         return;\r
5465     } else if (strncmp(message, "checkmate", 9) == 0) {\r
5466         if (WhiteOnMove(forwardMostMove)) {\r
5467             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5468         } else {\r
5469             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5470         }\r
5471         return;\r
5472     } else if (strstr(message, "Draw") != NULL ||\r
5473                strstr(message, "game is a draw") != NULL) {\r
5474         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
5475         return;\r
5476     } else if (strstr(message, "offer") != NULL &&\r
5477                strstr(message, "draw") != NULL) {\r
5478 #if ZIPPY\r
5479         if (appData.zippyPlay && first.initDone) {\r
5480             /* Relay offer to ICS */\r
5481             SendToICS(ics_prefix);\r
5482             SendToICS("draw\n");\r
5483         }\r
5484 #endif\r
5485         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
5486         if (gameMode == TwoMachinesPlay) {\r
5487             if (cps->other->offeredDraw) {\r
5488                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
5489             } else {\r
5490                 if (cps->other->sendDrawOffers) {\r
5491                     SendToProgram("draw\n", cps->other);\r
5492                 }\r
5493             }\r
5494         } else if (gameMode == MachinePlaysWhite ||\r
5495                    gameMode == MachinePlaysBlack) {\r
5496           if (userOfferedDraw) {\r
5497             DisplayInformation("Machine accepts your draw offer");\r
5498             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
5499           } else {\r
5500             DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");\r
5501           }\r
5502         }\r
5503     }\r
5504 \r
5505     \r
5506     /*\r
5507      * Look for thinking output\r
5508      */\r
5509     if ( appData.showThinking) {\r
5510         int plylev, mvleft, mvtot, curscore, time;\r
5511         char mvname[MOVE_LEN];\r
5512         unsigned long nodes;\r
5513         char plyext;\r
5514         int ignore = FALSE;\r
5515         int prefixHint = FALSE;\r
5516         mvname[0] = NULLCHAR;\r
5517 \r
5518         switch (gameMode) {\r
5519           case MachinePlaysBlack:\r
5520           case IcsPlayingBlack:\r
5521             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
5522             break;\r
5523           case MachinePlaysWhite:\r
5524           case IcsPlayingWhite:\r
5525             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
5526             break;\r
5527           case AnalyzeMode:\r
5528           case AnalyzeFile:\r
5529             break;\r
5530           case TwoMachinesPlay:\r
5531             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
5532                 ignore = TRUE;\r
5533             }\r
5534             break;\r
5535           default:\r
5536             ignore = TRUE;\r
5537             break;\r
5538         }\r
5539 \r
5540         if (!ignore) {\r
5541             buf1[0] = NULLCHAR;\r
5542             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
5543                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
5544 \r
5545                 if (plyext != ' ' && plyext != '\t') {\r
5546                     time *= 100;\r
5547                 }\r
5548 \r
5549                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
5550                 if( cps->scoreIsAbsolute && \r
5551                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
5552                 {\r
5553                     curscore = -curscore;\r
5554                 }\r
5555 \r
5556 \r
5557                 programStats.depth = plylev;\r
5558                 programStats.nodes = nodes;\r
5559                 programStats.time = time;\r
5560                 programStats.score = curscore;\r
5561                 programStats.got_only_move = 0;\r
5562 \r
5563                 /* Buffer overflow protection */\r
5564                 if (buf1[0] != NULLCHAR) {\r
5565                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
5566                         && appData.debugMode) {\r
5567                         fprintf(debugFP,\r
5568                                 "PV is too long; using the first %d bytes.\n",\r
5569                                 sizeof(programStats.movelist) - 1);\r
5570                     }\r
5571 \r
5572                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
5573                 } else {\r
5574                     sprintf(programStats.movelist, " no PV\n");\r
5575                 }\r
5576 \r
5577                 if (programStats.seen_stat) {\r
5578                     programStats.ok_to_send = 1;\r
5579                 }\r
5580 \r
5581                 if (strchr(programStats.movelist, '(') != NULL) {\r
5582                     programStats.line_is_book = 1;\r
5583                     programStats.nr_moves = 0;\r
5584                     programStats.moves_left = 0;\r
5585                 } else {\r
5586                     programStats.line_is_book = 0;\r
5587                 }\r
5588 \r
5589                 SendProgramStatsToFrontend( cps, &programStats );\r
5590 \r
5591                 /* \r
5592                     [AS] Protect the thinkOutput buffer from overflow... this\r
5593                     is only useful if buf1 hasn't overflowed first!\r
5594                 */\r
5595                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
5596                         plylev, \r
5597                         (gameMode == TwoMachinesPlay ?\r
5598                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
5599                         ((double) curscore) / 100.0,\r
5600                         prefixHint ? lastHint : "",\r
5601                         prefixHint ? " " : "" );\r
5602 \r
5603                 if( buf1[0] != NULLCHAR ) {\r
5604                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
5605 \r
5606                     if( strlen(buf1) > max_len ) {\r
5607                         if( appData.debugMode) {\r
5608                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
5609                         }\r
5610                         buf1[max_len+1] = '\0';\r
5611                     }\r
5612 \r
5613                     strcat( thinkOutput, buf1 );\r
5614                 }\r
5615 \r
5616                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
5617                     DisplayMove(currentMove - 1);\r
5618                     DisplayAnalysis();\r
5619                 }\r
5620                 return;\r
5621 \r
5622             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
5623                 /* crafty (9.25+) says "(only move) <move>"\r
5624                  * if there is only 1 legal move\r
5625                  */\r
5626                 sscanf(p, "(only move) %s", buf1);\r
5627                 sprintf(thinkOutput, "%s (only move)", buf1);\r
5628                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
5629                 programStats.depth = 1;\r
5630                 programStats.nr_moves = 1;\r
5631                 programStats.moves_left = 1;\r
5632                 programStats.nodes = 1;\r
5633                 programStats.time = 1;\r
5634                 programStats.got_only_move = 1;\r
5635 \r
5636                 /* Not really, but we also use this member to\r
5637                    mean "line isn't going to change" (Crafty\r
5638                    isn't searching, so stats won't change) */\r
5639                 programStats.line_is_book = 1;\r
5640 \r
5641                 SendProgramStatsToFrontend( cps, &programStats );\r
5642                 \r
5643                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
5644                     DisplayMove(currentMove - 1);\r
5645                     DisplayAnalysis();\r
5646                 }\r
5647                 return;\r
5648             } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",\r
5649                               &time, &nodes, &plylev, &mvleft,\r
5650                               &mvtot, mvname) >= 5) {\r
5651                 /* The stat01: line is from Crafty (9.29+) in response\r
5652                    to the "." command */\r
5653                 programStats.seen_stat = 1;\r
5654                 cps->maybeThinking = TRUE;\r
5655 \r
5656                 if (programStats.got_only_move || !appData.periodicUpdates)\r
5657                   return;\r
5658 \r
5659                 programStats.depth = plylev;\r
5660                 programStats.time = time;\r
5661                 programStats.nodes = nodes;\r
5662                 programStats.moves_left = mvleft;\r
5663                 programStats.nr_moves = mvtot;\r
5664                 strcpy(programStats.move_name, mvname);\r
5665                 programStats.ok_to_send = 1;\r
5666                 programStats.movelist[0] = '\0';\r
5667 \r
5668                 SendProgramStatsToFrontend( cps, &programStats );\r
5669 \r
5670                 DisplayAnalysis();\r
5671                 return;\r
5672 \r
5673             } else if (strncmp(message,"++",2) == 0) {\r
5674                 /* Crafty 9.29+ outputs this */\r
5675                 programStats.got_fail = 2;\r
5676                 return;\r
5677 \r
5678             } else if (strncmp(message,"--",2) == 0) {\r
5679                 /* Crafty 9.29+ outputs this */\r
5680                 programStats.got_fail = 1;\r
5681                 return;\r
5682 \r
5683             } else if (thinkOutput[0] != NULLCHAR &&\r
5684                        strncmp(message, "    ", 4) == 0) {\r
5685                 unsigned message_len;\r
5686 \r
5687                 p = message;\r
5688                 while (*p && *p == ' ') p++;\r
5689 \r
5690                 message_len = strlen( p );\r
5691 \r
5692                 /* [AS] Avoid buffer overflow */\r
5693                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
5694                     strcat(thinkOutput, " ");\r
5695                     strcat(thinkOutput, p);\r
5696                 }\r
5697 \r
5698                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
5699                     strcat(programStats.movelist, " ");\r
5700                     strcat(programStats.movelist, p);\r
5701                 }\r
5702 \r
5703                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
5704                     DisplayMove(currentMove - 1);\r
5705                     DisplayAnalysis();\r
5706                 }\r
5707                 return;\r
5708             }\r
5709         }\r
5710         else {\r
5711             buf1[0] = NULLCHAR;\r
5712 \r
5713             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
5714                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
5715             {\r
5716                 ChessProgramStats cpstats;\r
5717 \r
5718                 if (plyext != ' ' && plyext != '\t') {\r
5719                     time *= 100;\r
5720                 }\r
5721 \r
5722                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
5723                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
5724                     curscore = -curscore;\r
5725                 }\r
5726 \r
5727                 cpstats.depth = plylev;\r
5728                 cpstats.nodes = nodes;\r
5729                 cpstats.time = time;\r
5730                 cpstats.score = curscore;\r
5731                 cpstats.got_only_move = 0;\r
5732                 cpstats.movelist[0] = '\0';\r
5733 \r
5734                 if (buf1[0] != NULLCHAR) {\r
5735                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
5736                 }\r
5737 \r
5738                 cpstats.ok_to_send = 0;\r
5739                 cpstats.line_is_book = 0;\r
5740                 cpstats.nr_moves = 0;\r
5741                 cpstats.moves_left = 0;\r
5742 \r
5743                 SendProgramStatsToFrontend( cps, &cpstats );\r
5744             }\r
5745         }\r
5746     }\r
5747 }\r
5748 \r
5749 \r
5750 /* Parse a game score from the character string "game", and\r
5751    record it as the history of the current game.  The game\r
5752    score is NOT assumed to start from the standard position. \r
5753    The display is not updated in any way.\r
5754    */\r
5755 void\r
5756 ParseGameHistory(game)\r
5757      char *game;\r
5758 {\r
5759     ChessMove moveType;\r
5760     int fromX, fromY, toX, toY, boardIndex;\r
5761     char promoChar;\r
5762     char *p, *q;\r
5763     char buf[MSG_SIZ];\r
5764 \r
5765     if (appData.debugMode)\r
5766       fprintf(debugFP, "Parsing game history: %s\n", game);\r
5767 \r
5768     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
5769     gameInfo.site = StrSave(appData.icsHost);\r
5770     gameInfo.date = PGNDate();\r
5771     gameInfo.round = StrSave("-");\r
5772 \r
5773     /* Parse out names of players */\r
5774     while (*game == ' ') game++;\r
5775     p = buf;\r
5776     while (*game != ' ') *p++ = *game++;\r
5777     *p = NULLCHAR;\r
5778     gameInfo.white = StrSave(buf);\r
5779     while (*game == ' ') game++;\r
5780     p = buf;\r
5781     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
5782     *p = NULLCHAR;\r
5783     gameInfo.black = StrSave(buf);\r
5784 \r
5785     /* Parse moves */\r
5786     boardIndex = blackPlaysFirst ? 1 : 0;\r
5787     yynewstr(game);\r
5788     for (;;) {\r
5789         yyboardindex = boardIndex;\r
5790         moveType = (ChessMove) yylex();\r
5791         switch (moveType) {\r
5792 #ifdef FAIRY\r
5793           case WhitePromotionChancellor:\r
5794           case BlackPromotionChancellor:\r
5795           case WhitePromotionArchbishop:\r
5796           case BlackPromotionArchbishop:\r
5797 #endif\r
5798           case WhitePromotionQueen:\r
5799           case BlackPromotionQueen:\r
5800           case WhitePromotionRook:\r
5801           case BlackPromotionRook:\r
5802           case WhitePromotionBishop:\r
5803           case BlackPromotionBishop:\r
5804           case WhitePromotionKnight:\r
5805           case BlackPromotionKnight:\r
5806           case WhitePromotionKing:\r
5807           case BlackPromotionKing:\r
5808           case NormalMove:\r
5809           case WhiteCapturesEnPassant:\r
5810           case BlackCapturesEnPassant:\r
5811           case WhiteKingSideCastle:\r
5812           case WhiteQueenSideCastle:\r
5813           case BlackKingSideCastle:\r
5814           case BlackQueenSideCastle:\r
5815           case WhiteKingSideCastleWild:\r
5816           case WhiteQueenSideCastleWild:\r
5817           case BlackKingSideCastleWild:\r
5818           case BlackQueenSideCastleWild:\r
5819           /* PUSH Fabien */\r
5820           case WhiteHSideCastleFR:\r
5821           case WhiteASideCastleFR:\r
5822           case BlackHSideCastleFR:\r
5823           case BlackASideCastleFR:\r
5824           /* POP Fabien */\r
5825           case IllegalMove:             /* maybe suicide chess, etc. */\r
5826             fromX = currentMoveString[0] - AAA;\r
5827             fromY = currentMoveString[1] - ONE;\r
5828             toX = currentMoveString[2] - AAA;\r
5829             toY = currentMoveString[3] - ONE;\r
5830             promoChar = currentMoveString[4];\r
5831             break;\r
5832           case WhiteDrop:\r
5833           case BlackDrop:\r
5834             fromX = moveType == WhiteDrop ?\r
5835               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
5836             (int) CharToPiece(ToLower(currentMoveString[0]));\r
5837             fromY = DROP_RANK;\r
5838             toX = currentMoveString[2] - AAA;\r
5839             toY = currentMoveString[3] - ONE;\r
5840             promoChar = NULLCHAR;\r
5841             break;\r
5842           case AmbiguousMove:\r
5843             /* bug? */\r
5844             sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);\r
5845             DisplayError(buf, 0);\r
5846             return;\r
5847           case ImpossibleMove:\r
5848             /* bug? */\r
5849             sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);\r
5850             DisplayError(buf, 0);\r
5851             return;\r
5852           case (ChessMove) 0:   /* end of file */\r
5853             if (boardIndex < backwardMostMove) {\r
5854                 /* Oops, gap.  How did that happen? */\r
5855                 DisplayError("Gap in move list", 0);\r
5856                 return;\r
5857             }\r
5858             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
5859             if (boardIndex > forwardMostMove) {\r
5860                 forwardMostMove = boardIndex;\r
5861             }\r
5862             return;\r
5863           case ElapsedTime:\r
5864             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
5865                 strcat(parseList[boardIndex-1], " ");\r
5866                 strcat(parseList[boardIndex-1], yy_text);\r
5867             }\r
5868             continue;\r
5869           case Comment:\r
5870           case PGNTag:\r
5871           case NAG:\r
5872           default:\r
5873             /* ignore */\r
5874             continue;\r
5875           case WhiteWins:\r
5876           case BlackWins:\r
5877           case GameIsDrawn:\r
5878           case GameUnfinished:\r
5879             if (gameMode == IcsExamining) {\r
5880                 if (boardIndex < backwardMostMove) {\r
5881                     /* Oops, gap.  How did that happen? */\r
5882                     return;\r
5883                 }\r
5884                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
5885                 return;\r
5886             }\r
5887             gameInfo.result = moveType;\r
5888             p = strchr(yy_text, '{');\r
5889             if (p == NULL) p = strchr(yy_text, '(');\r
5890             if (p == NULL) {\r
5891                 p = yy_text;\r
5892                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
5893             } else {\r
5894                 q = strchr(p, *p == '{' ? '}' : ')');\r
5895                 if (q != NULL) *q = NULLCHAR;\r
5896                 p++;\r
5897             }\r
5898             gameInfo.resultDetails = StrSave(p);\r
5899             continue;\r
5900         }\r
5901         if (boardIndex >= forwardMostMove &&\r
5902             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
5903             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
5904             return;\r
5905         }\r
5906         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
5907                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
5908                                  parseList[boardIndex]);\r
5909         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
5910         /* currentMoveString is set as a side-effect of yylex */\r
5911         strcpy(moveList[boardIndex], currentMoveString);\r
5912         strcat(moveList[boardIndex], "\n");\r
5913         boardIndex++;\r
5914         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
5915         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
5916                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
5917           case MT_NONE:\r
5918           case MT_STALEMATE:\r
5919           default:\r
5920             break;\r
5921           case MT_CHECK:\r
5922             if(gameInfo.variant != VariantShogi)\r
5923                 strcat(parseList[boardIndex - 1], "+");\r
5924             break;\r
5925           case MT_CHECKMATE:\r
5926             strcat(parseList[boardIndex - 1], "#");\r
5927             break;\r
5928         }\r
5929     }\r
5930 }\r
5931 \r
5932 \r
5933 /* Apply a move to the given board  */\r
5934 void\r
5935 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
5936      int fromX, fromY, toX, toY;\r
5937      int promoChar;\r
5938      Board board;\r
5939 {\r
5940   ChessSquare captured = board[toY][toX], piece; int p;\r
5941 \r
5942   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
5943   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
5944        && promoChar != 0) promoChar = 'F';\r
5945          \r
5946   if (fromX == toX && fromY == toY) return;\r
5947 \r
5948   if (fromY == DROP_RANK) {\r
5949         /* must be first */\r
5950         board[toY][toX] = (ChessSquare) fromX;\r
5951   } else {\r
5952      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
5953 \r
5954     /* Code added by Tord: */\r
5955     /* FRC castling assumed when king captures friendly rook. */\r
5956     if (board[fromY][fromX] == WhiteKing &&\r
5957              board[toY][toX] == WhiteRook) {\r
5958       board[fromY][fromX] = EmptySquare;\r
5959       board[toY][toX] = EmptySquare;\r
5960       if(toX > fromX) {\r
5961         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
5962       } else {\r
5963         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
5964       }\r
5965     } else if (board[fromY][fromX] == BlackKing &&\r
5966                board[toY][toX] == BlackRook) {\r
5967       board[fromY][fromX] = EmptySquare;\r
5968       board[toY][toX] = EmptySquare;\r
5969       if(toX > fromX) {\r
5970         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
5971       } else {\r
5972         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
5973       }\r
5974     /* End of code added by Tord */\r
5975 \r
5976     } else if (initialPosition[fromY][fromX] == WhiteKing\r
5977         && board[fromY][fromX] == WhiteKing\r
5978         && toY == fromY && toX > fromX+1) {\r
5979         board[fromY][fromX] = EmptySquare;\r
5980         board[toY][toX] = WhiteKing;\r
5981         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
5982         board[toY][toX-1] = WhiteRook;\r
5983     } else if (initialPosition[fromY][fromX] == WhiteKing\r
5984                && board[fromY][fromX] == WhiteKing\r
5985                && toY == fromY && toX < fromX-1) {\r
5986         board[fromY][fromX] = EmptySquare;\r
5987         board[toY][toX] = WhiteKing;\r
5988         board[fromY][BOARD_LEFT] = EmptySquare;\r
5989         board[toY][toX+1] = WhiteRook;\r
5990     } else if (fromY == 0 && fromX == 3\r
5991                && board[fromY][fromX] == WhiteKing\r
5992                && toY == 0 && toX == 5) {\r
5993         board[fromY][fromX] = EmptySquare;\r
5994         board[toY][toX] = WhiteKing;\r
5995         board[fromY][7] = EmptySquare;\r
5996         board[toY][4] = WhiteRook;\r
5997     } else if (fromY == 0 && fromX == 3\r
5998                && board[fromY][fromX] == WhiteKing\r
5999                && toY == 0 && toX == 1) {\r
6000         board[fromY][fromX] = EmptySquare;\r
6001         board[toY][toX] = WhiteKing;\r
6002         board[fromY][0] = EmptySquare;\r
6003         board[toY][2] = WhiteRook;\r
6004     } else if (board[fromY][fromX] == WhitePawn\r
6005                && toY == BOARD_HEIGHT-1\r
6006                && gameInfo.variant != VariantXiangqi\r
6007                ) {\r
6008         /* white pawn promotion */\r
6009         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
6010         if (board[toY][toX] == EmptySquare) {\r
6011             board[toY][toX] = WhiteQueen;\r
6012         }\r
6013         if(gameInfo.variant==VariantBughouse ||\r
6014            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
6015             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
6016         board[fromY][fromX] = EmptySquare;\r
6017     } else if ((fromY == BOARD_HEIGHT-4)\r
6018                && (toX != fromX)\r
6019                && (board[fromY][fromX] == WhitePawn)\r
6020                && (board[toY][toX] == EmptySquare)) {\r
6021         board[fromY][fromX] = EmptySquare;\r
6022         board[toY][toX] = WhitePawn;\r
6023         captured = board[toY - 1][toX];\r
6024         board[toY - 1][toX] = EmptySquare;\r
6025     } else if (initialPosition[fromY][fromX] == BlackKing\r
6026                && board[fromY][fromX] == BlackKing\r
6027                && toY == fromY && toX > fromX+1) {\r
6028         board[fromY][fromX] = EmptySquare;\r
6029         board[toY][toX] = BlackKing;\r
6030         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
6031         board[toY][toX-1] = BlackRook;\r
6032     } else if (initialPosition[fromY][fromX] == BlackKing\r
6033                && board[fromY][fromX] == BlackKing\r
6034                && toY == fromY && toX < fromX-1) {\r
6035         board[fromY][fromX] = EmptySquare;\r
6036         board[toY][toX] = BlackKing;\r
6037         board[fromY][BOARD_LEFT] = EmptySquare;\r
6038         board[toY][toX+1] = BlackRook;\r
6039     } else if (fromY == 7 && fromX == 3\r
6040                && board[fromY][fromX] == BlackKing\r
6041                && toY == 7 && toX == 5) {\r
6042         board[fromY][fromX] = EmptySquare;\r
6043         board[toY][toX] = BlackKing;\r
6044         board[fromY][7] = EmptySquare;\r
6045         board[toY][4] = BlackRook;\r
6046     } else if (fromY == 7 && fromX == 3\r
6047                && board[fromY][fromX] == BlackKing\r
6048                && toY == 7 && toX == 1) {\r
6049         board[fromY][fromX] = EmptySquare;\r
6050         board[toY][toX] = BlackKing;\r
6051         board[fromY][0] = EmptySquare;\r
6052         board[toY][2] = BlackRook;\r
6053     } else if (board[fromY][fromX] == BlackPawn\r
6054                && toY == 0\r
6055                && gameInfo.variant != VariantXiangqi\r
6056                ) {\r
6057         /* black pawn promotion */\r
6058         board[0][toX] = CharToPiece(ToLower(promoChar));\r
6059         if (board[0][toX] == EmptySquare) {\r
6060             board[0][toX] = BlackQueen;\r
6061         }\r
6062         if(gameInfo.variant==VariantBughouse ||\r
6063            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
6064             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
6065         board[fromY][fromX] = EmptySquare;\r
6066     } else if ((fromY == 3)\r
6067                && (toX != fromX)\r
6068                && (board[fromY][fromX] == BlackPawn)\r
6069                && (board[toY][toX] == EmptySquare)) {\r
6070         board[fromY][fromX] = EmptySquare;\r
6071         board[toY][toX] = BlackPawn;\r
6072         captured = board[toY + 1][toX];\r
6073         board[toY + 1][toX] = EmptySquare;\r
6074     } else {\r
6075         board[toY][toX] = board[fromY][fromX];\r
6076         board[fromY][fromX] = EmptySquare;\r
6077     }\r
6078 \r
6079     /* [HGM] now we promote for Shogi, if needed */\r
6080     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
6081         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
6082   }\r
6083 \r
6084     if (gameInfo.holdingsWidth != 0) {\r
6085 \r
6086       /* !!A lot more code needs to be written to support holdings  */\r
6087       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
6088       /* penultimate board files, so they are automaticlly stored   */\r
6089       /* in the game history.                                       */\r
6090       if (fromY == DROP_RANK) {\r
6091         /* Delete from holdings, by decreasing count */\r
6092         /* and erasing image if necessary            */\r
6093         p = (int) fromX;\r
6094         if(p < (int) BlackPawn) { /* white drop */\r
6095              p -= (int)WhitePawn;\r
6096              if(p >= gameInfo.holdingsSize) p = 0;\r
6097              if(--board[p][BOARD_WIDTH-2] == 0)\r
6098                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
6099         } else {                  /* black drop */\r
6100              p -= (int)BlackPawn;\r
6101              if(p >= gameInfo.holdingsSize) p = 0;\r
6102              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
6103                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
6104         }\r
6105       }\r
6106       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
6107           && gameInfo.variant != VariantBughouse        ) {\r
6108         /* Add to holdings, if holdings exist */\r
6109         p = (int) captured;\r
6110         if (p >= (int) BlackPawn) {\r
6111           p -= (int)BlackPawn;\r
6112           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
6113                   /* in Shogi restore piece to its original  first */\r
6114                   captured = (ChessSquare) (DEMOTED captured);\r
6115                   p = DEMOTED p;\r
6116           }\r
6117           p = PieceToNumber((ChessSquare)p);\r
6118           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
6119           board[p][BOARD_WIDTH-2]++;\r
6120           board[p][BOARD_WIDTH-1] =\r
6121                                    BLACK_TO_WHITE captured;\r
6122         } else {\r
6123           p -= (int)WhitePawn;\r
6124           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
6125                   captured = (ChessSquare) (DEMOTED captured);\r
6126                   p = DEMOTED p;\r
6127           }\r
6128           p = PieceToNumber((ChessSquare)p);\r
6129           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
6130           board[BOARD_HEIGHT-1-p][1]++;\r
6131           board[BOARD_HEIGHT-1-p][0] =\r
6132                                   WHITE_TO_BLACK captured;\r
6133         }\r
6134       }\r
6135 \r
6136     } else if (gameInfo.variant == VariantAtomic) {\r
6137       if (captured != EmptySquare) {\r
6138         int y, x;\r
6139         for (y = toY-1; y <= toY+1; y++) {\r
6140           for (x = toX-1; x <= toX+1; x++) {\r
6141             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
6142                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
6143               board[y][x] = EmptySquare;\r
6144             }\r
6145           }\r
6146         }\r
6147         board[toY][toX] = EmptySquare;\r
6148       }\r
6149     }\r
6150     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
6151         /* [HGM] Shogi promotions */\r
6152         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
6153     }\r
6154 \r
6155 }\r
6156 \r
6157 /* Updates forwardMostMove */\r
6158 void\r
6159 MakeMove(fromX, fromY, toX, toY, promoChar)\r
6160      int fromX, fromY, toX, toY;\r
6161      int promoChar;\r
6162 {\r
6163     forwardMostMove++;\r
6164 \r
6165     if (forwardMostMove >= MAX_MOVES) {\r
6166       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
6167                         0, 1);\r
6168       return;\r
6169     }\r
6170     SwitchClocks();\r
6171     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
6172     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
6173     if (commentList[forwardMostMove] != NULL) {\r
6174         free(commentList[forwardMostMove]);\r
6175         commentList[forwardMostMove] = NULL;\r
6176     }\r
6177     CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);\r
6178     /* [HGM] compute & store e.p. status and castling rights for new position */\r
6179     { int i, j;\r
6180 \r
6181       epStatus[forwardMostMove] = EP_NONE;\r
6182 \r
6183       if( boards[forwardMostMove][toY][toX] != EmptySquare ) \r
6184            epStatus[forwardMostMove] = EP_CAPTURE;  \r
6185 \r
6186       if( boards[forwardMostMove][fromY][fromX] == WhitePawn ) {\r
6187            epStatus[forwardMostMove] = EP_PAWN_MOVE; \r
6188            if( toY-fromY==2 &&\r
6189                (toX>BOARD_LEFT+1 && boards[forwardMostMove][toY][toX-1] == BlackPawn ||\r
6190                 toX<BOARD_RGHT-1 && boards[forwardMostMove][toY][toX+1] == BlackPawn ) )\r
6191               epStatus[forwardMostMove] = toX;\r
6192       } else \r
6193       if( boards[forwardMostMove][fromY][fromX] == BlackPawn ) {\r
6194            epStatus[forwardMostMove] = EP_PAWN_MOVE; \r
6195            if( toY-fromY== -2 &&\r
6196                (toX>BOARD_LEFT+1 && boards[forwardMostMove][toY][toX-1] == WhitePawn ||\r
6197                 toX<BOARD_RGHT-1 && boards[forwardMostMove][toY][toX+1] == WhitePawn ) )\r
6198               epStatus[forwardMostMove] = toX;\r
6199        }\r
6200 \r
6201        for(i=0; i<nrCastlingRights; i++) {\r
6202            castlingRights[forwardMostMove][i] = castlingRights[forwardMostMove-1][i];\r
6203            if(castlingRights[forwardMostMove][i] == fromX && castlingRank[i] == fromY ||\r
6204               castlingRights[forwardMostMove][i] == toX   && castlingRank[i] == toY   \r
6205              ) castlingRights[forwardMostMove][i] = -1; // revoke for moved or captured piece\r
6206 \r
6207        }\r
6208 \r
6209     }\r
6210     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);\r
6211     gameInfo.result = GameUnfinished;\r
6212     if (gameInfo.resultDetails != NULL) {\r
6213         free(gameInfo.resultDetails);\r
6214         gameInfo.resultDetails = NULL;\r
6215     }\r
6216     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
6217                               moveList[forwardMostMove - 1]);\r
6218     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
6219                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
6220                              fromY, fromX, toY, toX, promoChar,\r
6221                              parseList[forwardMostMove - 1]);\r
6222     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
6223                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
6224                             castlingRights[forwardMostMove]) ) {\r
6225       case MT_NONE:\r
6226       case MT_STALEMATE:\r
6227       default:\r
6228         break;\r
6229       case MT_CHECK:\r
6230         if(gameInfo.variant != VariantShogi)\r
6231             strcat(parseList[forwardMostMove - 1], "+");\r
6232         break;\r
6233       case MT_CHECKMATE:\r
6234         strcat(parseList[forwardMostMove - 1], "#");\r
6235         break;\r
6236     }\r
6237     if (appData.debugMode) {\r
6238         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
6239     }\r
6240 \r
6241 }\r
6242 \r
6243 /* Updates currentMove if not pausing */\r
6244 void\r
6245 ShowMove(fromX, fromY, toX, toY)\r
6246 {\r
6247     int instant = (gameMode == PlayFromGameFile) ?\r
6248         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
6249     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
6250         if (!instant) {\r
6251             if (forwardMostMove == currentMove + 1) {\r
6252                 AnimateMove(boards[forwardMostMove - 1],\r
6253                             fromX, fromY, toX, toY);\r
6254             }\r
6255             if (appData.highlightLastMove) {\r
6256                 SetHighlights(fromX, fromY, toX, toY);\r
6257             }\r
6258         }\r
6259         currentMove = forwardMostMove;\r
6260     }\r
6261 \r
6262     if (instant) return;\r
6263 \r
6264     DisplayMove(currentMove - 1);\r
6265     DrawPosition(FALSE, boards[currentMove]);\r
6266     DisplayBothClocks();\r
6267     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
6268 }\r
6269 \r
6270 \r
6271 void\r
6272 InitChessProgram(cps)\r
6273      ChessProgramState *cps;\r
6274 {\r
6275     char buf[MSG_SIZ], *b; int overruled;\r
6276     if (appData.noChessProgram) return;\r
6277     hintRequested = FALSE;\r
6278     bookRequested = FALSE;\r
6279     SendToProgram(cps->initString, cps);\r
6280     if (gameInfo.variant != VariantNormal &&\r
6281         gameInfo.variant != VariantLoadable\r
6282         /* [HGM] also send variant if board size non-standard */\r
6283         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8\r
6284                                             ) {\r
6285       char *v = VariantName(gameInfo.variant);\r
6286       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
6287         /* [HGM] in protocol 1 we have to assume all variants valid */\r
6288         sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);\r
6289         DisplayFatalError(buf, 0, 1);\r
6290         return;\r
6291       }\r
6292       b = buf;\r
6293       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
6294       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
6295       if( gameInfo.variant == VariantXiangqi )\r
6296            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
6297       if( gameInfo.variant == VariantShogi )\r
6298            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
6299       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
6300            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
6301       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantGothic )\r
6302            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
6303       if( gameInfo.variant == VariantCourier )\r
6304            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
6305 \r
6306       if(overruled) {\r
6307            if (cps->protocolVersion != 1 && StrStr(cps->variants, "boardsize") == NULL) {\r
6308              sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
6309                   gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
6310              DisplayFatalError(buf, 0, 1);\r
6311              return;\r
6312            }\r
6313            /* [HGM] here we really should compare with the maximum supported board size */\r
6314            sprintf(buf, "%dx%d+%d_", gameInfo.boardWidth,\r
6315                               gameInfo.boardHeight, gameInfo.holdingsSize );\r
6316            while(*b++ != '_');\r
6317       }\r
6318       sprintf(b, "variant %s\n", VariantName(gameInfo.variant));\r
6319       SendToProgram(buf, cps);\r
6320     }\r
6321     if (cps->sendICS) {\r
6322       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
6323       SendToProgram(buf, cps);\r
6324     }\r
6325     cps->maybeThinking = FALSE;\r
6326     cps->offeredDraw = 0;\r
6327     if (!appData.icsActive) {\r
6328         SendTimeControl(cps, movesPerSession, timeControl,\r
6329                         timeIncrement, appData.searchDepth,\r
6330                         searchTime);\r
6331     }\r
6332     if (appData.showThinking) {\r
6333         SendToProgram("post\n", cps);\r
6334     }\r
6335     SendToProgram("hard\n", cps);\r
6336     if (!appData.ponderNextMove) {\r
6337         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
6338            it without being sure what state we are in first.  "hard"\r
6339            is not a toggle, so that one is OK.\r
6340          */\r
6341         SendToProgram("easy\n", cps);\r
6342     }\r
6343     if (cps->usePing) {\r
6344       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
6345       SendToProgram(buf, cps);\r
6346     }\r
6347     cps->initDone = TRUE;\r
6348 }   \r
6349 \r
6350 \r
6351 void\r
6352 StartChessProgram(cps)\r
6353      ChessProgramState *cps;\r
6354 {\r
6355     char buf[MSG_SIZ];\r
6356     int err;\r
6357 \r
6358     if (appData.noChessProgram) return;\r
6359     cps->initDone = FALSE;\r
6360 \r
6361     if (strcmp(cps->host, "localhost") == 0) {\r
6362         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
6363     } else if (*appData.remoteShell == NULLCHAR) {\r
6364         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
6365     } else {\r
6366         if (*appData.remoteUser == NULLCHAR) {\r
6367             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
6368                     cps->program);\r
6369         } else {\r
6370             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
6371                     cps->host, appData.remoteUser, cps->program);\r
6372         }\r
6373         err = StartChildProcess(buf, "", &cps->pr);\r
6374     }\r
6375     \r
6376     if (err != 0) {\r
6377         sprintf(buf, "Startup failure on '%s'", cps->program);\r
6378         DisplayFatalError(buf, err, 1);\r
6379         cps->pr = NoProc;\r
6380         cps->isr = NULL;\r
6381         return;\r
6382     }\r
6383     \r
6384     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
6385     if (cps->protocolVersion > 1) {\r
6386       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
6387       SendToProgram(buf, cps);\r
6388     } else {\r
6389       SendToProgram("xboard\n", cps);\r
6390     }\r
6391 }\r
6392 \r
6393 \r
6394 void\r
6395 TwoMachinesEventIfReady P((void))\r
6396 {\r
6397   if (first.lastPing != first.lastPong) {\r
6398     DisplayMessage("", "Waiting for first chess program");\r
6399     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
6400     return;\r
6401   }\r
6402   if (second.lastPing != second.lastPong) {\r
6403     DisplayMessage("", "Waiting for second chess program");\r
6404     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
6405     return;\r
6406   }\r
6407   ThawUI();\r
6408   TwoMachinesEvent();\r
6409 }\r
6410 \r
6411 void\r
6412 NextMatchGame P((void))\r
6413 {\r
6414     Reset(FALSE, TRUE);\r
6415     if (*appData.loadGameFile != NULLCHAR) {\r
6416         LoadGameFromFile(appData.loadGameFile,\r
6417                          appData.loadGameIndex,\r
6418                          appData.loadGameFile, FALSE);\r
6419     } else if (*appData.loadPositionFile != NULLCHAR) {\r
6420         LoadPositionFromFile(appData.loadPositionFile,\r
6421                              appData.loadPositionIndex,\r
6422                              appData.loadPositionFile);\r
6423     }\r
6424     TwoMachinesEventIfReady();\r
6425 }\r
6426 \r
6427 void UserAdjudicationEvent( int result )\r
6428 {\r
6429     ChessMove gameResult = GameIsDrawn;\r
6430 \r
6431     if( result > 0 ) {\r
6432         gameResult = WhiteWins;\r
6433     }\r
6434     else if( result < 0 ) {\r
6435         gameResult = BlackWins;\r
6436     }\r
6437 \r
6438     if( gameMode == TwoMachinesPlay ) {\r
6439         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
6440     }\r
6441 }\r
6442 \r
6443 \r
6444 void\r
6445 GameEnds(result, resultDetails, whosays)\r
6446      ChessMove result;\r
6447      char *resultDetails;\r
6448      int whosays;\r
6449 {\r
6450     GameMode nextGameMode;\r
6451     int isIcsGame;\r
6452     char buf[MSG_SIZ];\r
6453 \r
6454     if (appData.debugMode) {\r
6455       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
6456               result, resultDetails ? resultDetails : "(null)", whosays);\r
6457     }\r
6458 \r
6459     if (appData.icsActive && whosays == (GE_ENGINE || whosays >= GE_ENGINE1)) {\r
6460         /* If we are playing on ICS, the server decides when the\r
6461            game is over, but the engine can offer to draw, claim \r
6462            a draw, or resign. \r
6463          */\r
6464 #if ZIPPY\r
6465         if (appData.zippyPlay && first.initDone) {\r
6466             if (result == GameIsDrawn) {\r
6467                 /* In case draw still needs to be claimed */\r
6468                 SendToICS(ics_prefix);\r
6469                 SendToICS("draw\n");\r
6470             } else if (StrCaseStr(resultDetails, "resign")) {\r
6471                 SendToICS(ics_prefix);\r
6472                 SendToICS("resign\n");\r
6473             }\r
6474         }\r
6475 #endif\r
6476         return;\r
6477     }\r
6478 \r
6479     /* If we're loading the game from a file, stop */\r
6480     if (whosays == GE_FILE) {\r
6481       (void) StopLoadGameTimer();\r
6482       gameFileFP = NULL;\r
6483     }\r
6484 \r
6485     /* Cancel draw offers */\r
6486    first.offeredDraw = second.offeredDraw = 0;\r
6487 \r
6488     /* If this is an ICS game, only ICS can really say it's done;\r
6489        if not, anyone can. */\r
6490     isIcsGame = (gameMode == IcsPlayingWhite || \r
6491                  gameMode == IcsPlayingBlack || \r
6492                  gameMode == IcsObserving    || \r
6493                  gameMode == IcsExamining);\r
6494 \r
6495     if (!isIcsGame || whosays == GE_ICS) {\r
6496         /* OK -- not an ICS game, or ICS said it was done */\r
6497         StopClocks();\r
6498     if (appData.debugMode) {\r
6499       fprintf(debugFP, "GameEnds(%d, %s, %d) clock stopped\n",\r
6500               result, resultDetails ? resultDetails : "(null)", whosays);\r
6501     }\r
6502         if (!isIcsGame && !appData.noChessProgram) \r
6503           SetUserThinkingEnables();\r
6504     \r
6505         /* [HGM] if a machine claims the game end we verify this claim */\r
6506         if( appData.testLegality && gameMode == TwoMachinesPlay &&\r
6507             appData.testClaims && whosays >= GE_ENGINE1 ) {\r
6508                 char claimer;\r
6509 \r
6510     if (appData.debugMode) {\r
6511       fprintf(debugFP, "GameEnds(%d, %s, %d) test claims\n",\r
6512               result, resultDetails ? resultDetails : "(null)", whosays);\r
6513     }\r
6514                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
6515                                             first.twoMachinesColor[0] :\r
6516                                             second.twoMachinesColor[0] ;\r
6517                 if( result == WhiteWins && claimer == 'w' ||\r
6518                     result == BlackWins && claimer == 'b' ) {\r
6519                       /* Xboard immediately adjudicates all mates, so win claims must be false */\r
6520                       sprintf(buf, "False win claim: '%s'", resultDetails);\r
6521                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
6522                       resultDetails = buf;\r
6523                 } else\r
6524                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS ) {\r
6525                       /* Draw that was not flagged by Xboard is false */\r
6526                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
6527                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
6528                       resultDetails = buf;\r
6529                 }\r
6530                 /* (Claiming a loss is accepted no questions asked!) */\r
6531         }\r
6532 \r
6533     if (appData.debugMode) {\r
6534       fprintf(debugFP, "GameEnds(%d, %s, %d) after test\n",\r
6535               result, resultDetails ? resultDetails : "(null)", whosays);\r
6536     }\r
6537         if (resultDetails != NULL) {\r
6538             gameInfo.result = result;\r
6539             gameInfo.resultDetails = StrSave(resultDetails);\r
6540 \r
6541             /* Tell program how game ended in case it is learning */\r
6542             if (gameMode == MachinePlaysWhite ||\r
6543                 gameMode == MachinePlaysBlack ||\r
6544                 gameMode == TwoMachinesPlay ||\r
6545                 gameMode == IcsPlayingWhite ||\r
6546                 gameMode == IcsPlayingBlack ||\r
6547                 gameMode == BeginningOfGame) {\r
6548                 char buf[MSG_SIZ];\r
6549                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
6550                         resultDetails);\r
6551                 if (first.pr != NoProc) {\r
6552                     SendToProgram(buf, &first);\r
6553                 }\r
6554                 if (second.pr != NoProc &&\r
6555                     gameMode == TwoMachinesPlay) {\r
6556                     SendToProgram(buf, &second);\r
6557                 }\r
6558             }\r
6559 \r
6560             /* display last move only if game was not loaded from file */\r
6561             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
6562                 DisplayMove(currentMove - 1);\r
6563     \r
6564             if (forwardMostMove != 0) {\r
6565                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
6566                     if (*appData.saveGameFile != NULLCHAR) {\r
6567                         SaveGameToFile(appData.saveGameFile, TRUE);\r
6568                     } else if (appData.autoSaveGames) {\r
6569                         AutoSaveGame();\r
6570                     }\r
6571                     if (*appData.savePositionFile != NULLCHAR) {\r
6572                         SavePositionToFile(appData.savePositionFile);\r
6573                     }\r
6574                 }\r
6575             }\r
6576         }\r
6577 \r
6578         if (appData.icsActive) {\r
6579             if (appData.quietPlay &&\r
6580                 (gameMode == IcsPlayingWhite ||\r
6581                  gameMode == IcsPlayingBlack)) {\r
6582                 SendToICS(ics_prefix);\r
6583                 SendToICS("set shout 1\n");\r
6584             }\r
6585             nextGameMode = IcsIdle;\r
6586             ics_user_moved = FALSE;\r
6587             /* clean up premove.  It's ugly when the game has ended and the\r
6588              * premove highlights are still on the board.\r
6589              */\r
6590             if (gotPremove) {\r
6591               gotPremove = FALSE;\r
6592               ClearPremoveHighlights();\r
6593               DrawPosition(FALSE, boards[currentMove]);\r
6594             }\r
6595             if (whosays == GE_ICS) {\r
6596                 switch (result) {\r
6597                 case WhiteWins:\r
6598                     if (gameMode == IcsPlayingWhite)\r
6599                         PlayIcsWinSound();\r
6600                     else if(gameMode == IcsPlayingBlack)\r
6601                         PlayIcsLossSound();\r
6602                     break;\r
6603                 case BlackWins:\r
6604                     if (gameMode == IcsPlayingBlack)\r
6605                         PlayIcsWinSound();\r
6606                     else if(gameMode == IcsPlayingWhite)\r
6607                         PlayIcsLossSound();\r
6608                     break;\r
6609                 case GameIsDrawn:\r
6610                     PlayIcsDrawSound();\r
6611                     break;\r
6612                 default:\r
6613                     PlayIcsUnfinishedSound();\r
6614                 }\r
6615             }\r
6616         } else if (gameMode == EditGame ||\r
6617                    gameMode == PlayFromGameFile || \r
6618                    gameMode == AnalyzeMode || \r
6619                    gameMode == AnalyzeFile) {\r
6620             nextGameMode = gameMode;\r
6621         } else {\r
6622             nextGameMode = EndOfGame;\r
6623         }\r
6624         pausing = FALSE;\r
6625         ModeHighlight();\r
6626     } else {\r
6627         nextGameMode = gameMode;\r
6628     }\r
6629 \r
6630     if (appData.noChessProgram) {\r
6631         gameMode = nextGameMode;\r
6632         ModeHighlight();\r
6633         return;\r
6634     }\r
6635 \r
6636     if (first.reuse) {\r
6637         /* Put first chess program into idle state */\r
6638         if (first.pr != NoProc &&\r
6639             (gameMode == MachinePlaysWhite ||\r
6640              gameMode == MachinePlaysBlack ||\r
6641              gameMode == TwoMachinesPlay ||\r
6642              gameMode == IcsPlayingWhite ||\r
6643              gameMode == IcsPlayingBlack ||\r
6644              gameMode == BeginningOfGame)) {\r
6645             SendToProgram("force\n", &first);\r
6646             if (first.usePing) {\r
6647               char buf[MSG_SIZ];\r
6648               sprintf(buf, "ping %d\n", ++first.lastPing);\r
6649               SendToProgram(buf, &first);\r
6650             }\r
6651         }\r
6652     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
6653         /* Kill off first chess program */\r
6654         if (first.isr != NULL)\r
6655           RemoveInputSource(first.isr);\r
6656         first.isr = NULL;\r
6657     \r
6658         if (first.pr != NoProc) {\r
6659             ExitAnalyzeMode();\r
6660             DoSleep( appData.delayBeforeQuit );\r
6661             SendToProgram("quit\n", &first);\r
6662             DoSleep( appData.delayAfterQuit );\r
6663             DestroyChildProcess(first.pr, first.useSigterm);\r
6664         }\r
6665         first.pr = NoProc;\r
6666     }\r
6667     if (second.reuse) {\r
6668         /* Put second chess program into idle state */\r
6669         if (second.pr != NoProc &&\r
6670             gameMode == TwoMachinesPlay) {\r
6671             SendToProgram("force\n", &second);\r
6672             if (second.usePing) {\r
6673               char buf[MSG_SIZ];\r
6674               sprintf(buf, "ping %d\n", ++second.lastPing);\r
6675               SendToProgram(buf, &second);\r
6676             }\r
6677         }\r
6678     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
6679         /* Kill off second chess program */\r
6680         if (second.isr != NULL)\r
6681           RemoveInputSource(second.isr);\r
6682         second.isr = NULL;\r
6683     \r
6684         if (second.pr != NoProc) {\r
6685             DoSleep( appData.delayBeforeQuit );\r
6686             SendToProgram("quit\n", &second);\r
6687             DoSleep( appData.delayAfterQuit );\r
6688             DestroyChildProcess(second.pr, second.useSigterm);\r
6689         }\r
6690         second.pr = NoProc;\r
6691     }\r
6692 \r
6693     if (matchMode && gameMode == TwoMachinesPlay) {\r
6694         switch (result) {\r
6695         case WhiteWins:\r
6696           if (first.twoMachinesColor[0] == 'w') {\r
6697             first.matchWins++;\r
6698           } else {\r
6699             second.matchWins++;\r
6700           }\r
6701           break;\r
6702         case BlackWins:\r
6703           if (first.twoMachinesColor[0] == 'b') {\r
6704             first.matchWins++;\r
6705           } else {\r
6706             second.matchWins++;\r
6707           }\r
6708           break;\r
6709         default:\r
6710           break;\r
6711         }\r
6712         if (matchGame < appData.matchGames) {\r
6713             char *tmp;\r
6714             tmp = first.twoMachinesColor;\r
6715             first.twoMachinesColor = second.twoMachinesColor;\r
6716             second.twoMachinesColor = tmp;\r
6717             gameMode = nextGameMode;\r
6718             matchGame++;\r
6719             if(appData.matchPause>10000 || appData.matchPause<10)\r
6720                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
6721             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
6722             return;\r
6723         } else {\r
6724             char buf[MSG_SIZ];\r
6725             gameMode = nextGameMode;\r
6726             sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",\r
6727                     first.tidy, second.tidy,\r
6728                     first.matchWins, second.matchWins,\r
6729                     appData.matchGames - (first.matchWins + second.matchWins));\r
6730             DisplayFatalError(buf, 0, 0);\r
6731         }\r
6732     }\r
6733     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
6734         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
6735       ExitAnalyzeMode();\r
6736     gameMode = nextGameMode;\r
6737     ModeHighlight();\r
6738 }\r
6739 \r
6740 /* Assumes program was just initialized (initString sent).\r
6741    Leaves program in force mode. */\r
6742 void\r
6743 FeedMovesToProgram(cps, upto) \r
6744      ChessProgramState *cps;\r
6745      int upto;\r
6746 {\r
6747     int i;\r
6748     \r
6749     if (appData.debugMode)\r
6750       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
6751               startedFromSetupPosition ? "position and " : "",\r
6752               backwardMostMove, upto, cps->which);\r
6753     SendToProgram("force\n", cps);\r
6754     if (startedFromSetupPosition) {\r
6755         SendBoard(cps, backwardMostMove);\r
6756     }\r
6757     for (i = backwardMostMove; i < upto; i++) {\r
6758         SendMoveToProgram(i, cps);\r
6759     }\r
6760 }\r
6761 \r
6762 \r
6763 void\r
6764 ResurrectChessProgram()\r
6765 {\r
6766      /* The chess program may have exited.\r
6767         If so, restart it and feed it all the moves made so far. */\r
6768 \r
6769     if (appData.noChessProgram || first.pr != NoProc) return;\r
6770     \r
6771     StartChessProgram(&first);\r
6772     InitChessProgram(&first);\r
6773     FeedMovesToProgram(&first, currentMove);\r
6774 \r
6775     if (!first.sendTime) {\r
6776         /* can't tell gnuchess what its clock should read,\r
6777            so we bow to its notion. */\r
6778         ResetClocks();\r
6779         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
6780         timeRemaining[1][currentMove] = blackTimeRemaining;\r
6781     }\r
6782 \r
6783     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
6784         first.analysisSupport) {\r
6785       SendToProgram("analyze\n", &first);\r
6786       first.analyzing = TRUE;\r
6787     }\r
6788 }\r
6789 \r
6790 /*\r
6791  * Button procedures\r
6792  */\r
6793 void\r
6794 Reset(redraw, init)\r
6795      int redraw, init;\r
6796 {\r
6797     int i;\r
6798 \r
6799     if (appData.debugMode) {\r
6800         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
6801                 redraw, init, gameMode);\r
6802     }\r
6803 \r
6804     pausing = pauseExamInvalid = FALSE;\r
6805     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
6806     firstMove = TRUE;\r
6807     whiteFlag = blackFlag = FALSE;\r
6808     userOfferedDraw = FALSE;\r
6809     hintRequested = bookRequested = FALSE;\r
6810     first.maybeThinking = FALSE;\r
6811     second.maybeThinking = FALSE;\r
6812     thinkOutput[0] = NULLCHAR;\r
6813     lastHint[0] = NULLCHAR;\r
6814     ClearGameInfo(&gameInfo);\r
6815     gameInfo.variant = StringToVariant(appData.variant);\r
6816     ics_user_moved = ics_clock_paused = FALSE;\r
6817     ics_getting_history = H_FALSE;\r
6818     ics_gamenum = -1;\r
6819     white_holding[0] = black_holding[0] = NULLCHAR;\r
6820     ClearProgramStats();\r
6821     \r
6822     ResetFrontEnd();\r
6823     ClearHighlights();\r
6824     flipView = appData.flipView;\r
6825     ClearPremoveHighlights();\r
6826     gotPremove = FALSE;\r
6827     alarmSounded = FALSE;\r
6828 \r
6829     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
6830     ExitAnalyzeMode();\r
6831     gameMode = BeginningOfGame;\r
6832     ModeHighlight();\r
6833     InitPosition(redraw);\r
6834     for (i = 0; i < MAX_MOVES; i++) {\r
6835         if (commentList[i] != NULL) {\r
6836             free(commentList[i]);\r
6837             commentList[i] = NULL;\r
6838         }\r
6839     }\r
6840     ResetClocks();\r
6841     timeRemaining[0][0] = whiteTimeRemaining;\r
6842     timeRemaining[1][0] = blackTimeRemaining;\r
6843     if (first.pr == NULL) {\r
6844         StartChessProgram(&first);\r
6845     }\r
6846     if (init) InitChessProgram(&first);\r
6847     DisplayTitle("");\r
6848     DisplayMessage("", "");\r
6849     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
6850 }\r
6851 \r
6852 void\r
6853 AutoPlayGameLoop()\r
6854 {\r
6855     for (;;) {\r
6856         if (!AutoPlayOneMove())\r
6857           return;\r
6858         if (matchMode || appData.timeDelay == 0)\r
6859           continue;\r
6860         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
6861           return;\r
6862         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
6863         break;\r
6864     }\r
6865 }\r
6866 \r
6867 \r
6868 int\r
6869 AutoPlayOneMove()\r
6870 {\r
6871     int fromX, fromY, toX, toY;\r
6872 \r
6873     if (appData.debugMode) {\r
6874       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
6875     }\r
6876 \r
6877     if (gameMode != PlayFromGameFile)\r
6878       return FALSE;\r
6879 \r
6880     if (currentMove >= forwardMostMove) {\r
6881       gameMode = EditGame;\r
6882       ModeHighlight();\r
6883 \r
6884       /* [AS] Clear current move marker at the end of a game */\r
6885       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
6886 \r
6887       return FALSE;\r
6888     }\r
6889     \r
6890     toX = moveList[currentMove][2] - AAA;\r
6891     toY = moveList[currentMove][3] - ONE;\r
6892 \r
6893     if (moveList[currentMove][1] == '@') {\r
6894         if (appData.highlightLastMove) {\r
6895             SetHighlights(-1, -1, toX, toY);\r
6896         }\r
6897     } else {\r
6898         fromX = moveList[currentMove][0] - AAA;\r
6899         fromY = moveList[currentMove][1] - ONE;\r
6900 \r
6901         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
6902 \r
6903         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
6904 \r
6905         if (appData.highlightLastMove) {\r
6906             SetHighlights(fromX, fromY, toX, toY);\r
6907         }\r
6908     }\r
6909     DisplayMove(currentMove);\r
6910     SendMoveToProgram(currentMove++, &first);\r
6911     DisplayBothClocks();\r
6912     DrawPosition(FALSE, boards[currentMove]);\r
6913     if (commentList[currentMove] != NULL) {\r
6914         DisplayComment(currentMove - 1, commentList[currentMove]);\r
6915     }\r
6916     return TRUE;\r
6917 }\r
6918 \r
6919 \r
6920 int\r
6921 LoadGameOneMove(readAhead)\r
6922      ChessMove readAhead;\r
6923 {\r
6924     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
6925     char promoChar = NULLCHAR;\r
6926     ChessMove moveType;\r
6927     char move[MSG_SIZ];\r
6928     char *p, *q;\r
6929     \r
6930     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
6931         gameMode != AnalyzeMode && gameMode != Training) {\r
6932         gameFileFP = NULL;\r
6933         return FALSE;\r
6934     }\r
6935     \r
6936     yyboardindex = forwardMostMove;\r
6937     if (readAhead != (ChessMove)0) {\r
6938       moveType = readAhead;\r
6939     } else {\r
6940       if (gameFileFP == NULL)\r
6941           return FALSE;\r
6942       moveType = (ChessMove) yylex();\r
6943     }\r
6944     \r
6945     done = FALSE;\r
6946     switch (moveType) {\r
6947       case Comment:\r
6948         if (appData.debugMode) \r
6949           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
6950         p = yy_text;\r
6951         if (*p == '{' || *p == '[' || *p == '(') {\r
6952             p[strlen(p) - 1] = NULLCHAR;\r
6953             p++;\r
6954         }\r
6955 \r
6956         /* append the comment but don't display it */\r
6957         while (*p == '\n') p++;\r
6958         AppendComment(currentMove, p);\r
6959         return TRUE;\r
6960 \r
6961       case WhiteCapturesEnPassant:\r
6962       case BlackCapturesEnPassant:\r
6963       case WhitePromotionChancellor:\r
6964       case BlackPromotionChancellor:\r
6965       case WhitePromotionArchbishop:\r
6966       case BlackPromotionArchbishop:\r
6967       case WhitePromotionQueen:\r
6968       case BlackPromotionQueen:\r
6969       case WhitePromotionRook:\r
6970       case BlackPromotionRook:\r
6971       case WhitePromotionBishop:\r
6972       case BlackPromotionBishop:\r
6973       case WhitePromotionKnight:\r
6974       case BlackPromotionKnight:\r
6975       case WhitePromotionKing:\r
6976       case BlackPromotionKing:\r
6977       case NormalMove:\r
6978       case WhiteKingSideCastle:\r
6979       case WhiteQueenSideCastle:\r
6980       case BlackKingSideCastle:\r
6981       case BlackQueenSideCastle:\r
6982       case WhiteKingSideCastleWild:\r
6983       case WhiteQueenSideCastleWild:\r
6984       case BlackKingSideCastleWild:\r
6985       case BlackQueenSideCastleWild:\r
6986       /* PUSH Fabien */\r
6987       case WhiteHSideCastleFR:\r
6988       case WhiteASideCastleFR:\r
6989       case BlackHSideCastleFR:\r
6990       case BlackASideCastleFR:\r
6991       /* POP Fabien */\r
6992         if (appData.debugMode)\r
6993           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
6994         fromX = currentMoveString[0] - AAA;\r
6995         fromY = currentMoveString[1] - ONE;\r
6996         toX = currentMoveString[2] - AAA;\r
6997         toY = currentMoveString[3] - ONE;\r
6998         promoChar = currentMoveString[4];\r
6999         break;\r
7000 \r
7001       case WhiteDrop:\r
7002       case BlackDrop:\r
7003         if (appData.debugMode)\r
7004           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
7005         fromX = moveType == WhiteDrop ?\r
7006           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
7007         (int) CharToPiece(ToLower(currentMoveString[0]));\r
7008         fromY = DROP_RANK;\r
7009         toX = currentMoveString[2] - AAA;\r
7010         toY = currentMoveString[3] - ONE;\r
7011         break;\r
7012 \r
7013       case WhiteWins:\r
7014       case BlackWins:\r
7015       case GameIsDrawn:\r
7016       case GameUnfinished:\r
7017         if (appData.debugMode)\r
7018           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
7019         p = strchr(yy_text, '{');\r
7020         if (p == NULL) p = strchr(yy_text, '(');\r
7021         if (p == NULL) {\r
7022             p = yy_text;\r
7023             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
7024         } else {\r
7025             q = strchr(p, *p == '{' ? '}' : ')');\r
7026             if (q != NULL) *q = NULLCHAR;\r
7027             p++;\r
7028         }\r
7029         GameEnds(moveType, p, GE_FILE);\r
7030         done = TRUE;\r
7031         if (cmailMsgLoaded) {\r
7032             ClearHighlights();\r
7033             flipView = WhiteOnMove(currentMove);\r
7034             if (moveType == GameUnfinished) flipView = !flipView;\r
7035             if (appData.debugMode)\r
7036               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
7037         }\r
7038         break;\r
7039 \r
7040       case (ChessMove) 0:       /* end of file */\r
7041         if (appData.debugMode)\r
7042           fprintf(debugFP, "Parser hit end of file\n");\r
7043         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
7044                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
7045           case MT_NONE:\r
7046           case MT_CHECK:\r
7047             break;\r
7048           case MT_CHECKMATE:\r
7049             if (WhiteOnMove(currentMove)) {\r
7050                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
7051             } else {\r
7052                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
7053             }\r
7054             break;\r
7055           case MT_STALEMATE:\r
7056             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
7057             break;\r
7058         }\r
7059         done = TRUE;\r
7060         break;\r
7061 \r
7062       case MoveNumberOne:\r
7063         if (lastLoadGameStart == GNUChessGame) {\r
7064             /* GNUChessGames have numbers, but they aren't move numbers */\r
7065             if (appData.debugMode)\r
7066               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
7067                       yy_text, (int) moveType);\r
7068             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
7069         }\r
7070         /* else fall thru */\r
7071 \r
7072       case XBoardGame:\r
7073       case GNUChessGame:\r
7074       case PGNTag:\r
7075         /* Reached start of next game in file */\r
7076         if (appData.debugMode)\r
7077           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
7078         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
7079                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
7080           case MT_NONE:\r
7081           case MT_CHECK:\r
7082             break;\r
7083           case MT_CHECKMATE:\r
7084             if (WhiteOnMove(currentMove)) {\r
7085                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
7086             } else {\r
7087                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
7088             }\r
7089             break;\r
7090           case MT_STALEMATE:\r
7091             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
7092             break;\r
7093         }\r
7094         done = TRUE;\r
7095         break;\r
7096 \r
7097       case PositionDiagram:     /* should not happen; ignore */\r
7098       case ElapsedTime:         /* ignore */\r
7099       case NAG:                 /* ignore */\r
7100         if (appData.debugMode)\r
7101           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
7102                   yy_text, (int) moveType);\r
7103         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
7104 \r
7105       case IllegalMove:\r
7106         if (appData.testLegality) {\r
7107             if (appData.debugMode)\r
7108               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
7109             sprintf(move, "Illegal move: %d.%s%s",\r
7110                     (forwardMostMove / 2) + 1,\r
7111                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
7112             DisplayError(move, 0);\r
7113             done = TRUE;\r
7114         } else {\r
7115             if (appData.debugMode)\r
7116               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
7117                       yy_text, currentMoveString);\r
7118             fromX = currentMoveString[0] - AAA;\r
7119             fromY = currentMoveString[1] - ONE;\r
7120             toX = currentMoveString[2] - AAA;\r
7121             toY = currentMoveString[3] - ONE;\r
7122             promoChar = currentMoveString[4];\r
7123         }\r
7124         break;\r
7125 \r
7126       case AmbiguousMove:\r
7127         if (appData.debugMode)\r
7128           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
7129         sprintf(move, "Ambiguous move: %d.%s%s",\r
7130                 (forwardMostMove / 2) + 1,\r
7131                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
7132         DisplayError(move, 0);\r
7133         done = TRUE;\r
7134         break;\r
7135 \r
7136       default:\r
7137       case ImpossibleMove:\r
7138         if (appData.debugMode)\r
7139           fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);\r
7140         sprintf(move, "Illegal move: %d.%s%s",\r
7141                 (forwardMostMove / 2) + 1,\r
7142                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
7143         DisplayError(move, 0);\r
7144         done = TRUE;\r
7145         break;\r
7146     }\r
7147 \r
7148     if (done) {\r
7149         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
7150             DrawPosition(FALSE, boards[currentMove]);\r
7151             DisplayBothClocks();\r
7152             if (!appData.matchMode && commentList[currentMove] != NULL)\r
7153               DisplayComment(currentMove - 1, commentList[currentMove]);\r
7154         }\r
7155         (void) StopLoadGameTimer();\r
7156         gameFileFP = NULL;\r
7157         cmailOldMove = forwardMostMove;\r
7158         return FALSE;\r
7159     } else {\r
7160         /* currentMoveString is set as a side-effect of yylex */\r
7161         strcat(currentMoveString, "\n");\r
7162         strcpy(moveList[forwardMostMove], currentMoveString);\r
7163         \r
7164         thinkOutput[0] = NULLCHAR;\r
7165         MakeMove(fromX, fromY, toX, toY, promoChar);\r
7166         currentMove = forwardMostMove;\r
7167         return TRUE;\r
7168     }\r
7169 }\r
7170 \r
7171 /* Load the nth game from the given file */\r
7172 int\r
7173 LoadGameFromFile(filename, n, title, useList)\r
7174      char *filename;\r
7175      int n;\r
7176      char *title;\r
7177      /*Boolean*/ int useList;\r
7178 {\r
7179     FILE *f;\r
7180     char buf[MSG_SIZ];\r
7181 \r
7182     if (strcmp(filename, "-") == 0) {\r
7183         f = stdin;\r
7184         title = "stdin";\r
7185     } else {\r
7186         f = fopen(filename, "rb");\r
7187         if (f == NULL) {\r
7188             sprintf(buf, "Can't open \"%s\"", filename);\r
7189             DisplayError(buf, errno);\r
7190             return FALSE;\r
7191         }\r
7192     }\r
7193     if (fseek(f, 0, 0) == -1) {\r
7194         /* f is not seekable; probably a pipe */\r
7195         useList = FALSE;\r
7196     }\r
7197     if (useList && n == 0) {\r
7198         int error = GameListBuild(f);\r
7199         if (error) {\r
7200             DisplayError("Cannot build game list", error);\r
7201         } else if (!ListEmpty(&gameList) &&\r
7202                    ((ListGame *) gameList.tailPred)->number > 1) {\r
7203             GameListPopUp(f, title);\r
7204             return TRUE;\r
7205         }\r
7206         GameListDestroy();\r
7207         n = 1;\r
7208     }\r
7209     if (n == 0) n = 1;\r
7210     return LoadGame(f, n, title, FALSE);\r
7211 }\r
7212 \r
7213 \r
7214 void\r
7215 MakeRegisteredMove()\r
7216 {\r
7217     int fromX, fromY, toX, toY;\r
7218     char promoChar;\r
7219     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
7220         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
7221           case CMAIL_MOVE:\r
7222           case CMAIL_DRAW:\r
7223             if (appData.debugMode)\r
7224               fprintf(debugFP, "Restoring %s for game %d\n",\r
7225                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
7226     \r
7227             thinkOutput[0] = NULLCHAR;\r
7228             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
7229             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
7230             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
7231             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
7232             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
7233             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
7234             MakeMove(fromX, fromY, toX, toY, promoChar);\r
7235             ShowMove(fromX, fromY, toX, toY);\r
7236               \r
7237             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
7238                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
7239               case MT_NONE:\r
7240               case MT_CHECK:\r
7241                 break;\r
7242                 \r
7243               case MT_CHECKMATE:\r
7244                 if (WhiteOnMove(currentMove)) {\r
7245                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
7246                 } else {\r
7247                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
7248                 }\r
7249                 break;\r
7250                 \r
7251               case MT_STALEMATE:\r
7252                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
7253                 break;\r
7254             }\r
7255 \r
7256             break;\r
7257             \r
7258           case CMAIL_RESIGN:\r
7259             if (WhiteOnMove(currentMove)) {\r
7260                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
7261             } else {\r
7262                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
7263             }\r
7264             break;\r
7265             \r
7266           case CMAIL_ACCEPT:\r
7267             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
7268             break;\r
7269               \r
7270           default:\r
7271             break;\r
7272         }\r
7273     }\r
7274 \r
7275     return;\r
7276 }\r
7277 \r
7278 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
7279 int\r
7280 CmailLoadGame(f, gameNumber, title, useList)\r
7281      FILE *f;\r
7282      int gameNumber;\r
7283      char *title;\r
7284      int useList;\r
7285 {\r
7286     int retVal;\r
7287 \r
7288     if (gameNumber > nCmailGames) {\r
7289         DisplayError("No more games in this message", 0);\r
7290         return FALSE;\r
7291     }\r
7292     if (f == lastLoadGameFP) {\r
7293         int offset = gameNumber - lastLoadGameNumber;\r
7294         if (offset == 0) {\r
7295             cmailMsg[0] = NULLCHAR;\r
7296             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
7297                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
7298                 nCmailMovesRegistered--;\r
7299             }\r
7300             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
7301             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
7302                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
7303             }\r
7304         } else {\r
7305             if (! RegisterMove()) return FALSE;\r
7306         }\r
7307     }\r
7308 \r
7309     retVal = LoadGame(f, gameNumber, title, useList);\r
7310 \r
7311     /* Make move registered during previous look at this game, if any */\r
7312     MakeRegisteredMove();\r
7313 \r
7314     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
7315         commentList[currentMove]\r
7316           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
7317         DisplayComment(currentMove - 1, commentList[currentMove]);\r
7318     }\r
7319 \r
7320     return retVal;\r
7321 }\r
7322 \r
7323 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
7324 int\r
7325 ReloadGame(offset)\r
7326      int offset;\r
7327 {\r
7328     int gameNumber = lastLoadGameNumber + offset;\r
7329     if (lastLoadGameFP == NULL) {\r
7330         DisplayError("No game has been loaded yet", 0);\r
7331         return FALSE;\r
7332     }\r
7333     if (gameNumber <= 0) {\r
7334         DisplayError("Can't back up any further", 0);\r
7335         return FALSE;\r
7336     }\r
7337     if (cmailMsgLoaded) {\r
7338         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
7339                              lastLoadGameTitle, lastLoadGameUseList);\r
7340     } else {\r
7341         return LoadGame(lastLoadGameFP, gameNumber,\r
7342                         lastLoadGameTitle, lastLoadGameUseList);\r
7343     }\r
7344 }\r
7345 \r
7346 \r
7347 \r
7348 /* Load the nth game from open file f */\r
7349 int\r
7350 LoadGame(f, gameNumber, title, useList)\r
7351      FILE *f;\r
7352      int gameNumber;\r
7353      char *title;\r
7354      int useList;\r
7355 {\r
7356     ChessMove cm;\r
7357     char buf[MSG_SIZ];\r
7358     int gn = gameNumber;\r
7359     ListGame *lg = NULL;\r
7360     int numPGNTags = 0;\r
7361     int err;\r
7362     GameMode oldGameMode;\r
7363 \r
7364     if (appData.debugMode) \r
7365         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
7366 \r
7367     if (gameMode == Training )\r
7368         SetTrainingModeOff();\r
7369 \r
7370     oldGameMode = gameMode;\r
7371     if (gameMode != BeginningOfGame) {\r
7372       Reset(FALSE, TRUE);\r
7373     }\r
7374 \r
7375     gameFileFP = f;\r
7376     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
7377         fclose(lastLoadGameFP);\r
7378     }\r
7379 \r
7380     if (useList) {\r
7381         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
7382         \r
7383         if (lg) {\r
7384             fseek(f, lg->offset, 0);\r
7385             GameListHighlight(gameNumber);\r
7386             gn = 1;\r
7387         }\r
7388         else {\r
7389             DisplayError("Game number out of range", 0);\r
7390             return FALSE;\r
7391         }\r
7392     } else {\r
7393         GameListDestroy();\r
7394         if (fseek(f, 0, 0) == -1) {\r
7395             if (f == lastLoadGameFP ?\r
7396                 gameNumber == lastLoadGameNumber + 1 :\r
7397                 gameNumber == 1) {\r
7398                 gn = 1;\r
7399             } else {\r
7400                 DisplayError("Can't seek on game file", 0);\r
7401                 return FALSE;\r
7402             }\r
7403         }\r
7404     }\r
7405     lastLoadGameFP = f;\r
7406     lastLoadGameNumber = gameNumber;\r
7407     strcpy(lastLoadGameTitle, title);\r
7408     lastLoadGameUseList = useList;\r
7409 \r
7410     yynewfile(f);\r
7411 \r
7412 \r
7413     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
7414         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
7415                 lg->gameInfo.black);\r
7416             DisplayTitle(buf);\r
7417     } else if (*title != NULLCHAR) {\r
7418         if (gameNumber > 1) {\r
7419             sprintf(buf, "%s %d", title, gameNumber);\r
7420             DisplayTitle(buf);\r
7421         } else {\r
7422             DisplayTitle(title);\r
7423         }\r
7424     }\r
7425 \r
7426     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
7427         gameMode = PlayFromGameFile;\r
7428         ModeHighlight();\r
7429     }\r
7430 \r
7431     currentMove = forwardMostMove = backwardMostMove = 0;\r
7432     CopyBoard(boards[0], initialPosition);\r
7433     StopClocks();\r
7434 \r
7435     /*\r
7436      * Skip the first gn-1 games in the file.\r
7437      * Also skip over anything that precedes an identifiable \r
7438      * start of game marker, to avoid being confused by \r
7439      * garbage at the start of the file.  Currently \r
7440      * recognized start of game markers are the move number "1",\r
7441      * the pattern "gnuchess .* game", the pattern\r
7442      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
7443      * A game that starts with one of the latter two patterns\r
7444      * will also have a move number 1, possibly\r
7445      * following a position diagram.\r
7446      * 5-4-02: Let's try being more lenient and allowing a game to\r
7447      * start with an unnumbered move.  Does that break anything?\r
7448      */\r
7449     cm = lastLoadGameStart = (ChessMove) 0;\r
7450     while (gn > 0) {\r
7451         yyboardindex = forwardMostMove;\r
7452         cm = (ChessMove) yylex();\r
7453         switch (cm) {\r
7454           case (ChessMove) 0:\r
7455             if (cmailMsgLoaded) {\r
7456                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
7457             } else {\r
7458                 Reset(TRUE, TRUE);\r
7459                 DisplayError("Game not found in file", 0);\r
7460             }\r
7461             return FALSE;\r
7462 \r
7463           case GNUChessGame:\r
7464           case XBoardGame:\r
7465             gn--;\r
7466             lastLoadGameStart = cm;\r
7467             break;\r
7468             \r
7469           case MoveNumberOne:\r
7470             switch (lastLoadGameStart) {\r
7471               case GNUChessGame:\r
7472               case XBoardGame:\r
7473               case PGNTag:\r
7474                 break;\r
7475               case MoveNumberOne:\r
7476               case (ChessMove) 0:\r
7477                 gn--;           /* count this game */\r
7478                 lastLoadGameStart = cm;\r
7479                 break;\r
7480               default:\r
7481                 /* impossible */\r
7482                 break;\r
7483             }\r
7484             break;\r
7485 \r
7486           case PGNTag:\r
7487             switch (lastLoadGameStart) {\r
7488               case GNUChessGame:\r
7489               case PGNTag:\r
7490               case MoveNumberOne:\r
7491               case (ChessMove) 0:\r
7492                 gn--;           /* count this game */\r
7493                 lastLoadGameStart = cm;\r
7494                 break;\r
7495               case XBoardGame:\r
7496                 lastLoadGameStart = cm; /* game counted already */\r
7497                 break;\r
7498               default:\r
7499                 /* impossible */\r
7500                 break;\r
7501             }\r
7502             if (gn > 0) {\r
7503                 do {\r
7504                     yyboardindex = forwardMostMove;\r
7505                     cm = (ChessMove) yylex();\r
7506                 } while (cm == PGNTag || cm == Comment);\r
7507             }\r
7508             break;\r
7509 \r
7510           case WhiteWins:\r
7511           case BlackWins:\r
7512           case GameIsDrawn:\r
7513             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
7514                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
7515                     != CMAIL_OLD_RESULT) {\r
7516                     nCmailResults ++ ;\r
7517                     cmailResult[  CMAIL_MAX_GAMES\r
7518                                 - gn - 1] = CMAIL_OLD_RESULT;\r
7519                 }\r
7520             }\r
7521             break;\r
7522 \r
7523           case NormalMove:\r
7524             /* Only a NormalMove can be at the start of a game\r
7525              * without a position diagram. */\r
7526             if (lastLoadGameStart == (ChessMove) 0) {\r
7527               gn--;\r
7528               lastLoadGameStart = MoveNumberOne;\r
7529             }\r
7530             break;\r
7531 \r
7532           default:\r
7533             break;\r
7534         }\r
7535     }\r
7536     \r
7537     if (appData.debugMode)\r
7538       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
7539 \r
7540     if (cm == XBoardGame) {\r
7541         /* Skip any header junk before position diagram and/or move 1 */\r
7542         for (;;) {\r
7543             yyboardindex = forwardMostMove;\r
7544             cm = (ChessMove) yylex();\r
7545 \r
7546             if (cm == (ChessMove) 0 ||\r
7547                 cm == GNUChessGame || cm == XBoardGame) {\r
7548                 /* Empty game; pretend end-of-file and handle later */\r
7549                 cm = (ChessMove) 0;\r
7550                 break;\r
7551             }\r
7552 \r
7553             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
7554                 cm == PGNTag || cm == Comment)\r
7555               break;\r
7556         }\r
7557     } else if (cm == GNUChessGame) {\r
7558         if (gameInfo.event != NULL) {\r
7559             free(gameInfo.event);\r
7560         }\r
7561         gameInfo.event = StrSave(yy_text);\r
7562     }   \r
7563 \r
7564     startedFromSetupPosition = FALSE;\r
7565     while (cm == PGNTag) {\r
7566         if (appData.debugMode) \r
7567           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
7568         err = ParsePGNTag(yy_text, &gameInfo);\r
7569         if (!err) numPGNTags++;\r
7570 \r
7571         if (gameInfo.fen != NULL) {\r
7572           Board initial_position;\r
7573           startedFromSetupPosition = TRUE;\r
7574           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
7575             Reset(TRUE, TRUE);\r
7576             DisplayError("Bad FEN position in file", 0);\r
7577             return FALSE;\r
7578           }\r
7579           CopyBoard(boards[0], initial_position);\r
7580           /* [HGM] copy FEN attributes as well */\r
7581           {   int i;\r
7582               initialRulePlies = FENrulePlies;\r
7583               epStatus[0] = FENepStatus;\r
7584               for( i=0; i< nrCastlingRights; i++ )\r
7585                   castlingRights[0][i] = FENcastlingRights[i];\r
7586           }\r
7587           if (blackPlaysFirst) {\r
7588             currentMove = forwardMostMove = backwardMostMove = 1;\r
7589             CopyBoard(boards[1], initial_position);\r
7590             strcpy(moveList[0], "");\r
7591             strcpy(parseList[0], "");\r
7592             timeRemaining[0][1] = whiteTimeRemaining;\r
7593             timeRemaining[1][1] = blackTimeRemaining;\r
7594             if (commentList[0] != NULL) {\r
7595               commentList[1] = commentList[0];\r
7596               commentList[0] = NULL;\r
7597             }\r
7598           } else {\r
7599             currentMove = forwardMostMove = backwardMostMove = 0;\r
7600           }\r
7601           yyboardindex = forwardMostMove;\r
7602           free(gameInfo.fen);\r
7603           gameInfo.fen = NULL;\r
7604         }\r
7605 \r
7606         yyboardindex = forwardMostMove;\r
7607         cm = (ChessMove) yylex();\r
7608 \r
7609         /* Handle comments interspersed among the tags */\r
7610         while (cm == Comment) {\r
7611             char *p;\r
7612             if (appData.debugMode) \r
7613               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
7614             p = yy_text;\r
7615             if (*p == '{' || *p == '[' || *p == '(') {\r
7616                 p[strlen(p) - 1] = NULLCHAR;\r
7617                 p++;\r
7618             }\r
7619             while (*p == '\n') p++;\r
7620             AppendComment(currentMove, p);\r
7621             yyboardindex = forwardMostMove;\r
7622             cm = (ChessMove) yylex();\r
7623         }\r
7624     }\r
7625 \r
7626     /* don't rely on existence of Event tag since if game was\r
7627      * pasted from clipboard the Event tag may not exist\r
7628      */\r
7629     if (numPGNTags > 0){\r
7630         char *tags;\r
7631         if (gameInfo.variant == VariantNormal) {\r
7632           gameInfo.variant = StringToVariant(gameInfo.event);\r
7633         }\r
7634         if (!matchMode) {\r
7635           if( appData.autoDisplayTags ) {\r
7636             tags = PGNTags(&gameInfo);\r
7637             TagsPopUp(tags, CmailMsg());\r
7638             free(tags);\r
7639           }\r
7640         }\r
7641     } else {\r
7642         /* Make something up, but don't display it now */\r
7643         SetGameInfo();\r
7644         TagsPopDown();\r
7645     }\r
7646 \r
7647     if (cm == PositionDiagram) {\r
7648         int i, j;\r
7649         char *p;\r
7650         Board initial_position;\r
7651 \r
7652         if (appData.debugMode)\r
7653           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
7654 \r
7655         if (!startedFromSetupPosition) {\r
7656             p = yy_text;\r
7657             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
7658               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
7659                 switch (*p) {\r
7660                   case '[':\r
7661                   case '-':\r
7662                   case ' ':\r
7663                   case '\t':\r
7664                   case '\n':\r
7665                   case '\r':\r
7666                     break;\r
7667                   default:\r
7668                     initial_position[i][j++] = CharToPiece(*p);\r
7669                     break;\r
7670                 }\r
7671             while (*p == ' ' || *p == '\t' ||\r
7672                    *p == '\n' || *p == '\r') p++;\r
7673         \r
7674             if (strncmp(p, "black", strlen("black"))==0)\r
7675               blackPlaysFirst = TRUE;\r
7676             else\r
7677               blackPlaysFirst = FALSE;\r
7678             startedFromSetupPosition = TRUE;\r
7679         \r
7680             CopyBoard(boards[0], initial_position);\r
7681             if (blackPlaysFirst) {\r
7682                 currentMove = forwardMostMove = backwardMostMove = 1;\r
7683                 CopyBoard(boards[1], initial_position);\r
7684                 strcpy(moveList[0], "");\r
7685                 strcpy(parseList[0], "");\r
7686                 timeRemaining[0][1] = whiteTimeRemaining;\r
7687                 timeRemaining[1][1] = blackTimeRemaining;\r
7688                 if (commentList[0] != NULL) {\r
7689                     commentList[1] = commentList[0];\r
7690                     commentList[0] = NULL;\r
7691                 }\r
7692             } else {\r
7693                 currentMove = forwardMostMove = backwardMostMove = 0;\r
7694             }\r
7695         }\r
7696         yyboardindex = forwardMostMove;\r
7697         cm = (ChessMove) yylex();\r
7698     }\r
7699 \r
7700     if (first.pr == NoProc) {\r
7701         StartChessProgram(&first);\r
7702     }\r
7703     InitChessProgram(&first);\r
7704     SendToProgram("force\n", &first);\r
7705     if (startedFromSetupPosition) {\r
7706         SendBoard(&first, forwardMostMove);\r
7707         DisplayBothClocks();\r
7708     }      \r
7709 \r
7710     while (cm == Comment) {\r
7711         char *p;\r
7712         if (appData.debugMode) \r
7713           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
7714         p = yy_text;\r
7715         if (*p == '{' || *p == '[' || *p == '(') {\r
7716             p[strlen(p) - 1] = NULLCHAR;\r
7717             p++;\r
7718         }\r
7719         while (*p == '\n') p++;\r
7720         AppendComment(currentMove, p);\r
7721         yyboardindex = forwardMostMove;\r
7722         cm = (ChessMove) yylex();\r
7723     }\r
7724 \r
7725     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
7726         cm == WhiteWins || cm == BlackWins ||\r
7727         cm == GameIsDrawn || cm == GameUnfinished) {\r
7728         DisplayMessage("", "No moves in game");\r
7729         if (cmailMsgLoaded) {\r
7730             if (appData.debugMode)\r
7731               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
7732             ClearHighlights();\r
7733             flipView = FALSE;\r
7734         }\r
7735         DrawPosition(FALSE, boards[currentMove]);\r
7736         DisplayBothClocks();\r
7737         gameMode = EditGame;\r
7738         ModeHighlight();\r
7739         gameFileFP = NULL;\r
7740         cmailOldMove = 0;\r
7741         return TRUE;\r
7742     }\r
7743 \r
7744     if (commentList[currentMove] != NULL) {\r
7745       if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
7746         DisplayComment(currentMove - 1, commentList[currentMove]);\r
7747       }\r
7748     }\r
7749     if (!matchMode && appData.timeDelay != 0) \r
7750       DrawPosition(FALSE, boards[currentMove]);\r
7751 \r
7752     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
7753       programStats.ok_to_send = 1;\r
7754     }\r
7755 \r
7756     /* if the first token after the PGN tags is a move\r
7757      * and not move number 1, retrieve it from the parser \r
7758      */\r
7759     if (cm != MoveNumberOne)\r
7760         LoadGameOneMove(cm);\r
7761 \r
7762     /* load the remaining moves from the file */\r
7763     while (LoadGameOneMove((ChessMove)0)) {\r
7764       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
7765       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
7766     }\r
7767 \r
7768     /* rewind to the start of the game */\r
7769     currentMove = backwardMostMove;\r
7770 \r
7771     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
7772 \r
7773     if (oldGameMode == AnalyzeFile ||\r
7774         oldGameMode == AnalyzeMode) {\r
7775       AnalyzeFileEvent();\r
7776     }\r
7777 \r
7778     if (matchMode || appData.timeDelay == 0) {\r
7779       ToEndEvent();\r
7780       gameMode = EditGame;\r
7781       ModeHighlight();\r
7782     } else if (appData.timeDelay > 0) {\r
7783       AutoPlayGameLoop();\r
7784     }\r
7785 \r
7786     if (appData.debugMode) \r
7787         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
7788     return TRUE;\r
7789 }\r
7790 \r
7791 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
7792 int\r
7793 ReloadPosition(offset)\r
7794      int offset;\r
7795 {\r
7796     int positionNumber = lastLoadPositionNumber + offset;\r
7797     if (lastLoadPositionFP == NULL) {\r
7798         DisplayError("No position has been loaded yet", 0);\r
7799         return FALSE;\r
7800     }\r
7801     if (positionNumber <= 0) {\r
7802         DisplayError("Can't back up any further", 0);\r
7803         return FALSE;\r
7804     }\r
7805     return LoadPosition(lastLoadPositionFP, positionNumber,\r
7806                         lastLoadPositionTitle);\r
7807 }\r
7808 \r
7809 /* Load the nth position from the given file */\r
7810 int\r
7811 LoadPositionFromFile(filename, n, title)\r
7812      char *filename;\r
7813      int n;\r
7814      char *title;\r
7815 {\r
7816     FILE *f;\r
7817     char buf[MSG_SIZ];\r
7818 \r
7819     if (strcmp(filename, "-") == 0) {\r
7820         return LoadPosition(stdin, n, "stdin");\r
7821     } else {\r
7822         f = fopen(filename, "rb");\r
7823         if (f == NULL) {\r
7824             sprintf(buf, "Can't open \"%s\"", filename);\r
7825             DisplayError(buf, errno);\r
7826             return FALSE;\r
7827         } else {\r
7828             return LoadPosition(f, n, title);\r
7829         }\r
7830     }\r
7831 }\r
7832 \r
7833 /* Load the nth position from the given open file, and close it */\r
7834 int\r
7835 LoadPosition(f, positionNumber, title)\r
7836      FILE *f;\r
7837      int positionNumber;\r
7838      char *title;\r
7839 {\r
7840     char *p, line[MSG_SIZ];\r
7841     Board initial_position;\r
7842     int i, j, fenMode, pn;\r
7843     \r
7844     if (gameMode == Training )\r
7845         SetTrainingModeOff();\r
7846 \r
7847     if (gameMode != BeginningOfGame) {\r
7848         Reset(FALSE, TRUE);\r
7849     }\r
7850     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
7851         fclose(lastLoadPositionFP);\r
7852     }\r
7853     if (positionNumber == 0) positionNumber = 1;\r
7854     lastLoadPositionFP = f;\r
7855     lastLoadPositionNumber = positionNumber;\r
7856     strcpy(lastLoadPositionTitle, title);\r
7857     if (first.pr == NoProc) {\r
7858       StartChessProgram(&first);\r
7859       InitChessProgram(&first);\r
7860     }    \r
7861     pn = positionNumber;\r
7862     if (positionNumber < 0) {\r
7863         /* Negative position number means to seek to that byte offset */\r
7864         if (fseek(f, -positionNumber, 0) == -1) {\r
7865             DisplayError("Can't seek on position file", 0);\r
7866             return FALSE;\r
7867         };\r
7868         pn = 1;\r
7869     } else {\r
7870         if (fseek(f, 0, 0) == -1) {\r
7871             if (f == lastLoadPositionFP ?\r
7872                 positionNumber == lastLoadPositionNumber + 1 :\r
7873                 positionNumber == 1) {\r
7874                 pn = 1;\r
7875             } else {\r
7876                 DisplayError("Can't seek on position file", 0);\r
7877                 return FALSE;\r
7878             }\r
7879         }\r
7880     }\r
7881     /* See if this file is FEN or old-style xboard */\r
7882     if (fgets(line, MSG_SIZ, f) == NULL) {\r
7883         DisplayError("Position not found in file", 0);\r
7884         return FALSE;\r
7885     }\r
7886     switch (line[0]) {\r
7887       case '#':  case 'x':\r
7888       default:\r
7889         fenMode = FALSE;\r
7890         break;\r
7891       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
7892       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
7893       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
7894       case '7':  case '8':  case '9':\r
7895 #ifdef FAIRY\r
7896       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
7897       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
7898       case 'C':  case 'W':             case 'c':  case 'w': \r
7899 #endif\r
7900         fenMode = TRUE;\r
7901         break;\r
7902     }\r
7903 \r
7904     if (pn >= 2) {\r
7905         if (fenMode || line[0] == '#') pn--;\r
7906         while (pn > 0) {\r
7907             /* skip postions before number pn */\r
7908             if (fgets(line, MSG_SIZ, f) == NULL) {\r
7909                 Reset(TRUE, TRUE);\r
7910                 DisplayError("Position not found in file", 0);\r
7911                 return FALSE;\r
7912             }\r
7913             if (fenMode || line[0] == '#') pn--;\r
7914         }\r
7915     }\r
7916 \r
7917     if (fenMode) {\r
7918         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
7919             DisplayError("Bad FEN position in file", 0);\r
7920             return FALSE;\r
7921         }\r
7922     } else {\r
7923         (void) fgets(line, MSG_SIZ, f);\r
7924         (void) fgets(line, MSG_SIZ, f);\r
7925     \r
7926         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
7927             (void) fgets(line, MSG_SIZ, f);\r
7928             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
7929                 if (*p == ' ')\r
7930                   continue;\r
7931                 initial_position[i][j++] = CharToPiece(*p);\r
7932             }\r
7933         }\r
7934     \r
7935         blackPlaysFirst = FALSE;\r
7936         if (!feof(f)) {\r
7937             (void) fgets(line, MSG_SIZ, f);\r
7938             if (strncmp(line, "black", strlen("black"))==0)\r
7939               blackPlaysFirst = TRUE;\r
7940         }\r
7941     }\r
7942     startedFromSetupPosition = TRUE;\r
7943     \r
7944     SendToProgram("force\n", &first);\r
7945     CopyBoard(boards[0], initial_position);\r
7946           /* [HGM] copy FEN attributes as well */\r
7947           {   int i;\r
7948               initialRulePlies = FENrulePlies;\r
7949               epStatus[0] = FENepStatus;\r
7950               for( i=0; i< nrCastlingRights; i++ )\r
7951                   castlingRights[0][i] = FENcastlingRights[i];\r
7952           }\r
7953     if (blackPlaysFirst) {\r
7954         currentMove = forwardMostMove = backwardMostMove = 1;\r
7955         strcpy(moveList[0], "");\r
7956         strcpy(parseList[0], "");\r
7957         CopyBoard(boards[1], initial_position);\r
7958         DisplayMessage("", "Black to play");\r
7959     } else {\r
7960         currentMove = forwardMostMove = backwardMostMove = 0;\r
7961         DisplayMessage("", "White to play");\r
7962     }\r
7963     SendBoard(&first, forwardMostMove);\r
7964 \r
7965     if (positionNumber > 1) {\r
7966         sprintf(line, "%s %d", title, positionNumber);\r
7967         DisplayTitle(line);\r
7968     } else {\r
7969         DisplayTitle(title);\r
7970     }\r
7971     gameMode = EditGame;\r
7972     ModeHighlight();\r
7973     ResetClocks();\r
7974     timeRemaining[0][1] = whiteTimeRemaining;\r
7975     timeRemaining[1][1] = blackTimeRemaining;\r
7976     DrawPosition(FALSE, boards[currentMove]);\r
7977    \r
7978     return TRUE;\r
7979 }\r
7980 \r
7981 \r
7982 void\r
7983 CopyPlayerNameIntoFileName(dest, src)\r
7984      char **dest, *src;\r
7985 {\r
7986     while (*src != NULLCHAR && *src != ',') {\r
7987         if (*src == ' ') {\r
7988             *(*dest)++ = '_';\r
7989             src++;\r
7990         } else {\r
7991             *(*dest)++ = *src++;\r
7992         }\r
7993     }\r
7994 }\r
7995 \r
7996 char *DefaultFileName(ext)\r
7997      char *ext;\r
7998 {\r
7999     static char def[MSG_SIZ];\r
8000     char *p;\r
8001 \r
8002     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
8003         p = def;\r
8004         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
8005         *p++ = '-';\r
8006         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
8007         *p++ = '.';\r
8008         strcpy(p, ext);\r
8009     } else {\r
8010         def[0] = NULLCHAR;\r
8011     }\r
8012     return def;\r
8013 }\r
8014 \r
8015 /* Save the current game to the given file */\r
8016 int\r
8017 SaveGameToFile(filename, append)\r
8018      char *filename;\r
8019      int append;\r
8020 {\r
8021     FILE *f;\r
8022     char buf[MSG_SIZ];\r
8023 \r
8024     if (strcmp(filename, "-") == 0) {\r
8025         return SaveGame(stdout, 0, NULL);\r
8026     } else {\r
8027         f = fopen(filename, append ? "a" : "w");\r
8028         if (f == NULL) {\r
8029             sprintf(buf, "Can't open \"%s\"", filename);\r
8030             DisplayError(buf, errno);\r
8031             return FALSE;\r
8032         } else {\r
8033             return SaveGame(f, 0, NULL);\r
8034         }\r
8035     }\r
8036 }\r
8037 \r
8038 char *\r
8039 SavePart(str)\r
8040      char *str;\r
8041 {\r
8042     static char buf[MSG_SIZ];\r
8043     char *p;\r
8044     \r
8045     p = strchr(str, ' ');\r
8046     if (p == NULL) return str;\r
8047     strncpy(buf, str, p - str);\r
8048     buf[p - str] = NULLCHAR;\r
8049     return buf;\r
8050 }\r
8051 \r
8052 #define PGN_MAX_LINE 75\r
8053 \r
8054 #define PGN_SIDE_WHITE  0\r
8055 #define PGN_SIDE_BLACK  1\r
8056 \r
8057 /* [AS] */\r
8058 static int FindFirstMoveOutOfBook( int side )\r
8059 {\r
8060     int result = -1;\r
8061 \r
8062     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
8063         int index = backwardMostMove;\r
8064         int has_book_hit = 0;\r
8065 \r
8066         if( (index % 2) != side ) {\r
8067             index++;\r
8068         }\r
8069 \r
8070         while( index < forwardMostMove ) {\r
8071             /* Check to see if engine is in book */\r
8072             int depth = pvInfoList[index].depth;\r
8073             int score = pvInfoList[index].score;\r
8074             int in_book = 0;\r
8075 \r
8076             if( depth <= 2 ) {\r
8077                 in_book = 1;\r
8078             }\r
8079             else if( score == 0 && depth == 63 ) {\r
8080                 in_book = 1; /* Zappa */\r
8081             }\r
8082             else if( score == 2 && depth == 99 ) {\r
8083                 in_book = 1; /* Abrok */\r
8084             }\r
8085 \r
8086             has_book_hit += in_book;\r
8087 \r
8088             if( ! in_book ) {\r
8089                 result = index;\r
8090 \r
8091                 break;\r
8092             }\r
8093 \r
8094             index += 2;\r
8095         }\r
8096     }\r
8097 \r
8098     return result;\r
8099 }\r
8100 \r
8101 /* [AS] */\r
8102 void GetOutOfBookInfo( char * buf )\r
8103 {\r
8104     int oob[2];\r
8105     int i;\r
8106     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
8107 \r
8108     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
8109     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
8110 \r
8111     *buf = '\0';\r
8112 \r
8113     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
8114         for( i=0; i<2; i++ ) {\r
8115             int idx = oob[i];\r
8116 \r
8117             if( idx >= 0 ) {\r
8118                 if( i > 0 && oob[0] >= 0 ) {\r
8119                     strcat( buf, "   " );\r
8120                 }\r
8121 \r
8122                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
8123                 sprintf( buf+strlen(buf), "%s%.2f", \r
8124                     pvInfoList[idx].score >= 0 ? "+" : "",\r
8125                     pvInfoList[idx].score / 100.0 );\r
8126             }\r
8127         }\r
8128     }\r
8129 }\r
8130 \r
8131 /* Save game in PGN style and close the file */\r
8132 int\r
8133 SaveGamePGN(f)\r
8134      FILE *f;\r
8135 {\r
8136     int i, offset, linelen, newblock;\r
8137     time_t tm;\r
8138     char *movetext;\r
8139     char numtext[32];\r
8140     int movelen, numlen, blank;\r
8141     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
8142 \r
8143     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
8144     \r
8145     tm = time((time_t *) NULL);\r
8146     \r
8147     PrintPGNTags(f, &gameInfo);\r
8148     \r
8149     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
8150         char *fen = PositionToFEN(backwardMostMove, 1);\r
8151         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
8152         fprintf(f, "\n{--------------\n");\r
8153         PrintPosition(f, backwardMostMove);\r
8154         fprintf(f, "--------------}\n");\r
8155         free(fen);\r
8156     }\r
8157     else {\r
8158         /* [AS] Out of book annotation */\r
8159         if( appData.saveOutOfBookInfo ) {\r
8160             char buf[64];\r
8161 \r
8162             GetOutOfBookInfo( buf );\r
8163 \r
8164             if( buf[0] != '\0' ) {\r
8165                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
8166             }\r
8167         }\r
8168 \r
8169         fprintf(f, "\n");\r
8170     }\r
8171 \r
8172     i = backwardMostMove;\r
8173     linelen = 0;\r
8174     newblock = TRUE;\r
8175 \r
8176     while (i < forwardMostMove) {\r
8177         /* Print comments preceding this move */\r
8178         if (commentList[i] != NULL) {\r
8179             if (linelen > 0) fprintf(f, "\n");\r
8180             fprintf(f, "{\n%s}\n", commentList[i]);\r
8181             linelen = 0;\r
8182             newblock = TRUE;\r
8183         }\r
8184 \r
8185         /* Format move number */\r
8186         if ((i % 2) == 0) {\r
8187             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
8188         } else {\r
8189             if (newblock) {\r
8190                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
8191             } else {\r
8192                 numtext[0] = NULLCHAR;\r
8193             }\r
8194         }\r
8195         numlen = strlen(numtext);\r
8196         newblock = FALSE;\r
8197 \r
8198         /* Print move number */\r
8199         blank = linelen > 0 && numlen > 0;\r
8200         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
8201             fprintf(f, "\n");\r
8202             linelen = 0;\r
8203             blank = 0;\r
8204         }\r
8205         if (blank) {\r
8206             fprintf(f, " ");\r
8207             linelen++;\r
8208         }\r
8209         fprintf(f, numtext);\r
8210         linelen += numlen;\r
8211 \r
8212         /* Get move */\r
8213         movetext = SavePart(parseList[i]);\r
8214 \r
8215         /* [AS] Add PV info if present */\r
8216         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
8217             /* [HGM] add time */\r
8218             char buf[MSG_SIZ]; int seconds = 0;\r
8219 \r
8220             if(i >= backwardMostMove) {\r
8221                 /* take the time that changed */\r
8222                 seconds = timeRemaining[0][i] - timeRemaining[0][i+1];\r
8223                 if(seconds <= 0)\r
8224                     seconds = timeRemaining[1][i] - timeRemaining[1][i+1];\r
8225             }\r
8226             seconds /= 1000;\r
8227     if (appData.debugMode) {\r
8228         fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",\r
8229                 timeRemaining[0][i+1], timeRemaining[0][i],\r
8230                      timeRemaining[1][i+1], timeRemaining[1][i], seconds\r
8231         );\r
8232     }\r
8233 \r
8234             if( seconds < 0 ) buf[0] = 0; else\r
8235             if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0);\r
8236             else    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
8237 \r
8238             sprintf( move_buffer, "%s {%s%.2f/%d%s}", \r
8239                 movetext, \r
8240                 pvInfoList[i].score >= 0 ? "+" : "",\r
8241                 pvInfoList[i].score / 100.0,\r
8242                 pvInfoList[i].depth,\r
8243                 buf );\r
8244             movetext = move_buffer;\r
8245         }\r
8246 \r
8247         movelen = strlen(movetext);\r
8248 \r
8249         /* Print move */\r
8250         blank = linelen > 0 && movelen > 0;\r
8251         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
8252             fprintf(f, "\n");\r
8253             linelen = 0;\r
8254             blank = 0;\r
8255         }\r
8256         if (blank) {\r
8257             fprintf(f, " ");\r
8258             linelen++;\r
8259         }\r
8260         fprintf(f, movetext);\r
8261         linelen += movelen;\r
8262 \r
8263         i++;\r
8264     }\r
8265     \r
8266     /* Start a new line */\r
8267     if (linelen > 0) fprintf(f, "\n");\r
8268 \r
8269     /* Print comments after last move */\r
8270     if (commentList[i] != NULL) {\r
8271         fprintf(f, "{\n%s}\n", commentList[i]);\r
8272     }\r
8273 \r
8274     /* Print result */\r
8275     if (gameInfo.resultDetails != NULL &&\r
8276         gameInfo.resultDetails[0] != NULLCHAR) {\r
8277         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
8278                 PGNResult(gameInfo.result));\r
8279     } else {\r
8280         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
8281     }\r
8282 \r
8283     fclose(f);\r
8284     return TRUE;\r
8285 }\r
8286 \r
8287 /* Save game in old style and close the file */\r
8288 int\r
8289 SaveGameOldStyle(f)\r
8290      FILE *f;\r
8291 {\r
8292     int i, offset;\r
8293     time_t tm;\r
8294     \r
8295     tm = time((time_t *) NULL);\r
8296     \r
8297     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
8298     PrintOpponents(f);\r
8299     \r
8300     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
8301         fprintf(f, "\n[--------------\n");\r
8302         PrintPosition(f, backwardMostMove);\r
8303         fprintf(f, "--------------]\n");\r
8304     } else {\r
8305         fprintf(f, "\n");\r
8306     }\r
8307 \r
8308     i = backwardMostMove;\r
8309     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
8310 \r
8311     while (i < forwardMostMove) {\r
8312         if (commentList[i] != NULL) {\r
8313             fprintf(f, "[%s]\n", commentList[i]);\r
8314         }\r
8315 \r
8316         if ((i % 2) == 1) {\r
8317             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
8318             i++;\r
8319         } else {\r
8320             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
8321             i++;\r
8322             if (commentList[i] != NULL) {\r
8323                 fprintf(f, "\n");\r
8324                 continue;\r
8325             }\r
8326             if (i >= forwardMostMove) {\r
8327                 fprintf(f, "\n");\r
8328                 break;\r
8329             }\r
8330             fprintf(f, "%s\n", parseList[i]);\r
8331             i++;\r
8332         }\r
8333     }\r
8334     \r
8335     if (commentList[i] != NULL) {\r
8336         fprintf(f, "[%s]\n", commentList[i]);\r
8337     }\r
8338 \r
8339     /* This isn't really the old style, but it's close enough */\r
8340     if (gameInfo.resultDetails != NULL &&\r
8341         gameInfo.resultDetails[0] != NULLCHAR) {\r
8342         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
8343                 gameInfo.resultDetails);\r
8344     } else {\r
8345         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
8346     }\r
8347 \r
8348     fclose(f);\r
8349     return TRUE;\r
8350 }\r
8351 \r
8352 /* Save the current game to open file f and close the file */\r
8353 int\r
8354 SaveGame(f, dummy, dummy2)\r
8355      FILE *f;\r
8356      int dummy;\r
8357      char *dummy2;\r
8358 {\r
8359     if (gameMode == EditPosition) EditPositionDone();\r
8360     if (appData.oldSaveStyle)\r
8361       return SaveGameOldStyle(f);\r
8362     else\r
8363       return SaveGamePGN(f);\r
8364 }\r
8365 \r
8366 /* Save the current position to the given file */\r
8367 int\r
8368 SavePositionToFile(filename)\r
8369      char *filename;\r
8370 {\r
8371     FILE *f;\r
8372     char buf[MSG_SIZ];\r
8373 \r
8374     if (strcmp(filename, "-") == 0) {\r
8375         return SavePosition(stdout, 0, NULL);\r
8376     } else {\r
8377         f = fopen(filename, "a");\r
8378         if (f == NULL) {\r
8379             sprintf(buf, "Can't open \"%s\"", filename);\r
8380             DisplayError(buf, errno);\r
8381             return FALSE;\r
8382         } else {\r
8383             SavePosition(f, 0, NULL);\r
8384             return TRUE;\r
8385         }\r
8386     }\r
8387 }\r
8388 \r
8389 /* Save the current position to the given open file and close the file */\r
8390 int\r
8391 SavePosition(f, dummy, dummy2)\r
8392      FILE *f;\r
8393      int dummy;\r
8394      char *dummy2;\r
8395 {\r
8396     time_t tm;\r
8397     char *fen;\r
8398     \r
8399     if (appData.oldSaveStyle) {\r
8400         tm = time((time_t *) NULL);\r
8401     \r
8402         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
8403         PrintOpponents(f);\r
8404         fprintf(f, "[--------------\n");\r
8405         PrintPosition(f, currentMove);\r
8406         fprintf(f, "--------------]\n");\r
8407     } else {\r
8408         fen = PositionToFEN(currentMove, 1);\r
8409         fprintf(f, "%s\n", fen);\r
8410         free(fen);\r
8411     }\r
8412     fclose(f);\r
8413     return TRUE;\r
8414 }\r
8415 \r
8416 void\r
8417 ReloadCmailMsgEvent(unregister)\r
8418      int unregister;\r
8419 {\r
8420 #if !WIN32\r
8421     static char *inFilename = NULL;\r
8422     static char *outFilename;\r
8423     int i;\r
8424     struct stat inbuf, outbuf;\r
8425     int status;\r
8426     \r
8427     /* Any registered moves are unregistered if unregister is set, */\r
8428     /* i.e. invoked by the signal handler */\r
8429     if (unregister) {\r
8430         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
8431             cmailMoveRegistered[i] = FALSE;\r
8432             if (cmailCommentList[i] != NULL) {\r
8433                 free(cmailCommentList[i]);\r
8434                 cmailCommentList[i] = NULL;\r
8435             }\r
8436         }\r
8437         nCmailMovesRegistered = 0;\r
8438     }\r
8439 \r
8440     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
8441         cmailResult[i] = CMAIL_NOT_RESULT;\r
8442     }\r
8443     nCmailResults = 0;\r
8444 \r
8445     if (inFilename == NULL) {\r
8446         /* Because the filenames are static they only get malloced once  */\r
8447         /* and they never get freed                                      */\r
8448         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
8449         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
8450 \r
8451         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
8452         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
8453     }\r
8454     \r
8455     status = stat(outFilename, &outbuf);\r
8456     if (status < 0) {\r
8457         cmailMailedMove = FALSE;\r
8458     } else {\r
8459         status = stat(inFilename, &inbuf);\r
8460         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
8461     }\r
8462     \r
8463     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
8464        counts the games, notes how each one terminated, etc.\r
8465        \r
8466        It would be nice to remove this kludge and instead gather all\r
8467        the information while building the game list.  (And to keep it\r
8468        in the game list nodes instead of having a bunch of fixed-size\r
8469        parallel arrays.)  Note this will require getting each game's\r
8470        termination from the PGN tags, as the game list builder does\r
8471        not process the game moves.  --mann\r
8472        */\r
8473     cmailMsgLoaded = TRUE;\r
8474     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
8475     \r
8476     /* Load first game in the file or popup game menu */\r
8477     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
8478 \r
8479 #endif /* !WIN32 */\r
8480     return;\r
8481 }\r
8482 \r
8483 int\r
8484 RegisterMove()\r
8485 {\r
8486     FILE *f;\r
8487     char string[MSG_SIZ];\r
8488 \r
8489     if (   cmailMailedMove\r
8490         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
8491         return TRUE;            /* Allow free viewing  */\r
8492     }\r
8493 \r
8494     /* Unregister move to ensure that we don't leave RegisterMove        */\r
8495     /* with the move registered when the conditions for registering no   */\r
8496     /* longer hold                                                       */\r
8497     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8498         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8499         nCmailMovesRegistered --;\r
8500 \r
8501         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
8502           {\r
8503               free(cmailCommentList[lastLoadGameNumber - 1]);\r
8504               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
8505           }\r
8506     }\r
8507 \r
8508     if (cmailOldMove == -1) {\r
8509         DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);\r
8510         return FALSE;\r
8511     }\r
8512 \r
8513     if (currentMove > cmailOldMove + 1) {\r
8514         DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);\r
8515         return FALSE;\r
8516     }\r
8517 \r
8518     if (currentMove < cmailOldMove) {\r
8519         DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);\r
8520         return FALSE;\r
8521     }\r
8522 \r
8523     if (forwardMostMove > currentMove) {\r
8524         /* Silently truncate extra moves */\r
8525         TruncateGame();\r
8526     }\r
8527 \r
8528     if (   (currentMove == cmailOldMove + 1)\r
8529         || (   (currentMove == cmailOldMove)\r
8530             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
8531                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
8532         if (gameInfo.result != GameUnfinished) {\r
8533             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
8534         }\r
8535 \r
8536         if (commentList[currentMove] != NULL) {\r
8537             cmailCommentList[lastLoadGameNumber - 1]\r
8538               = StrSave(commentList[currentMove]);\r
8539         }\r
8540         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
8541 \r
8542         if (appData.debugMode)\r
8543           fprintf(debugFP, "Saving %s for game %d\n",\r
8544                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8545 \r
8546         sprintf(string,\r
8547                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
8548         \r
8549         f = fopen(string, "w");\r
8550         if (appData.oldSaveStyle) {\r
8551             SaveGameOldStyle(f); /* also closes the file */\r
8552             \r
8553             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
8554             f = fopen(string, "w");\r
8555             SavePosition(f, 0, NULL); /* also closes the file */\r
8556         } else {\r
8557             fprintf(f, "{--------------\n");\r
8558             PrintPosition(f, currentMove);\r
8559             fprintf(f, "--------------}\n\n");\r
8560             \r
8561             SaveGame(f, 0, NULL); /* also closes the file*/\r
8562         }\r
8563         \r
8564         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
8565         nCmailMovesRegistered ++;\r
8566     } else if (nCmailGames == 1) {\r
8567         DisplayError("You have not made a move yet", 0);\r
8568         return FALSE;\r
8569     }\r
8570 \r
8571     return TRUE;\r
8572 }\r
8573 \r
8574 void\r
8575 MailMoveEvent()\r
8576 {\r
8577 #if !WIN32\r
8578     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
8579     FILE *commandOutput;\r
8580     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
8581     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
8582     int nBuffers;\r
8583     int i;\r
8584     int archived;\r
8585     char *arcDir;\r
8586 \r
8587     if (! cmailMsgLoaded) {\r
8588         DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);\r
8589         return;\r
8590     }\r
8591 \r
8592     if (nCmailGames == nCmailResults) {\r
8593         DisplayError("No unfinished games", 0);\r
8594         return;\r
8595     }\r
8596 \r
8597 #if CMAIL_PROHIBIT_REMAIL\r
8598     if (cmailMailedMove) {\r
8599         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
8600         DisplayError(msg, 0);\r
8601         return;\r
8602     }\r
8603 #endif\r
8604 \r
8605     if (! (cmailMailedMove || RegisterMove())) return;\r
8606     \r
8607     if (   cmailMailedMove\r
8608         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
8609         sprintf(string, partCommandString,\r
8610                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
8611         commandOutput = popen(string, "rb");\r
8612 \r
8613         if (commandOutput == NULL) {\r
8614             DisplayError("Failed to invoke cmail", 0);\r
8615         } else {\r
8616             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
8617                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
8618             }\r
8619             if (nBuffers > 1) {\r
8620                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
8621                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
8622                 nBytes = MSG_SIZ - 1;\r
8623             } else {\r
8624                 (void) memcpy(msg, buffer, nBytes);\r
8625             }\r
8626             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
8627 \r
8628             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
8629                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
8630 \r
8631                 archived = TRUE;\r
8632                 for (i = 0; i < nCmailGames; i ++) {\r
8633                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
8634                         archived = FALSE;\r
8635                     }\r
8636                 }\r
8637                 if (   archived\r
8638                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
8639                         != NULL)) {\r
8640                     sprintf(buffer, "%s/%s.%s.archive",\r
8641                             arcDir,\r
8642                             appData.cmailGameName,\r
8643                             gameInfo.date);\r
8644                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
8645                     cmailMsgLoaded = FALSE;\r
8646                 }\r
8647             }\r
8648 \r
8649             DisplayInformation(msg);\r
8650             pclose(commandOutput);\r
8651         }\r
8652     } else {\r
8653         if ((*cmailMsg) != '\0') {\r
8654             DisplayInformation(cmailMsg);\r
8655         }\r
8656     }\r
8657 \r
8658     return;\r
8659 #endif /* !WIN32 */\r
8660 }\r
8661 \r
8662 char *\r
8663 CmailMsg()\r
8664 {\r
8665 #if WIN32\r
8666     return NULL;\r
8667 #else\r
8668     int  prependComma = 0;\r
8669     char number[5];\r
8670     char string[MSG_SIZ];       /* Space for game-list */\r
8671     int  i;\r
8672     \r
8673     if (!cmailMsgLoaded) return "";\r
8674 \r
8675     if (cmailMailedMove) {\r
8676         sprintf(cmailMsg, "Waiting for reply from opponent\n");\r
8677     } else {\r
8678         /* Create a list of games left */\r
8679         sprintf(string, "[");\r
8680         for (i = 0; i < nCmailGames; i ++) {\r
8681             if (! (   cmailMoveRegistered[i]\r
8682                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
8683                 if (prependComma) {\r
8684                     sprintf(number, ",%d", i + 1);\r
8685                 } else {\r
8686                     sprintf(number, "%d", i + 1);\r
8687                     prependComma = 1;\r
8688                 }\r
8689                 \r
8690                 strcat(string, number);\r
8691             }\r
8692         }\r
8693         strcat(string, "]");\r
8694 \r
8695         if (nCmailMovesRegistered + nCmailResults == 0) {\r
8696             switch (nCmailGames) {\r
8697               case 1:\r
8698                 sprintf(cmailMsg,\r
8699                         "Still need to make move for game\n");\r
8700                 break;\r
8701                 \r
8702               case 2:\r
8703                 sprintf(cmailMsg,\r
8704                         "Still need to make moves for both games\n");\r
8705                 break;\r
8706                 \r
8707               default:\r
8708                 sprintf(cmailMsg,\r
8709                         "Still need to make moves for all %d games\n",\r
8710                         nCmailGames);\r
8711                 break;\r
8712             }\r
8713         } else {\r
8714             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
8715               case 1:\r
8716                 sprintf(cmailMsg,\r
8717                         "Still need to make a move for game %s\n",\r
8718                         string);\r
8719                 break;\r
8720                 \r
8721               case 0:\r
8722                 if (nCmailResults == nCmailGames) {\r
8723                     sprintf(cmailMsg, "No unfinished games\n");\r
8724                 } else {\r
8725                     sprintf(cmailMsg, "Ready to send mail\n");\r
8726                 }\r
8727                 break;\r
8728                 \r
8729               default:\r
8730                 sprintf(cmailMsg,\r
8731                         "Still need to make moves for games %s\n",\r
8732                         string);\r
8733             }\r
8734         }\r
8735     }\r
8736     return cmailMsg;\r
8737 #endif /* WIN32 */\r
8738 }\r
8739 \r
8740 void\r
8741 ResetGameEvent()\r
8742 {\r
8743     if (gameMode == Training)\r
8744       SetTrainingModeOff();\r
8745 \r
8746     Reset(TRUE, TRUE);\r
8747     cmailMsgLoaded = FALSE;\r
8748     if (appData.icsActive) {\r
8749       SendToICS(ics_prefix);\r
8750       SendToICS("refresh\n");\r
8751     }\r
8752 }\r
8753 \r
8754 static int exiting = 0;\r
8755 \r
8756 void\r
8757 ExitEvent(status)\r
8758      int status;\r
8759 {\r
8760     exiting++;\r
8761     if (exiting > 2) {\r
8762       /* Give up on clean exit */\r
8763       exit(status);\r
8764     }\r
8765     if (exiting > 1) {\r
8766       /* Keep trying for clean exit */\r
8767       return;\r
8768     }\r
8769 \r
8770     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
8771 \r
8772     if (telnetISR != NULL) {\r
8773       RemoveInputSource(telnetISR);\r
8774     }\r
8775     if (icsPR != NoProc) {\r
8776       DestroyChildProcess(icsPR, TRUE);\r
8777     }\r
8778     /* Save game if resource set and not already saved by GameEnds() */\r
8779     if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {\r
8780       if (*appData.saveGameFile != NULLCHAR) {\r
8781         SaveGameToFile(appData.saveGameFile, TRUE);\r
8782       } else if (appData.autoSaveGames) {\r
8783         AutoSaveGame();\r
8784       }\r
8785       if (*appData.savePositionFile != NULLCHAR) {\r
8786         SavePositionToFile(appData.savePositionFile);\r
8787       }\r
8788     }\r
8789     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8790 \r
8791     /* Kill off chess programs */\r
8792     if (first.pr != NoProc) {\r
8793         ExitAnalyzeMode();\r
8794         \r
8795         DoSleep( appData.delayBeforeQuit );\r
8796         SendToProgram("quit\n", &first);\r
8797         DoSleep( appData.delayAfterQuit );\r
8798         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
8799     }\r
8800     if (second.pr != NoProc) {\r
8801         DoSleep( appData.delayBeforeQuit );\r
8802         SendToProgram("quit\n", &second);\r
8803         DoSleep( appData.delayAfterQuit );\r
8804         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
8805     }\r
8806     if (first.isr != NULL) {\r
8807         RemoveInputSource(first.isr);\r
8808     }\r
8809     if (second.isr != NULL) {\r
8810         RemoveInputSource(second.isr);\r
8811     }\r
8812 \r
8813     ShutDownFrontEnd();\r
8814     exit(status);\r
8815 }\r
8816 \r
8817 void\r
8818 PauseEvent()\r
8819 {\r
8820     if (appData.debugMode)\r
8821         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
8822     if (pausing) {\r
8823         pausing = FALSE;\r
8824         ModeHighlight();\r
8825         if (gameMode == MachinePlaysWhite ||\r
8826             gameMode == MachinePlaysBlack) {\r
8827             StartClocks();\r
8828         } else {\r
8829             DisplayBothClocks();\r
8830         }\r
8831         if (gameMode == PlayFromGameFile) {\r
8832             if (appData.timeDelay >= 0) \r
8833                 AutoPlayGameLoop();\r
8834         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
8835             Reset(FALSE, TRUE);\r
8836             SendToICS(ics_prefix);\r
8837             SendToICS("refresh\n");\r
8838         } else if (currentMove < forwardMostMove) {\r
8839             ForwardInner(forwardMostMove);\r
8840         }\r
8841         pauseExamInvalid = FALSE;\r
8842     } else {\r
8843         switch (gameMode) {\r
8844           default:\r
8845             return;\r
8846           case IcsExamining:\r
8847             pauseExamForwardMostMove = forwardMostMove;\r
8848             pauseExamInvalid = FALSE;\r
8849             /* fall through */\r
8850           case IcsObserving:\r
8851           case IcsPlayingWhite:\r
8852           case IcsPlayingBlack:\r
8853             pausing = TRUE;\r
8854             ModeHighlight();\r
8855             return;\r
8856           case PlayFromGameFile:\r
8857             (void) StopLoadGameTimer();\r
8858             pausing = TRUE;\r
8859             ModeHighlight();\r
8860             break;\r
8861           case BeginningOfGame:\r
8862             if (appData.icsActive) return;\r
8863             /* else fall through */\r
8864           case MachinePlaysWhite:\r
8865           case MachinePlaysBlack:\r
8866           case TwoMachinesPlay:\r
8867             if (forwardMostMove == 0)\r
8868               return;           /* don't pause if no one has moved */\r
8869             if ((gameMode == MachinePlaysWhite &&\r
8870                  !WhiteOnMove(forwardMostMove)) ||\r
8871                 (gameMode == MachinePlaysBlack &&\r
8872                  WhiteOnMove(forwardMostMove))) {\r
8873                 StopClocks();\r
8874             }\r
8875             pausing = TRUE;\r
8876             ModeHighlight();\r
8877             break;\r
8878         }\r
8879     }\r
8880 }\r
8881 \r
8882 void\r
8883 EditCommentEvent()\r
8884 {\r
8885     char title[MSG_SIZ];\r
8886 \r
8887     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
8888         strcpy(title, "Edit comment");\r
8889     } else {\r
8890         sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,\r
8891                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
8892                 parseList[currentMove - 1]);\r
8893     }\r
8894 \r
8895     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
8896 }\r
8897 \r
8898 \r
8899 void\r
8900 EditTagsEvent()\r
8901 {\r
8902     char *tags = PGNTags(&gameInfo);\r
8903     EditTagsPopUp(tags);\r
8904     free(tags);\r
8905 }\r
8906 \r
8907 void\r
8908 AnalyzeModeEvent()\r
8909 {\r
8910     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
8911       return;\r
8912 \r
8913     if (gameMode != AnalyzeFile) {\r
8914         EditGameEvent();\r
8915         if (gameMode != EditGame) return;\r
8916         ResurrectChessProgram();\r
8917         SendToProgram("analyze\n", &first);\r
8918         first.analyzing = TRUE;\r
8919         /*first.maybeThinking = TRUE;*/\r
8920         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
8921         AnalysisPopUp("Analysis",\r
8922                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
8923     }\r
8924     gameMode = AnalyzeMode;\r
8925     pausing = FALSE;\r
8926     ModeHighlight();\r
8927     SetGameInfo();\r
8928 \r
8929     StartAnalysisClock();\r
8930     GetTimeMark(&lastNodeCountTime);\r
8931     lastNodeCount = 0;\r
8932 }\r
8933 \r
8934 void\r
8935 AnalyzeFileEvent()\r
8936 {\r
8937     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
8938       return;\r
8939 \r
8940     if (gameMode != AnalyzeMode) {\r
8941         EditGameEvent();\r
8942         if (gameMode != EditGame) return;\r
8943         ResurrectChessProgram();\r
8944         SendToProgram("analyze\n", &first);\r
8945         first.analyzing = TRUE;\r
8946         /*first.maybeThinking = TRUE;*/\r
8947         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
8948         AnalysisPopUp("Analysis",\r
8949                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
8950     }\r
8951     gameMode = AnalyzeFile;\r
8952     pausing = FALSE;\r
8953     ModeHighlight();\r
8954     SetGameInfo();\r
8955 \r
8956     StartAnalysisClock();\r
8957     GetTimeMark(&lastNodeCountTime);\r
8958     lastNodeCount = 0;\r
8959 }\r
8960 \r
8961 void\r
8962 MachineWhiteEvent()\r
8963 {\r
8964     char buf[MSG_SIZ];\r
8965 \r
8966     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
8967       return;\r
8968 \r
8969 \r
8970     if (gameMode == PlayFromGameFile || \r
8971         gameMode == TwoMachinesPlay  || \r
8972         gameMode == Training         || \r
8973         gameMode == AnalyzeMode      || \r
8974         gameMode == EndOfGame)\r
8975         EditGameEvent();\r
8976 \r
8977     if (gameMode == EditPosition) \r
8978         EditPositionDone();\r
8979 \r
8980     if (!WhiteOnMove(currentMove)) {\r
8981         DisplayError("It is not White's turn", 0);\r
8982         return;\r
8983     }\r
8984   \r
8985     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
8986       ExitAnalyzeMode();\r
8987 \r
8988     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
8989         gameMode == AnalyzeFile)\r
8990         TruncateGame();\r
8991 \r
8992     ResurrectChessProgram();    /* in case it isn't running */\r
8993     gameMode = MachinePlaysWhite;\r
8994     pausing = FALSE;\r
8995     ModeHighlight();\r
8996     SetGameInfo();\r
8997     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
8998     DisplayTitle(buf);\r
8999     if (first.sendName) {\r
9000       sprintf(buf, "name %s\n", gameInfo.black);\r
9001       SendToProgram(buf, &first);\r
9002     }\r
9003     if (first.sendTime) {\r
9004       if (first.useColors) {\r
9005         SendToProgram("black\n", &first); /*gnu kludge*/\r
9006       }\r
9007       SendTimeRemaining(&first, TRUE);\r
9008     }\r
9009     if (first.useColors) {\r
9010       SendToProgram("white\ngo\n", &first);\r
9011     } else {\r
9012       SendToProgram("go\n", &first);\r
9013     }\r
9014     SetMachineThinkingEnables();\r
9015     first.maybeThinking = TRUE;\r
9016     StartClocks();\r
9017 \r
9018     if (appData.autoFlipView && !flipView) {\r
9019       flipView = !flipView;\r
9020       DrawPosition(FALSE, NULL);\r
9021     }\r
9022 }\r
9023 \r
9024 void\r
9025 MachineBlackEvent()\r
9026 {\r
9027     char buf[MSG_SIZ];\r
9028 \r
9029     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
9030         return;\r
9031 \r
9032 \r
9033     if (gameMode == PlayFromGameFile || \r
9034         gameMode == TwoMachinesPlay  || \r
9035         gameMode == Training         || \r
9036         gameMode == AnalyzeMode      || \r
9037         gameMode == EndOfGame)\r
9038         EditGameEvent();\r
9039 \r
9040     if (gameMode == EditPosition) \r
9041         EditPositionDone();\r
9042 \r
9043     if (WhiteOnMove(currentMove)) {\r
9044         DisplayError("It is not Black's turn", 0);\r
9045         return;\r
9046     }\r
9047     \r
9048     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
9049       ExitAnalyzeMode();\r
9050 \r
9051     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
9052         gameMode == AnalyzeFile)\r
9053         TruncateGame();\r
9054 \r
9055     ResurrectChessProgram();    /* in case it isn't running */\r
9056     gameMode = MachinePlaysBlack;\r
9057     pausing = FALSE;\r
9058     ModeHighlight();\r
9059     SetGameInfo();\r
9060     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
9061     DisplayTitle(buf);\r
9062     if (first.sendName) {\r
9063       sprintf(buf, "name %s\n", gameInfo.white);\r
9064       SendToProgram(buf, &first);\r
9065     }\r
9066     if (first.sendTime) {\r
9067       if (first.useColors) {\r
9068         SendToProgram("white\n", &first); /*gnu kludge*/\r
9069       }\r
9070       SendTimeRemaining(&first, FALSE);\r
9071     }\r
9072     if (first.useColors) {\r
9073       SendToProgram("black\ngo\n", &first);\r
9074     } else {\r
9075       SendToProgram("go\n", &first);\r
9076     }\r
9077     SetMachineThinkingEnables();\r
9078     first.maybeThinking = TRUE;\r
9079     StartClocks();\r
9080 \r
9081     if (appData.autoFlipView && flipView) {\r
9082       flipView = !flipView;\r
9083       DrawPosition(FALSE, NULL);\r
9084     }\r
9085 }\r
9086 \r
9087 \r
9088 void\r
9089 DisplayTwoMachinesTitle()\r
9090 {\r
9091     char buf[MSG_SIZ];\r
9092     if (appData.matchGames > 0) {\r
9093         if (first.twoMachinesColor[0] == 'w') {\r
9094             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
9095                     gameInfo.white, gameInfo.black,\r
9096                     first.matchWins, second.matchWins,\r
9097                     matchGame - 1 - (first.matchWins + second.matchWins));\r
9098         } else {\r
9099             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
9100                     gameInfo.white, gameInfo.black,\r
9101                     second.matchWins, first.matchWins,\r
9102                     matchGame - 1 - (first.matchWins + second.matchWins));\r
9103         }\r
9104     } else {\r
9105         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
9106     }\r
9107     DisplayTitle(buf);\r
9108 }\r
9109 \r
9110 void\r
9111 TwoMachinesEvent P((void))\r
9112 {\r
9113     int i;\r
9114     char buf[MSG_SIZ];\r
9115     ChessProgramState *onmove;\r
9116     \r
9117     if (appData.noChessProgram) return;\r
9118 \r
9119     switch (gameMode) {\r
9120       case TwoMachinesPlay:\r
9121         return;\r
9122       case MachinePlaysWhite:\r
9123       case MachinePlaysBlack:\r
9124         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
9125             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
9126             return;\r
9127         }\r
9128         /* fall through */\r
9129       case BeginningOfGame:\r
9130       case PlayFromGameFile:\r
9131       case EndOfGame:\r
9132         EditGameEvent();\r
9133         if (gameMode != EditGame) return;\r
9134         break;\r
9135       case EditPosition:\r
9136         EditPositionDone();\r
9137         break;\r
9138       case AnalyzeMode:\r
9139       case AnalyzeFile:\r
9140         ExitAnalyzeMode();\r
9141         break;\r
9142       case EditGame:\r
9143       default:\r
9144         break;\r
9145     }\r
9146 \r
9147     forwardMostMove = currentMove;\r
9148     ResurrectChessProgram();    /* in case first program isn't running */\r
9149 \r
9150     if (second.pr == NULL) {\r
9151         StartChessProgram(&second);\r
9152         if (second.protocolVersion == 1) {\r
9153           TwoMachinesEventIfReady();\r
9154         } else {\r
9155           /* kludge: allow timeout for initial "feature" command */\r
9156           FreezeUI();\r
9157           DisplayMessage("", "Starting second chess program");\r
9158           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
9159         }\r
9160         return;\r
9161     }\r
9162     DisplayMessage("", "");\r
9163     InitChessProgram(&second);\r
9164     SendToProgram("force\n", &second);\r
9165     if (startedFromSetupPosition) {\r
9166         SendBoard(&second, backwardMostMove);\r
9167     }\r
9168     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
9169         SendMoveToProgram(i, &second);\r
9170     }\r
9171 \r
9172     gameMode = TwoMachinesPlay;\r
9173     pausing = FALSE;\r
9174     ModeHighlight();\r
9175     SetGameInfo();\r
9176     DisplayTwoMachinesTitle();\r
9177     firstMove = TRUE;\r
9178     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
9179         onmove = &first;\r
9180     } else {\r
9181         onmove = &second;\r
9182     }\r
9183 \r
9184     SendToProgram(first.computerString, &first);\r
9185     if (first.sendName) {\r
9186       sprintf(buf, "name %s\n", second.tidy);\r
9187       SendToProgram(buf, &first);\r
9188     }\r
9189     SendToProgram(second.computerString, &second);\r
9190     if (second.sendName) {\r
9191       sprintf(buf, "name %s\n", first.tidy);\r
9192       SendToProgram(buf, &second);\r
9193     }\r
9194 \r
9195     if (!first.sendTime || !second.sendTime) {\r
9196         ResetClocks();\r
9197         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9198         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9199     }\r
9200     if (onmove->sendTime) {\r
9201       if (onmove->useColors) {\r
9202         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
9203       }\r
9204       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
9205     }\r
9206     if (onmove->useColors) {\r
9207       SendToProgram(onmove->twoMachinesColor, onmove);\r
9208     }\r
9209     SendToProgram("go\n", onmove);\r
9210     onmove->maybeThinking = TRUE;\r
9211     SetMachineThinkingEnables();\r
9212 \r
9213     StartClocks();\r
9214 }\r
9215 \r
9216 void\r
9217 TrainingEvent()\r
9218 {\r
9219     if (gameMode == Training) {\r
9220       SetTrainingModeOff();\r
9221       gameMode = PlayFromGameFile;\r
9222       DisplayMessage("", "Training mode off");\r
9223     } else {\r
9224       gameMode = Training;\r
9225       animateTraining = appData.animate;\r
9226 \r
9227       /* make sure we are not already at the end of the game */\r
9228       if (currentMove < forwardMostMove) {\r
9229         SetTrainingModeOn();\r
9230         DisplayMessage("", "Training mode on");\r
9231       } else {\r
9232         gameMode = PlayFromGameFile;\r
9233         DisplayError("Already at end of game", 0);\r
9234       }\r
9235     }\r
9236     ModeHighlight();\r
9237 }\r
9238 \r
9239 void\r
9240 IcsClientEvent()\r
9241 {\r
9242     if (!appData.icsActive) return;\r
9243     switch (gameMode) {\r
9244       case IcsPlayingWhite:\r
9245       case IcsPlayingBlack:\r
9246       case IcsObserving:\r
9247       case IcsIdle:\r
9248       case BeginningOfGame:\r
9249       case IcsExamining:\r
9250         return;\r
9251 \r
9252       case EditGame:\r
9253         break;\r
9254 \r
9255       case EditPosition:\r
9256         EditPositionDone();\r
9257         break;\r
9258 \r
9259       case AnalyzeMode:\r
9260       case AnalyzeFile:\r
9261         ExitAnalyzeMode();\r
9262         break;\r
9263         \r
9264       default:\r
9265         EditGameEvent();\r
9266         break;\r
9267     }\r
9268 \r
9269     gameMode = IcsIdle;\r
9270     ModeHighlight();\r
9271     return;\r
9272 }\r
9273 \r
9274 \r
9275 void\r
9276 EditGameEvent()\r
9277 {\r
9278     int i;\r
9279 \r
9280     switch (gameMode) {\r
9281       case Training:\r
9282         SetTrainingModeOff();\r
9283         break;\r
9284       case MachinePlaysWhite:\r
9285       case MachinePlaysBlack:\r
9286       case BeginningOfGame:\r
9287         SendToProgram("force\n", &first);\r
9288         SetUserThinkingEnables();\r
9289         break;\r
9290       case PlayFromGameFile:\r
9291         (void) StopLoadGameTimer();\r
9292         if (gameFileFP != NULL) {\r
9293             gameFileFP = NULL;\r
9294         }\r
9295         break;\r
9296       case EditPosition:\r
9297         EditPositionDone();\r
9298         break;\r
9299       case AnalyzeMode:\r
9300       case AnalyzeFile:\r
9301         ExitAnalyzeMode();\r
9302         SendToProgram("force\n", &first);\r
9303         break;\r
9304       case TwoMachinesPlay:\r
9305         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
9306         ResurrectChessProgram();\r
9307         SetUserThinkingEnables();\r
9308         break;\r
9309       case EndOfGame:\r
9310         ResurrectChessProgram();\r
9311         break;\r
9312       case IcsPlayingBlack:\r
9313       case IcsPlayingWhite:\r
9314         DisplayError("Warning: You are still playing a game", 0);\r
9315         break;\r
9316       case IcsObserving:\r
9317         DisplayError("Warning: You are still observing a game", 0);\r
9318         break;\r
9319       case IcsExamining:\r
9320         DisplayError("Warning: You are still examining a game", 0);\r
9321         break;\r
9322       case IcsIdle:\r
9323         break;\r
9324       case EditGame:\r
9325       default:\r
9326         return;\r
9327     }\r
9328     \r
9329     pausing = FALSE;\r
9330     StopClocks();\r
9331     first.offeredDraw = second.offeredDraw = 0;\r
9332 \r
9333     if (gameMode == PlayFromGameFile) {\r
9334         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9335         blackTimeRemaining = timeRemaining[1][currentMove];\r
9336         DisplayTitle("");\r
9337     }\r
9338 \r
9339     if (gameMode == MachinePlaysWhite ||\r
9340         gameMode == MachinePlaysBlack ||\r
9341         gameMode == TwoMachinesPlay ||\r
9342         gameMode == EndOfGame) {\r
9343         i = forwardMostMove;\r
9344         while (i > currentMove) {\r
9345             SendToProgram("undo\n", &first);\r
9346             i--;\r
9347         }\r
9348         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9349         blackTimeRemaining = timeRemaining[1][currentMove];\r
9350         DisplayBothClocks();\r
9351         if (whiteFlag || blackFlag) {\r
9352             whiteFlag = blackFlag = 0;\r
9353         }\r
9354         DisplayTitle("");\r
9355     }           \r
9356     \r
9357     gameMode = EditGame;\r
9358     ModeHighlight();\r
9359     SetGameInfo();\r
9360 }\r
9361 \r
9362 \r
9363 void\r
9364 EditPositionEvent()\r
9365 {\r
9366     if (gameMode == EditPosition) {\r
9367         EditGameEvent();\r
9368         return;\r
9369     }\r
9370     \r
9371     EditGameEvent();\r
9372     if (gameMode != EditGame) return;\r
9373     \r
9374     gameMode = EditPosition;\r
9375     ModeHighlight();\r
9376     SetGameInfo();\r
9377     if (currentMove > 0)\r
9378       CopyBoard(boards[0], boards[currentMove]);\r
9379     \r
9380     blackPlaysFirst = !WhiteOnMove(currentMove);\r
9381     ResetClocks();\r
9382     currentMove = forwardMostMove = backwardMostMove = 0;\r
9383     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9384     DisplayMove(-1);\r
9385 }\r
9386 \r
9387 void\r
9388 ExitAnalyzeMode()\r
9389 {\r
9390     if (first.analysisSupport && first.analyzing) {\r
9391       SendToProgram("exit\n", &first);\r
9392       first.analyzing = FALSE;\r
9393     }\r
9394     AnalysisPopDown();\r
9395     thinkOutput[0] = NULLCHAR;\r
9396 }\r
9397 \r
9398 void\r
9399 EditPositionDone()\r
9400 {\r
9401     startedFromSetupPosition = TRUE;\r
9402     InitChessProgram(&first);\r
9403     SendToProgram("force\n", &first);\r
9404     if (blackPlaysFirst) {\r
9405         strcpy(moveList[0], "");\r
9406         strcpy(parseList[0], "");\r
9407         currentMove = forwardMostMove = backwardMostMove = 1;\r
9408         CopyBoard(boards[1], boards[0]);\r
9409     } else {\r
9410         currentMove = forwardMostMove = backwardMostMove = 0;\r
9411     }\r
9412     SendBoard(&first, forwardMostMove);\r
9413     DisplayTitle("");\r
9414     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9415     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9416     gameMode = EditGame;\r
9417     ModeHighlight();\r
9418     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9419     ClearHighlights(); /* [AS] */\r
9420 }\r
9421 \r
9422 /* Pause for `ms' milliseconds */\r
9423 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
9424 void\r
9425 TimeDelay(ms)\r
9426      long ms;\r
9427 {\r
9428     TimeMark m1, m2;\r
9429 \r
9430     GetTimeMark(&m1);\r
9431     do {\r
9432         GetTimeMark(&m2);\r
9433     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
9434 }\r
9435 \r
9436 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
9437 void\r
9438 SendMultiLineToICS(buf)\r
9439      char *buf;\r
9440 {\r
9441     char temp[MSG_SIZ+1], *p;\r
9442     int len;\r
9443 \r
9444     len = strlen(buf);\r
9445     if (len > MSG_SIZ)\r
9446       len = MSG_SIZ;\r
9447   \r
9448     strncpy(temp, buf, len);\r
9449     temp[len] = 0;\r
9450 \r
9451     p = temp;\r
9452     while (*p) {\r
9453         if (*p == '\n' || *p == '\r')\r
9454           *p = ' ';\r
9455         ++p;\r
9456     }\r
9457 \r
9458     strcat(temp, "\n");\r
9459     SendToICS(temp);\r
9460     SendToPlayer(temp, strlen(temp));\r
9461 }\r
9462 \r
9463 void\r
9464 SetWhiteToPlayEvent()\r
9465 {\r
9466     if (gameMode == EditPosition) {\r
9467         blackPlaysFirst = FALSE;\r
9468         DisplayBothClocks();    /* works because currentMove is 0 */\r
9469     } else if (gameMode == IcsExamining) {\r
9470         SendToICS(ics_prefix);\r
9471         SendToICS("tomove white\n");\r
9472     }\r
9473 }\r
9474 \r
9475 void\r
9476 SetBlackToPlayEvent()\r
9477 {\r
9478     if (gameMode == EditPosition) {\r
9479         blackPlaysFirst = TRUE;\r
9480         currentMove = 1;        /* kludge */\r
9481         DisplayBothClocks();\r
9482         currentMove = 0;\r
9483     } else if (gameMode == IcsExamining) {\r
9484         SendToICS(ics_prefix);\r
9485         SendToICS("tomove black\n");\r
9486     }\r
9487 }\r
9488 \r
9489 void\r
9490 EditPositionMenuEvent(selection, x, y)\r
9491      ChessSquare selection;\r
9492      int x, y;\r
9493 {\r
9494     char buf[MSG_SIZ];\r
9495     ChessSquare piece = boards[0][y][x];\r
9496 \r
9497     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
9498 \r
9499     switch (selection) {\r
9500       case ClearBoard:\r
9501         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
9502             SendToICS(ics_prefix);\r
9503             SendToICS("bsetup clear\n");\r
9504         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
9505             SendToICS(ics_prefix);\r
9506             SendToICS("clearboard\n");\r
9507         } else {\r
9508             for (x = 0; x < BOARD_WIDTH; x++) {\r
9509                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
9510                     if (gameMode == IcsExamining) {\r
9511                         if (boards[currentMove][y][x] != EmptySquare) {\r
9512                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
9513                                     AAA + x, ONE + y);\r
9514                             SendToICS(buf);\r
9515                         }\r
9516                     } else {\r
9517                         boards[0][y][x] = EmptySquare;\r
9518                     }\r
9519                 }\r
9520             }\r
9521         }\r
9522         if (gameMode == EditPosition) {\r
9523             DrawPosition(FALSE, boards[0]);\r
9524         }\r
9525         break;\r
9526 \r
9527       case WhitePlay:\r
9528         SetWhiteToPlayEvent();\r
9529         break;\r
9530 \r
9531       case BlackPlay:\r
9532         SetBlackToPlayEvent();\r
9533         break;\r
9534 \r
9535       case EmptySquare:\r
9536         if (gameMode == IcsExamining) {\r
9537             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
9538             SendToICS(buf);\r
9539         } else {\r
9540             boards[0][y][x] = EmptySquare;\r
9541             DrawPosition(FALSE, boards[0]);\r
9542         }\r
9543         break;\r
9544 \r
9545       case PromotePiece:\r
9546         if(piece >= (int)WhitePawn && piece < (int)WhiteWazir ||\r
9547            piece >= (int)BlackPawn && piece < (int)BlackWazir   ) {\r
9548             selection = (ChessSquare) (PROMOTED piece);\r
9549         } else if(piece == EmptySquare) selection = WhiteWazir;\r
9550         else selection = (ChessSquare)((int)piece - 1);\r
9551         goto defaultlabel;\r
9552 \r
9553       case DemotePiece:\r
9554         if(piece >= (int)WhiteUnicorn && piece < (int)WhiteKing ||\r
9555            piece >= (int)BlackUnicorn && piece < (int)BlackKing   ) {\r
9556             selection = (ChessSquare) (DEMOTED piece);\r
9557         } else if( piece == WhiteKing || piece == BlackKing )\r
9558             selection = (ChessSquare)((int)piece - (int)WhiteKing + (int)WhiteMan);\r
9559         else if(piece == EmptySquare) selection = BlackWazir;\r
9560         else selection = (ChessSquare)((int)piece + 1);       \r
9561         goto defaultlabel;\r
9562 \r
9563       case WhiteQueen:\r
9564       case BlackQueen:\r
9565         if(gameInfo.variant == VariantShatranj ||\r
9566            gameInfo.variant == VariantXiangqi  ||\r
9567            gameInfo.variant == VariantCourier    )\r
9568             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
9569         goto defaultlabel;\r
9570 \r
9571       case WhiteKing:\r
9572       case BlackKing:\r
9573         if(gameInfo.variant == VariantXiangqi)\r
9574             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
9575         if(gameInfo.variant == VariantKnightmate)\r
9576             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
9577       default:\r
9578         defaultlabel:\r
9579         if (gameMode == IcsExamining) {\r
9580             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
9581                     PieceToChar(selection), AAA + x, ONE + y);\r
9582             SendToICS(buf);\r
9583         } else {\r
9584             boards[0][y][x] = selection;\r
9585             DrawPosition(FALSE, boards[0]);\r
9586         }\r
9587         break;\r
9588     }\r
9589 }\r
9590 \r
9591 \r
9592 void\r
9593 DropMenuEvent(selection, x, y)\r
9594      ChessSquare selection;\r
9595      int x, y;\r
9596 {\r
9597     ChessMove moveType;\r
9598 \r
9599     switch (gameMode) {\r
9600       case IcsPlayingWhite:\r
9601       case MachinePlaysBlack:\r
9602         if (!WhiteOnMove(currentMove)) {\r
9603             DisplayMoveError("It is Black's turn");\r
9604             return;\r
9605         }\r
9606         moveType = WhiteDrop;\r
9607         break;\r
9608       case IcsPlayingBlack:\r
9609       case MachinePlaysWhite:\r
9610         if (WhiteOnMove(currentMove)) {\r
9611             DisplayMoveError("It is White's turn");\r
9612             return;\r
9613         }\r
9614         moveType = BlackDrop;\r
9615         break;\r
9616       case EditGame:\r
9617         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
9618         break;\r
9619       default:\r
9620         return;\r
9621     }\r
9622 \r
9623     if (moveType == BlackDrop && selection < BlackPawn) {\r
9624       selection = (ChessSquare) ((int) selection\r
9625                                  + (int) BlackPawn - (int) WhitePawn);\r
9626     }\r
9627     if (boards[currentMove][y][x] != EmptySquare) {\r
9628         DisplayMoveError("That square is occupied");\r
9629         return;\r
9630     }\r
9631 \r
9632     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
9633 }\r
9634 \r
9635 void\r
9636 AcceptEvent()\r
9637 {\r
9638     /* Accept a pending offer of any kind from opponent */\r
9639     \r
9640     if (appData.icsActive) {\r
9641         SendToICS(ics_prefix);\r
9642         SendToICS("accept\n");\r
9643     } else if (cmailMsgLoaded) {\r
9644         if (currentMove == cmailOldMove &&\r
9645             commentList[cmailOldMove] != NULL &&\r
9646             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
9647                    "Black offers a draw" : "White offers a draw")) {\r
9648             TruncateGame();\r
9649             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
9650             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
9651         } else {\r
9652             DisplayError("There is no pending offer on this move", 0);\r
9653             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
9654         }\r
9655     } else {\r
9656         /* Not used for offers from chess program */\r
9657     }\r
9658 }\r
9659 \r
9660 void\r
9661 DeclineEvent()\r
9662 {\r
9663     /* Decline a pending offer of any kind from opponent */\r
9664     \r
9665     if (appData.icsActive) {\r
9666         SendToICS(ics_prefix);\r
9667         SendToICS("decline\n");\r
9668     } else if (cmailMsgLoaded) {\r
9669         if (currentMove == cmailOldMove &&\r
9670             commentList[cmailOldMove] != NULL &&\r
9671             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
9672                    "Black offers a draw" : "White offers a draw")) {\r
9673 #ifdef NOTDEF\r
9674             AppendComment(cmailOldMove, "Draw declined");\r
9675             DisplayComment(cmailOldMove - 1, "Draw declined");\r
9676 #endif /*NOTDEF*/\r
9677         } else {\r
9678             DisplayError("There is no pending offer on this move", 0);\r
9679         }\r
9680     } else {\r
9681         /* Not used for offers from chess program */\r
9682     }\r
9683 }\r
9684 \r
9685 void\r
9686 RematchEvent()\r
9687 {\r
9688     /* Issue ICS rematch command */\r
9689     if (appData.icsActive) {\r
9690         SendToICS(ics_prefix);\r
9691         SendToICS("rematch\n");\r
9692     }\r
9693 }\r
9694 \r
9695 void\r
9696 CallFlagEvent()\r
9697 {\r
9698     /* Call your opponent's flag (claim a win on time) */\r
9699     if (appData.icsActive) {\r
9700         SendToICS(ics_prefix);\r
9701         SendToICS("flag\n");\r
9702     } else {\r
9703         switch (gameMode) {\r
9704           default:\r
9705             return;\r
9706           case MachinePlaysWhite:\r
9707             if (whiteFlag) {\r
9708                 if (blackFlag)\r
9709                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
9710                            GE_PLAYER);\r
9711                 else\r
9712                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
9713             } else {\r
9714                 DisplayError("Your opponent is not out of time", 0);\r
9715             }\r
9716             break;\r
9717           case MachinePlaysBlack:\r
9718             if (blackFlag) {\r
9719                 if (whiteFlag)\r
9720                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
9721                            GE_PLAYER);\r
9722                 else\r
9723                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
9724             } else {\r
9725                 DisplayError("Your opponent is not out of time", 0);\r
9726             }\r
9727             break;\r
9728         }\r
9729     }\r
9730 }\r
9731 \r
9732 void\r
9733 DrawEvent()\r
9734 {\r
9735     /* Offer draw or accept pending draw offer from opponent */\r
9736     \r
9737     if (appData.icsActive) {\r
9738         /* Note: tournament rules require draw offers to be\r
9739            made after you make your move but before you punch\r
9740            your clock.  Currently ICS doesn't let you do that;\r
9741            instead, you immediately punch your clock after making\r
9742            a move, but you can offer a draw at any time. */\r
9743         \r
9744         SendToICS(ics_prefix);\r
9745         SendToICS("draw\n");\r
9746     } else if (cmailMsgLoaded) {\r
9747         if (currentMove == cmailOldMove &&\r
9748             commentList[cmailOldMove] != NULL &&\r
9749             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
9750                    "Black offers a draw" : "White offers a draw")) {\r
9751             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
9752             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
9753         } else if (currentMove == cmailOldMove + 1) {\r
9754             char *offer = WhiteOnMove(cmailOldMove) ?\r
9755               "White offers a draw" : "Black offers a draw";\r
9756             AppendComment(currentMove, offer);\r
9757             DisplayComment(currentMove - 1, offer);\r
9758             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
9759         } else {\r
9760             DisplayError("You must make your move before offering a draw", 0);\r
9761             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
9762         }\r
9763     } else if (first.offeredDraw) {\r
9764         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
9765     } else {\r
9766         if (first.sendDrawOffers) {\r
9767             SendToProgram("draw\n", &first);\r
9768             userOfferedDraw = TRUE;\r
9769         }\r
9770     }\r
9771 }\r
9772 \r
9773 void\r
9774 AdjournEvent()\r
9775 {\r
9776     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
9777     \r
9778     if (appData.icsActive) {\r
9779         SendToICS(ics_prefix);\r
9780         SendToICS("adjourn\n");\r
9781     } else {\r
9782         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
9783     }\r
9784 }\r
9785 \r
9786 \r
9787 void\r
9788 AbortEvent()\r
9789 {\r
9790     /* Offer Abort or accept pending Abort offer from opponent */\r
9791     \r
9792     if (appData.icsActive) {\r
9793         SendToICS(ics_prefix);\r
9794         SendToICS("abort\n");\r
9795     } else {\r
9796         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
9797     }\r
9798 }\r
9799 \r
9800 void\r
9801 ResignEvent()\r
9802 {\r
9803     /* Resign.  You can do this even if it's not your turn. */\r
9804     \r
9805     if (appData.icsActive) {\r
9806         SendToICS(ics_prefix);\r
9807         SendToICS("resign\n");\r
9808     } else {\r
9809         switch (gameMode) {\r
9810           case MachinePlaysWhite:\r
9811             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
9812             break;\r
9813           case MachinePlaysBlack:\r
9814             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
9815             break;\r
9816           case EditGame:\r
9817             if (cmailMsgLoaded) {\r
9818                 TruncateGame();\r
9819                 if (WhiteOnMove(cmailOldMove)) {\r
9820                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
9821                 } else {\r
9822                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
9823                 }\r
9824                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
9825             }\r
9826             break;\r
9827           default:\r
9828             break;\r
9829         }\r
9830     }\r
9831 }\r
9832 \r
9833 \r
9834 void\r
9835 StopObservingEvent()\r
9836 {\r
9837     /* Stop observing current games */\r
9838     SendToICS(ics_prefix);\r
9839     SendToICS("unobserve\n");\r
9840 }\r
9841 \r
9842 void\r
9843 StopExaminingEvent()\r
9844 {\r
9845     /* Stop observing current game */\r
9846     SendToICS(ics_prefix);\r
9847     SendToICS("unexamine\n");\r
9848 }\r
9849 \r
9850 void\r
9851 ForwardInner(target)\r
9852      int target;\r
9853 {\r
9854     int limit;\r
9855 \r
9856     if (appData.debugMode)\r
9857         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
9858                 target, currentMove, forwardMostMove);\r
9859 \r
9860     if (gameMode == EditPosition)\r
9861       return;\r
9862 \r
9863     if (gameMode == PlayFromGameFile && !pausing)\r
9864       PauseEvent();\r
9865     \r
9866     if (gameMode == IcsExamining && pausing)\r
9867       limit = pauseExamForwardMostMove;\r
9868     else\r
9869       limit = forwardMostMove;\r
9870     \r
9871     if (target > limit) target = limit;\r
9872 \r
9873     if (target > 0 && moveList[target - 1][0]) {\r
9874         int fromX, fromY, toX, toY;\r
9875         toX = moveList[target - 1][2] - AAA;\r
9876         toY = moveList[target - 1][3] - ONE;\r
9877         if (moveList[target - 1][1] == '@') {\r
9878             if (appData.highlightLastMove) {\r
9879                 SetHighlights(-1, -1, toX, toY);\r
9880             }\r
9881         } else {\r
9882             fromX = moveList[target - 1][0] - AAA;\r
9883             fromY = moveList[target - 1][1] - ONE;\r
9884             if (target == currentMove + 1) {\r
9885                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
9886             }\r
9887             if (appData.highlightLastMove) {\r
9888                 SetHighlights(fromX, fromY, toX, toY);\r
9889             }\r
9890         }\r
9891     }\r
9892     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
9893         gameMode == Training || gameMode == PlayFromGameFile || \r
9894         gameMode == AnalyzeFile) {\r
9895         while (currentMove < target) {\r
9896             SendMoveToProgram(currentMove++, &first);\r
9897         }\r
9898     } else {\r
9899         currentMove = target;\r
9900     }\r
9901     \r
9902     if (gameMode == EditGame || gameMode == EndOfGame) {\r
9903         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9904         blackTimeRemaining = timeRemaining[1][currentMove];\r
9905     }\r
9906     DisplayBothClocks();\r
9907     DisplayMove(currentMove - 1);\r
9908     DrawPosition(FALSE, boards[currentMove]);\r
9909     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
9910     if (commentList[currentMove] && !matchMode && gameMode != Training) {\r
9911         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9912     }\r
9913 }\r
9914 \r
9915 \r
9916 void\r
9917 ForwardEvent()\r
9918 {\r
9919     if (gameMode == IcsExamining && !pausing) {\r
9920         SendToICS(ics_prefix);\r
9921         SendToICS("forward\n");\r
9922     } else {\r
9923         ForwardInner(currentMove + 1);\r
9924     }\r
9925 }\r
9926 \r
9927 void\r
9928 ToEndEvent()\r
9929 {\r
9930     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
9931         /* to optimze, we temporarily turn off analysis mode while we feed\r
9932          * the remaining moves to the engine. Otherwise we get analysis output\r
9933          * after each move.\r
9934          */ \r
9935         if (first.analysisSupport) {\r
9936           SendToProgram("exit\nforce\n", &first);\r
9937           first.analyzing = FALSE;\r
9938         }\r
9939     }\r
9940         \r
9941     if (gameMode == IcsExamining && !pausing) {\r
9942         SendToICS(ics_prefix);\r
9943         SendToICS("forward 999999\n");\r
9944     } else {\r
9945         ForwardInner(forwardMostMove);\r
9946     }\r
9947 \r
9948     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
9949         /* we have fed all the moves, so reactivate analysis mode */\r
9950         SendToProgram("analyze\n", &first);\r
9951         first.analyzing = TRUE;\r
9952         /*first.maybeThinking = TRUE;*/\r
9953         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
9954     }\r
9955 }\r
9956 \r
9957 void\r
9958 BackwardInner(target)\r
9959      int target;\r
9960 {\r
9961     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
9962 \r
9963     if (appData.debugMode)\r
9964         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
9965                 target, currentMove, forwardMostMove);\r
9966 \r
9967     if (gameMode == EditPosition) return;\r
9968     if (currentMove <= backwardMostMove) {\r
9969         ClearHighlights();\r
9970         DrawPosition(full_redraw, boards[currentMove]);\r
9971         return;\r
9972     }\r
9973     if (gameMode == PlayFromGameFile && !pausing)\r
9974       PauseEvent();\r
9975     \r
9976     if (moveList[target][0]) {\r
9977         int fromX, fromY, toX, toY;\r
9978         toX = moveList[target][2] - AAA;\r
9979         toY = moveList[target][3] - ONE;\r
9980         if (moveList[target][1] == '@') {\r
9981             if (appData.highlightLastMove) {\r
9982                 SetHighlights(-1, -1, toX, toY);\r
9983             }\r
9984         } else {\r
9985             fromX = moveList[target][0] - AAA;\r
9986             fromY = moveList[target][1] - ONE;\r
9987             if (target == currentMove - 1) {\r
9988                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
9989             }\r
9990             if (appData.highlightLastMove) {\r
9991                 SetHighlights(fromX, fromY, toX, toY);\r
9992             }\r
9993         }\r
9994     }\r
9995     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
9996         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
9997         while (currentMove > target) {\r
9998             SendToProgram("undo\n", &first);\r
9999             currentMove--;\r
10000         }\r
10001     } else {\r
10002         currentMove = target;\r
10003     }\r
10004     \r
10005     if (gameMode == EditGame || gameMode == EndOfGame) {\r
10006         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10007         blackTimeRemaining = timeRemaining[1][currentMove];\r
10008     }\r
10009     DisplayBothClocks();\r
10010     DisplayMove(currentMove - 1);\r
10011     DrawPosition(full_redraw, boards[currentMove]);\r
10012     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
10013     if (commentList[currentMove] != NULL) {\r
10014         DisplayComment(currentMove - 1, commentList[currentMove]);\r
10015     }\r
10016 }\r
10017 \r
10018 void\r
10019 BackwardEvent()\r
10020 {\r
10021     if (gameMode == IcsExamining && !pausing) {\r
10022         SendToICS(ics_prefix);\r
10023         SendToICS("backward\n");\r
10024     } else {\r
10025         BackwardInner(currentMove - 1);\r
10026     }\r
10027 }\r
10028 \r
10029 void\r
10030 ToStartEvent()\r
10031 {\r
10032     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10033         /* to optimze, we temporarily turn off analysis mode while we undo\r
10034          * all the moves. Otherwise we get analysis output after each undo.\r
10035          */ \r
10036         if (first.analysisSupport) {\r
10037           SendToProgram("exit\nforce\n", &first);\r
10038           first.analyzing = FALSE;\r
10039         }\r
10040     }\r
10041 \r
10042     if (gameMode == IcsExamining && !pausing) {\r
10043         SendToICS(ics_prefix);\r
10044         SendToICS("backward 999999\n");\r
10045     } else {\r
10046         BackwardInner(backwardMostMove);\r
10047     }\r
10048 \r
10049     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10050         /* we have fed all the moves, so reactivate analysis mode */\r
10051         SendToProgram("analyze\n", &first);\r
10052         first.analyzing = TRUE;\r
10053         /*first.maybeThinking = TRUE;*/\r
10054         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10055     }\r
10056 }\r
10057 \r
10058 void\r
10059 ToNrEvent(int to)\r
10060 {\r
10061   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
10062   if (to >= forwardMostMove) to = forwardMostMove;\r
10063   if (to <= backwardMostMove) to = backwardMostMove;\r
10064   if (to < currentMove) {\r
10065     BackwardInner(to);\r
10066   } else {\r
10067     ForwardInner(to);\r
10068   }\r
10069 }\r
10070 \r
10071 void\r
10072 RevertEvent()\r
10073 {\r
10074     if (gameMode != IcsExamining) {\r
10075         DisplayError("You are not examining a game", 0);\r
10076         return;\r
10077     }\r
10078     if (pausing) {\r
10079         DisplayError("You can't revert while pausing", 0);\r
10080         return;\r
10081     }\r
10082     SendToICS(ics_prefix);\r
10083     SendToICS("revert\n");\r
10084 }\r
10085 \r
10086 void\r
10087 RetractMoveEvent()\r
10088 {\r
10089     switch (gameMode) {\r
10090       case MachinePlaysWhite:\r
10091       case MachinePlaysBlack:\r
10092         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10093             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
10094             return;\r
10095         }\r
10096         if (forwardMostMove < 2) return;\r
10097         currentMove = forwardMostMove = forwardMostMove - 2;\r
10098         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10099         blackTimeRemaining = timeRemaining[1][currentMove];\r
10100         DisplayBothClocks();\r
10101         DisplayMove(currentMove - 1);\r
10102         ClearHighlights();/*!! could figure this out*/\r
10103         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
10104         SendToProgram("remove\n", &first);\r
10105         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
10106         break;\r
10107 \r
10108       case BeginningOfGame:\r
10109       default:\r
10110         break;\r
10111 \r
10112       case IcsPlayingWhite:\r
10113       case IcsPlayingBlack:\r
10114         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
10115             SendToICS(ics_prefix);\r
10116             SendToICS("takeback 2\n");\r
10117         } else {\r
10118             SendToICS(ics_prefix);\r
10119             SendToICS("takeback 1\n");\r
10120         }\r
10121         break;\r
10122     }\r
10123 }\r
10124 \r
10125 void\r
10126 MoveNowEvent()\r
10127 {\r
10128     ChessProgramState *cps;\r
10129 \r
10130     switch (gameMode) {\r
10131       case MachinePlaysWhite:\r
10132         if (!WhiteOnMove(forwardMostMove)) {\r
10133             DisplayError("It is your turn", 0);\r
10134             return;\r
10135         }\r
10136         cps = &first;\r
10137         break;\r
10138       case MachinePlaysBlack:\r
10139         if (WhiteOnMove(forwardMostMove)) {\r
10140             DisplayError("It is your turn", 0);\r
10141             return;\r
10142         }\r
10143         cps = &first;\r
10144         break;\r
10145       case TwoMachinesPlay:\r
10146         if (WhiteOnMove(forwardMostMove) ==\r
10147             (first.twoMachinesColor[0] == 'w')) {\r
10148             cps = &first;\r
10149         } else {\r
10150             cps = &second;\r
10151         }\r
10152         break;\r
10153       case BeginningOfGame:\r
10154       default:\r
10155         return;\r
10156     }\r
10157     SendToProgram("?\n", cps);\r
10158 }\r
10159 \r
10160 void\r
10161 TruncateGameEvent()\r
10162 {\r
10163     EditGameEvent();\r
10164     if (gameMode != EditGame) return;\r
10165     TruncateGame();\r
10166 }\r
10167 \r
10168 void\r
10169 TruncateGame()\r
10170 {\r
10171     if (forwardMostMove > currentMove) {\r
10172         if (gameInfo.resultDetails != NULL) {\r
10173             free(gameInfo.resultDetails);\r
10174             gameInfo.resultDetails = NULL;\r
10175             gameInfo.result = GameUnfinished;\r
10176         }\r
10177         forwardMostMove = currentMove;\r
10178         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
10179                    currentMove-1);\r
10180     }\r
10181 }\r
10182 \r
10183 void\r
10184 HintEvent()\r
10185 {\r
10186     if (appData.noChessProgram) return;\r
10187     switch (gameMode) {\r
10188       case MachinePlaysWhite:\r
10189         if (WhiteOnMove(forwardMostMove)) {\r
10190             DisplayError("Wait until your turn", 0);\r
10191             return;\r
10192         }\r
10193         break;\r
10194       case BeginningOfGame:\r
10195       case MachinePlaysBlack:\r
10196         if (!WhiteOnMove(forwardMostMove)) {\r
10197             DisplayError("Wait until your turn", 0);\r
10198             return;\r
10199         }\r
10200         break;\r
10201       default:\r
10202         DisplayError("No hint available", 0);\r
10203         return;\r
10204     }\r
10205     SendToProgram("hint\n", &first);\r
10206     hintRequested = TRUE;\r
10207 }\r
10208 \r
10209 void\r
10210 BookEvent()\r
10211 {\r
10212     if (appData.noChessProgram) return;\r
10213     switch (gameMode) {\r
10214       case MachinePlaysWhite:\r
10215         if (WhiteOnMove(forwardMostMove)) {\r
10216             DisplayError("Wait until your turn", 0);\r
10217             return;\r
10218         }\r
10219         break;\r
10220       case BeginningOfGame:\r
10221       case MachinePlaysBlack:\r
10222         if (!WhiteOnMove(forwardMostMove)) {\r
10223             DisplayError("Wait until your turn", 0);\r
10224             return;\r
10225         }\r
10226         break;\r
10227       case EditPosition:\r
10228         EditPositionDone();\r
10229         break;\r
10230       case TwoMachinesPlay:\r
10231         return;\r
10232       default:\r
10233         break;\r
10234     }\r
10235     SendToProgram("bk\n", &first);\r
10236     bookOutput[0] = NULLCHAR;\r
10237     bookRequested = TRUE;\r
10238 }\r
10239 \r
10240 void\r
10241 AboutGameEvent()\r
10242 {\r
10243     char *tags = PGNTags(&gameInfo);\r
10244     TagsPopUp(tags, CmailMsg());\r
10245     free(tags);\r
10246 }\r
10247 \r
10248 /* end button procedures */\r
10249 \r
10250 void\r
10251 PrintPosition(fp, move)\r
10252      FILE *fp;\r
10253      int move;\r
10254 {\r
10255     int i, j;\r
10256     \r
10257     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
10258         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
10259             char c = PieceToChar(boards[move][i][j]);\r
10260             fputc(c == 'x' ? '.' : c, fp);\r
10261             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
10262         }\r
10263     }\r
10264     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
10265       fprintf(fp, "white to play\n");\r
10266     else\r
10267       fprintf(fp, "black to play\n");\r
10268 }\r
10269 \r
10270 void\r
10271 PrintOpponents(fp)\r
10272      FILE *fp;\r
10273 {\r
10274     if (gameInfo.white != NULL) {\r
10275         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
10276     } else {\r
10277         fprintf(fp, "\n");\r
10278     }\r
10279 }\r
10280 \r
10281 /* Find last component of program's own name, using some heuristics */\r
10282 void\r
10283 TidyProgramName(prog, host, buf)\r
10284      char *prog, *host, buf[MSG_SIZ];\r
10285 {\r
10286     char *p, *q;\r
10287     int local = (strcmp(host, "localhost") == 0);\r
10288     while (!local && (p = strchr(prog, ';')) != NULL) {\r
10289         p++;\r
10290         while (*p == ' ') p++;\r
10291         prog = p;\r
10292     }\r
10293     if (*prog == '"' || *prog == '\'') {\r
10294         q = strchr(prog + 1, *prog);\r
10295     } else {\r
10296         q = strchr(prog, ' ');\r
10297     }\r
10298     if (q == NULL) q = prog + strlen(prog);\r
10299     p = q;\r
10300     while (p >= prog && *p != '/' && *p != '\\') p--;\r
10301     p++;\r
10302     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
10303     memcpy(buf, p, q - p);\r
10304     buf[q - p] = NULLCHAR;\r
10305     if (!local) {\r
10306         strcat(buf, "@");\r
10307         strcat(buf, host);\r
10308     }\r
10309 }\r
10310 \r
10311 char *\r
10312 TimeControlTagValue()\r
10313 {\r
10314     char buf[MSG_SIZ];\r
10315     if (!appData.clockMode) {\r
10316         strcpy(buf, "-");\r
10317     } else if (movesPerSession > 0) {\r
10318         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
10319     } else if (timeIncrement == 0) {\r
10320         sprintf(buf, "%ld", timeControl/1000);\r
10321     } else {\r
10322         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
10323     }\r
10324     return StrSave(buf);\r
10325 }\r
10326 \r
10327 void\r
10328 SetGameInfo()\r
10329 {\r
10330     /* This routine is used only for certain modes */\r
10331     VariantClass v = gameInfo.variant;\r
10332     ClearGameInfo(&gameInfo);\r
10333     gameInfo.variant = v;\r
10334 \r
10335     switch (gameMode) {\r
10336       case MachinePlaysWhite:\r
10337         gameInfo.event = StrSave( appData.pgnEventHeader );\r
10338         gameInfo.site = StrSave(HostName());\r
10339         gameInfo.date = PGNDate();\r
10340         gameInfo.round = StrSave("-");\r
10341         gameInfo.white = StrSave(first.tidy);\r
10342         gameInfo.black = StrSave(UserName());\r
10343         gameInfo.timeControl = TimeControlTagValue();\r
10344         break;\r
10345 \r
10346       case MachinePlaysBlack:\r
10347         gameInfo.event = StrSave( appData.pgnEventHeader );\r
10348         gameInfo.site = StrSave(HostName());\r
10349         gameInfo.date = PGNDate();\r
10350         gameInfo.round = StrSave("-");\r
10351         gameInfo.white = StrSave(UserName());\r
10352         gameInfo.black = StrSave(first.tidy);\r
10353         gameInfo.timeControl = TimeControlTagValue();\r
10354         break;\r
10355 \r
10356       case TwoMachinesPlay:\r
10357         gameInfo.event = StrSave( appData.pgnEventHeader );\r
10358         gameInfo.site = StrSave(HostName());\r
10359         gameInfo.date = PGNDate();\r
10360         if (matchGame > 0) {\r
10361             char buf[MSG_SIZ];\r
10362             sprintf(buf, "%d", matchGame);\r
10363             gameInfo.round = StrSave(buf);\r
10364         } else {\r
10365             gameInfo.round = StrSave("-");\r
10366         }\r
10367         if (first.twoMachinesColor[0] == 'w') {\r
10368             gameInfo.white = StrSave(first.tidy);\r
10369             gameInfo.black = StrSave(second.tidy);\r
10370         } else {\r
10371             gameInfo.white = StrSave(second.tidy);\r
10372             gameInfo.black = StrSave(first.tidy);\r
10373         }\r
10374         gameInfo.timeControl = TimeControlTagValue();\r
10375         break;\r
10376 \r
10377       case EditGame:\r
10378         gameInfo.event = StrSave("Edited game");\r
10379         gameInfo.site = StrSave(HostName());\r
10380         gameInfo.date = PGNDate();\r
10381         gameInfo.round = StrSave("-");\r
10382         gameInfo.white = StrSave("-");\r
10383         gameInfo.black = StrSave("-");\r
10384         break;\r
10385 \r
10386       case EditPosition:\r
10387         gameInfo.event = StrSave("Edited position");\r
10388         gameInfo.site = StrSave(HostName());\r
10389         gameInfo.date = PGNDate();\r
10390         gameInfo.round = StrSave("-");\r
10391         gameInfo.white = StrSave("-");\r
10392         gameInfo.black = StrSave("-");\r
10393         break;\r
10394 \r
10395       case IcsPlayingWhite:\r
10396       case IcsPlayingBlack:\r
10397       case IcsObserving:\r
10398       case IcsExamining:\r
10399         break;\r
10400 \r
10401       case PlayFromGameFile:\r
10402         gameInfo.event = StrSave("Game from non-PGN file");\r
10403         gameInfo.site = StrSave(HostName());\r
10404         gameInfo.date = PGNDate();\r
10405         gameInfo.round = StrSave("-");\r
10406         gameInfo.white = StrSave("?");\r
10407         gameInfo.black = StrSave("?");\r
10408         break;\r
10409 \r
10410       default:\r
10411         break;\r
10412     }\r
10413 }\r
10414 \r
10415 void\r
10416 ReplaceComment(index, text)\r
10417      int index;\r
10418      char *text;\r
10419 {\r
10420     int len;\r
10421 \r
10422     while (*text == '\n') text++;\r
10423     len = strlen(text);\r
10424     while (len > 0 && text[len - 1] == '\n') len--;\r
10425 \r
10426     if (commentList[index] != NULL)\r
10427       free(commentList[index]);\r
10428 \r
10429     if (len == 0) {\r
10430         commentList[index] = NULL;\r
10431         return;\r
10432     }\r
10433     commentList[index] = (char *) malloc(len + 2);\r
10434     strncpy(commentList[index], text, len);\r
10435     commentList[index][len] = '\n';\r
10436     commentList[index][len + 1] = NULLCHAR;\r
10437 }\r
10438 \r
10439 void\r
10440 CrushCRs(text)\r
10441      char *text;\r
10442 {\r
10443   char *p = text;\r
10444   char *q = text;\r
10445   char ch;\r
10446 \r
10447   do {\r
10448     ch = *p++;\r
10449     if (ch == '\r') continue;\r
10450     *q++ = ch;\r
10451   } while (ch != '\0');\r
10452 }\r
10453 \r
10454 void\r
10455 AppendComment(index, text)\r
10456      int index;\r
10457      char *text;\r
10458 {\r
10459     int oldlen, len;\r
10460     char *old;\r
10461 \r
10462     GetInfoFromComment( index, text );\r
10463 \r
10464     CrushCRs(text);\r
10465     while (*text == '\n') text++;\r
10466     len = strlen(text);\r
10467     while (len > 0 && text[len - 1] == '\n') len--;\r
10468 \r
10469     if (len == 0) return;\r
10470 \r
10471     if (commentList[index] != NULL) {\r
10472         old = commentList[index];\r
10473         oldlen = strlen(old);\r
10474         commentList[index] = (char *) malloc(oldlen + len + 2);\r
10475         strcpy(commentList[index], old);\r
10476         free(old);\r
10477         strncpy(&commentList[index][oldlen], text, len);\r
10478         commentList[index][oldlen + len] = '\n';\r
10479         commentList[index][oldlen + len + 1] = NULLCHAR;\r
10480     } else {\r
10481         commentList[index] = (char *) malloc(len + 2);\r
10482         strncpy(commentList[index], text, len);\r
10483         commentList[index][len] = '\n';\r
10484         commentList[index][len + 1] = NULLCHAR;\r
10485     }\r
10486 }\r
10487 \r
10488 static char * FindStr( char * text, char * sub_text )\r
10489 {\r
10490     char * result = strstr( text, sub_text );\r
10491 \r
10492     if( result != NULL ) {\r
10493         result += strlen( sub_text );\r
10494     }\r
10495 \r
10496     return result;\r
10497 }\r
10498 \r
10499 /* [AS] Try to extract PV info from PGN comment */\r
10500 void GetInfoFromComment( int index, char * text )\r
10501 {\r
10502     if( text != NULL && index > 0 ) {\r
10503         int score = 0;\r
10504         int depth = 0;\r
10505         int time = -1;\r
10506         char * s_eval = FindStr( text, "[%eval " );\r
10507         char * s_emt = FindStr( text, "[%emt " );\r
10508 \r
10509         if( s_eval != NULL || s_emt != NULL ) {\r
10510             /* New style */\r
10511             char delim;\r
10512 \r
10513             if( s_eval != NULL ) {\r
10514                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
10515                     return;\r
10516                 }\r
10517 \r
10518                 if( delim != ']' ) {\r
10519                     return;\r
10520                 }\r
10521             }\r
10522 \r
10523             if( s_emt != NULL ) {\r
10524             }\r
10525         }\r
10526         else {\r
10527             /* We expect something like: [+|-]nnn.nn/dd */\r
10528             char * sep = strchr( text, '/' );\r
10529             int score_lo = 0;\r
10530 \r
10531             if( sep == NULL || sep < (text+4) ) {\r
10532                 return;\r
10533             }\r
10534 \r
10535             if( sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {\r
10536                 return;\r
10537             }\r
10538 \r
10539             if( score_lo < 0 || score_lo >= 100 ) {\r
10540                 return;\r
10541             }\r
10542 \r
10543             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
10544         }\r
10545 \r
10546         if( depth <= 0 ) {\r
10547             return;\r
10548         }\r
10549 \r
10550         if( time < 0 ) {\r
10551             time = -1;\r
10552         }\r
10553 \r
10554         pvInfoList[index-1].depth = depth;\r
10555         pvInfoList[index-1].score = score;\r
10556         pvInfoList[index-1].time = time;\r
10557     }\r
10558 }\r
10559 \r
10560 void\r
10561 SendToProgram(message, cps)\r
10562      char *message;\r
10563      ChessProgramState *cps;\r
10564 {\r
10565     int count, outCount, error;\r
10566     char buf[MSG_SIZ];\r
10567 \r
10568     if (cps->pr == NULL) return;\r
10569     Attention(cps);\r
10570     \r
10571     if (appData.debugMode) {\r
10572         TimeMark now;\r
10573         GetTimeMark(&now);\r
10574         fprintf(debugFP, "%ld >%-6s: %s", \r
10575                 SubtractTimeMarks(&now, &programStartTime),\r
10576                 cps->which, message);\r
10577     }\r
10578     \r
10579     count = strlen(message);\r
10580     outCount = OutputToProcess(cps->pr, message, count, &error);\r
10581     if (outCount < count && !exiting) {\r
10582         sprintf(buf, "Error writing to %s chess program", cps->which);\r
10583         DisplayFatalError(buf, error, 1);\r
10584     }\r
10585 }\r
10586 \r
10587 void\r
10588 ReceiveFromProgram(isr, closure, message, count, error)\r
10589      InputSourceRef isr;\r
10590      VOIDSTAR closure;\r
10591      char *message;\r
10592      int count;\r
10593      int error;\r
10594 {\r
10595     char *end_str;\r
10596     char buf[MSG_SIZ];\r
10597     ChessProgramState *cps = (ChessProgramState *)closure;\r
10598 \r
10599     if (isr != cps->isr) return; /* Killed intentionally */\r
10600     if (count <= 0) {\r
10601         if (count == 0) {\r
10602             sprintf(buf,\r
10603                     "Error: %s chess program (%s) exited unexpectedly",\r
10604                     cps->which, cps->program);\r
10605             RemoveInputSource(cps->isr);\r
10606             DisplayFatalError(buf, 0, 1);\r
10607         } else {\r
10608             sprintf(buf,\r
10609                     "Error reading from %s chess program (%s)",\r
10610                     cps->which, cps->program);\r
10611             RemoveInputSource(cps->isr);\r
10612 \r
10613             /* [AS] Program is misbehaving badly... kill it */\r
10614             if( count == -2 ) {\r
10615                 DestroyChildProcess( cps->pr, 9 );\r
10616                 cps->pr = NoProc;\r
10617             }\r
10618 \r
10619             DisplayFatalError(buf, error, 1);\r
10620         }\r
10621         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10622         return;\r
10623     }\r
10624     \r
10625     if ((end_str = strchr(message, '\r')) != NULL)\r
10626       *end_str = NULLCHAR;\r
10627     if ((end_str = strchr(message, '\n')) != NULL)\r
10628       *end_str = NULLCHAR;\r
10629     \r
10630     if (appData.debugMode) {\r
10631         TimeMark now;\r
10632         GetTimeMark(&now);\r
10633         fprintf(debugFP, "%ld <%-6s: %s\n", \r
10634                 SubtractTimeMarks(&now, &programStartTime),\r
10635                 cps->which, message);\r
10636     }\r
10637     HandleMachineMove(message, cps);\r
10638 }\r
10639 \r
10640 \r
10641 void\r
10642 SendTimeControl(cps, mps, tc, inc, sd, st)\r
10643      ChessProgramState *cps;\r
10644      int mps, inc, sd, st;\r
10645      long tc;\r
10646 {\r
10647     char buf[MSG_SIZ];\r
10648     int seconds = (tc / 1000) % 60;\r
10649 \r
10650     if( timeControl_2 > 0 ) {\r
10651         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
10652             tc = timeControl_2;\r
10653         }\r
10654     }\r
10655 \r
10656     if (st > 0) {\r
10657       /* Set exact time per move, normally using st command */\r
10658       if (cps->stKludge) {\r
10659         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
10660         seconds = st % 60;\r
10661         if (seconds == 0) {\r
10662           sprintf(buf, "level 1 %d\n", st/60);\r
10663         } else {\r
10664           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
10665         }\r
10666       } else {\r
10667         sprintf(buf, "st %d\n", st);\r
10668       }\r
10669     } else {\r
10670       /* Set conventional or incremental time control, using level command */\r
10671       if (seconds == 0) {\r
10672         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
10673            Fixed in later versions, but still avoid :seconds\r
10674            when seconds is 0. */\r
10675         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
10676       } else {\r
10677         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
10678                 seconds, inc/1000);\r
10679       }\r
10680     }\r
10681     SendToProgram(buf, cps);\r
10682 \r
10683     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
10684     /* Orthogonally, limit search to given depth */\r
10685     if (sd > 0) {\r
10686       if (cps->sdKludge) {\r
10687         sprintf(buf, "depth\n%d\n", sd);\r
10688       } else {\r
10689         sprintf(buf, "sd %d\n", sd);\r
10690       }\r
10691       SendToProgram(buf, cps);\r
10692     }\r
10693 }\r
10694 \r
10695 void\r
10696 SendTimeRemaining(cps, machineWhite)\r
10697      ChessProgramState *cps;\r
10698      int /*boolean*/ machineWhite;\r
10699 {\r
10700     char message[MSG_SIZ];\r
10701     long time, otime;\r
10702 \r
10703     /* Note: this routine must be called when the clocks are stopped\r
10704        or when they have *just* been set or switched; otherwise\r
10705        it will be off by the time since the current tick started.\r
10706     */\r
10707     if (machineWhite) {\r
10708         time = whiteTimeRemaining / 10;\r
10709         otime = blackTimeRemaining / 10;\r
10710     } else {\r
10711         time = blackTimeRemaining / 10;\r
10712         otime = whiteTimeRemaining / 10;\r
10713     }\r
10714     if (time <= 0) time = 1;\r
10715     if (otime <= 0) otime = 1;\r
10716     \r
10717     sprintf(message, "time %ld\n", time);\r
10718     SendToProgram(message, cps);\r
10719 \r
10720     sprintf(message, "otim %ld\n", otime);\r
10721     SendToProgram(message, cps);\r
10722 }\r
10723 \r
10724 int\r
10725 BoolFeature(p, name, loc, cps)\r
10726      char **p;\r
10727      char *name;\r
10728      int *loc;\r
10729      ChessProgramState *cps;\r
10730 {\r
10731   char buf[MSG_SIZ];\r
10732   int len = strlen(name);\r
10733   int val;\r
10734   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
10735     (*p) += len + 1;\r
10736     sscanf(*p, "%d", &val);\r
10737     *loc = (val != 0);\r
10738     while (**p && **p != ' ') (*p)++;\r
10739     sprintf(buf, "accepted %s\n", name);\r
10740     SendToProgram(buf, cps);\r
10741     return TRUE;\r
10742   }\r
10743   return FALSE;\r
10744 }\r
10745 \r
10746 int\r
10747 IntFeature(p, name, loc, cps)\r
10748      char **p;\r
10749      char *name;\r
10750      int *loc;\r
10751      ChessProgramState *cps;\r
10752 {\r
10753   char buf[MSG_SIZ];\r
10754   int len = strlen(name);\r
10755   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
10756     (*p) += len + 1;\r
10757     sscanf(*p, "%d", loc);\r
10758     while (**p && **p != ' ') (*p)++;\r
10759     sprintf(buf, "accepted %s\n", name);\r
10760     SendToProgram(buf, cps);\r
10761     return TRUE;\r
10762   }\r
10763   return FALSE;\r
10764 }\r
10765 \r
10766 int\r
10767 StringFeature(p, name, loc, cps)\r
10768      char **p;\r
10769      char *name;\r
10770      char loc[];\r
10771      ChessProgramState *cps;\r
10772 {\r
10773   char buf[MSG_SIZ];\r
10774   int len = strlen(name);\r
10775   if (strncmp((*p), name, len) == 0\r
10776       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
10777     (*p) += len + 2;\r
10778     sscanf(*p, "%[^\"]", loc);\r
10779     while (**p && **p != '\"') (*p)++;\r
10780     if (**p == '\"') (*p)++;\r
10781     sprintf(buf, "accepted %s\n", name);\r
10782     SendToProgram(buf, cps);\r
10783     return TRUE;\r
10784   }\r
10785   return FALSE;\r
10786 }\r
10787 \r
10788 void\r
10789 FeatureDone(cps, val)\r
10790      ChessProgramState* cps;\r
10791      int val;\r
10792 {\r
10793   DelayedEventCallback cb = GetDelayedEvent();\r
10794   if ((cb == InitBackEnd3 && cps == &first) ||\r
10795       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
10796     CancelDelayedEvent();\r
10797     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
10798   }\r
10799   cps->initDone = val;\r
10800 }\r
10801 \r
10802 /* Parse feature command from engine */\r
10803 void\r
10804 ParseFeatures(args, cps)\r
10805      char* args;\r
10806      ChessProgramState *cps;  \r
10807 {\r
10808   char *p = args;\r
10809   char *q;\r
10810   int val;\r
10811   char buf[MSG_SIZ];\r
10812 \r
10813   for (;;) {\r
10814     while (*p == ' ') p++;\r
10815     if (*p == NULLCHAR) return;\r
10816 \r
10817     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
10818     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
10819     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
10820     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
10821     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
10822     if (BoolFeature(&p, "reuse", &val, cps)) {\r
10823       /* Engine can disable reuse, but can't enable it if user said no */\r
10824       if (!val) cps->reuse = FALSE;\r
10825       continue;\r
10826     }\r
10827     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
10828     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
10829       if (gameMode == TwoMachinesPlay) {\r
10830         DisplayTwoMachinesTitle();\r
10831       } else {\r
10832         DisplayTitle("");\r
10833       }\r
10834       continue;\r
10835     }\r
10836     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
10837     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
10838     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
10839     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
10840     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
10841     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
10842     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
10843     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
10844     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
10845     if (IntFeature(&p, "done", &val, cps)) {\r
10846       FeatureDone(cps, val);\r
10847       continue;\r
10848     }\r
10849     /* Added by Tord: */\r
10850     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
10851     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
10852     /* End of additions by Tord */\r
10853 \r
10854     /* unknown feature: complain and skip */\r
10855     q = p;\r
10856     while (*q && *q != '=') q++;\r
10857     sprintf(buf, "rejected %.*s\n", q-p, p);\r
10858     SendToProgram(buf, cps);\r
10859     p = q;\r
10860     if (*p == '=') {\r
10861       p++;\r
10862       if (*p == '\"') {\r
10863         p++;\r
10864         while (*p && *p != '\"') p++;\r
10865         if (*p == '\"') p++;\r
10866       } else {\r
10867         while (*p && *p != ' ') p++;\r
10868       }\r
10869     }\r
10870   }\r
10871 \r
10872 }\r
10873 \r
10874 void\r
10875 PeriodicUpdatesEvent(newState)\r
10876      int newState;\r
10877 {\r
10878     if (newState == appData.periodicUpdates)\r
10879       return;\r
10880 \r
10881     appData.periodicUpdates=newState;\r
10882 \r
10883     /* Display type changes, so update it now */\r
10884     DisplayAnalysis();\r
10885 \r
10886     /* Get the ball rolling again... */\r
10887     if (newState) {\r
10888         AnalysisPeriodicEvent(1);\r
10889         StartAnalysisClock();\r
10890     }\r
10891 }\r
10892 \r
10893 void\r
10894 PonderNextMoveEvent(newState)\r
10895      int newState;\r
10896 {\r
10897     if (newState == appData.ponderNextMove) return;\r
10898     if (gameMode == EditPosition) EditPositionDone();\r
10899     if (newState) {\r
10900         SendToProgram("hard\n", &first);\r
10901         if (gameMode == TwoMachinesPlay) {\r
10902             SendToProgram("hard\n", &second);\r
10903         }\r
10904     } else {\r
10905         SendToProgram("easy\n", &first);\r
10906         thinkOutput[0] = NULLCHAR;\r
10907         if (gameMode == TwoMachinesPlay) {\r
10908             SendToProgram("easy\n", &second);\r
10909         }\r
10910     }\r
10911     appData.ponderNextMove = newState;\r
10912 }\r
10913 \r
10914 void\r
10915 ShowThinkingEvent(newState)\r
10916      int newState;\r
10917 {\r
10918     if (newState == appData.showThinking) return;\r
10919     if (gameMode == EditPosition) EditPositionDone();\r
10920     if (newState) {\r
10921         SendToProgram("post\n", &first);\r
10922         if (gameMode == TwoMachinesPlay) {\r
10923             SendToProgram("post\n", &second);\r
10924         }\r
10925     } else {\r
10926         SendToProgram("nopost\n", &first);\r
10927         thinkOutput[0] = NULLCHAR;\r
10928         if (gameMode == TwoMachinesPlay) {\r
10929             SendToProgram("nopost\n", &second);\r
10930         }\r
10931     }\r
10932     appData.showThinking = newState;\r
10933 }\r
10934 \r
10935 void\r
10936 AskQuestionEvent(title, question, replyPrefix, which)\r
10937      char *title; char *question; char *replyPrefix; char *which;\r
10938 {\r
10939   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
10940   if (pr == NoProc) return;\r
10941   AskQuestion(title, question, replyPrefix, pr);\r
10942 }\r
10943 \r
10944 void\r
10945 DisplayMove(moveNumber)\r
10946      int moveNumber;\r
10947 {\r
10948     char message[MSG_SIZ];\r
10949     char res[MSG_SIZ];\r
10950     char cpThinkOutput[MSG_SIZ];\r
10951 \r
10952     if (moveNumber == forwardMostMove - 1 || \r
10953         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10954 \r
10955         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
10956 \r
10957         if (strchr(cpThinkOutput, '\n')) {\r
10958             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
10959         }\r
10960     } else {\r
10961         *cpThinkOutput = NULLCHAR;\r
10962     }\r
10963 \r
10964     /* [AS] Hide thinking from human user */\r
10965     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
10966         *cpThinkOutput = NULLCHAR;\r
10967         if( thinkOutput[0] != NULLCHAR ) {\r
10968             int i;\r
10969 \r
10970             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
10971                 cpThinkOutput[i] = '.';\r
10972             }\r
10973             cpThinkOutput[i] = NULLCHAR;\r
10974             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
10975         }\r
10976     }\r
10977 \r
10978     if (moveNumber == forwardMostMove - 1 &&\r
10979         gameInfo.resultDetails != NULL) {\r
10980         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
10981             sprintf(res, " %s", PGNResult(gameInfo.result));\r
10982         } else {\r
10983             sprintf(res, " {%s} %s",\r
10984                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
10985         }\r
10986     } else {\r
10987         res[0] = NULLCHAR;\r
10988     }\r
10989     \r
10990     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
10991         DisplayMessage(res, cpThinkOutput);\r
10992     } else {\r
10993         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
10994                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
10995                 parseList[moveNumber], res);\r
10996         DisplayMessage(message, cpThinkOutput);\r
10997     }\r
10998 }\r
10999 \r
11000 void\r
11001 DisplayAnalysisText(text)\r
11002      char *text;\r
11003 {\r
11004     char buf[MSG_SIZ];\r
11005 \r
11006     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11007         sprintf(buf, "Analysis (%s)", first.tidy);\r
11008         AnalysisPopUp(buf, text);\r
11009     }\r
11010 }\r
11011 \r
11012 static int\r
11013 only_one_move(str)\r
11014      char *str;\r
11015 {\r
11016     while (*str && isspace(*str)) ++str;\r
11017     while (*str && !isspace(*str)) ++str;\r
11018     if (!*str) return 1;\r
11019     while (*str && isspace(*str)) ++str;\r
11020     if (!*str) return 1;\r
11021     return 0;\r
11022 }\r
11023 \r
11024 void\r
11025 DisplayAnalysis()\r
11026 {\r
11027     char buf[MSG_SIZ];\r
11028     char lst[MSG_SIZ / 2];\r
11029     double nps;\r
11030     static char *xtra[] = { "", " (--)", " (++)" };\r
11031     int h, m, s, cs;\r
11032   \r
11033     if (programStats.time == 0) {\r
11034         programStats.time = 1;\r
11035     }\r
11036   \r
11037     if (programStats.got_only_move) {\r
11038         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
11039     } else {\r
11040         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
11041 \r
11042         nps = (((double)programStats.nodes) /\r
11043                (((double)programStats.time)/100.0));\r
11044 \r
11045         cs = programStats.time % 100;\r
11046         s = programStats.time / 100;\r
11047         h = (s / (60*60));\r
11048         s = s - h*60*60;\r
11049         m = (s/60);\r
11050         s = s - m*60;\r
11051 \r
11052         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
11053           if (programStats.move_name[0] != NULLCHAR) {\r
11054             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
11055                     programStats.depth,\r
11056                     programStats.nr_moves-programStats.moves_left,\r
11057                     programStats.nr_moves, programStats.move_name,\r
11058                     ((float)programStats.score)/100.0, lst,\r
11059                     only_one_move(lst)?\r
11060                     xtra[programStats.got_fail] : "",\r
11061                     programStats.nodes, (int)nps, h, m, s, cs);\r
11062           } else {\r
11063             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
11064                     programStats.depth,\r
11065                     programStats.nr_moves-programStats.moves_left,\r
11066                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
11067                     lst,\r
11068                     only_one_move(lst)?\r
11069                     xtra[programStats.got_fail] : "",\r
11070                     programStats.nodes, (int)nps, h, m, s, cs);\r
11071           }\r
11072         } else {\r
11073             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
11074                     programStats.depth,\r
11075                     ((float)programStats.score)/100.0,\r
11076                     lst,\r
11077                     only_one_move(lst)?\r
11078                     xtra[programStats.got_fail] : "",\r
11079                     programStats.nodes, (int)nps, h, m, s, cs);\r
11080         }\r
11081     }\r
11082     DisplayAnalysisText(buf);\r
11083 }\r
11084 \r
11085 void\r
11086 DisplayComment(moveNumber, text)\r
11087      int moveNumber;\r
11088      char *text;\r
11089 {\r
11090     char title[MSG_SIZ];\r
11091 \r
11092     if( appData.autoDisplayComment ) {\r
11093         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
11094             strcpy(title, "Comment");\r
11095         } else {\r
11096             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
11097                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
11098                     parseList[moveNumber]);\r
11099         }\r
11100 \r
11101         CommentPopUp(title, text);\r
11102     }\r
11103 }\r
11104 \r
11105 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
11106  * might be busy thinking or pondering.  It can be omitted if your\r
11107  * gnuchess is configured to stop thinking immediately on any user\r
11108  * input.  However, that gnuchess feature depends on the FIONREAD\r
11109  * ioctl, which does not work properly on some flavors of Unix.\r
11110  */\r
11111 void\r
11112 Attention(cps)\r
11113      ChessProgramState *cps;\r
11114 {\r
11115 #if ATTENTION\r
11116     if (!cps->useSigint) return;\r
11117     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
11118     switch (gameMode) {\r
11119       case MachinePlaysWhite:\r
11120       case MachinePlaysBlack:\r
11121       case TwoMachinesPlay:\r
11122       case IcsPlayingWhite:\r
11123       case IcsPlayingBlack:\r
11124       case AnalyzeMode:\r
11125       case AnalyzeFile:\r
11126         /* Skip if we know it isn't thinking */\r
11127         if (!cps->maybeThinking) return;\r
11128         if (appData.debugMode)\r
11129           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
11130         InterruptChildProcess(cps->pr);\r
11131         cps->maybeThinking = FALSE;\r
11132         break;\r
11133       default:\r
11134         break;\r
11135     }\r
11136 #endif /*ATTENTION*/\r
11137 }\r
11138 \r
11139 int\r
11140 CheckFlags()\r
11141 {\r
11142     if (whiteTimeRemaining <= 0) {\r
11143         if (!whiteFlag) {\r
11144             whiteFlag = TRUE;\r
11145             if (appData.icsActive) {\r
11146                 if (appData.autoCallFlag &&\r
11147                     gameMode == IcsPlayingBlack && !blackFlag) {\r
11148                   SendToICS(ics_prefix);\r
11149                   SendToICS("flag\n");\r
11150                 }\r
11151             } else {\r
11152                 if (blackFlag) {\r
11153                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
11154                 } else {\r
11155                     if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell");\r
11156                     if (appData.autoCallFlag) {\r
11157                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
11158                         return TRUE;\r
11159                     }\r
11160                 }\r
11161             }\r
11162         }\r
11163     }\r
11164     if (blackTimeRemaining <= 0) {\r
11165         if (!blackFlag) {\r
11166             blackFlag = TRUE;\r
11167             if (appData.icsActive) {\r
11168                 if (appData.autoCallFlag &&\r
11169                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
11170                   SendToICS(ics_prefix);\r
11171                   SendToICS("flag\n");\r
11172                 }\r
11173             } else {\r
11174                 if (whiteFlag) {\r
11175                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
11176                 } else {\r
11177                     if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell");\r
11178                     if (appData.autoCallFlag) {\r
11179                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
11180                         return TRUE;\r
11181                     }\r
11182                 }\r
11183             }\r
11184         }\r
11185     }\r
11186     return FALSE;\r
11187 }\r
11188 \r
11189 void\r
11190 CheckTimeControl()\r
11191 {\r
11192     if (!appData.clockMode || appData.icsActive ||\r
11193         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
11194 \r
11195     if (timeIncrement >= 0) {\r
11196         if (WhiteOnMove(forwardMostMove)) {\r
11197             blackTimeRemaining += timeIncrement;\r
11198         } else {\r
11199             whiteTimeRemaining += timeIncrement;\r
11200         }\r
11201     }\r
11202     /*\r
11203      * add time to clocks when time control is achieved\r
11204      */\r
11205     if (movesPerSession) {\r
11206       switch ((forwardMostMove + 1) % (movesPerSession * 2)) {\r
11207       case 0:\r
11208         /* White made time control */\r
11209         whiteTimeRemaining += GetTimeControlForWhite();\r
11210         break;\r
11211       case 1:\r
11212         /* Black made time control */\r
11213         blackTimeRemaining += GetTimeControlForBlack();\r
11214         break;\r
11215       default:\r
11216         break;\r
11217       }\r
11218     }\r
11219 }\r
11220 \r
11221 void\r
11222 DisplayBothClocks()\r
11223 {\r
11224     int wom = gameMode == EditPosition ?\r
11225       !blackPlaysFirst : WhiteOnMove(currentMove);\r
11226     DisplayWhiteClock(whiteTimeRemaining, wom);\r
11227     DisplayBlackClock(blackTimeRemaining, !wom);\r
11228 }\r
11229 \r
11230 \r
11231 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
11232    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
11233    to use other calls if you don't.  Clocks will be less accurate if\r
11234    you have neither ftime nor gettimeofday.\r
11235 */\r
11236 \r
11237 /* Get the current time as a TimeMark */\r
11238 void\r
11239 GetTimeMark(tm)\r
11240      TimeMark *tm;\r
11241 {\r
11242 #if HAVE_GETTIMEOFDAY\r
11243 \r
11244     struct timeval timeVal;\r
11245     struct timezone timeZone;\r
11246 \r
11247     gettimeofday(&timeVal, &timeZone);\r
11248     tm->sec = (long) timeVal.tv_sec; \r
11249     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
11250 \r
11251 #else /*!HAVE_GETTIMEOFDAY*/\r
11252 #if HAVE_FTIME\r
11253 \r
11254 #include <sys/timeb.h>\r
11255     struct timeb timeB;\r
11256 \r
11257     ftime(&timeB);\r
11258     tm->sec = (long) timeB.time;\r
11259     tm->ms = (int) timeB.millitm;\r
11260 \r
11261 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
11262     tm->sec = (long) time(NULL);\r
11263     tm->ms = 0;\r
11264 #endif\r
11265 #endif\r
11266 }\r
11267 \r
11268 /* Return the difference in milliseconds between two\r
11269    time marks.  We assume the difference will fit in a long!\r
11270 */\r
11271 long\r
11272 SubtractTimeMarks(tm2, tm1)\r
11273      TimeMark *tm2, *tm1;\r
11274 {\r
11275     return 1000L*(tm2->sec - tm1->sec) +\r
11276            (long) (tm2->ms - tm1->ms);\r
11277 }\r
11278 \r
11279 \r
11280 /*\r
11281  * Code to manage the game clocks.\r
11282  *\r
11283  * In tournament play, black starts the clock and then white makes a move.\r
11284  * We give the human user a slight advantage if he is playing white---the\r
11285  * clocks don't run until he makes his first move, so it takes zero time.\r
11286  * Also, we don't account for network lag, so we could get out of sync\r
11287  * with GNU Chess's clock -- but then, referees are always right.  \r
11288  */\r
11289 \r
11290 static TimeMark tickStartTM;\r
11291 static long intendedTickLength;\r
11292 \r
11293 long\r
11294 NextTickLength(timeRemaining)\r
11295      long timeRemaining;\r
11296 {\r
11297     long nominalTickLength, nextTickLength;\r
11298 \r
11299     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
11300       nominalTickLength = 100L;\r
11301     else\r
11302       nominalTickLength = 1000L;\r
11303     nextTickLength = timeRemaining % nominalTickLength;\r
11304     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
11305 \r
11306     return nextTickLength;\r
11307 }\r
11308 \r
11309 /* Adjust clock one minute up or down */\r
11310 void\r
11311 AdjustClock(Boolean which, int dir)\r
11312 {\r
11313     if(which) blackTimeRemaining += 60000*dir;\r
11314     else      whiteTimeRemaining += 60000*dir;\r
11315     DisplayBothClocks();\r
11316 }\r
11317 \r
11318 /* Stop clocks and reset to a fresh time control */\r
11319 void\r
11320 ResetClocks() \r
11321 {\r
11322     (void) StopClockTimer();\r
11323     if (appData.icsActive) {\r
11324         whiteTimeRemaining = blackTimeRemaining = 0;\r
11325     } else {\r
11326         whiteTimeRemaining = GetTimeControlForWhite();\r
11327         blackTimeRemaining = GetTimeControlForBlack();\r
11328     }\r
11329     if (whiteFlag || blackFlag) {\r
11330         DisplayTitle("");\r
11331         whiteFlag = blackFlag = FALSE;\r
11332     }\r
11333     DisplayBothClocks();\r
11334 }\r
11335 \r
11336 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
11337 \r
11338 /* Decrement running clock by amount of time that has passed */\r
11339 void\r
11340 DecrementClocks()\r
11341 {\r
11342     long timeRemaining;\r
11343     long lastTickLength, fudge;\r
11344     TimeMark now;\r
11345 \r
11346     if (!appData.clockMode) return;\r
11347     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
11348         \r
11349     GetTimeMark(&now);\r
11350 \r
11351     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
11352 \r
11353     /* Fudge if we woke up a little too soon */\r
11354     fudge = intendedTickLength - lastTickLength;\r
11355     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
11356 \r
11357     if (WhiteOnMove(forwardMostMove)) {\r
11358         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
11359         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
11360                           WhiteOnMove(currentMove));\r
11361     } else {\r
11362         timeRemaining = blackTimeRemaining -= lastTickLength;\r
11363         DisplayBlackClock(blackTimeRemaining - fudge,\r
11364                           !WhiteOnMove(currentMove));\r
11365     }\r
11366 \r
11367     if (CheckFlags()) return;\r
11368         \r
11369     tickStartTM = now;\r
11370     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
11371     StartClockTimer(intendedTickLength);\r
11372 \r
11373     /* if the time remaining has fallen below the alarm threshold, sound the\r
11374      * alarm. if the alarm has sounded and (due to a takeback or time control\r
11375      * with increment) the time remaining has increased to a level above the\r
11376      * threshold, reset the alarm so it can sound again. \r
11377      */\r
11378     \r
11379     if (appData.icsActive && appData.icsAlarm) {\r
11380 \r
11381         /* make sure we are dealing with the user's clock */\r
11382         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
11383                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
11384            )) return;\r
11385 \r
11386         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
11387             alarmSounded = FALSE;\r
11388         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
11389             PlayAlarmSound();\r
11390             alarmSounded = TRUE;\r
11391         }\r
11392     }\r
11393 }\r
11394 \r
11395 \r
11396 /* A player has just moved, so stop the previously running\r
11397    clock and (if in clock mode) start the other one.\r
11398    We redisplay both clocks in case we're in ICS mode, because\r
11399    ICS gives us an update to both clocks after every move.\r
11400    Note that this routine is called *after* forwardMostMove\r
11401    is updated, so the last fractional tick must be subtracted\r
11402    from the color that is *not* on move now.\r
11403 */\r
11404 void\r
11405 SwitchClocks()\r
11406 {\r
11407     long lastTickLength;\r
11408     TimeMark now;\r
11409     int flagged = FALSE;\r
11410 \r
11411     GetTimeMark(&now);\r
11412 \r
11413     if (StopClockTimer() && appData.clockMode) {\r
11414         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
11415         if (WhiteOnMove(forwardMostMove)) {\r
11416             blackTimeRemaining -= lastTickLength;\r
11417         } else {\r
11418             whiteTimeRemaining -= lastTickLength;\r
11419         }\r
11420         /* [HGM] save time for PGN file if engine did not give it */\r
11421         if(pvInfoList[forwardMostMove-1].time == -1)\r
11422              pvInfoList[forwardMostMove-1].time = lastTickLength/100;\r
11423         flagged = CheckFlags();\r
11424     }\r
11425     CheckTimeControl();\r
11426 \r
11427     if (flagged || !appData.clockMode) return;\r
11428 \r
11429     switch (gameMode) {\r
11430       case MachinePlaysBlack:\r
11431       case MachinePlaysWhite:\r
11432       case BeginningOfGame:\r
11433         if (pausing) return;\r
11434         break;\r
11435 \r
11436       case EditGame:\r
11437       case PlayFromGameFile:\r
11438       case IcsExamining:\r
11439         return;\r
11440 \r
11441       default:\r
11442         break;\r
11443     }\r
11444 \r
11445     tickStartTM = now;\r
11446     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
11447       whiteTimeRemaining : blackTimeRemaining);\r
11448     StartClockTimer(intendedTickLength);\r
11449 }\r
11450         \r
11451 \r
11452 /* Stop both clocks */\r
11453 void\r
11454 StopClocks()\r
11455 {       \r
11456     long lastTickLength;\r
11457     TimeMark now;\r
11458 \r
11459     if (!StopClockTimer()) return;\r
11460     if (!appData.clockMode) return;\r
11461 \r
11462     GetTimeMark(&now);\r
11463 \r
11464     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
11465     if (WhiteOnMove(forwardMostMove)) {\r
11466         whiteTimeRemaining -= lastTickLength;\r
11467         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
11468     } else {\r
11469         blackTimeRemaining -= lastTickLength;\r
11470         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
11471     }\r
11472     CheckFlags();\r
11473 }\r
11474         \r
11475 /* Start clock of player on move.  Time may have been reset, so\r
11476    if clock is already running, stop and restart it. */\r
11477 void\r
11478 StartClocks()\r
11479 {\r
11480     (void) StopClockTimer(); /* in case it was running already */\r
11481     DisplayBothClocks();\r
11482     if (CheckFlags()) return;\r
11483 \r
11484     if (!appData.clockMode) return;\r
11485     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
11486 \r
11487     GetTimeMark(&tickStartTM);\r
11488     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
11489       whiteTimeRemaining : blackTimeRemaining);\r
11490     StartClockTimer(intendedTickLength);\r
11491 }\r
11492 \r
11493 char *\r
11494 TimeString(ms)\r
11495      long ms;\r
11496 {\r
11497     long second, minute, hour, day;\r
11498     char *sign = "";\r
11499     static char buf[32];\r
11500     \r
11501     if (ms > 0 && ms <= 9900) {\r
11502       /* convert milliseconds to tenths, rounding up */\r
11503       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
11504 \r
11505       sprintf(buf, " %03.1f ", tenths/10.0);\r
11506       return buf;\r
11507     }\r
11508 \r
11509     /* convert milliseconds to seconds, rounding up */\r
11510     /* use floating point to avoid strangeness of integer division\r
11511        with negative dividends on many machines */\r
11512     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
11513 \r
11514     if (second < 0) {\r
11515         sign = "-";\r
11516         second = -second;\r
11517     }\r
11518     \r
11519     day = second / (60 * 60 * 24);\r
11520     second = second % (60 * 60 * 24);\r
11521     hour = second / (60 * 60);\r
11522     second = second % (60 * 60);\r
11523     minute = second / 60;\r
11524     second = second % 60;\r
11525     \r
11526     if (day > 0)\r
11527       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
11528               sign, day, hour, minute, second);\r
11529     else if (hour > 0)\r
11530       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
11531     else\r
11532       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
11533     \r
11534     return buf;\r
11535 }\r
11536 \r
11537 \r
11538 /*\r
11539  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
11540  */\r
11541 char *\r
11542 StrStr(string, match)\r
11543      char *string, *match;\r
11544 {\r
11545     int i, length;\r
11546     \r
11547     length = strlen(match);\r
11548     \r
11549     for (i = strlen(string) - length; i >= 0; i--, string++)\r
11550       if (!strncmp(match, string, length))\r
11551         return string;\r
11552     \r
11553     return NULL;\r
11554 }\r
11555 \r
11556 char *\r
11557 StrCaseStr(string, match)\r
11558      char *string, *match;\r
11559 {\r
11560     int i, j, length;\r
11561     \r
11562     length = strlen(match);\r
11563     \r
11564     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
11565         for (j = 0; j < length; j++) {\r
11566             if (ToLower(match[j]) != ToLower(string[j]))\r
11567               break;\r
11568         }\r
11569         if (j == length) return string;\r
11570     }\r
11571 \r
11572     return NULL;\r
11573 }\r
11574 \r
11575 #ifndef _amigados\r
11576 int\r
11577 StrCaseCmp(s1, s2)\r
11578      char *s1, *s2;\r
11579 {\r
11580     char c1, c2;\r
11581     \r
11582     for (;;) {\r
11583         c1 = ToLower(*s1++);\r
11584         c2 = ToLower(*s2++);\r
11585         if (c1 > c2) return 1;\r
11586         if (c1 < c2) return -1;\r
11587         if (c1 == NULLCHAR) return 0;\r
11588     }\r
11589 }\r
11590 \r
11591 \r
11592 int\r
11593 ToLower(c)\r
11594      int c;\r
11595 {\r
11596     return isupper(c) ? tolower(c) : c;\r
11597 }\r
11598 \r
11599 \r
11600 int\r
11601 ToUpper(c)\r
11602      int c;\r
11603 {\r
11604     return islower(c) ? toupper(c) : c;\r
11605 }\r
11606 #endif /* !_amigados    */\r
11607 \r
11608 char *\r
11609 StrSave(s)\r
11610      char *s;\r
11611 {\r
11612     char *ret;\r
11613 \r
11614     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
11615         strcpy(ret, s);\r
11616     }\r
11617     return ret;\r
11618 }\r
11619 \r
11620 char *\r
11621 StrSavePtr(s, savePtr)\r
11622      char *s, **savePtr;\r
11623 {\r
11624     if (*savePtr) {\r
11625         free(*savePtr);\r
11626     }\r
11627     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
11628         strcpy(*savePtr, s);\r
11629     }\r
11630     return(*savePtr);\r
11631 }\r
11632 \r
11633 char *\r
11634 PGNDate()\r
11635 {\r
11636     time_t clock;\r
11637     struct tm *tm;\r
11638     char buf[MSG_SIZ];\r
11639 \r
11640     clock = time((time_t *)NULL);\r
11641     tm = localtime(&clock);\r
11642     sprintf(buf, "%04d.%02d.%02d",\r
11643             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
11644     return StrSave(buf);\r
11645 }\r
11646 \r
11647 \r
11648 char *\r
11649 PositionToFEN(move, useFEN960)\r
11650      int move;\r
11651      int useFEN960;\r
11652 {\r
11653     int i, j, fromX, fromY, toX, toY;\r
11654     int whiteToPlay;\r
11655     char buf[128];\r
11656     char *p, *q;\r
11657     int emptycount;\r
11658     ChessSquare piece;\r
11659 \r
11660     whiteToPlay = (gameMode == EditPosition) ?\r
11661       !blackPlaysFirst : (move % 2 == 0);\r
11662     p = buf;\r
11663 \r
11664     /* Piece placement data */\r
11665     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11666         emptycount = 0;\r
11667         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11668             if (boards[move][i][j] == EmptySquare) {\r
11669                 emptycount++;\r
11670             } else { ChessSquare piece = boards[move][i][j];\r
11671                 if (emptycount > 0) {\r
11672                     if(emptycount<10) /* [HGM] can be >= 10 */\r
11673                         *p++ = '0' + emptycount;\r
11674                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
11675                     emptycount = 0;\r
11676                 }\r
11677                 if(gameInfo.variant == VariantShogi) {\r
11678                     /* [HGM] write Shogi promoted pieces as +<unpromoted> */\r
11679                     if( (int)piece > (int) WhiteCannon && (int)piece < (int) WhiteKing ||\r
11680                         (int)piece > (int) BlackCannon && (int)piece < (int) BlackKing ) {\r
11681                         *p++ = '+';\r
11682                         piece = (ChessSquare)(DEMOTED piece);\r
11683                     }\r
11684                 } \r
11685                 *p++ = PieceToChar(piece);\r
11686                 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantBughouse) {\r
11687                     /* [HGM] flag Crazyhouse promoted pieces */\r
11688                     if( (int)piece > (int) WhiteQueen && (int)piece < (int) WhiteKing ||\r
11689                         (int)piece > (int) BlackQueen && (int)piece < (int) BlackKing ) {\r
11690                         p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
11691                         *p++ = '~';\r
11692                     }\r
11693                 }\r
11694             }\r
11695         }\r
11696         if (emptycount > 0) {\r
11697             if(emptycount<10) /* [HGM] can be >= 10 */\r
11698                 *p++ = '0' + emptycount;\r
11699             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
11700             emptycount = 0;\r
11701         }\r
11702         *p++ = '/';\r
11703     }\r
11704     *(p - 1) = ' ';\r
11705 \r
11706     /* Active color */\r
11707     *p++ = whiteToPlay ? 'w' : 'b';\r
11708     *p++ = ' ';\r
11709 \r
11710     /* HACK: we don't keep track of castling availability, so fake it! */\r
11711     /* Tord! please fix with the aid of castlingRights[move][...] */\r
11712 \r
11713     /* PUSH Fabien & Tord */\r
11714 \r
11715     /* Declare all potential FRC castling rights (conservative) */\r
11716     /* outermost rook on each side of the king */\r
11717 \r
11718     if( gameInfo.variant == VariantFischeRandom ) {\r
11719        int fk, fr;\r
11720 \r
11721        q = p;\r
11722 \r
11723        /* White castling rights */\r
11724 \r
11725        for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) {\r
11726 \r
11727           if (boards[move][0][fk] == WhiteKing) {\r
11728 \r
11729              for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */\r
11730                 if (boards[move][0][fr] == WhiteRook) {\r
11731                    *p++ = useFEN960 ? 'A' + fr : 'K';\r
11732                    break;\r
11733                 }\r
11734              }\r
11735 \r
11736              for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */\r
11737                 if (boards[move][0][fr] == WhiteRook) {\r
11738                    *p++ = useFEN960 ? 'A' + fr : 'Q';\r
11739                    break;\r
11740                 }\r
11741              }\r
11742           }\r
11743        }\r
11744 \r
11745        /* Black castling rights */\r
11746 \r
11747        for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) {\r
11748 \r
11749           if (boards[move][BOARD_HEIGHT-1][fk] == BlackKing) {\r
11750 \r
11751              for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */\r
11752                 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {\r
11753                    *p++ = useFEN960 ? 'a' + fr : 'k';\r
11754                    break;\r
11755                 }\r
11756              }\r
11757 \r
11758              for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */\r
11759                 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {\r
11760                    *p++ = useFEN960 ? 'a' + fr : 'q';\r
11761                    break;\r
11762                 }\r
11763              }\r
11764           }\r
11765        }\r
11766 \r
11767        if (q == p) *p++ = '-'; /* No castling rights */\r
11768        *p++ = ' ';\r
11769     }\r
11770     else {\r
11771         q = p;\r
11772 \r
11773 #ifdef OLDCASTLINGCODE\r
11774         if (boards[move][0][BOARD_WIDTH>>1] == WhiteKing) {\r
11775             if (boards[move][0][BOARD_RGHT-1] == WhiteRook) *p++ = 'K';\r
11776             if (boards[move][0][BOARD_LEFT] == WhiteRook) *p++ = 'Q';\r
11777         }\r
11778         if (boards[move][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == BlackKing) {\r
11779             if (boards[move][BOARD_HEIGHT-1][BOARD_HEIGHT-1] == BlackRook) *p++ = 'k';\r
11780             if (boards[move][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook) *p++ = 'q';\r
11781         }           \r
11782 #else\r
11783         /* [HGM] write true castling rights */\r
11784         if( nrCastlingRights == 6 ) {\r
11785             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
11786                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
11787             if(castlingRights[move][1] == BOARD_LEFT &&\r
11788                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
11789             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
11790                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
11791             if(castlingRights[move][4] == BOARD_LEFT &&\r
11792                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
11793         }\r
11794 #endif\r
11795         if (q == p) *p++ = '-';\r
11796         *p++ = ' ';\r
11797     }\r
11798 \r
11799     /* POP Fabien & Tord */\r
11800 \r
11801     /* En passant target square */\r
11802     if (move > backwardMostMove) {\r
11803         fromX = moveList[move - 1][0] - AAA;\r
11804         fromY = moveList[move - 1][1] - ONE;\r
11805         toX = moveList[move - 1][2] - AAA;\r
11806         toY = moveList[move - 1][3] - ONE;\r
11807         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
11808             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
11809             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
11810             fromX == toX) {\r
11811             /* 2-square pawn move just happened */\r
11812             *p++ = toX + AAA;\r
11813             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
11814         } else {\r
11815             *p++ = '-';\r
11816         }\r
11817     } else {\r
11818         *p++ = '-';\r
11819     }\r
11820 \r
11821     /* [HGM] print Crazyhouse holdings */\r
11822     if( gameInfo.variant == VariantCrazyhouse ) {\r
11823         *p++ = ' '; q = p;\r
11824         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
11825             piece = boards[move][i][BOARD_WIDTH-1];\r
11826             if( piece != EmptySquare )\r
11827               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
11828                   *p++ = PieceToChar(piece);\r
11829         }\r
11830         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
11831             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
11832             if( piece != EmptySquare )\r
11833               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
11834                   *p++ = PieceToChar(piece);\r
11835         }\r
11836 \r
11837         if( q == p ) *p++ = '-';\r
11838         *p++ = ' ';\r
11839     }\r
11840 \r
11841     /* [HGM] find reversible plies */\r
11842     {   int i = 0, j=move;\r
11843 \r
11844     if (appData.debugMode) { int k;\r
11845         fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
11846         for(k=backwardMostMove; k<=forwardMostMove; k++)\r
11847             fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
11848 \r
11849     }\r
11850 \r
11851         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
11852         if( j == backwardMostMove ) i += initialRulePlies;\r
11853         sprintf(p, " %d", i);\r
11854       p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
11855     }\r
11856     /* Fullmove number */\r
11857     sprintf(p, " %d", (move / 2) + 1);\r
11858     \r
11859     return StrSave(buf);\r
11860 }\r
11861 \r
11862 Boolean\r
11863 ParseFEN(board, blackPlaysFirst, fen)\r
11864     Board board;\r
11865      int *blackPlaysFirst;\r
11866      char *fen;\r
11867 {\r
11868     int i, j;\r
11869     char *p;\r
11870     int emptycount;\r
11871     ChessSquare piece;\r
11872 \r
11873     p = fen;\r
11874 \r
11875     /* [HGM] by default clear Crazyhouse holdings, if present */\r
11876    if(gameInfo.holdingsWidth) {\r
11877        for(i=0; i<BOARD_HEIGHT; i++) {\r
11878            board[i][0]             = EmptySquare; /* black holdings */\r
11879            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
11880            board[i][1]             = (ChessSquare) 0; /* black counts */\r
11881            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
11882        }\r
11883    }\r
11884 \r
11885     /* Piece placement data */\r
11886     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11887         j = 0;\r
11888         for (;;) {\r
11889             if (*p == '/' || *p == ' ') {\r
11890                 if (*p == '/') p++;\r
11891                 emptycount = gameInfo.boardWidth - j;\r
11892                 while (emptycount--)\r
11893                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
11894                 break;\r
11895 #if(BOARD_SIZE >= 10)\r
11896             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
11897                 p++; emptycount=10;\r
11898                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
11899                 while (emptycount--)\r
11900                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
11901 #endif\r
11902             } else if (isdigit(*p)) {\r
11903                 emptycount = *p++ - '0';\r
11904                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
11905                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
11906                 while (emptycount--)\r
11907                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
11908             } else if (*p == '+' || isalpha(*p)) {\r
11909                 if (j >= gameInfo.boardWidth) return FALSE;\r
11910                 if(*p=='+') { piece = (ChessSquare) (PROMOTED CharToPiece(*++p) ); p++; }\r
11911                 else piece = CharToPiece(*p++);\r
11912                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
11913                     piece = (ChessSquare) (PROMOTED piece);\r
11914                     p++;\r
11915                 }\r
11916                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
11917             } else {\r
11918                 return FALSE;\r
11919             }\r
11920         }\r
11921     }\r
11922     while (*p == '/' || *p == ' ') p++;\r
11923 \r
11924     /* Active color */\r
11925     switch (*p++) {\r
11926       case 'w':\r
11927         *blackPlaysFirst = FALSE;\r
11928         break;\r
11929       case 'b': \r
11930         *blackPlaysFirst = TRUE;\r
11931         break;\r
11932       default:\r
11933         return FALSE;\r
11934     }\r
11935 \r
11936     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
11937     /* return the extra info in global variiables             */\r
11938   {\r
11939     /* set defaults in case FEN is incomplete */\r
11940     FENepStatus = EP_UNKNOWN;\r
11941     for(i=0; i<nrCastlingRights; i++ ) {\r
11942         FENcastlingRights[i] = initialRights[i];\r
11943     }   /* assume possible unless obviously impossible */\r
11944     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
11945     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
11946     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
11947     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
11948     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
11949     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
11950     FENrulePlies = 0;\r
11951 \r
11952     while(*p==' ') p++;\r
11953 \r
11954     if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
11955               /* castling indicator present, so default is no castlings */\r
11956               for(i=0; i<nrCastlingRights; i++ ) {\r
11957                      FENcastlingRights[i] = -1;\r
11958               }\r
11959     }\r
11960     while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
11961         switch(*p++) {\r
11962           case'K':\r
11963               FENcastlingRights[0] = BOARD_RGHT-1;\r
11964               FENcastlingRights[2] = BOARD_WIDTH>>1;\r
11965               break;\r
11966           case'Q':\r
11967               FENcastlingRights[1] = BOARD_LEFT;\r
11968               FENcastlingRights[2] = BOARD_WIDTH>>1;\r
11969               break;\r
11970           case'k':\r
11971               FENcastlingRights[3] = BOARD_RGHT-1;\r
11972               FENcastlingRights[5] = BOARD_WIDTH>>1;\r
11973               break;\r
11974           case'q':\r
11975               FENcastlingRights[4] = BOARD_LEFT;\r
11976               FENcastlingRights[5] = BOARD_WIDTH>>1;\r
11977               break;\r
11978           /* Tord! FRC! */\r
11979         }\r
11980     }\r
11981 \r
11982     while(*p==' ') p++;\r
11983 \r
11984 \r
11985     if(*p=='-') {\r
11986         p++; FENepStatus = EP_NONE;\r
11987     } else {\r
11988        char c = *p++ - AAA;\r
11989 \r
11990        if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
11991        if(*p >= '0' && *p <='9') *p++;\r
11992        FENepStatus = c;\r
11993     }\r
11994 \r
11995     /* [HGM] look for Crazyhouse holdings here */\r
11996     while(*p==' ') p++;\r
11997     if( !isdigit(*p) ) {\r
11998         if(*p == '-' ) *p++; /* empty holdings */ else {\r
11999             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
12000             /* if we would allow FEN reading to set board size, we would   */\r
12001             /* have to add holdings and shift the board read so far here   */\r
12002             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
12003                 *p++;\r
12004                 if((int) piece >= (int) BlackPawn ) {\r
12005                     i = (int)piece - (int)BlackPawn;\r
12006                     if( i >= BOARD_HEIGHT ) return FALSE;\r
12007                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
12008                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
12009                 } else {\r
12010                     i = (int)piece - (int)WhitePawn;\r
12011                     if( i >= BOARD_HEIGHT ) return FALSE;\r
12012                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
12013                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
12014                 }\r
12015             }\r
12016         }\r
12017     }\r
12018 \r
12019 \r
12020 \r
12021     if(sscanf(p, "%d", &i) == 1) {\r
12022         FENrulePlies = i; /* 50-move ply counter */\r
12023         /* (The move number is still ignored)    */\r
12024     }\r
12025  }\r
12026     return TRUE;\r
12027 }\r
12028       \r
12029 void\r
12030 EditPositionPasteFEN(char *fen)\r
12031 {\r
12032   if (fen != NULL) {\r
12033     Board initial_position;\r
12034 \r
12035     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
12036       DisplayError("Bad FEN position in clipboard", 0);\r
12037       return ;\r
12038     } else {\r
12039       int savedBlackPlaysFirst = blackPlaysFirst;\r
12040       EditPositionEvent();\r
12041       blackPlaysFirst = savedBlackPlaysFirst;\r
12042       CopyBoard(boards[0], initial_position);\r
12043           /* [HGM] copy FEN attributes as well */\r
12044           {   int i;\r
12045               initialRulePlies = FENrulePlies;\r
12046               epStatus[0] = FENepStatus;\r
12047               for( i=0; i<nrCastlingRights; i++ )\r
12048                   castlingRights[0][i] = FENcastlingRights[i];\r
12049           }\r
12050       EditPositionDone();\r
12051       DisplayBothClocks();\r
12052       DrawPosition(FALSE, boards[currentMove]);\r
12053     }\r
12054   }\r
12055 }\r