changes from H.G. Muller; version 4.3.8
[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_", &i, &i) == 2 ||\r
1371         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1372         while( *e++ != '_');\r
1373     }\r
1374 \r
1375     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1376       if (StrCaseStr(e, variantNames[i])) {\r
1377         v = (VariantClass) i;\r
1378         found = TRUE;\r
1379         break;\r
1380       }\r
1381     }\r
1382 \r
1383     if (!found) {\r
1384       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1385           || StrCaseStr(e, "wild/fr")) {\r
1386         v = VariantFischeRandom;\r
1387       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1388                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1389         p += i;\r
1390         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1391         if (isdigit(*p)) {\r
1392           wnum = atoi(p);\r
1393         } else {\r
1394           wnum = -1;\r
1395         }\r
1396         switch (wnum) {\r
1397         case 0: /* FICS only, actually */\r
1398         case 1:\r
1399           /* Castling legal even if K starts on d-file */\r
1400           v = VariantWildCastle;\r
1401           break;\r
1402         case 2:\r
1403         case 3:\r
1404         case 4:\r
1405           /* Castling illegal even if K & R happen to start in\r
1406              normal positions. */\r
1407           v = VariantNoCastle;\r
1408           break;\r
1409         case 5:\r
1410         case 7:\r
1411         case 8:\r
1412         case 10:\r
1413         case 11:\r
1414         case 12:\r
1415         case 13:\r
1416         case 14:\r
1417         case 15:\r
1418         case 18:\r
1419         case 19:\r
1420           /* Castling legal iff K & R start in normal positions */\r
1421           v = VariantNormal;\r
1422           break;\r
1423         case 6:\r
1424         case 20:\r
1425         case 21:\r
1426           /* Special wilds for position setup; unclear what to do here */\r
1427           v = VariantLoadable;\r
1428           break;\r
1429         case 9:\r
1430           /* Bizarre ICC game */\r
1431           v = VariantTwoKings;\r
1432           break;\r
1433         case 16:\r
1434           v = VariantKriegspiel;\r
1435           break;\r
1436         case 17:\r
1437           v = VariantLosers;\r
1438           break;\r
1439         case 22:\r
1440           v = VariantFischeRandom;\r
1441           break;\r
1442         case 23:\r
1443           v = VariantCrazyhouse;\r
1444           break;\r
1445         case 24:\r
1446           v = VariantBughouse;\r
1447           break;\r
1448         case 25:\r
1449           v = Variant3Check;\r
1450           break;\r
1451         case 26:\r
1452           /* Not quite the same as FICS suicide! */\r
1453           v = VariantGiveaway;\r
1454           break;\r
1455         case 27:\r
1456           v = VariantAtomic;\r
1457           break;\r
1458         case 28:\r
1459           v = VariantShatranj;\r
1460           break;\r
1461 \r
1462         /* Temporary names for future ICC types.  The name *will* change in \r
1463            the next xboard/WinBoard release after ICC defines it. */\r
1464         case 29:\r
1465           v = Variant29;\r
1466           break;\r
1467         case 30:\r
1468           v = Variant30;\r
1469           break;\r
1470         case 31:\r
1471           v = Variant31;\r
1472           break;\r
1473         case 32:\r
1474           v = Variant32;\r
1475           break;\r
1476         case 33:\r
1477           v = Variant33;\r
1478           break;\r
1479         case 34:\r
1480           v = Variant34;\r
1481           break;\r
1482         case 35:\r
1483           v = Variant35;\r
1484           break;\r
1485         case 36:\r
1486           v = Variant36;\r
1487           break;\r
1488         case 37:\r
1489           v = VariantShogi;\r
1490           break;\r
1491         case 38:\r
1492           v = VariantXiangqi;\r
1493           break;\r
1494         case 39:\r
1495           v = VariantCourier;\r
1496           break;\r
1497         case 40:\r
1498           v = VariantGothic;\r
1499           break;\r
1500         case 41:\r
1501           v = VariantCapablanca;\r
1502           break;\r
1503         case 42:\r
1504           v = VariantKnightmate;\r
1505           break;\r
1506         case 43:\r
1507           v = VariantFairy;\r
1508           break;\r
1509         case 44:\r
1510           v = VariantShowgi;\r
1511           break;\r
1512 \r
1513         case -1:\r
1514           /* Found "wild" or "w" in the string but no number;\r
1515              must assume it's normal chess. */\r
1516           v = VariantNormal;\r
1517           break;\r
1518         default:\r
1519           sprintf(buf, "Unknown wild type %d", wnum);\r
1520           DisplayError(buf, 0);\r
1521           v = VariantUnknown;\r
1522           break;\r
1523         }\r
1524       }\r
1525     }\r
1526     if (appData.debugMode) {\r
1527       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",\r
1528               e, wnum, VariantName(v));\r
1529     }\r
1530     return v;\r
1531 }\r
1532 \r
1533 static int leftover_start = 0, leftover_len = 0;\r
1534 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1535 \r
1536 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1537    advance *index beyond it, and set leftover_start to the new value of\r
1538    *index; else return FALSE.  If pattern contains the character '*', it\r
1539    matches any sequence of characters not containing '\r', '\n', or the\r
1540    character following the '*' (if any), and the matched sequence(s) are\r
1541    copied into star_match.\r
1542    */\r
1543 int\r
1544 looking_at(buf, index, pattern)\r
1545      char *buf;\r
1546      int *index;\r
1547      char *pattern;\r
1548 {\r
1549     char *bufp = &buf[*index], *patternp = pattern;\r
1550     int star_count = 0;\r
1551     char *matchp = star_match[0];\r
1552     \r
1553     for (;;) {\r
1554         if (*patternp == NULLCHAR) {\r
1555             *index = leftover_start = bufp - buf;\r
1556             *matchp = NULLCHAR;\r
1557             return TRUE;\r
1558         }\r
1559         if (*bufp == NULLCHAR) return FALSE;\r
1560         if (*patternp == '*') {\r
1561             if (*bufp == *(patternp + 1)) {\r
1562                 *matchp = NULLCHAR;\r
1563                 matchp = star_match[++star_count];\r
1564                 patternp += 2;\r
1565                 bufp++;\r
1566                 continue;\r
1567             } else if (*bufp == '\n' || *bufp == '\r') {\r
1568                 patternp++;\r
1569                 if (*patternp == NULLCHAR)\r
1570                   continue;\r
1571                 else\r
1572                   return FALSE;\r
1573             } else {\r
1574                 *matchp++ = *bufp++;\r
1575                 continue;\r
1576             }\r
1577         }\r
1578         if (*patternp != *bufp) return FALSE;\r
1579         patternp++;\r
1580         bufp++;\r
1581     }\r
1582 }\r
1583 \r
1584 void\r
1585 SendToPlayer(data, length)\r
1586      char *data;\r
1587      int length;\r
1588 {\r
1589     int error, outCount;\r
1590     outCount = OutputToProcess(NoProc, data, length, &error);\r
1591     if (outCount < length) {\r
1592         DisplayFatalError("Error writing to display", error, 1);\r
1593     }\r
1594 }\r
1595 \r
1596 void\r
1597 PackHolding(packed, holding)\r
1598      char packed[];\r
1599      char *holding;\r
1600 {\r
1601     char *p = holding;\r
1602     char *q = packed;\r
1603     int runlength = 0;\r
1604     int curr = 9999;\r
1605     do {\r
1606         if (*p == curr) {\r
1607             runlength++;\r
1608         } else {\r
1609             switch (runlength) {\r
1610               case 0:\r
1611                 break;\r
1612               case 1:\r
1613                 *q++ = curr;\r
1614                 break;\r
1615               case 2:\r
1616                 *q++ = curr;\r
1617                 *q++ = curr;\r
1618                 break;\r
1619               default:\r
1620                 sprintf(q, "%d", runlength);\r
1621                 while (*q) q++;\r
1622                 *q++ = curr;\r
1623                 break;\r
1624             }\r
1625             runlength = 1;\r
1626             curr = *p;\r
1627         }\r
1628     } while (*p++);\r
1629     *q = NULLCHAR;\r
1630 }\r
1631 \r
1632 /* Telnet protocol requests from the front end */\r
1633 void\r
1634 TelnetRequest(ddww, option)\r
1635      unsigned char ddww, option;\r
1636 {\r
1637     unsigned char msg[3];\r
1638     int outCount, outError;\r
1639 \r
1640     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1641 \r
1642     if (appData.debugMode) {\r
1643         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1644         switch (ddww) {\r
1645           case TN_DO:\r
1646             ddwwStr = "DO";\r
1647             break;\r
1648           case TN_DONT:\r
1649             ddwwStr = "DONT";\r
1650             break;\r
1651           case TN_WILL:\r
1652             ddwwStr = "WILL";\r
1653             break;\r
1654           case TN_WONT:\r
1655             ddwwStr = "WONT";\r
1656             break;\r
1657           default:\r
1658             ddwwStr = buf1;\r
1659             sprintf(buf1, "%d", ddww);\r
1660             break;\r
1661         }\r
1662         switch (option) {\r
1663           case TN_ECHO:\r
1664             optionStr = "ECHO";\r
1665             break;\r
1666           default:\r
1667             optionStr = buf2;\r
1668             sprintf(buf2, "%d", option);\r
1669             break;\r
1670         }\r
1671         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1672     }\r
1673     msg[0] = TN_IAC;\r
1674     msg[1] = ddww;\r
1675     msg[2] = option;\r
1676     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1677     if (outCount < 3) {\r
1678         DisplayFatalError("Error writing to ICS", outError, 1);\r
1679     }\r
1680 }\r
1681 \r
1682 void\r
1683 DoEcho()\r
1684 {\r
1685     if (!appData.icsActive) return;\r
1686     TelnetRequest(TN_DO, TN_ECHO);\r
1687 }\r
1688 \r
1689 void\r
1690 DontEcho()\r
1691 {\r
1692     if (!appData.icsActive) return;\r
1693     TelnetRequest(TN_DONT, TN_ECHO);\r
1694 }\r
1695 \r
1696 void\r
1697 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1698 {\r
1699     /* put the holdings sent to us by the server on the board holdings area */\r
1700     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1701     char p;\r
1702     ChessSquare piece;\r
1703 \r
1704     if(gameInfo.holdingsWidth < 2)  return;\r
1705 \r
1706     if( (int)lowestPiece >= BlackPawn ) {\r
1707         holdingsColumn = 0;\r
1708         countsColumn = 1;\r
1709         holdingsStartRow = BOARD_HEIGHT-1;\r
1710         direction = -1;\r
1711     } else {\r
1712         holdingsColumn = BOARD_WIDTH-1;\r
1713         countsColumn = BOARD_WIDTH-2;\r
1714         holdingsStartRow = 0;\r
1715         direction = 1;\r
1716     }\r
1717 \r
1718     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1719         board[i][holdingsColumn] = EmptySquare;\r
1720         board[i][countsColumn]   = (ChessSquare) 0;\r
1721     }\r
1722     while( (p=*holdings++) != NULLCHAR ) {\r
1723         piece = CharToPiece( ToUpper(p) );\r
1724         if(piece == EmptySquare) continue;\r
1725         /*j = (int) piece - (int) WhitePawn;*/\r
1726         j = PieceToNumber(piece);\r
1727         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1728         if(j < 0) continue;               /* should not happen */\r
1729         piece = (ChessSquare) ( j + (int)lowestPiece );\r
1730         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1731         board[holdingsStartRow+j*direction][countsColumn]++;\r
1732     }\r
1733 \r
1734 }\r
1735 \r
1736 void\r
1737 VariantSwitch(Board board, VariantClass newVariant)\r
1738 {\r
1739    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
1740    if(gameInfo.variant == newVariant) return;\r
1741 \r
1742    /* [HGM] This routine is called each time an assignment is made to\r
1743     * gameInfo.variant during a game, to make sure the board sizes\r
1744     * are set to match the new variant. If that means adding or deleting\r
1745     * holdings, we shift the playing board accordingly\r
1746     */\r
1747 \r
1748   if (appData.debugMode) {\r
1749     fprintf(debugFP, "Switch board from %s to %s\n",\r
1750                VariantName(gameInfo.variant), VariantName(newVariant));\r
1751     setbuf(debugFP, NULL);\r
1752   }\r
1753     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1754          switch(newVariant) {\r
1755             case VariantShogi:\r
1756             case VariantShowgi:\r
1757               newWidth = 9;  newHeight = 9;\r
1758               gameInfo.holdingsSize = 7;\r
1759             case VariantBughouse:\r
1760             case VariantCrazyhouse:\r
1761               newHoldingsWidth = 2; break;\r
1762             default:\r
1763               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1764     }\r
1765 \r
1766     if(newWidth  != gameInfo.boardWidth  ||\r
1767        newHeight != gameInfo.boardHeight ||\r
1768        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1769 \r
1770         /* shift position to new playing area, if needed */\r
1771         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
1772            for(i=0; i<BOARD_HEIGHT; i++) \r
1773                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
1774                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1775                                                      board[i][j];\r
1776            for(i=0; i<newHeight; i++) {\r
1777                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
1778                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
1779            }\r
1780         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
1781            for(i=0; i<BOARD_HEIGHT; i++)\r
1782                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
1783                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1784                                                  board[i][j];\r
1785         }\r
1786 \r
1787         gameInfo.boardWidth  = newWidth;\r
1788         gameInfo.boardHeight = newHeight;\r
1789         gameInfo.holdingsWidth = newHoldingsWidth;\r
1790         gameInfo.variant = newVariant;\r
1791         InitDrawingSizes(-2, 0);\r
1792     } else gameInfo.variant = newVariant;\r
1793 }\r
1794 \r
1795 static int loggedOn = FALSE;\r
1796 \r
1797 /*-- Game start info cache: --*/\r
1798 int gs_gamenum;\r
1799 char gs_kind[MSG_SIZ];\r
1800 static char player1Name[128] = "";\r
1801 static char player2Name[128] = "";\r
1802 static int player1Rating = -1;\r
1803 static int player2Rating = -1;\r
1804 /*----------------------------*/\r
1805 \r
1806 ColorClass curColor = ColorNormal;\r
1807 \r
1808 void\r
1809 read_from_ics(isr, closure, data, count, error)\r
1810      InputSourceRef isr;\r
1811      VOIDSTAR closure;\r
1812      char *data;\r
1813      int count;\r
1814      int error;\r
1815 {\r
1816 #define BUF_SIZE 8192\r
1817 #define STARTED_NONE 0\r
1818 #define STARTED_MOVES 1\r
1819 #define STARTED_BOARD 2\r
1820 #define STARTED_OBSERVE 3\r
1821 #define STARTED_HOLDINGS 4\r
1822 #define STARTED_CHATTER 5\r
1823 #define STARTED_COMMENT 6\r
1824 #define STARTED_MOVES_NOHIDE 7\r
1825     \r
1826     static int started = STARTED_NONE;\r
1827     static char parse[20000];\r
1828     static int parse_pos = 0;\r
1829     static char buf[BUF_SIZE + 1];\r
1830     static int firstTime = TRUE, intfSet = FALSE;\r
1831     static ColorClass prevColor = ColorNormal;\r
1832     static int savingComment = FALSE;\r
1833     char str[500];\r
1834     int i, oldi;\r
1835     int buf_len;\r
1836     int next_out;\r
1837     int tkind;\r
1838     char *p;\r
1839 \r
1840 #ifdef WIN32\r
1841     if (appData.debugMode) {\r
1842       if (!error) {\r
1843         fprintf(debugFP, "<ICS: ");\r
1844         show_bytes(debugFP, data, count);\r
1845         fprintf(debugFP, "\n");\r
1846       }\r
1847     }\r
1848 #endif\r
1849 \r
1850     if (count > 0) {\r
1851         /* If last read ended with a partial line that we couldn't parse,\r
1852            prepend it to the new read and try again. */\r
1853         if (leftover_len > 0) {\r
1854             for (i=0; i<leftover_len; i++)\r
1855               buf[i] = buf[leftover_start + i];\r
1856         }\r
1857 \r
1858         /* Copy in new characters, removing nulls and \r's */\r
1859         buf_len = leftover_len;\r
1860         for (i = 0; i < count; i++) {\r
1861             if (data[i] != NULLCHAR && data[i] != '\r')\r
1862               buf[buf_len++] = data[i];\r
1863         }\r
1864 \r
1865         buf[buf_len] = NULLCHAR;\r
1866         next_out = leftover_len;\r
1867         leftover_start = 0;\r
1868         \r
1869         i = 0;\r
1870         while (i < buf_len) {\r
1871             /* Deal with part of the TELNET option negotiation\r
1872                protocol.  We refuse to do anything beyond the\r
1873                defaults, except that we allow the WILL ECHO option,\r
1874                which ICS uses to turn off password echoing when we are\r
1875                directly connected to it.  We reject this option\r
1876                if localLineEditing mode is on (always on in xboard)\r
1877                and we are talking to port 23, which might be a real\r
1878                telnet server that will try to keep WILL ECHO on permanently.\r
1879              */\r
1880             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
1881                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
1882                 unsigned char option;\r
1883                 oldi = i;\r
1884                 switch ((unsigned char) buf[++i]) {\r
1885                   case TN_WILL:\r
1886                     if (appData.debugMode)\r
1887                       fprintf(debugFP, "\n<WILL ");\r
1888                     switch (option = (unsigned char) buf[++i]) {\r
1889                       case TN_ECHO:\r
1890                         if (appData.debugMode)\r
1891                           fprintf(debugFP, "ECHO ");\r
1892                         /* Reply only if this is a change, according\r
1893                            to the protocol rules. */\r
1894                         if (remoteEchoOption) break;\r
1895                         if (appData.localLineEditing &&\r
1896                             atoi(appData.icsPort) == TN_PORT) {\r
1897                             TelnetRequest(TN_DONT, TN_ECHO);\r
1898                         } else {\r
1899                             EchoOff();\r
1900                             TelnetRequest(TN_DO, TN_ECHO);\r
1901                             remoteEchoOption = TRUE;\r
1902                         }\r
1903                         break;\r
1904                       default:\r
1905                         if (appData.debugMode)\r
1906                           fprintf(debugFP, "%d ", option);\r
1907                         /* Whatever this is, we don't want it. */\r
1908                         TelnetRequest(TN_DONT, option);\r
1909                         break;\r
1910                     }\r
1911                     break;\r
1912                   case TN_WONT:\r
1913                     if (appData.debugMode)\r
1914                       fprintf(debugFP, "\n<WONT ");\r
1915                     switch (option = (unsigned char) buf[++i]) {\r
1916                       case TN_ECHO:\r
1917                         if (appData.debugMode)\r
1918                           fprintf(debugFP, "ECHO ");\r
1919                         /* Reply only if this is a change, according\r
1920                            to the protocol rules. */\r
1921                         if (!remoteEchoOption) break;\r
1922                         EchoOn();\r
1923                         TelnetRequest(TN_DONT, TN_ECHO);\r
1924                         remoteEchoOption = FALSE;\r
1925                         break;\r
1926                       default:\r
1927                         if (appData.debugMode)\r
1928                           fprintf(debugFP, "%d ", (unsigned char) option);\r
1929                         /* Whatever this is, it must already be turned\r
1930                            off, because we never agree to turn on\r
1931                            anything non-default, so according to the\r
1932                            protocol rules, we don't reply. */\r
1933                         break;\r
1934                     }\r
1935                     break;\r
1936                   case TN_DO:\r
1937                     if (appData.debugMode)\r
1938                       fprintf(debugFP, "\n<DO ");\r
1939                     switch (option = (unsigned char) buf[++i]) {\r
1940                       default:\r
1941                         /* Whatever this is, we refuse to do it. */\r
1942                         if (appData.debugMode)\r
1943                           fprintf(debugFP, "%d ", option);\r
1944                         TelnetRequest(TN_WONT, option);\r
1945                         break;\r
1946                     }\r
1947                     break;\r
1948                   case TN_DONT:\r
1949                     if (appData.debugMode)\r
1950                       fprintf(debugFP, "\n<DONT ");\r
1951                     switch (option = (unsigned char) buf[++i]) {\r
1952                       default:\r
1953                         if (appData.debugMode)\r
1954                           fprintf(debugFP, "%d ", option);\r
1955                         /* Whatever this is, we are already not doing\r
1956                            it, because we never agree to do anything\r
1957                            non-default, so according to the protocol\r
1958                            rules, we don't reply. */\r
1959                         break;\r
1960                     }\r
1961                     break;\r
1962                   case TN_IAC:\r
1963                     if (appData.debugMode)\r
1964                       fprintf(debugFP, "\n<IAC ");\r
1965                     /* Doubled IAC; pass it through */\r
1966                     i--;\r
1967                     break;\r
1968                   default:\r
1969                     if (appData.debugMode)\r
1970                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
1971                     /* Drop all other telnet commands on the floor */\r
1972                     break;\r
1973                 }\r
1974                 if (oldi > next_out)\r
1975                   SendToPlayer(&buf[next_out], oldi - next_out);\r
1976                 if (++i > next_out)\r
1977                   next_out = i;\r
1978                 continue;\r
1979             }\r
1980                 \r
1981             /* OK, this at least will *usually* work */\r
1982             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
1983                 loggedOn = TRUE;\r
1984             }\r
1985             \r
1986             if (loggedOn && !intfSet) {\r
1987                 if (ics_type == ICS_ICC) {\r
1988                   sprintf(str,\r
1989                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
1990                           programVersion);\r
1991 \r
1992                 } else if (ics_type == ICS_CHESSNET) {\r
1993                   sprintf(str, "/style 12\n");\r
1994                 } else {\r
1995                   strcpy(str, "alias $ @\n$set interface ");\r
1996                   strcat(str, programVersion);\r
1997                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
1998 #ifdef WIN32\r
1999                   strcat(str, "$iset nohighlight 1\n");\r
2000 #endif\r
2001                   strcat(str, "$iset lock 1\n$style 12\n");\r
2002                 }\r
2003                 SendToICS(str);\r
2004                 intfSet = TRUE;\r
2005             }\r
2006 \r
2007             if (started == STARTED_COMMENT) {\r
2008                 /* Accumulate characters in comment */\r
2009                 parse[parse_pos++] = buf[i];\r
2010                 if (buf[i] == '\n') {\r
2011                     parse[parse_pos] = NULLCHAR;\r
2012                     AppendComment(forwardMostMove, StripHighlight(parse));\r
2013                     started = STARTED_NONE;\r
2014                 } else {\r
2015                     /* Don't match patterns against characters in chatter */\r
2016                     i++;\r
2017                     continue;\r
2018                 }\r
2019             }\r
2020             if (started == STARTED_CHATTER) {\r
2021                 if (buf[i] != '\n') {\r
2022                     /* Don't match patterns against characters in chatter */\r
2023                     i++;\r
2024                     continue;\r
2025                 }\r
2026                 started = STARTED_NONE;\r
2027             }\r
2028 \r
2029             /* Kludge to deal with rcmd protocol */\r
2030             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2031                 DisplayFatalError(&buf[1], 0, 1);\r
2032                 continue;\r
2033             } else {\r
2034                 firstTime = FALSE;\r
2035             }\r
2036 \r
2037             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2038                 ics_type = ICS_ICC;\r
2039                 ics_prefix = "/";\r
2040                 if (appData.debugMode)\r
2041                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2042                 continue;\r
2043             }\r
2044             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2045                 ics_type = ICS_FICS;\r
2046                 ics_prefix = "$";\r
2047                 if (appData.debugMode)\r
2048                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2049                 continue;\r
2050             }\r
2051             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2052                 ics_type = ICS_CHESSNET;\r
2053                 ics_prefix = "/";\r
2054                 if (appData.debugMode)\r
2055                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2056                 continue;\r
2057             }\r
2058 \r
2059             if (!loggedOn &&\r
2060                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2061                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2062                  looking_at(buf, &i, "will be \"*\""))) {\r
2063               strcpy(ics_handle, star_match[0]);\r
2064               continue;\r
2065             }\r
2066 \r
2067             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2068               char buf[MSG_SIZ];\r
2069               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2070               DisplayIcsInteractionTitle(buf);\r
2071               have_set_title = TRUE;\r
2072             }\r
2073 \r
2074             /* skip finger notes */\r
2075             if (started == STARTED_NONE &&\r
2076                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2077                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2078                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2079               started = STARTED_CHATTER;\r
2080               i += 3;\r
2081               continue;\r
2082             }\r
2083 \r
2084             /* skip formula vars */\r
2085             if (started == STARTED_NONE &&\r
2086                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2087               started = STARTED_CHATTER;\r
2088               i += 3;\r
2089               continue;\r
2090             }\r
2091 \r
2092             oldi = i;\r
2093             if (appData.zippyTalk || appData.zippyPlay) {\r
2094 #if ZIPPY\r
2095                 if (ZippyControl(buf, &i) ||\r
2096                     ZippyConverse(buf, &i) ||\r
2097                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2098                     loggedOn = TRUE;\r
2099                     continue;\r
2100                 }\r
2101 #endif\r
2102             } else {\r
2103                 if (/* Don't color "message" or "messages" output */\r
2104                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2105                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2106                     looking_at(buf, &i, "--* (*:*): ") ||\r
2107                     /* Regular tells and says */\r
2108                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2109                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2110                     looking_at(buf, &i, "* says: ") ||\r
2111                     /* Message notifications (same color as tells) */\r
2112                     looking_at(buf, &i, "* has left a message ") ||\r
2113                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2114                     /* Whispers and kibitzes */\r
2115                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2116                     looking_at(buf, &i, "* kibitzes: ") ||\r
2117                     /* Channel tells */\r
2118                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2119 \r
2120                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2121                       /* Avoid "tells you:" spoofs in channels */\r
2122                      tkind = 3;\r
2123                   }\r
2124                   if (star_match[0][0] == NULLCHAR ||\r
2125                       strchr(star_match[0], ' ') ||\r
2126                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2127                     /* Reject bogus matches */\r
2128                     i = oldi;\r
2129                   } else {\r
2130                     if (appData.colorize) {\r
2131                       if (oldi > next_out) {\r
2132                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2133                         next_out = oldi;\r
2134                       }\r
2135                       switch (tkind) {\r
2136                       case 1:\r
2137                         Colorize(ColorTell, FALSE);\r
2138                         curColor = ColorTell;\r
2139                         break;\r
2140                       case 2:\r
2141                         Colorize(ColorKibitz, FALSE);\r
2142                         curColor = ColorKibitz;\r
2143                         break;\r
2144                       case 3:\r
2145                         p = strrchr(star_match[1], '(');\r
2146                         if (p == NULL) {\r
2147                           p = star_match[1];\r
2148                         } else {\r
2149                           p++;\r
2150                         }\r
2151                         if (atoi(p) == 1) {\r
2152                           Colorize(ColorChannel1, FALSE);\r
2153                           curColor = ColorChannel1;\r
2154                         } else {\r
2155                           Colorize(ColorChannel, FALSE);\r
2156                           curColor = ColorChannel;\r
2157                         }\r
2158                         break;\r
2159                       case 5:\r
2160                         curColor = ColorNormal;\r
2161                         break;\r
2162                       }\r
2163                     }\r
2164                     if (started == STARTED_NONE && appData.autoComment &&\r
2165                         (gameMode == IcsObserving ||\r
2166                          gameMode == IcsPlayingWhite ||\r
2167                          gameMode == IcsPlayingBlack)) {\r
2168                       parse_pos = i - oldi;\r
2169                       memcpy(parse, &buf[oldi], parse_pos);\r
2170                       parse[parse_pos] = NULLCHAR;\r
2171                       started = STARTED_COMMENT;\r
2172                       savingComment = TRUE;\r
2173                     } else {\r
2174                       started = STARTED_CHATTER;\r
2175                       savingComment = FALSE;\r
2176                     }\r
2177                     loggedOn = TRUE;\r
2178                     continue;\r
2179                   }\r
2180                 }\r
2181 \r
2182                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2183                     looking_at(buf, &i, "* c-shouts: ")) {\r
2184                     if (appData.colorize) {\r
2185                         if (oldi > next_out) {\r
2186                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2187                             next_out = oldi;\r
2188                         }\r
2189                         Colorize(ColorSShout, FALSE);\r
2190                         curColor = ColorSShout;\r
2191                     }\r
2192                     loggedOn = TRUE;\r
2193                     started = STARTED_CHATTER;\r
2194                     continue;\r
2195                 }\r
2196 \r
2197                 if (looking_at(buf, &i, "--->")) {\r
2198                     loggedOn = TRUE;\r
2199                     continue;\r
2200                 }\r
2201 \r
2202                 if (looking_at(buf, &i, "* shouts: ") ||\r
2203                     looking_at(buf, &i, "--> ")) {\r
2204                     if (appData.colorize) {\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(ColorShout, FALSE);\r
2210                         curColor = ColorShout;\r
2211                     }\r
2212                     loggedOn = TRUE;\r
2213                     started = STARTED_CHATTER;\r
2214                     continue;\r
2215                 }\r
2216 \r
2217                 if (looking_at( buf, &i, "Challenge:")) {\r
2218                     if (appData.colorize) {\r
2219                         if (oldi > next_out) {\r
2220                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2221                             next_out = oldi;\r
2222                         }\r
2223                         Colorize(ColorChallenge, FALSE);\r
2224                         curColor = ColorChallenge;\r
2225                     }\r
2226                     loggedOn = TRUE;\r
2227                     continue;\r
2228                 }\r
2229 \r
2230                 if (looking_at(buf, &i, "* offers you") ||\r
2231                     looking_at(buf, &i, "* offers to be") ||\r
2232                     looking_at(buf, &i, "* would like to") ||\r
2233                     looking_at(buf, &i, "* requests to") ||\r
2234                     looking_at(buf, &i, "Your opponent offers") ||\r
2235                     looking_at(buf, &i, "Your opponent requests")) {\r
2236 \r
2237                     if (appData.colorize) {\r
2238                         if (oldi > next_out) {\r
2239                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2240                             next_out = oldi;\r
2241                         }\r
2242                         Colorize(ColorRequest, FALSE);\r
2243                         curColor = ColorRequest;\r
2244                     }\r
2245                     continue;\r
2246                 }\r
2247 \r
2248                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2249                     if (appData.colorize) {\r
2250                         if (oldi > next_out) {\r
2251                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2252                             next_out = oldi;\r
2253                         }\r
2254                         Colorize(ColorSeek, FALSE);\r
2255                         curColor = ColorSeek;\r
2256                     }\r
2257                     continue;\r
2258                 }\r
2259             }\r
2260 \r
2261             if (looking_at(buf, &i, "\\   ")) {\r
2262                 if (prevColor != ColorNormal) {\r
2263                     if (oldi > next_out) {\r
2264                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2265                         next_out = oldi;\r
2266                     }\r
2267                     Colorize(prevColor, TRUE);\r
2268                     curColor = prevColor;\r
2269                 }\r
2270                 if (savingComment) {\r
2271                     parse_pos = i - oldi;\r
2272                     memcpy(parse, &buf[oldi], parse_pos);\r
2273                     parse[parse_pos] = NULLCHAR;\r
2274                     started = STARTED_COMMENT;\r
2275                 } else {\r
2276                     started = STARTED_CHATTER;\r
2277                 }\r
2278                 continue;\r
2279             }\r
2280 \r
2281             if (looking_at(buf, &i, "Black Strength :") ||\r
2282                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2283                 looking_at(buf, &i, "<10>") ||\r
2284                 looking_at(buf, &i, "#@#")) {\r
2285                 /* Wrong board style */\r
2286                 loggedOn = TRUE;\r
2287                 SendToICS(ics_prefix);\r
2288                 SendToICS("set style 12\n");\r
2289                 SendToICS(ics_prefix);\r
2290                 SendToICS("refresh\n");\r
2291                 continue;\r
2292             }\r
2293             \r
2294             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2295                 ICSInitScript();\r
2296                 have_sent_ICS_logon = 1;\r
2297                 continue;\r
2298             }\r
2299               \r
2300             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2301                 (looking_at(buf, &i, "\n<12> ") ||\r
2302                  looking_at(buf, &i, "<12> "))) {\r
2303                 loggedOn = TRUE;\r
2304                 if (oldi > next_out) {\r
2305                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2306                 }\r
2307                 next_out = i;\r
2308                 started = STARTED_BOARD;\r
2309                 parse_pos = 0;\r
2310                 continue;\r
2311             }\r
2312 \r
2313             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2314                 looking_at(buf, &i, "<b1> ")) {\r
2315                 if (oldi > next_out) {\r
2316                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2317                 }\r
2318                 next_out = i;\r
2319                 started = STARTED_HOLDINGS;\r
2320                 parse_pos = 0;\r
2321                 continue;\r
2322             }\r
2323 \r
2324             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2325                 loggedOn = TRUE;\r
2326                 /* Header for a move list -- first line */\r
2327 \r
2328                 switch (ics_getting_history) {\r
2329                   case H_FALSE:\r
2330                     switch (gameMode) {\r
2331                       case IcsIdle:\r
2332                       case BeginningOfGame:\r
2333                         /* User typed "moves" or "oldmoves" while we\r
2334                            were idle.  Pretend we asked for these\r
2335                            moves and soak them up so user can step\r
2336                            through them and/or save them.\r
2337                            */\r
2338                         Reset(FALSE, TRUE);\r
2339                         gameMode = IcsObserving;\r
2340                         ModeHighlight();\r
2341                         ics_gamenum = -1;\r
2342                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2343                         break;\r
2344                       case EditGame: /*?*/\r
2345                       case EditPosition: /*?*/\r
2346                         /* Should above feature work in these modes too? */\r
2347                         /* For now it doesn't */\r
2348                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2349                         break;\r
2350                       default:\r
2351                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2352                         break;\r
2353                     }\r
2354                     break;\r
2355                   case H_REQUESTED:\r
2356                     /* Is this the right one? */\r
2357                     if (gameInfo.white && gameInfo.black &&\r
2358                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2359                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2360                         /* All is well */\r
2361                         ics_getting_history = H_GOT_REQ_HEADER;\r
2362                     }\r
2363                     break;\r
2364                   case H_GOT_REQ_HEADER:\r
2365                   case H_GOT_UNREQ_HEADER:\r
2366                   case H_GOT_UNWANTED_HEADER:\r
2367                   case H_GETTING_MOVES:\r
2368                     /* Should not happen */\r
2369                     DisplayError("Error gathering move list: two headers", 0);\r
2370                     ics_getting_history = H_FALSE;\r
2371                     break;\r
2372                 }\r
2373 \r
2374                 /* Save player ratings into gameInfo if needed */\r
2375                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2376                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2377                     (gameInfo.whiteRating == -1 ||\r
2378                      gameInfo.blackRating == -1)) {\r
2379 \r
2380                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2381                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2382                     if (appData.debugMode)\r
2383                       fprintf(debugFP, "Ratings from header: W %d, B %d\n", \r
2384                               gameInfo.whiteRating, gameInfo.blackRating);\r
2385                 }\r
2386                 continue;\r
2387             }\r
2388 \r
2389             if (looking_at(buf, &i,\r
2390               "* * match, initial time: * minute*, increment: * second")) {\r
2391                 /* Header for a move list -- second line */\r
2392                 /* Initial board will follow if this is a wild game */\r
2393                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2394                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2395                 gameInfo.event = StrSave(str);\r
2396                 /* [HGM] we switched variant. Translate boards if needed. */\r
2397                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2398                 continue;\r
2399             }\r
2400 \r
2401             if (looking_at(buf, &i, "Move  ")) {\r
2402                 /* Beginning of a move list */\r
2403                 switch (ics_getting_history) {\r
2404                   case H_FALSE:\r
2405                     /* Normally should not happen */\r
2406                     /* Maybe user hit reset while we were parsing */\r
2407                     break;\r
2408                   case H_REQUESTED:\r
2409                     /* Happens if we are ignoring a move list that is not\r
2410                      * the one we just requested.  Common if the user\r
2411                      * tries to observe two games without turning off\r
2412                      * getMoveList */\r
2413                     break;\r
2414                   case H_GETTING_MOVES:\r
2415                     /* Should not happen */\r
2416                     DisplayError("Error gathering move list: nested", 0);\r
2417                     ics_getting_history = H_FALSE;\r
2418                     break;\r
2419                   case H_GOT_REQ_HEADER:\r
2420                     ics_getting_history = H_GETTING_MOVES;\r
2421                     started = STARTED_MOVES;\r
2422                     parse_pos = 0;\r
2423                     if (oldi > next_out) {\r
2424                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2425                     }\r
2426                     break;\r
2427                   case H_GOT_UNREQ_HEADER:\r
2428                     ics_getting_history = H_GETTING_MOVES;\r
2429                     started = STARTED_MOVES_NOHIDE;\r
2430                     parse_pos = 0;\r
2431                     break;\r
2432                   case H_GOT_UNWANTED_HEADER:\r
2433                     ics_getting_history = H_FALSE;\r
2434                     break;\r
2435                 }\r
2436                 continue;\r
2437             }                           \r
2438             \r
2439             if (looking_at(buf, &i, "% ") ||\r
2440                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2441                  && looking_at(buf, &i, "}*"))) {\r
2442                 savingComment = FALSE;\r
2443                 switch (started) {\r
2444                   case STARTED_MOVES:\r
2445                   case STARTED_MOVES_NOHIDE:\r
2446                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2447                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2448                     ParseGameHistory(parse);\r
2449 #if ZIPPY\r
2450                     if (appData.zippyPlay && first.initDone) {\r
2451                         FeedMovesToProgram(&first, forwardMostMove);\r
2452                         if (gameMode == IcsPlayingWhite) {\r
2453                             if (WhiteOnMove(forwardMostMove)) {\r
2454                                 if (first.sendTime) {\r
2455                                   if (first.useColors) {\r
2456                                     SendToProgram("black\n", &first); \r
2457                                   }\r
2458                                   SendTimeRemaining(&first, TRUE);\r
2459                                 }\r
2460                                 if (first.useColors) {\r
2461                                   SendToProgram("white\ngo\n", &first);\r
2462                                 } else {\r
2463                                   SendToProgram("go\n", &first);\r
2464                                 }\r
2465                                 first.maybeThinking = TRUE;\r
2466                             } else {\r
2467                                 if (first.usePlayother) {\r
2468                                   if (first.sendTime) {\r
2469                                     SendTimeRemaining(&first, TRUE);\r
2470                                   }\r
2471                                   SendToProgram("playother\n", &first);\r
2472                                   firstMove = FALSE;\r
2473                                 } else {\r
2474                                   firstMove = TRUE;\r
2475                                 }\r
2476                             }\r
2477                         } else if (gameMode == IcsPlayingBlack) {\r
2478                             if (!WhiteOnMove(forwardMostMove)) {\r
2479                                 if (first.sendTime) {\r
2480                                   if (first.useColors) {\r
2481                                     SendToProgram("white\n", &first);\r
2482                                   }\r
2483                                   SendTimeRemaining(&first, FALSE);\r
2484                                 }\r
2485                                 if (first.useColors) {\r
2486                                   SendToProgram("black\ngo\n", &first);\r
2487                                 } else {\r
2488                                   SendToProgram("go\n", &first);\r
2489                                 }\r
2490                                 first.maybeThinking = TRUE;\r
2491                             } else {\r
2492                                 if (first.usePlayother) {\r
2493                                   if (first.sendTime) {\r
2494                                     SendTimeRemaining(&first, FALSE);\r
2495                                   }\r
2496                                   SendToProgram("playother\n", &first);\r
2497                                   firstMove = FALSE;\r
2498                                 } else {\r
2499                                   firstMove = TRUE;\r
2500                                 }\r
2501                             }\r
2502                         }                       \r
2503                     }\r
2504 #endif\r
2505                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2506                         /* Moves came from oldmoves or moves command\r
2507                            while we weren't doing anything else.\r
2508                            */\r
2509                         currentMove = forwardMostMove;\r
2510                         ClearHighlights();/*!!could figure this out*/\r
2511                         flipView = appData.flipView;\r
2512                         DrawPosition(FALSE, boards[currentMove]);\r
2513                         DisplayBothClocks();\r
2514                         sprintf(str, "%s vs. %s",\r
2515                                 gameInfo.white, gameInfo.black);\r
2516                         DisplayTitle(str);\r
2517                         gameMode = IcsIdle;\r
2518                     } else {\r
2519                         /* Moves were history of an active game */\r
2520                         if (gameInfo.resultDetails != NULL) {\r
2521                             free(gameInfo.resultDetails);\r
2522                             gameInfo.resultDetails = NULL;\r
2523                         }\r
2524                     }\r
2525                     HistorySet(parseList, backwardMostMove,\r
2526                                forwardMostMove, currentMove-1);\r
2527                     DisplayMove(currentMove - 1);\r
2528                     if (started == STARTED_MOVES) next_out = i;\r
2529                     started = STARTED_NONE;\r
2530                     ics_getting_history = H_FALSE;\r
2531                     break;\r
2532 \r
2533                   case STARTED_OBSERVE:\r
2534                     started = STARTED_NONE;\r
2535                     SendToICS(ics_prefix);\r
2536                     SendToICS("refresh\n");\r
2537                     break;\r
2538 \r
2539                   default:\r
2540                     break;\r
2541                 }\r
2542                 continue;\r
2543             }\r
2544             \r
2545             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2546                  started == STARTED_HOLDINGS ||\r
2547                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2548                 /* Accumulate characters in move list or board */\r
2549                 parse[parse_pos++] = buf[i];\r
2550             }\r
2551             \r
2552             /* Start of game messages.  Mostly we detect start of game\r
2553                when the first board image arrives.  On some versions\r
2554                of the ICS, though, we need to do a "refresh" after starting\r
2555                to observe in order to get the current board right away. */\r
2556             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2557                 started = STARTED_OBSERVE;\r
2558                 continue;\r
2559             }\r
2560 \r
2561             /* Handle auto-observe */\r
2562             if (appData.autoObserve &&\r
2563                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2564                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2565                 char *player;\r
2566                 /* Choose the player that was highlighted, if any. */\r
2567                 if (star_match[0][0] == '\033' ||\r
2568                     star_match[1][0] != '\033') {\r
2569                     player = star_match[0];\r
2570                 } else {\r
2571                     player = star_match[2];\r
2572                 }\r
2573                 sprintf(str, "%sobserve %s\n",\r
2574                         ics_prefix, StripHighlightAndTitle(player));\r
2575                 SendToICS(str);\r
2576 \r
2577                 /* Save ratings from notify string */\r
2578                 strcpy(player1Name, star_match[0]);\r
2579                 player1Rating = string_to_rating(star_match[1]);\r
2580                 strcpy(player2Name, star_match[2]);\r
2581                 player2Rating = string_to_rating(star_match[3]);\r
2582 \r
2583                 if (appData.debugMode)\r
2584                   fprintf(debugFP, \r
2585                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2586                           player1Name, player1Rating,\r
2587                           player2Name, player2Rating);\r
2588 \r
2589                 continue;\r
2590             }\r
2591 \r
2592             /* Deal with automatic examine mode after a game,\r
2593                and with IcsObserving -> IcsExamining transition */\r
2594             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2595                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2596 \r
2597                 int gamenum = atoi(star_match[0]);\r
2598                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2599                     gamenum == ics_gamenum) {\r
2600                     /* We were already playing or observing this game;\r
2601                        no need to refetch history */\r
2602                     gameMode = IcsExamining;\r
2603                     if (pausing) {\r
2604                         pauseExamForwardMostMove = forwardMostMove;\r
2605                     } else if (currentMove < forwardMostMove) {\r
2606                         ForwardInner(forwardMostMove);\r
2607                     }\r
2608                 } else {\r
2609                     /* I don't think this case really can happen */\r
2610                     SendToICS(ics_prefix);\r
2611                     SendToICS("refresh\n");\r
2612                 }\r
2613                 continue;\r
2614             }    \r
2615             \r
2616             /* Error messages */\r
2617             if (ics_user_moved) {\r
2618                 if (looking_at(buf, &i, "Illegal move") ||\r
2619                     looking_at(buf, &i, "Not a legal move") ||\r
2620                     looking_at(buf, &i, "Your king is in check") ||\r
2621                     looking_at(buf, &i, "It isn't your turn") ||\r
2622                     looking_at(buf, &i, "It is not your move")) {\r
2623                     /* Illegal move */\r
2624                     ics_user_moved = 0;\r
2625                     if (forwardMostMove > backwardMostMove) {\r
2626                         currentMove = --forwardMostMove;\r
2627                         DisplayMove(currentMove - 1); /* before DMError */\r
2628                         DisplayMoveError("Illegal move (rejected by ICS)");\r
2629                         DrawPosition(FALSE, boards[currentMove]);\r
2630                         SwitchClocks();\r
2631                         DisplayBothClocks();\r
2632                     }\r
2633                     continue;\r
2634                 }\r
2635             }\r
2636 \r
2637             if (looking_at(buf, &i, "still have time") ||\r
2638                 looking_at(buf, &i, "not out of time") ||\r
2639                 looking_at(buf, &i, "either player is out of time") ||\r
2640                 looking_at(buf, &i, "has timeseal; checking")) {\r
2641                 /* We must have called his flag a little too soon */\r
2642                 whiteFlag = blackFlag = FALSE;\r
2643                 continue;\r
2644             }\r
2645 \r
2646             if (looking_at(buf, &i, "added * seconds to") ||\r
2647                 looking_at(buf, &i, "seconds were added to")) {\r
2648                 /* Update the clocks */\r
2649                 SendToICS(ics_prefix);\r
2650                 SendToICS("refresh\n");\r
2651                 continue;\r
2652             }\r
2653 \r
2654             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2655                 ics_clock_paused = TRUE;\r
2656                 StopClocks();\r
2657                 continue;\r
2658             }\r
2659 \r
2660             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2661                 ics_clock_paused = FALSE;\r
2662                 StartClocks();\r
2663                 continue;\r
2664             }\r
2665 \r
2666             /* Grab player ratings from the Creating: message.\r
2667                Note we have to check for the special case when\r
2668                the ICS inserts things like [white] or [black]. */\r
2669             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
2670                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
2671                 /* star_matches:\r
2672                    0    player 1 name (not necessarily white)\r
2673                    1    player 1 rating\r
2674                    2    empty, white, or black (IGNORED)\r
2675                    3    player 2 name (not necessarily black)\r
2676                    4    player 2 rating\r
2677                    \r
2678                    The names/ratings are sorted out when the game\r
2679                    actually starts (below).\r
2680                 */\r
2681                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
2682                 player1Rating = string_to_rating(star_match[1]);\r
2683                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
2684                 player2Rating = string_to_rating(star_match[4]);\r
2685 \r
2686                 if (appData.debugMode)\r
2687                   fprintf(debugFP, \r
2688                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
2689                           player1Name, player1Rating,\r
2690                           player2Name, player2Rating);\r
2691 \r
2692                 continue;\r
2693             }\r
2694             \r
2695             /* Improved generic start/end-of-game messages */\r
2696             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
2697                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
2698                 /* If tkind == 0: */\r
2699                 /* star_match[0] is the game number */\r
2700                 /*           [1] is the white player's name */\r
2701                 /*           [2] is the black player's name */\r
2702                 /* For end-of-game: */\r
2703                 /*           [3] is the reason for the game end */\r
2704                 /*           [4] is a PGN end game-token, preceded by " " */\r
2705                 /* For start-of-game: */\r
2706                 /*           [3] begins with "Creating" or "Continuing" */\r
2707                 /*           [4] is " *" or empty (don't care). */\r
2708                 int gamenum = atoi(star_match[0]);\r
2709                 char *whitename, *blackname, *why, *endtoken;\r
2710                 ChessMove endtype = (ChessMove) 0;\r
2711 \r
2712                 if (tkind == 0) {\r
2713                   whitename = star_match[1];\r
2714                   blackname = star_match[2];\r
2715                   why = star_match[3];\r
2716                   endtoken = star_match[4];\r
2717                 } else {\r
2718                   whitename = star_match[1];\r
2719                   blackname = star_match[3];\r
2720                   why = star_match[5];\r
2721                   endtoken = star_match[6];\r
2722                 }\r
2723 \r
2724                 /* Game start messages */\r
2725                 if (strncmp(why, "Creating ", 9) == 0 ||\r
2726                     strncmp(why, "Continuing ", 11) == 0) {\r
2727                     gs_gamenum = gamenum;\r
2728                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
2729 #if ZIPPY\r
2730                     if (appData.zippyPlay) {\r
2731                         ZippyGameStart(whitename, blackname);\r
2732                     }\r
2733 #endif /*ZIPPY*/\r
2734                     continue;\r
2735                 }\r
2736 \r
2737                 /* Game end messages */\r
2738                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
2739                     ics_gamenum != gamenum) {\r
2740                     continue;\r
2741                 }\r
2742                 while (endtoken[0] == ' ') endtoken++;\r
2743                 switch (endtoken[0]) {\r
2744                   case '*':\r
2745                   default:\r
2746                     endtype = GameUnfinished;\r
2747                     break;\r
2748                   case '0':\r
2749                     endtype = BlackWins;\r
2750                     break;\r
2751                   case '1':\r
2752                     if (endtoken[1] == '/')\r
2753                       endtype = GameIsDrawn;\r
2754                     else\r
2755                       endtype = WhiteWins;\r
2756                     break;\r
2757                 }\r
2758                 GameEnds(endtype, why, GE_ICS);\r
2759 #if ZIPPY\r
2760                 if (appData.zippyPlay && first.initDone) {\r
2761                     ZippyGameEnd(endtype, why);\r
2762                     if (first.pr == NULL) {\r
2763                       /* Start the next process early so that we'll\r
2764                          be ready for the next challenge */\r
2765                       StartChessProgram(&first);\r
2766                     }\r
2767                     /* Send "new" early, in case this command takes\r
2768                        a long time to finish, so that we'll be ready\r
2769                        for the next challenge. */\r
2770                     Reset(TRUE, TRUE);\r
2771                 }\r
2772 #endif /*ZIPPY*/\r
2773                 continue;\r
2774             }\r
2775 \r
2776             if (looking_at(buf, &i, "Removing game * from observation") ||\r
2777                 looking_at(buf, &i, "no longer observing game *") ||\r
2778                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
2779                 if (gameMode == IcsObserving &&\r
2780                     atoi(star_match[0]) == ics_gamenum)\r
2781                   {\r
2782                       StopClocks();\r
2783                       gameMode = IcsIdle;\r
2784                       ics_gamenum = -1;\r
2785                       ics_user_moved = FALSE;\r
2786                   }\r
2787                 continue;\r
2788             }\r
2789 \r
2790             if (looking_at(buf, &i, "no longer examining game *")) {\r
2791                 if (gameMode == IcsExamining &&\r
2792                     atoi(star_match[0]) == ics_gamenum)\r
2793                   {\r
2794                       gameMode = IcsIdle;\r
2795                       ics_gamenum = -1;\r
2796                       ics_user_moved = FALSE;\r
2797                   }\r
2798                 continue;\r
2799             }\r
2800 \r
2801             /* Advance leftover_start past any newlines we find,\r
2802                so only partial lines can get reparsed */\r
2803             if (looking_at(buf, &i, "\n")) {\r
2804                 prevColor = curColor;\r
2805                 if (curColor != ColorNormal) {\r
2806                     if (oldi > next_out) {\r
2807                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2808                         next_out = oldi;\r
2809                     }\r
2810                     Colorize(ColorNormal, FALSE);\r
2811                     curColor = ColorNormal;\r
2812                 }\r
2813                 if (started == STARTED_BOARD) {\r
2814                     started = STARTED_NONE;\r
2815                     parse[parse_pos] = NULLCHAR;\r
2816                     ParseBoard12(parse);\r
2817                     ics_user_moved = 0;\r
2818 \r
2819                     /* Send premove here */\r
2820                     if (appData.premove) {\r
2821                       char str[MSG_SIZ];\r
2822                       if (currentMove == 0 &&\r
2823                           gameMode == IcsPlayingWhite &&\r
2824                           appData.premoveWhite) {\r
2825                         sprintf(str, "%s%s\n", ics_prefix,\r
2826                                 appData.premoveWhiteText);\r
2827                         if (appData.debugMode)\r
2828                           fprintf(debugFP, "Sending premove:\n");\r
2829                         SendToICS(str);\r
2830                       } else if (currentMove == 1 &&\r
2831                                  gameMode == IcsPlayingBlack &&\r
2832                                  appData.premoveBlack) {\r
2833                         sprintf(str, "%s%s\n", ics_prefix,\r
2834                                 appData.premoveBlackText);\r
2835                         if (appData.debugMode)\r
2836                           fprintf(debugFP, "Sending premove:\n");\r
2837                         SendToICS(str);\r
2838                       } else if (gotPremove) {\r
2839                         gotPremove = 0;\r
2840                         ClearPremoveHighlights();\r
2841                         if (appData.debugMode)\r
2842                           fprintf(debugFP, "Sending premove:\n");\r
2843                           UserMoveEvent(premoveFromX, premoveFromY, \r
2844                                         premoveToX, premoveToY, \r
2845                                         premovePromoChar);\r
2846                       }\r
2847                     }\r
2848 \r
2849                     /* Usually suppress following prompt */\r
2850                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
2851                         if (looking_at(buf, &i, "*% ")) {\r
2852                             savingComment = FALSE;\r
2853                         }\r
2854                     }\r
2855                     next_out = i;\r
2856                 } else if (started == STARTED_HOLDINGS) {\r
2857                     int gamenum;\r
2858                     char new_piece[MSG_SIZ];\r
2859                     started = STARTED_NONE;\r
2860                     parse[parse_pos] = NULLCHAR;\r
2861                     if (appData.debugMode)\r
2862                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
2863                                                         parse, currentMove);\r
2864                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
2865                         gamenum == ics_gamenum) {\r
2866                         if (gameInfo.variant == VariantNormal) {\r
2867                           /* [HGM] We seem to switch variant during a game!\r
2868                            * Presumably no holdings were displayed, so we have\r
2869                            * to move the position two files to the right to\r
2870                            * create room for them!\r
2871                            */\r
2872                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
2873                           /* Get a move list just to see the header, which\r
2874                              will tell us whether this is really bug or zh */\r
2875                           if (ics_getting_history == H_FALSE) {\r
2876                             ics_getting_history = H_REQUESTED;\r
2877                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
2878                             SendToICS(str);\r
2879                           }\r
2880                         }\r
2881                         new_piece[0] = NULLCHAR;\r
2882                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
2883                                &gamenum, white_holding, black_holding,\r
2884                                new_piece);\r
2885                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
2886                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
2887                         /* [HGM] copy holdings to board holdings area */\r
2888                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
2889                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
2890 #if ZIPPY\r
2891                         if (appData.zippyPlay && first.initDone) {\r
2892                             ZippyHoldings(white_holding, black_holding,\r
2893                                           new_piece);\r
2894                         }\r
2895 #endif /*ZIPPY*/\r
2896                         if (tinyLayout || smallLayout) {\r
2897                             char wh[16], bh[16];\r
2898                             PackHolding(wh, white_holding);\r
2899                             PackHolding(bh, black_holding);\r
2900                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
2901                                     gameInfo.white, gameInfo.black);\r
2902                         } else {\r
2903                             sprintf(str, "%s [%s] vs. %s [%s]",\r
2904                                     gameInfo.white, white_holding,\r
2905                                     gameInfo.black, black_holding);\r
2906                         }\r
2907 \r
2908                         DrawPosition(FALSE, boards[currentMove]);\r
2909                         DisplayTitle(str);\r
2910                     }\r
2911                     /* Suppress following prompt */\r
2912                     if (looking_at(buf, &i, "*% ")) {\r
2913                         savingComment = FALSE;\r
2914                     }\r
2915                     next_out = i;\r
2916                 }\r
2917                 continue;\r
2918             }\r
2919 \r
2920             i++;                /* skip unparsed character and loop back */\r
2921         }\r
2922         \r
2923         if (started != STARTED_MOVES && started != STARTED_BOARD &&\r
2924             started != STARTED_HOLDINGS && i > next_out) {\r
2925             SendToPlayer(&buf[next_out], i - next_out);\r
2926             next_out = i;\r
2927         }\r
2928         \r
2929         leftover_len = buf_len - leftover_start;\r
2930         /* if buffer ends with something we couldn't parse,\r
2931            reparse it after appending the next read */\r
2932         \r
2933     } else if (count == 0) {\r
2934         RemoveInputSource(isr);\r
2935         DisplayFatalError("Connection closed by ICS", 0, 0);\r
2936     } else {\r
2937         DisplayFatalError("Error reading from ICS", error, 1);\r
2938     }\r
2939 }\r
2940 \r
2941 \r
2942 /* Board style 12 looks like this:\r
2943    \r
2944    <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
2945    \r
2946  * The "<12> " is stripped before it gets to this routine.  The two\r
2947  * trailing 0's (flip state and clock ticking) are later addition, and\r
2948  * some chess servers may not have them, or may have only the first.\r
2949  * Additional trailing fields may be added in the future.  \r
2950  */\r
2951 \r
2952 #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
2953 \r
2954 #define RELATION_OBSERVING_PLAYED    0\r
2955 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
2956 #define RELATION_PLAYING_MYMOVE      1\r
2957 #define RELATION_PLAYING_NOTMYMOVE  -1\r
2958 #define RELATION_EXAMINING           2\r
2959 #define RELATION_ISOLATED_BOARD     -3\r
2960 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
2961 \r
2962 void\r
2963 ParseBoard12(string)\r
2964      char *string;\r
2965\r
2966     GameMode newGameMode;\r
2967     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;\r
2968     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;\r
2969     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
2970     char to_play, board_chars[72];\r
2971     char move_str[500], str[500], elapsed_time[500];\r
2972     char black[32], white[32];\r
2973     Board board;\r
2974     int prevMove = currentMove;\r
2975     int ticking = 2;\r
2976     ChessMove moveType;\r
2977     int fromX, fromY, toX, toY;\r
2978     char promoChar;\r
2979 \r
2980     fromX = fromY = toX = toY = -1;\r
2981     \r
2982     newGame = FALSE;\r
2983 \r
2984     if (appData.debugMode)\r
2985       fprintf(debugFP, "Parsing board: %s\n", string);\r
2986 \r
2987     move_str[0] = NULLCHAR;\r
2988     elapsed_time[0] = NULLCHAR;\r
2989     n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,\r
2990                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
2991                &gamenum, white, black, &relation, &basetime, &increment,\r
2992                &white_stren, &black_stren, &white_time, &black_time,\r
2993                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
2994                &ticking);\r
2995 \r
2996     if (n < 22) {\r
2997         sprintf(str, "Failed to parse board string:\n\"%s\"", string);\r
2998         DisplayError(str, 0);\r
2999         return;\r
3000     }\r
3001 \r
3002     /* Convert the move number to internal form */\r
3003     moveNum = (moveNum - 1) * 2;\r
3004     if (to_play == 'B') moveNum++;\r
3005     if (moveNum >= MAX_MOVES) {\r
3006       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
3007                         0, 1);\r
3008       return;\r
3009     }\r
3010     \r
3011     switch (relation) {\r
3012       case RELATION_OBSERVING_PLAYED:\r
3013       case RELATION_OBSERVING_STATIC:\r
3014         if (gamenum == -1) {\r
3015             /* Old ICC buglet */\r
3016             relation = RELATION_OBSERVING_STATIC;\r
3017         }\r
3018         newGameMode = IcsObserving;\r
3019         break;\r
3020       case RELATION_PLAYING_MYMOVE:\r
3021       case RELATION_PLAYING_NOTMYMOVE:\r
3022         newGameMode =\r
3023           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3024             IcsPlayingWhite : IcsPlayingBlack;\r
3025         break;\r
3026       case RELATION_EXAMINING:\r
3027         newGameMode = IcsExamining;\r
3028         break;\r
3029       case RELATION_ISOLATED_BOARD:\r
3030       default:\r
3031         /* Just display this board.  If user was doing something else,\r
3032            we will forget about it until the next board comes. */ \r
3033         newGameMode = IcsIdle;\r
3034         break;\r
3035       case RELATION_STARTING_POSITION:\r
3036         newGameMode = gameMode;\r
3037         break;\r
3038     }\r
3039     \r
3040     /* Modify behavior for initial board display on move listing\r
3041        of wild games.\r
3042        */\r
3043     switch (ics_getting_history) {\r
3044       case H_FALSE:\r
3045       case H_REQUESTED:\r
3046         break;\r
3047       case H_GOT_REQ_HEADER:\r
3048       case H_GOT_UNREQ_HEADER:\r
3049         /* This is the initial position of the current game */\r
3050         gamenum = ics_gamenum;\r
3051         moveNum = 0;            /* old ICS bug workaround */\r
3052         if (to_play == 'B') {\r
3053           startedFromSetupPosition = TRUE;\r
3054           blackPlaysFirst = TRUE;\r
3055           moveNum = 1;\r
3056           if (forwardMostMove == 0) forwardMostMove = 1;\r
3057           if (backwardMostMove == 0) backwardMostMove = 1;\r
3058           if (currentMove == 0) currentMove = 1;\r
3059         }\r
3060         newGameMode = gameMode;\r
3061         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3062         break;\r
3063       case H_GOT_UNWANTED_HEADER:\r
3064         /* This is an initial board that we don't want */\r
3065         return;\r
3066       case H_GETTING_MOVES:\r
3067         /* Should not happen */\r
3068         DisplayError("Error gathering move list: extra board", 0);\r
3069         ics_getting_history = H_FALSE;\r
3070         return;\r
3071     }\r
3072     \r
3073     /* Take action if this is the first board of a new game, or of a\r
3074        different game than is currently being displayed.  */\r
3075     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3076         relation == RELATION_ISOLATED_BOARD) {\r
3077         \r
3078         /* Forget the old game and get the history (if any) of the new one */\r
3079         if (gameMode != BeginningOfGame) {\r
3080           Reset(FALSE, TRUE);\r
3081         }\r
3082         newGame = TRUE;\r
3083         if (appData.autoRaiseBoard) BoardToTop();\r
3084         prevMove = -3;\r
3085         if (gamenum == -1) {\r
3086             newGameMode = IcsIdle;\r
3087         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3088                    appData.getMoveList) {\r
3089             /* Need to get game history */\r
3090             ics_getting_history = H_REQUESTED;\r
3091             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3092             SendToICS(str);\r
3093         }\r
3094         \r
3095         /* Initially flip the board to have black on the bottom if playing\r
3096            black or if the ICS flip flag is set, but let the user change\r
3097            it with the Flip View button. */\r
3098         flipView = appData.autoFlipView ? \r
3099           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3100           appData.flipView;\r
3101         \r
3102         /* Done with values from previous mode; copy in new ones */\r
3103         gameMode = newGameMode;\r
3104         ModeHighlight();\r
3105         ics_gamenum = gamenum;\r
3106         if (gamenum == gs_gamenum) {\r
3107             int klen = strlen(gs_kind);\r
3108             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3109             sprintf(str, "ICS %s", gs_kind);\r
3110             gameInfo.event = StrSave(str);\r
3111         } else {\r
3112             gameInfo.event = StrSave("ICS game");\r
3113         }\r
3114         gameInfo.site = StrSave(appData.icsHost);\r
3115         gameInfo.date = PGNDate();\r
3116         gameInfo.round = StrSave("-");\r
3117         gameInfo.white = StrSave(white);\r
3118         gameInfo.black = StrSave(black);\r
3119         timeControl = basetime * 60 * 1000;\r
3120         timeControl_2 = 0;\r
3121         timeIncrement = increment * 1000;\r
3122         movesPerSession = 0;\r
3123         gameInfo.timeControl = TimeControlTagValue();\r
3124         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3125   if (appData.debugMode) {\r
3126     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3127     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3128     setbuf(debugFP, NULL);\r
3129   }\r
3130 \r
3131         gameInfo.outOfBook = NULL;\r
3132         \r
3133         /* Do we have the ratings? */\r
3134         if (strcmp(player1Name, white) == 0 &&\r
3135             strcmp(player2Name, black) == 0) {\r
3136             if (appData.debugMode)\r
3137               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3138                       player1Rating, player2Rating);\r
3139             gameInfo.whiteRating = player1Rating;\r
3140             gameInfo.blackRating = player2Rating;\r
3141         } else if (strcmp(player2Name, white) == 0 &&\r
3142                    strcmp(player1Name, black) == 0) {\r
3143             if (appData.debugMode)\r
3144               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3145                       player2Rating, player1Rating);\r
3146             gameInfo.whiteRating = player2Rating;\r
3147             gameInfo.blackRating = player1Rating;\r
3148         }\r
3149         player1Name[0] = player2Name[0] = NULLCHAR;\r
3150 \r
3151         /* Silence shouts if requested */\r
3152         if (appData.quietPlay &&\r
3153             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3154             SendToICS(ics_prefix);\r
3155             SendToICS("set shout 0\n");\r
3156         }\r
3157     }\r
3158     \r
3159     /* Deal with midgame name changes */\r
3160     if (!newGame) {\r
3161         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3162             if (gameInfo.white) free(gameInfo.white);\r
3163             gameInfo.white = StrSave(white);\r
3164         }\r
3165         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3166             if (gameInfo.black) free(gameInfo.black);\r
3167             gameInfo.black = StrSave(black);\r
3168         }\r
3169     }\r
3170     \r
3171     /* Throw away game result if anything actually changes in examine mode */\r
3172     if (gameMode == IcsExamining && !newGame) {\r
3173         gameInfo.result = GameUnfinished;\r
3174         if (gameInfo.resultDetails != NULL) {\r
3175             free(gameInfo.resultDetails);\r
3176             gameInfo.resultDetails = NULL;\r
3177         }\r
3178     }\r
3179     \r
3180     /* In pausing && IcsExamining mode, we ignore boards coming\r
3181        in if they are in a different variation than we are. */\r
3182     if (pauseExamInvalid) return;\r
3183     if (pausing && gameMode == IcsExamining) {\r
3184         if (moveNum <= pauseExamForwardMostMove) {\r
3185             pauseExamInvalid = TRUE;\r
3186             forwardMostMove = pauseExamForwardMostMove;\r
3187             return;\r
3188         }\r
3189     }\r
3190     \r
3191     /* Parse the board */\r
3192     for (k = 0; k < 8; k++) {\r
3193       for (j = 0; j < 8; j++)\r
3194         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(7-k)*9 + j]);\r
3195       if(gameInfo.holdingsWidth > 1) {\r
3196            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3197            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3198       }\r
3199     }\r
3200     CopyBoard(boards[moveNum], board);\r
3201     if (moveNum == 0) {\r
3202         startedFromSetupPosition =\r
3203           !CompareBoards(board, initialPosition);\r
3204     }\r
3205     \r
3206     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3207         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3208         /* This was an initial position from a move list, not\r
3209            the current position */\r
3210         return;\r
3211     }\r
3212     \r
3213     /* Update currentMove and known move number limits */\r
3214     newMove = newGame || moveNum > forwardMostMove;\r
3215     if (newGame) {\r
3216         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3217         if (gameMode == IcsExamining && moveNum == 0) {\r
3218           /* Workaround for ICS limitation: we are not told the wild\r
3219              type when starting to examine a game.  But if we ask for\r
3220              the move list, the move list header will tell us */\r
3221             ics_getting_history = H_REQUESTED;\r
3222             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3223             SendToICS(str);\r
3224         }\r
3225     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3226                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3227         forwardMostMove = moveNum;\r
3228         if (!pausing || currentMove > forwardMostMove)\r
3229           currentMove = forwardMostMove;\r
3230     } else {\r
3231         /* New part of history that is not contiguous with old part */ \r
3232         if (pausing && gameMode == IcsExamining) {\r
3233             pauseExamInvalid = TRUE;\r
3234             forwardMostMove = pauseExamForwardMostMove;\r
3235             return;\r
3236         }\r
3237         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3238         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3239             ics_getting_history = H_REQUESTED;\r
3240             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3241             SendToICS(str);\r
3242         }\r
3243     }\r
3244     \r
3245     /* Update the clocks */\r
3246     if (strchr(elapsed_time, '.')) {\r
3247       /* Time is in ms */\r
3248       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3249       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3250     } else {\r
3251       /* Time is in seconds */\r
3252       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3253       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3254     }\r
3255       \r
3256 \r
3257 #if ZIPPY\r
3258     if (appData.zippyPlay && newGame &&\r
3259         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3260         gameMode != IcsExamining)\r
3261       ZippyFirstBoard(moveNum, basetime, increment);\r
3262 #endif\r
3263     \r
3264     /* Put the move on the move list, first converting\r
3265        to canonical algebraic form. */\r
3266     if (moveNum > 0) {\r
3267   if (appData.debugMode) {\r
3268     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3269     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3270     setbuf(debugFP, NULL);\r
3271   }\r
3272         if (moveNum <= backwardMostMove) {\r
3273             /* We don't know what the board looked like before\r
3274                this move.  Punt. */\r
3275             strcpy(parseList[moveNum - 1], move_str);\r
3276             strcat(parseList[moveNum - 1], " ");\r
3277             strcat(parseList[moveNum - 1], elapsed_time);\r
3278             moveList[moveNum - 1][0] = NULLCHAR;\r
3279         } else if (ParseOneMove(move_str, moveNum - 1, &moveType,\r
3280                                 &fromX, &fromY, &toX, &toY, &promoChar)) {\r
3281             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3282                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3283                                      fromY, fromX, toY, toX, promoChar,\r
3284                                      parseList[moveNum-1]);\r
3285             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3286                              castlingRights[moveNum]) ) {\r
3287               case MT_NONE:\r
3288               case MT_STALEMATE:\r
3289               default:\r
3290                 break;\r
3291               case MT_CHECK:\r
3292                 if(gameInfo.variant != VariantShogi)\r
3293                     strcat(parseList[moveNum - 1], "+");\r
3294                 break;\r
3295               case MT_CHECKMATE:\r
3296                 strcat(parseList[moveNum - 1], "#");\r
3297                 break;\r
3298             }\r
3299             strcat(parseList[moveNum - 1], " ");\r
3300             strcat(parseList[moveNum - 1], elapsed_time);\r
3301             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3302             strcpy(moveList[moveNum - 1], currentMoveString);\r
3303             strcat(moveList[moveNum - 1], "\n");\r
3304         } else if (strcmp(move_str, "none") == 0) {\r
3305             /* Again, we don't know what the board looked like;\r
3306                this is really the start of the game. */\r
3307             parseList[moveNum - 1][0] = NULLCHAR;\r
3308             moveList[moveNum - 1][0] = NULLCHAR;\r
3309             backwardMostMove = moveNum;\r
3310             startedFromSetupPosition = TRUE;\r
3311             fromX = fromY = toX = toY = -1;\r
3312         } else {\r
3313             /* Move from ICS was illegal!?  Punt. */\r
3314 #if 0\r
3315             if (appData.testLegality && appData.debugMode) {\r
3316                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3317                 DisplayError(str, 0);\r
3318             }\r
3319 #endif\r
3320             strcpy(parseList[moveNum - 1], move_str);\r
3321             strcat(parseList[moveNum - 1], " ");\r
3322             strcat(parseList[moveNum - 1], elapsed_time);\r
3323             moveList[moveNum - 1][0] = NULLCHAR;\r
3324             fromX = fromY = toX = toY = -1;\r
3325         }\r
3326   if (appData.debugMode) {\r
3327     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3328     setbuf(debugFP, NULL);\r
3329   }\r
3330 \r
3331 #if ZIPPY\r
3332         /* Send move to chess program (BEFORE animating it). */\r
3333         if (appData.zippyPlay && !newGame && newMove && \r
3334            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3335 \r
3336             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3337                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3338                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3339                     sprintf(str, "Couldn't parse move \"%s\" from ICS",\r
3340                             move_str);\r
3341                     DisplayError(str, 0);\r
3342                 } else {\r
3343                     if (first.sendTime) {\r
3344                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3345                     }\r
3346                     SendMoveToProgram(moveNum - 1, &first);\r
3347                     if (firstMove) {\r
3348                         firstMove = FALSE;\r
3349                         if (first.useColors) {\r
3350                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3351                                         "white\ngo\n" :\r
3352                                         "black\ngo\n", &first);\r
3353                         } else {\r
3354                           SendToProgram("go\n", &first);\r
3355                         }\r
3356                         first.maybeThinking = TRUE;\r
3357                     }\r
3358                 }\r
3359             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3360               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3361                 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);\r
3362                 DisplayError(str, 0);\r
3363               } else {\r
3364                 SendMoveToProgram(moveNum - 1, &first);\r
3365               }\r
3366             }\r
3367         }\r
3368 #endif\r
3369     }\r
3370 \r
3371     if (moveNum > 0 && !gotPremove) {\r
3372         /* If move comes from a remote source, animate it.  If it\r
3373            isn't remote, it will have already been animated. */\r
3374         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3375             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3376         }\r
3377         if (!pausing && appData.highlightLastMove) {\r
3378             SetHighlights(fromX, fromY, toX, toY);\r
3379         }\r
3380     }\r
3381     \r
3382     /* Start the clocks */\r
3383     whiteFlag = blackFlag = FALSE;\r
3384     appData.clockMode = !(basetime == 0 && increment == 0);\r
3385     if (ticking == 0) {\r
3386       ics_clock_paused = TRUE;\r
3387       StopClocks();\r
3388     } else if (ticking == 1) {\r
3389       ics_clock_paused = FALSE;\r
3390     }\r
3391     if (gameMode == IcsIdle ||\r
3392         relation == RELATION_OBSERVING_STATIC ||\r
3393         relation == RELATION_EXAMINING ||\r
3394         ics_clock_paused)\r
3395       DisplayBothClocks();\r
3396     else\r
3397       StartClocks();\r
3398     \r
3399     /* Display opponents and material strengths */\r
3400     if (gameInfo.variant != VariantBughouse &&\r
3401         gameInfo.variant != VariantCrazyhouse) {\r
3402         if (tinyLayout || smallLayout) {\r
3403             sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3404                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3405                     basetime, increment);\r
3406         } else {\r
3407             sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3408                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3409                     basetime, increment);\r
3410         }\r
3411         DisplayTitle(str);\r
3412     }\r
3413 \r
3414    \r
3415     /* Display the board */\r
3416     if (!pausing) {\r
3417       \r
3418       if (appData.premove)\r
3419           if (!gotPremove || \r
3420              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3421              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3422               ClearPremoveHighlights();\r
3423 \r
3424       DrawPosition(FALSE, boards[currentMove]);\r
3425       DisplayMove(moveNum - 1);\r
3426       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3427         RingBell();\r
3428     }\r
3429 \r
3430     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3431 }\r
3432 \r
3433 void\r
3434 GetMoveListEvent()\r
3435 {\r
3436     char buf[MSG_SIZ];\r
3437     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3438         ics_getting_history = H_REQUESTED;\r
3439         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3440         SendToICS(buf);\r
3441     }\r
3442 }\r
3443 \r
3444 void\r
3445 AnalysisPeriodicEvent(force)\r
3446      int force;\r
3447 {\r
3448     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3449          && !force) || !appData.periodicUpdates)\r
3450       return;\r
3451 \r
3452     /* Send . command to Crafty to collect stats */\r
3453     SendToProgram(".\n", &first);\r
3454 \r
3455     /* Don't send another until we get a response (this makes\r
3456        us stop sending to old Crafty's which don't understand\r
3457        the "." command (sending illegal cmds resets node count & time,\r
3458        which looks bad)) */\r
3459     programStats.ok_to_send = 0;\r
3460 }\r
3461 \r
3462 void\r
3463 SendMoveToProgram(moveNum, cps)\r
3464      int moveNum;\r
3465      ChessProgramState *cps;\r
3466 {\r
3467     char buf[MSG_SIZ];\r
3468     if (cps->useUsermove) {\r
3469       SendToProgram("usermove ", cps);\r
3470     }\r
3471     if (cps->useSAN) {\r
3472       char *space;\r
3473       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3474         int len = space - parseList[moveNum];\r
3475         memcpy(buf, parseList[moveNum], len);\r
3476         buf[len++] = '\n';\r
3477         buf[len] = NULLCHAR;\r
3478       } else {\r
3479         sprintf(buf, "%s\n", parseList[moveNum]);\r
3480       }\r
3481       /* [HGM] decrement all digits to code ranks starting from 0 */\r
3482       if(BOARD_HEIGHT>9) {\r
3483           char *p = buf;\r
3484           while(*p) { if(*p < 'A') (*p)--; p++; }\r
3485       }\r
3486       SendToProgram(buf, cps);\r
3487     } else {\r
3488       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3489        * the engine. It would be nice to have a better way to identify castle \r
3490        * moves here. */\r
3491       if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {\r
3492         int fromX = moveList[moveNum][0] - AAA; \r
3493         int fromY = moveList[moveNum][1] - ONE;\r
3494         int toX = moveList[moveNum][2] - AAA; \r
3495         int toY = moveList[moveNum][3] - ONE;\r
3496         if((boards[currentMove][fromY][fromX] == WhiteKing \r
3497             && boards[currentMove][toY][toX] == WhiteRook)\r
3498            || (boards[currentMove][fromY][fromX] == BlackKing \r
3499                && boards[currentMove][toY][toX] == BlackRook)) {\r
3500           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3501           else SendToProgram("O-O-O\n", cps);\r
3502         }\r
3503         else SendToProgram(moveList[moveNum], cps);\r
3504       }\r
3505       else SendToProgram(moveList[moveNum], cps);\r
3506       /* End of additions by Tord */\r
3507     }\r
3508 }\r
3509 \r
3510 void\r
3511 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
3512      ChessMove moveType;\r
3513      int fromX, fromY, toX, toY;\r
3514 {\r
3515     char user_move[MSG_SIZ];\r
3516 \r
3517     switch (moveType) {\r
3518       default:\r
3519         sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",\r
3520                 (int)moveType, fromX, fromY, toX, toY);\r
3521         DisplayError(user_move + strlen("say "), 0);\r
3522         break;\r
3523       case WhiteKingSideCastle:\r
3524       case BlackKingSideCastle:\r
3525       case WhiteQueenSideCastleWild:\r
3526       case BlackQueenSideCastleWild:\r
3527       /* PUSH Fabien */\r
3528       case WhiteHSideCastleFR:\r
3529       case BlackHSideCastleFR:\r
3530       /* POP Fabien */\r
3531         sprintf(user_move, "o-o\n");\r
3532         break;\r
3533       case WhiteQueenSideCastle:\r
3534       case BlackQueenSideCastle:\r
3535       case WhiteKingSideCastleWild:\r
3536       case BlackKingSideCastleWild:\r
3537       /* PUSH Fabien */\r
3538       case WhiteASideCastleFR:\r
3539       case BlackASideCastleFR:\r
3540       /* POP Fabien */\r
3541         sprintf(user_move, "o-o-o\n");\r
3542         break;\r
3543       case WhitePromotionQueen:\r
3544       case BlackPromotionQueen:\r
3545       case WhitePromotionRook:\r
3546       case BlackPromotionRook:\r
3547       case WhitePromotionBishop:\r
3548       case BlackPromotionBishop:\r
3549       case WhitePromotionKnight:\r
3550       case BlackPromotionKnight:\r
3551       case WhitePromotionKing:\r
3552       case BlackPromotionKing:\r
3553 #ifdef FAIRY\r
3554       case WhitePromotionChancellor:\r
3555       case BlackPromotionChancellor:\r
3556       case WhitePromotionArchbishop:\r
3557       case BlackPromotionArchbishop:\r
3558 #endif\r
3559         sprintf(user_move, "%c%c%c%c=%c\n",\r
3560                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
3561                 PieceToChar(PromoPiece(moveType)));\r
3562         break;\r
3563       case WhiteDrop:\r
3564       case BlackDrop:\r
3565         sprintf(user_move, "%c@%c%c\n",\r
3566                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
3567                 AAA + toX, ONE + toY);\r
3568         break;\r
3569       case NormalMove:\r
3570       case WhiteCapturesEnPassant:\r
3571       case BlackCapturesEnPassant:\r
3572       case IllegalMove:  /* could be a variant we don't quite understand */\r
3573         sprintf(user_move, "%c%c%c%c\n",\r
3574                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
3575         break;\r
3576     }\r
3577     SendToICS(user_move);\r
3578 }\r
3579 \r
3580 void\r
3581 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
3582      int rf, ff, rt, ft;\r
3583      char promoChar;\r
3584      char move[7];\r
3585 {\r
3586     if (rf == DROP_RANK) {\r
3587         sprintf(move, "%c@%c%c\n",\r
3588                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
3589     } else {\r
3590         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
3591             sprintf(move, "%c%c%c%c\n",\r
3592                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
3593         } else {\r
3594             sprintf(move, "%c%c%c%c%c\n",\r
3595                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
3596         }\r
3597     }\r
3598     AlphaRank(move, 4);\r
3599 }\r
3600 \r
3601 void\r
3602 ProcessICSInitScript(f)\r
3603      FILE *f;\r
3604 {\r
3605     char buf[MSG_SIZ];\r
3606 \r
3607     while (fgets(buf, MSG_SIZ, f)) {\r
3608         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
3609     }\r
3610 \r
3611     fclose(f);\r
3612 }\r
3613 \r
3614 \r
3615 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
3616 void\r
3617 AlphaRank(char *move, int n)\r
3618 {\r
3619     char *p = move, c;\r
3620 \r
3621     if( !appData.alphaRank ) return;\r
3622 \r
3623     while(c = *p) {\r
3624         if(c>='0' && c<='9') *p += AAA-ONE; else\r
3625         if(c>='a' && c<'x') *p -= AAA-ONE;\r
3626         p++;\r
3627         if(--n < 1) break;\r
3628     }\r
3629 }\r
3630 \r
3631 /* Parser for moves from gnuchess, ICS, or user typein box */\r
3632 Boolean\r
3633 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
3634      char *move;\r
3635      int moveNum;\r
3636      ChessMove *moveType;\r
3637      int *fromX, *fromY, *toX, *toY;\r
3638      char *promoChar;\r
3639 {       \r
3640     if (appData.debugMode) {\r
3641         fprintf(debugFP, "move to parse: %s\n", move);\r
3642     }\r
3643     AlphaRank(move, 10);\r
3644     *moveType = yylexstr(moveNum, move);\r
3645 \r
3646     switch (*moveType) {\r
3647 #ifdef FAIRY\r
3648       case WhitePromotionChancellor:\r
3649       case BlackPromotionChancellor:\r
3650       case WhitePromotionArchbishop:\r
3651       case BlackPromotionArchbishop:\r
3652 #endif\r
3653       case WhitePromotionQueen:\r
3654       case BlackPromotionQueen:\r
3655       case WhitePromotionRook:\r
3656       case BlackPromotionRook:\r
3657       case WhitePromotionBishop:\r
3658       case BlackPromotionBishop:\r
3659       case WhitePromotionKnight:\r
3660       case BlackPromotionKnight:\r
3661       case WhitePromotionKing:\r
3662       case BlackPromotionKing:\r
3663       case NormalMove:\r
3664       case WhiteCapturesEnPassant:\r
3665       case BlackCapturesEnPassant:\r
3666       case WhiteKingSideCastle:\r
3667       case WhiteQueenSideCastle:\r
3668       case BlackKingSideCastle:\r
3669       case BlackQueenSideCastle:\r
3670       case WhiteKingSideCastleWild:\r
3671       case WhiteQueenSideCastleWild:\r
3672       case BlackKingSideCastleWild:\r
3673       case BlackQueenSideCastleWild:\r
3674       /* Code added by Tord: */\r
3675       case WhiteHSideCastleFR:\r
3676       case WhiteASideCastleFR:\r
3677       case BlackHSideCastleFR:\r
3678       case BlackASideCastleFR:\r
3679       /* End of code added by Tord */\r
3680       case IllegalMove:         /* bug or odd chess variant */\r
3681         *fromX = currentMoveString[0] - AAA;\r
3682         *fromY = currentMoveString[1] - ONE;\r
3683         *toX = currentMoveString[2] - AAA;\r
3684         *toY = currentMoveString[3] - ONE;\r
3685         *promoChar = currentMoveString[4];\r
3686         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
3687             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
3688             *fromX = *fromY = *toX = *toY = 0;\r
3689             return FALSE;\r
3690         }\r
3691         if (appData.testLegality) {\r
3692           return (*moveType != IllegalMove);\r
3693         } else {\r
3694           return !(fromX == fromY && toX == toY);\r
3695         }\r
3696 \r
3697       case WhiteDrop:\r
3698       case BlackDrop:\r
3699         *fromX = *moveType == WhiteDrop ?\r
3700           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
3701         (int) CharToPiece(ToLower(currentMoveString[0]));\r
3702         *fromY = DROP_RANK;\r
3703         *toX = currentMoveString[2] - AAA;\r
3704         *toY = currentMoveString[3] - ONE;\r
3705         *promoChar = NULLCHAR;\r
3706         return TRUE;\r
3707 \r
3708       case AmbiguousMove:\r
3709       case ImpossibleMove:\r
3710       case (ChessMove) 0:       /* end of file */\r
3711       case ElapsedTime:\r
3712       case Comment:\r
3713       case PGNTag:\r
3714       case NAG:\r
3715       case WhiteWins:\r
3716       case BlackWins:\r
3717       case GameIsDrawn:\r
3718       default:\r
3719         /* bug? */\r
3720         *fromX = *fromY = *toX = *toY = 0;\r
3721         *promoChar = NULLCHAR;\r
3722         return FALSE;\r
3723     }\r
3724 }\r
3725 \r
3726 /* [AS] FRC game initialization */\r
3727 static int FindEmptySquare( Board board, int n )\r
3728 {\r
3729     int i = 0;\r
3730 \r
3731     while( 1 ) {\r
3732         while( board[0][i] != EmptySquare ) i++;\r
3733         if( n == 0 )\r
3734             break;\r
3735         n--;\r
3736         i++;\r
3737     }\r
3738 \r
3739     return i;\r
3740 }\r
3741 \r
3742 static void ShuffleFRC( Board board )\r
3743 {\r
3744     int i;\r
3745 \r
3746     srand( time(0) );\r
3747     \r
3748     for( i=0; i<8; i++ ) {\r
3749         board[0][i] = EmptySquare;\r
3750     }\r
3751 \r
3752     board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */\r
3753     board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */\r
3754     board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;\r
3755     board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;\r
3756     board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;\r
3757     board[0][FindEmptySquare(board, 0)] = WhiteRook;\r
3758     board[0][FindEmptySquare(board, 0)] = WhiteKing;\r
3759     board[0][FindEmptySquare(board, 0)] = WhiteRook;\r
3760 \r
3761     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
3762         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
3763     }\r
3764 }\r
3765 \r
3766 static unsigned char FRC_KnightTable[10] = {\r
3767     0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33\r
3768 };\r
3769 \r
3770 static void SetupFRC( Board board, int pos_index )\r
3771 {\r
3772     int i;\r
3773     unsigned char knights;\r
3774 \r
3775     /* Bring the position index into a safe range (just in case...) */\r
3776     if( pos_index < 0 ) pos_index = 0;\r
3777 \r
3778     pos_index %= 960;\r
3779 \r
3780     /* Clear the board */\r
3781     for( i=0; i<8; i++ ) {\r
3782         board[0][i] = EmptySquare;\r
3783     }\r
3784 \r
3785     /* Place bishops and queen */\r
3786     board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */\r
3787     pos_index /= 4;\r
3788     \r
3789     board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */\r
3790     pos_index /= 4;\r
3791 \r
3792     board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;\r
3793     pos_index /= 6;\r
3794 \r
3795     /* Place knigths */\r
3796     knights = FRC_KnightTable[ pos_index ];\r
3797 \r
3798     board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;\r
3799     board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;\r
3800 \r
3801     /* Place rooks and king */\r
3802     board[0][ FindEmptySquare(board, 0) ] = WhiteRook;\r
3803     board[0][ FindEmptySquare(board, 0) ] = WhiteKing;\r
3804     board[0][ FindEmptySquare(board, 0) ] = WhiteRook;\r
3805 \r
3806     /* Mirror piece placement for black */\r
3807     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
3808         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
3809     }\r
3810 }\r
3811 \r
3812 BOOL SetCharTable( char *table, const char * map )\r
3813 /* [HGM] moved here from winboard.c because of its general usefulness */\r
3814 /*       Basically a safe strcpy that uses the last character as King */\r
3815 {\r
3816     BOOL result = FALSE; int NrPieces;\r
3817 \r
3818     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
3819                     && NrPieces >= 12 && !(NrPieces&1)) {\r
3820         int i; /* [HGM] Accept even length from 12 to 34 */\r
3821 \r
3822         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
3823         for( i=0; i<NrPieces/2-1; i++ ) {\r
3824             table[i] = map[i];\r
3825             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
3826         }\r
3827         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
3828         table[(int) BlackKing]  = map[NrPieces-1];\r
3829 \r
3830         result = TRUE;\r
3831     }\r
3832 \r
3833     return result;\r
3834 }\r
3835 \r
3836 void\r
3837 InitPosition(redraw)\r
3838      int redraw;\r
3839 {\r
3840     ChessSquare (* pieces)[BOARD_SIZE];\r
3841     int i, j, pawnRow, overrule,\r
3842     oldx = gameInfo.boardWidth,\r
3843     oldy = gameInfo.boardHeight,\r
3844     oldh = gameInfo.holdingsWidth,\r
3845     oldv = gameInfo.variant;\r
3846 \r
3847     currentMove = forwardMostMove = backwardMostMove = 0;\r
3848 \r
3849     /* [AS] Initialize pv info list [HGM] and game status */\r
3850     {\r
3851         for( i=0; i<MAX_MOVES; i++ ) {\r
3852             pvInfoList[i].depth = 0;\r
3853             epStatus[i]=EP_NONE;\r
3854             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
3855         }\r
3856 \r
3857         initialRulePlies = 0; /* 50-move counter start */\r
3858     }\r
3859 \r
3860     \r
3861     /* [HGM] logic here is completely changed. In stead of full positions */\r
3862     /* the initialized data only consist of the two backranks. The switch */\r
3863     /* selects which one we will use, which is than copied to the Board   */\r
3864     /* initialPosition, which for the rest is initialized by Pawns and    */\r
3865     /* empty squares. This initial position is then copied to boards[0],  */\r
3866     /* possibly after shuffling, so that it remains available.            */\r
3867 \r
3868     gameInfo.holdingsWidth = 0; /* default board sizes */\r
3869     gameInfo.boardWidth    = 8;\r
3870     gameInfo.boardHeight   = 8;\r
3871     gameInfo.holdingsSize  = 0;\r
3872     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
3873     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
3874     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
3875 \r
3876     switch (gameInfo.variant) {\r
3877     default:\r
3878       pieces = FIDEArray;\r
3879       break;\r
3880     case VariantShatranj:\r
3881       pieces = ShatranjArray;\r
3882       nrCastlingRights = 0;\r
3883       break;\r
3884     case VariantTwoKings:\r
3885       pieces = twoKingsArray;\r
3886       nrCastlingRights = 8;                 /* add rights for second King */\r
3887       castlingRights[0][6] = initialRights[2] = 5;\r
3888       castlingRights[0][7] = initialRights[5] = 5;\r
3889       castlingRank[6] = 0;\r
3890       castlingRank[6] = BOARD_HEIGHT-1;\r
3891       startedFromSetupPosition = TRUE;\r
3892       break;\r
3893     case VariantCapablanca:\r
3894       pieces = CapablancaArray;\r
3895       gameInfo.boardWidth = 10;\r
3896       SetCharTable(pieceToChar, "PNBRQ.......AC..Kpnbrq.......ac..k"); \r
3897       break;\r
3898     case VariantGothic:\r
3899       pieces = GothicArray;\r
3900       gameInfo.boardWidth = 10;\r
3901       SetCharTable(pieceToChar, "PNBRQ.......AC..Kpnbrq.......ac..k"); \r
3902       break;\r
3903     case VariantXiangqi:\r
3904       pieces = XiangqiArray;\r
3905       gameInfo.boardWidth  = 9;\r
3906       gameInfo.boardHeight = 10;\r
3907       nrCastlingRights = 0;\r
3908       SetCharTable(pieceToChar, "PH.R.AKE.C.......ph.r.ake.c......."); \r
3909       break;\r
3910     case VariantShogi:\r
3911       pieces = ShogiArray;\r
3912       gameInfo.boardWidth  = 9;\r
3913       gameInfo.boardHeight = 9;\r
3914       gameInfo.holdingsSize = 7;\r
3915       nrCastlingRights = 0;\r
3916       SetCharTable(pieceToChar, "PNBRLSG.........Kpnbrlsg.........k"); \r
3917       break;\r
3918     case VariantShowgi:\r
3919       pieces = ShogiArray;\r
3920       gameInfo.boardWidth  = 9;\r
3921       gameInfo.boardHeight = 9;\r
3922       gameInfo.holdingsSize = 7;\r
3923       nrCastlingRights = 0;\r
3924       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
3925       SetCharTable(pieceToChar, "PNBRQFWEMOUHACG.Kpnbrlsgpnbrls...k"); \r
3926       break;\r
3927     case VariantCourier:\r
3928       pieces = CourierArray;\r
3929       gameInfo.boardWidth  = 12;\r
3930       nrCastlingRights = 0;\r
3931       SetCharTable(pieceToChar, "PNBR.FWEM.......Kpnbr.fwem.......k"); \r
3932       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
3933       break;\r
3934     case VariantKnightmate:\r
3935       pieces = KnightmateArray;\r
3936       strcpy(pieceToChar, "P.BRQ...M.K......p.brq...m.k......"); \r
3937       break;\r
3938     case VariantFairy:\r
3939       pieces = fairyArray;\r
3940       SetCharTable(pieceToChar, "PNBRQFWEMOUHACGSKpnbrqfwemouhacgsk"); \r
3941       startedFromSetupPosition = TRUE;\r
3942       break;\r
3943     case VariantCrazyhouse:\r
3944     case VariantBughouse:\r
3945       pieces = FIDEArray;\r
3946       gameInfo.holdingsSize = 5;\r
3947       break;\r
3948     case VariantWildCastle:\r
3949       pieces = FIDEArray;\r
3950       /* !!?shuffle with kings guaranteed to be on d or e file */\r
3951       break;\r
3952     case VariantNoCastle:\r
3953       pieces = FIDEArray;\r
3954       nrCastlingRights = 0;\r
3955       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
3956       /* !!?unconstrained back-rank shuffle */\r
3957       break;\r
3958     }\r
3959 \r
3960     overrule = 0;\r
3961     if(appData.NrFiles >= 0) {\r
3962         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
3963         gameInfo.boardWidth = appData.NrFiles;\r
3964     }\r
3965     if(appData.NrRanks >= 0) {\r
3966         gameInfo.boardHeight = appData.NrRanks;\r
3967     }\r
3968     if(appData.holdingsSize >= 0) {\r
3969         i = appData.holdingsSize;\r
3970         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
3971         gameInfo.holdingsSize = i;\r
3972     }\r
3973     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
3974     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
3975         DisplayFatalError("Recompile to support this BOARD_SIZE!", 0, 2);\r
3976 \r
3977     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
3978     if(pawnRow < 1) pawnRow = 1;\r
3979 \r
3980     /* User pieceToChar list overrules defaults */\r
3981     if(appData.pieceToCharTable != NULL)\r
3982         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
3983 \r
3984     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
3985 \r
3986         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
3987             s = (ChessSquare) 0; /* account holding counts in guard band */\r
3988         for( i=0; i<BOARD_HEIGHT; i++ )\r
3989             initialPosition[i][j] = s;\r
3990 \r
3991         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
3992         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
3993         initialPosition[pawnRow][j] = WhitePawn;\r
3994         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
3995         if(gameInfo.variant == VariantXiangqi) {\r
3996             if(j&1) {\r
3997                 initialPosition[pawnRow][j] = \r
3998                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
3999                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4000                    initialPosition[2][j] = WhiteCannon;\r
4001                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4002                 }\r
4003             }\r
4004         }\r
4005         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4006     }\r
4007     if( (gameInfo.variant == VariantShogi\r
4008        ||gameInfo.variant == VariantShowgi\r
4009                                          ) && !overrule ) {\r
4010             j=BOARD_LEFT+1;\r
4011             initialPosition[1][j] = WhiteBishop;\r
4012             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4013             j=BOARD_RGHT-2;\r
4014             initialPosition[1][j] = WhiteRook;\r
4015             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4016     }\r
4017 \r
4018     if( nrCastlingRights == -1) {\r
4019         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4020         /*       This sets default castling rights from none to normal corners   */\r
4021         /* Variants with other castling rights must set them themselves above    */\r
4022         nrCastlingRights = 6;\r
4023        \r
4024         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4025         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4026         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4027         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4028         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4029         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4030 \r
4031         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4032         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4033      }\r
4034 \r
4035     if(gameInfo.variant == VariantFischeRandom) {\r
4036       if( appData.defaultFrcPosition < 0 ) {\r
4037         ShuffleFRC( initialPosition );\r
4038       }\r
4039       else {\r
4040         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4041       }\r
4042     }\r
4043 \r
4044     CopyBoard(boards[0], initialPosition);\r
4045 \r
4046     if(oldx != gameInfo.boardWidth ||\r
4047        oldy != gameInfo.boardHeight ||\r
4048        oldh != gameInfo.holdingsWidth\r
4049 #ifdef GOTHIC\r
4050        || oldv == VariantGothic ||\r
4051        gameInfo.variant == VariantGothic\r
4052 #endif\r
4053                                          )\r
4054             InitDrawingSizes(-2 ,0);\r
4055 \r
4056     if (redraw)\r
4057       DrawPosition(TRUE, boards[currentMove]);\r
4058 }\r
4059 \r
4060 void\r
4061 SendBoard(cps, moveNum)\r
4062      ChessProgramState *cps;\r
4063      int moveNum;\r
4064 {\r
4065     char message[MSG_SIZ];\r
4066     \r
4067     if (cps->useSetboard) {\r
4068       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
4069       sprintf(message, "setboard %s\n", fen);\r
4070       SendToProgram(message, cps);\r
4071       free(fen);\r
4072 \r
4073     } else {\r
4074       ChessSquare *bp;\r
4075       int i, j;\r
4076       /* Kludge to set black to move, avoiding the troublesome and now\r
4077        * deprecated "black" command.\r
4078        */\r
4079       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4080 \r
4081       SendToProgram("edit\n", cps);\r
4082       SendToProgram("#\n", cps);\r
4083       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4084         bp = &boards[moveNum][i][0];\r
4085         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4086           if ((int) *bp < (int) BlackPawn) {\r
4087             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4088                     AAA + j, ONE + i);\r
4089             SendToProgram(message, cps);\r
4090           }\r
4091         }\r
4092       }\r
4093     \r
4094       SendToProgram("c\n", cps);\r
4095       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4096         bp = &boards[moveNum][i][0];\r
4097         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4098           if (((int) *bp != (int) EmptySquare)\r
4099               && ((int) *bp >= (int) BlackPawn)) {\r
4100             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4101                     AAA + j, ONE + i);\r
4102             SendToProgram(message, cps);\r
4103           }\r
4104         }\r
4105       }\r
4106     \r
4107       SendToProgram(".\n", cps);\r
4108     }\r
4109 }\r
4110 \r
4111 int\r
4112 IsPromotion(fromX, fromY, toX, toY)\r
4113      int fromX, fromY, toX, toY;\r
4114 {\r
4115     /* [HGM] add Shogi promotions */\r
4116     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4117     ChessSquare piece;\r
4118 \r
4119     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4120       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4121    /* [HGM] Note to self: line above also weeds out drops */\r
4122     piece = boards[currentMove][fromY][fromX];\r
4123     if(gameInfo.variant == VariantShogi) {\r
4124         promotionZoneSize = 3;\r
4125         highestPromotingPiece = (int)WhiteKing;\r
4126         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4127            and if in normal chess we then allow promotion to King, why not\r
4128            allow promotion of other piece in Shogi?                         */\r
4129     }\r
4130     if((int)piece >= BlackPawn) {\r
4131         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4132              return FALSE;\r
4133         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4134     } else {\r
4135         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4136            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4137     }\r
4138     return ( (int)piece <= highestPromotingPiece );\r
4139 }\r
4140 \r
4141 int\r
4142 InPalace(row, column)\r
4143      int row, column;\r
4144 {   /* [HGM] for Xiangqi */\r
4145     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4146          column < (BOARD_WIDTH + 4)/2 &&\r
4147          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4148     return FALSE;\r
4149 }\r
4150 \r
4151 int\r
4152 PieceForSquare (x, y)\r
4153      int x;\r
4154      int y;\r
4155 {\r
4156   if (x < BOARD_LEFT || x >= BOARD_RGHT || y < 0 || y >= BOARD_HEIGHT)\r
4157      return -1;\r
4158   else\r
4159      return boards[currentMove][y][x];\r
4160 }\r
4161 \r
4162 int\r
4163 OKToStartUserMove(x, y)\r
4164      int x, y;\r
4165 {\r
4166     ChessSquare from_piece;\r
4167     int white_piece;\r
4168 \r
4169     if (matchMode) return FALSE;\r
4170     if (gameMode == EditPosition) return TRUE;\r
4171 \r
4172     if (x >= 0 && y >= 0)\r
4173       from_piece = boards[currentMove][y][x];\r
4174     else\r
4175       from_piece = EmptySquare;\r
4176 \r
4177     if (from_piece == EmptySquare) return FALSE;\r
4178 \r
4179     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4180       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4181 \r
4182     switch (gameMode) {\r
4183       case PlayFromGameFile:\r
4184       case AnalyzeFile:\r
4185       case TwoMachinesPlay:\r
4186       case EndOfGame:\r
4187         return FALSE;\r
4188 \r
4189       case IcsObserving:\r
4190       case IcsIdle:\r
4191         return FALSE;\r
4192 \r
4193       case MachinePlaysWhite:\r
4194       case IcsPlayingBlack:\r
4195         if (appData.zippyPlay) return FALSE;\r
4196         if (white_piece) {\r
4197             DisplayMoveError("You are playing Black");\r
4198             return FALSE;\r
4199         }\r
4200         break;\r
4201 \r
4202       case MachinePlaysBlack:\r
4203       case IcsPlayingWhite:\r
4204         if (appData.zippyPlay) return FALSE;\r
4205         if (!white_piece) {\r
4206             DisplayMoveError("You are playing White");\r
4207             return FALSE;\r
4208         }\r
4209         break;\r
4210 \r
4211       case EditGame:\r
4212         if (!white_piece && WhiteOnMove(currentMove)) {\r
4213             DisplayMoveError("It is White's turn");\r
4214             return FALSE;\r
4215         }           \r
4216         if (white_piece && !WhiteOnMove(currentMove)) {\r
4217             DisplayMoveError("It is Black's turn");\r
4218             return FALSE;\r
4219         }           \r
4220         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
4221             /* Editing correspondence game history */\r
4222             /* Could disallow this or prompt for confirmation */\r
4223             cmailOldMove = -1;\r
4224         }\r
4225         if (currentMove < forwardMostMove) {\r
4226             /* Discarding moves */\r
4227             /* Could prompt for confirmation here,\r
4228                but I don't think that's such a good idea */\r
4229             forwardMostMove = currentMove;\r
4230         }\r
4231         break;\r
4232 \r
4233       case BeginningOfGame:\r
4234         if (appData.icsActive) return FALSE;\r
4235         if (!appData.noChessProgram) {\r
4236             if (!white_piece) {\r
4237                 DisplayMoveError("You are playing White");\r
4238                 return FALSE;\r
4239             }\r
4240         }\r
4241         break;\r
4242         \r
4243       case Training:\r
4244         if (!white_piece && WhiteOnMove(currentMove)) {\r
4245             DisplayMoveError("It is White's turn");\r
4246             return FALSE;\r
4247         }           \r
4248         if (white_piece && !WhiteOnMove(currentMove)) {\r
4249             DisplayMoveError("It is Black's turn");\r
4250             return FALSE;\r
4251         }           \r
4252         break;\r
4253 \r
4254       default:\r
4255       case IcsExamining:\r
4256         break;\r
4257     }\r
4258     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
4259         && gameMode != AnalyzeFile && gameMode != Training) {\r
4260         DisplayMoveError("Displayed position is not current");\r
4261         return FALSE;\r
4262     }\r
4263     return TRUE;\r
4264 }\r
4265 \r
4266 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
4267 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
4268 int lastLoadGameUseList = FALSE;\r
4269 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
4270 ChessMove lastLoadGameStart = (ChessMove) 0;\r
4271 \r
4272 \r
4273 ChessMove\r
4274 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
4275      int fromX, fromY, toX, toY;\r
4276      int promoChar;\r
4277 {\r
4278     ChessMove moveType;\r
4279     ChessSquare pdown, pup;\r
4280 \r
4281     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
4282     if ((fromX == toX) && (fromY == toY)) {\r
4283         return ImpossibleMove;\r
4284     }\r
4285 \r
4286     /* Check if the user is playing in turn.  This is complicated because we\r
4287        let the user "pick up" a piece before it is his turn.  So the piece he\r
4288        tried to pick up may have been captured by the time he puts it down!\r
4289        Therefore we use the color the user is supposed to be playing in this\r
4290        test, not the color of the piece that is currently on the starting\r
4291        square---except in EditGame mode, where the user is playing both\r
4292        sides; fortunately there the capture race can't happen.  (It can\r
4293        now happen in IcsExamining mode, but that's just too bad.  The user\r
4294        will get a somewhat confusing message in that case.)\r
4295        */\r
4296 \r
4297     switch (gameMode) {\r
4298       case PlayFromGameFile:\r
4299       case AnalyzeFile:\r
4300       case TwoMachinesPlay:\r
4301       case EndOfGame:\r
4302       case IcsObserving:\r
4303       case IcsIdle:\r
4304         /* We switched into a game mode where moves are not accepted,\r
4305            perhaps while the mouse button was down. */\r
4306         return ImpossibleMove;\r
4307 \r
4308       case MachinePlaysWhite:\r
4309         /* User is moving for Black */\r
4310         if (WhiteOnMove(currentMove)) {\r
4311             DisplayMoveError("It is White's turn");\r
4312             return ImpossibleMove;\r
4313         }\r
4314         break;\r
4315 \r
4316       case MachinePlaysBlack:\r
4317         /* User is moving for White */\r
4318         if (!WhiteOnMove(currentMove)) {\r
4319             DisplayMoveError("It is Black's turn");\r
4320             return ImpossibleMove;\r
4321         }\r
4322         break;\r
4323 \r
4324       case EditGame:\r
4325       case IcsExamining:\r
4326       case BeginningOfGame:\r
4327       case AnalyzeMode:\r
4328       case Training:\r
4329         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
4330             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
4331             /* User is moving for Black */\r
4332             if (WhiteOnMove(currentMove)) {\r
4333                 DisplayMoveError("It is White's turn");\r
4334                 return ImpossibleMove;\r
4335             }\r
4336         } else {\r
4337             /* User is moving for White */\r
4338             if (!WhiteOnMove(currentMove)) {\r
4339                 DisplayMoveError("It is Black's turn");\r
4340                 return ImpossibleMove;\r
4341             }\r
4342         }\r
4343         break;\r
4344 \r
4345       case IcsPlayingBlack:\r
4346         /* User is moving for Black */\r
4347         if (WhiteOnMove(currentMove)) {\r
4348             if (!appData.premove) {\r
4349                 DisplayMoveError("It is White's turn");\r
4350             } else if (toX >= 0 && toY >= 0) {\r
4351                 premoveToX = toX;\r
4352                 premoveToY = toY;\r
4353                 premoveFromX = fromX;\r
4354                 premoveFromY = fromY;\r
4355                 premovePromoChar = promoChar;\r
4356                 gotPremove = 1;\r
4357                 if (appData.debugMode) \r
4358                     fprintf(debugFP, "Got premove: fromX %d,"\r
4359                             "fromY %d, toX %d, toY %d\n",\r
4360                             fromX, fromY, toX, toY);\r
4361             }\r
4362             return ImpossibleMove;\r
4363         }\r
4364         break;\r
4365 \r
4366       case IcsPlayingWhite:\r
4367         /* User is moving for White */\r
4368         if (!WhiteOnMove(currentMove)) {\r
4369             if (!appData.premove) {\r
4370                 DisplayMoveError("It is Black's turn");\r
4371             } else if (toX >= 0 && toY >= 0) {\r
4372                 premoveToX = toX;\r
4373                 premoveToY = toY;\r
4374                 premoveFromX = fromX;\r
4375                 premoveFromY = fromY;\r
4376                 premovePromoChar = promoChar;\r
4377                 gotPremove = 1;\r
4378                 if (appData.debugMode) \r
4379                     fprintf(debugFP, "Got premove: fromX %d,"\r
4380                             "fromY %d, toX %d, toY %d\n",\r
4381                             fromX, fromY, toX, toY);\r
4382             }\r
4383             return ImpossibleMove;\r
4384         }\r
4385         break;\r
4386 \r
4387       default:\r
4388         break;\r
4389 \r
4390       case EditPosition:\r
4391         /* EditPosition, empty square, or different color piece;\r
4392            click-click move is possible */\r
4393         if (toX == -2 || toY == -2) {\r
4394             boards[0][fromY][fromX] = EmptySquare;\r
4395             DrawPosition(FALSE, boards[currentMove]);\r
4396         } else if (toX >= 0 && toY >= 0) {\r
4397             boards[0][toY][toX] = boards[0][fromY][fromX];\r
4398             boards[0][fromY][fromX] = EmptySquare;\r
4399             DrawPosition(FALSE, boards[currentMove]);\r
4400         }\r
4401         return ImpossibleMove;\r
4402     }\r
4403 \r
4404     /* [HGM] suppress all moves into holdings area and guard band */\r
4405     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
4406             return ImpossibleMove;\r
4407 \r
4408     /* [HGM] <sameColor> moved to here from winboard.c */\r
4409     /* note: EditPosition already filtered out and performed! */\r
4410     pdown = boards[currentMove][fromY][fromX];\r
4411     pup = boards[currentMove][toY][toX];\r
4412     if ( \r
4413             (WhitePawn <= pdown && pdown < BlackPawn &&\r
4414              WhitePawn <= pup && pup < BlackPawn) ||\r
4415             (BlackPawn <= pdown && pdown < EmptySquare &&\r
4416              BlackPawn <= pup && pup < EmptySquare)      )\r
4417          return ImpossibleMove;\r
4418 \r
4419     /* [HGM] If move started in holdings, it means a drop */\r
4420     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
4421          if( pup != EmptySquare ) return ImpossibleMove;\r
4422          if(appData.testLegality) {\r
4423              /* it would be more logical if LegalityTest() also figured out\r
4424               * which drops are legal. For now we forbid pawns on back rank.\r
4425               * Shogi is on its own here...\r
4426               */\r
4427              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
4428                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
4429                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
4430          }\r
4431          return WhiteDrop; /* Not needed to specify white or black yet */\r
4432     }\r
4433 \r
4434     userOfferedDraw = FALSE;\r
4435         \r
4436     /* [HGM] always test for legality, to get promotion info */\r
4437     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
4438                           epStatus[currentMove], castlingRights[currentMove],\r
4439                                          fromY, fromX, toY, toX, promoChar);\r
4440 \r
4441     /* [HGM] but possibly ignore an IllegalMove result */\r
4442     if (appData.testLegality) {\r
4443         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
4444             DisplayMoveError("Illegal move");\r
4445             return ImpossibleMove;\r
4446         }\r
4447     }\r
4448 \r
4449     return moveType;\r
4450     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
4451        function is made into one that returns an OK move type if FinishMove\r
4452        should be called. This to give the calling driver routine the\r
4453        opportunity to finish the userMove input with a promotion popup,\r
4454        without bothering the user with this for invalid or illegal moves */\r
4455 \r
4456 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
4457 }\r
4458 \r
4459 /* Common tail of UserMoveEvent and DropMenuEvent */\r
4460 void\r
4461 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
4462      ChessMove moveType;\r
4463      int fromX, fromY, toX, toY;\r
4464      /*char*/int promoChar;\r
4465 {\r
4466     /* [HGM] <popupFix> kludge to avoid having know the exact promotion\r
4467        move type in caller when we know the move is a legal promotion */\r
4468     if(moveType == NormalMove)\r
4469         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
4470 \r
4471     /* [HGM] convert drag-and-drop piece drops to standard form */\r
4472     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
4473          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
4474          fromX = boards[currentMove][fromY][fromX];\r
4475          fromY = DROP_RANK;\r
4476     }\r
4477 \r
4478     /* [HGM] <popupFix> The following if has been moved here from\r
4479        UserMoveEnevt(). Because it seemed to belon here (why not allow\r
4480        piece drops in training games?), and because it can only be\r
4481        performed after it is known to what we promote. */\r
4482     if (gameMode == Training) {\r
4483       /* compare the move played on the board to the next move in the\r
4484        * game. If they match, display the move and the opponent's response. \r
4485        * If they don't match, display an error message.\r
4486        */\r
4487       int saveAnimate;\r
4488       Board testBoard;\r
4489       CopyBoard(testBoard, boards[currentMove]);\r
4490       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
4491 \r
4492       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
4493         ForwardInner(currentMove+1);\r
4494 \r
4495         /* Autoplay the opponent's response.\r
4496          * if appData.animate was TRUE when Training mode was entered,\r
4497          * the response will be animated.\r
4498          */\r
4499         saveAnimate = appData.animate;\r
4500         appData.animate = animateTraining;\r
4501         ForwardInner(currentMove+1);\r
4502         appData.animate = saveAnimate;\r
4503 \r
4504         /* check for the end of the game */\r
4505         if (currentMove >= forwardMostMove) {\r
4506           gameMode = PlayFromGameFile;\r
4507           ModeHighlight();\r
4508           SetTrainingModeOff();\r
4509           DisplayInformation("End of game");\r
4510         }\r
4511       } else {\r
4512         DisplayError("Incorrect move", 0);\r
4513       }\r
4514       return;\r
4515     }\r
4516 \r
4517   /* Ok, now we know that the move is good, so we can kill\r
4518      the previous line in Analysis Mode */\r
4519   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
4520     forwardMostMove = currentMove;\r
4521   }\r
4522 \r
4523   /* If we need the chess program but it's dead, restart it */\r
4524   ResurrectChessProgram();\r
4525 \r
4526   /* A user move restarts a paused game*/\r
4527   if (pausing)\r
4528     PauseEvent();\r
4529 \r
4530   thinkOutput[0] = NULLCHAR;\r
4531 \r
4532   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
4533 \r
4534   if (gameMode == BeginningOfGame) {\r
4535     if (appData.noChessProgram) {\r
4536       gameMode = EditGame;\r
4537       SetGameInfo();\r
4538     } else {\r
4539       char buf[MSG_SIZ];\r
4540       gameMode = MachinePlaysBlack;\r
4541       SetGameInfo();\r
4542       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
4543       DisplayTitle(buf);\r
4544       if (first.sendName) {\r
4545         sprintf(buf, "name %s\n", gameInfo.white);\r
4546         SendToProgram(buf, &first);\r
4547       }\r
4548     }\r
4549     ModeHighlight();\r
4550   }\r
4551 \r
4552   /* Relay move to ICS or chess engine */\r
4553   if (appData.icsActive) {\r
4554     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
4555         gameMode == IcsExamining) {\r
4556       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
4557       ics_user_moved = 1;\r
4558     }\r
4559   } else {\r
4560     if (first.sendTime && (gameMode == BeginningOfGame ||\r
4561                            gameMode == MachinePlaysWhite ||\r
4562                            gameMode == MachinePlaysBlack)) {\r
4563       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
4564     }\r
4565     SendMoveToProgram(forwardMostMove-1, &first);\r
4566     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
4567       first.maybeThinking = TRUE;\r
4568     }\r
4569     if (currentMove == cmailOldMove + 1) {\r
4570       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
4571     }\r
4572   }\r
4573 \r
4574   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4575 \r
4576   switch (gameMode) {\r
4577   case EditGame:\r
4578     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
4579                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
4580     case MT_NONE:\r
4581     case MT_CHECK:\r
4582       break;\r
4583     case MT_CHECKMATE:\r
4584       if (WhiteOnMove(currentMove)) {\r
4585         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
4586       } else {\r
4587         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
4588       }\r
4589       break;\r
4590     case MT_STALEMATE:\r
4591       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
4592       break;\r
4593     }\r
4594     break;\r
4595     \r
4596   case MachinePlaysBlack:\r
4597   case MachinePlaysWhite:\r
4598     /* disable certain menu options while machine is thinking */\r
4599     SetMachineThinkingEnables();\r
4600     break;\r
4601 \r
4602   default:\r
4603     break;\r
4604   }\r
4605 }\r
4606 \r
4607 void\r
4608 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
4609      int fromX, fromY, toX, toY;\r
4610      int promoChar;\r
4611 {\r
4612     /* [HGM] This routine was added to allow calling of its two logical\r
4613        parts from other modules in the old way. Before, UserMoveEvent()\r
4614        automatically called FinishMove() if the move was OK, and returned\r
4615        otherwise. I separated the two, in order to make it possible to\r
4616        slip a promotion popup in between. But that it always needs two\r
4617        calls, to the first part, (now called UserMoveTest() ), and to\r
4618        FinishMove if the first part succeeded. Calls that do not need\r
4619        to do anything in between, can call this routine the old way. \r
4620     */\r
4621     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
4622 \r
4623     if(moveType != ImpossibleMove)\r
4624         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
4625 }\r
4626 \r
4627 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
4628 {\r
4629     char * hint = lastHint;\r
4630     FrontEndProgramStats stats;\r
4631 \r
4632     stats.which = cps == &first ? 0 : 1;\r
4633     stats.depth = cpstats->depth;\r
4634     stats.nodes = cpstats->nodes;\r
4635     stats.score = cpstats->score;\r
4636     stats.time = cpstats->time;\r
4637     stats.pv = cpstats->movelist;\r
4638     stats.hint = lastHint;\r
4639     stats.an_move_index = 0;\r
4640     stats.an_move_count = 0;\r
4641 \r
4642     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
4643         stats.hint = cpstats->move_name;\r
4644         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
4645         stats.an_move_count = cpstats->nr_moves;\r
4646     }\r
4647 \r
4648     SetProgramStats( &stats );\r
4649 }\r
4650 \r
4651 void\r
4652 HandleMachineMove(message, cps)\r
4653      char *message;\r
4654      ChessProgramState *cps;\r
4655 {\r
4656     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
4657     char realname[MSG_SIZ];\r
4658     int fromX, fromY, toX, toY;\r
4659     ChessMove moveType;\r
4660     char promoChar;\r
4661     char *p;\r
4662     int machineWhite;\r
4663 \r
4664     /*\r
4665      * Kludge to ignore BEL characters\r
4666      */\r
4667     while (*message == '\007') message++;\r
4668 \r
4669     /*\r
4670      * Look for book output\r
4671      */\r
4672     if (cps == &first && bookRequested) {\r
4673         if (message[0] == '\t' || message[0] == ' ') {\r
4674             /* Part of the book output is here; append it */\r
4675             strcat(bookOutput, message);\r
4676             strcat(bookOutput, "  \n");\r
4677             return;\r
4678         } else if (bookOutput[0] != NULLCHAR) {\r
4679             /* All of book output has arrived; display it */\r
4680             char *p = bookOutput;\r
4681             while (*p != NULLCHAR) {\r
4682                 if (*p == '\t') *p = ' ';\r
4683                 p++;\r
4684             }\r
4685             DisplayInformation(bookOutput);\r
4686             bookRequested = FALSE;\r
4687             /* Fall through to parse the current output */\r
4688         }\r
4689     }\r
4690 \r
4691     /*\r
4692      * Look for machine move.\r
4693      */\r
4694     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
4695         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
4696     {\r
4697         /* This method is only useful on engines that support ping */\r
4698         if (cps->lastPing != cps->lastPong) {\r
4699           if (gameMode == BeginningOfGame) {\r
4700             /* Extra move from before last new; ignore */\r
4701             if (appData.debugMode) {\r
4702                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
4703             }\r
4704           } else {\r
4705             if (appData.debugMode) {\r
4706                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
4707                         cps->which, gameMode);\r
4708             }\r
4709 \r
4710             SendToProgram("undo\n", cps);\r
4711           }\r
4712           return;\r
4713         }\r
4714 \r
4715         switch (gameMode) {\r
4716           case BeginningOfGame:\r
4717             /* Extra move from before last reset; ignore */\r
4718             if (appData.debugMode) {\r
4719                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
4720             }\r
4721             return;\r
4722 \r
4723           case EndOfGame:\r
4724           case IcsIdle:\r
4725           default:\r
4726             /* Extra move after we tried to stop.  The mode test is\r
4727                not a reliable way of detecting this problem, but it's\r
4728                the best we can do on engines that don't support ping.\r
4729             */\r
4730             if (appData.debugMode) {\r
4731                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
4732                         cps->which, gameMode);\r
4733             }\r
4734             SendToProgram("undo\n", cps);\r
4735             return;\r
4736 \r
4737           case MachinePlaysWhite:\r
4738           case IcsPlayingWhite:\r
4739             machineWhite = TRUE;\r
4740             break;\r
4741 \r
4742           case MachinePlaysBlack:\r
4743           case IcsPlayingBlack:\r
4744             machineWhite = FALSE;\r
4745             break;\r
4746 \r
4747           case TwoMachinesPlay:\r
4748             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
4749             break;\r
4750         }\r
4751         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
4752             if (appData.debugMode) {\r
4753                 fprintf(debugFP,\r
4754                         "Ignoring move out of turn by %s, gameMode %d"\r
4755                         ", forwardMost %d\n",\r
4756                         cps->which, gameMode, forwardMostMove);\r
4757             }\r
4758             return;\r
4759         }\r
4760 \r
4761         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
4762                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
4763             /* Machine move could not be parsed; ignore it. */\r
4764             sprintf(buf1, "Illegal move \"%s\" from %s machine",\r
4765                     machineMove, cps->which);\r
4766             DisplayError(buf1, 0);\r
4767             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",\r
4768                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
4769             if (gameMode == TwoMachinesPlay) {\r
4770               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
4771                        buf1, GE_XBOARD);\r
4772             }\r
4773             return;\r
4774         }\r
4775 \r
4776         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
4777         /* So we have to redo legality test with true e.p. status here,  */\r
4778         /* to make sure an illegal e.p. capture does not slip through,   */\r
4779         /* to cause a forfeit on a justified illegal-move complaint      */\r
4780         /* of the opponent.                                              */\r
4781         if( gameMode==TwoMachinesPlay && appData.testLegality\r
4782             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
4783                                                               ) {\r
4784            ChessMove moveType;\r
4785            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
4786                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
4787                              fromY, fromX, toY, toX, promoChar);\r
4788             if (appData.debugMode) {\r
4789                 int i;\r
4790                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
4791                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
4792                 fprintf(debugFP, "castling rights\n");\r
4793             }\r
4794             if(moveType == IllegalMove) {\r
4795                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
4796                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
4797                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
4798                            buf1, GE_XBOARD);\r
4799            } else if(gameInfo.variant != VariantFischeRandom)\r
4800            /* [HGM] Kludge to handle engines that send FRC-style castling\r
4801               when they shouldn't (like TSCP-Gothic) */\r
4802            switch(moveType) {\r
4803              case WhiteASideCastleFR:\r
4804              case BlackASideCastleFR:\r
4805                toY++;\r
4806                currentMoveString[2]++;\r
4807                break;\r
4808              case WhiteHSideCastleFR:\r
4809              case BlackHSideCastleFR:\r
4810                toY--;\r
4811                currentMoveString[2]--;\r
4812                break;\r
4813            }\r
4814         }\r
4815         hintRequested = FALSE;\r
4816         lastHint[0] = NULLCHAR;\r
4817         bookRequested = FALSE;\r
4818         /* Program may be pondering now */\r
4819         cps->maybeThinking = TRUE;\r
4820         if (cps->sendTime == 2) cps->sendTime = 1;\r
4821         if (cps->offeredDraw) cps->offeredDraw--;\r
4822 \r
4823 #if ZIPPY\r
4824         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
4825             first.initDone) {\r
4826           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
4827           ics_user_moved = 1;\r
4828         }\r
4829 #endif\r
4830         /* currentMoveString is set as a side-effect of ParseOneMove */\r
4831         strcpy(machineMove, currentMoveString);\r
4832         strcat(machineMove, "\n");\r
4833         strcpy(moveList[forwardMostMove], machineMove);\r
4834 \r
4835         /* [AS] Save move info and clear stats for next move */\r
4836         pvInfoList[ forwardMostMove ].score = programStats.score;\r
4837         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
4838         pvInfoList[ forwardMostMove ].time = -1;\r
4839         ClearProgramStats();\r
4840         thinkOutput[0] = NULLCHAR;\r
4841         hiddenThinkOutputState = 0;\r
4842 \r
4843         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
4844 \r
4845         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
4846         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
4847             int count = 0;\r
4848 \r
4849             while( count < adjudicateLossPlies ) {\r
4850                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
4851 \r
4852                 if( count & 1 ) {\r
4853                     score = -score; /* Flip score for winning side */\r
4854                 }\r
4855 \r
4856                 if( score > adjudicateLossThreshold ) {\r
4857                     break;\r
4858                 }\r
4859 \r
4860                 count++;\r
4861             }\r
4862 \r
4863             if( count >= adjudicateLossPlies ) {\r
4864                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4865 \r
4866                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
4867                     "Xboard adjudication", \r
4868                     GE_XBOARD );\r
4869 \r
4870                 return;\r
4871             }\r
4872         }\r
4873 \r
4874 #ifdef ADJUDICATE // [HGM] some adjudications useful with buggy engines\r
4875 \r
4876         if( gameMode == TwoMachinesPlay && gameInfo.holdingsSize == 0) {\r
4877             int count = 0, epFile = epStatus[forwardMostMove];\r
4878 \r
4879             if(appData.testLegality && appData.checkMates) \r
4880             // don't wait for engine to announce game end if we can judge ourselves\r
4881             switch (MateTest(boards[forwardMostMove],\r
4882                                  PosFlags(forwardMostMove), epFile,\r
4883                                        castlingRights[forwardMostMove]) ) {\r
4884               case MT_NONE:\r
4885               case MT_CHECK:\r
4886               default:\r
4887                 break;\r
4888               case MT_STALEMATE:\r
4889                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4890                 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
4891                     GE_XBOARD );\r
4892                 break;\r
4893               case MT_CHECKMATE:\r
4894                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4895                 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
4896                     "Xboard adjudication: Checkmate", \r
4897                     GE_XBOARD );\r
4898                 break;\r
4899             }\r
4900 \r
4901             if( appData.testLegality )\r
4902             {   /* [HGM] Some more adjudications for obstinate engines */\r
4903                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
4904                     NrWQ=0, NrBQ=0,\r
4905                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;\r
4906                 static int moveCount;\r
4907 \r
4908                 /* First absolutely insufficient mating material. Count what is on board. */\r
4909                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
4910                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
4911                     int m=i;\r
4912 \r
4913                     switch((int) p)\r
4914                     {   /* count B,N,R and other of each side */\r
4915                         case WhiteKnight:\r
4916                              NrWN++; break;\r
4917                         case WhiteBishop:\r
4918                              NrWB++; break;\r
4919                         case BlackKnight:\r
4920                              NrWN++; break;\r
4921                         case BlackBishop:\r
4922                              NrBB++; break;\r
4923                         case WhiteRook:\r
4924                              NrWR++; break;\r
4925                         case BlackRook:\r
4926                              NrBR++; break;\r
4927                         case WhiteQueen:\r
4928                              NrWR++; break;\r
4929                         case BlackQueen:\r
4930                              NrBR++; break;\r
4931                         case EmptySquare: \r
4932                              break;\r
4933                         case BlackPawn:\r
4934                              m = 7-i;\r
4935                         case WhitePawn:\r
4936                              PawnAdvance += m; NrPawns++;\r
4937                     }\r
4938                     NrPieces += (p != EmptySquare);\r
4939                 }\r
4940 \r
4941                 if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2 )\r
4942                 {    /* KBK, KNK or KK */\r
4943 \r
4944                      /* always flag draws, for judging claims */\r
4945                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
4946 \r
4947                      if(appData.materialDraws) {\r
4948                          /* but only adjudicate them if adjudication enabled */\r
4949                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4950                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
4951                          return;\r
4952                      }\r
4953                 }\r
4954 \r
4955                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
4956                 if(NrPieces == 4 && \r
4957                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
4958                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
4959                    || NrWN==2 || NrBN==2     /* KNNK */\r
4960                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
4961                   ) ) {\r
4962                      if(--moveCount < 0 && appData.trivialDraws)\r
4963                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
4964                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4965                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
4966                           return;\r
4967                      }\r
4968                 } else moveCount = 6;\r
4969 \r
4970     if (appData.debugMode) { int i;\r
4971       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
4972               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
4973               appData.drawRepeats);\r
4974       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
4975            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
4976 \r
4977     }\r
4978                 /* Check for rep-draws */\r
4979                 count = 0;\r
4980                 for(k = forwardMostMove-2;\r
4981                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
4982                         epStatus[k] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
4983                     k-=2)\r
4984                 {   int rights=0;\r
4985     if (appData.debugMode) {\r
4986       fprintf(debugFP, " loop\n");\r
4987     }\r
4988                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
4989     if (appData.debugMode) {\r
4990       fprintf(debugFP, "match\n");\r
4991     }\r
4992                         /* compare castling rights */\r
4993                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
4994                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
4995                                 rights++; /* King lost rights, while rook still had them */\r
4996                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
4997                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
4998                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
4999                                    rights++; /* but at least one rook lost them */\r
5000                         }\r
5001                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5002                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5003                                 rights++; \r
5004                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5005                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5006                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5007                                    rights++;\r
5008                         }\r
5009     if (appData.debugMode) {\r
5010       for(i=0; i<nrCastlingRights; i++)\r
5011       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5012     }\r
5013 \r
5014     if (appData.debugMode) {\r
5015       fprintf(debugFP, " %d %d\n", rights, k);\r
5016     }\r
5017                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5018                             && appData.drawRepeats > 1) {\r
5019                              /* adjudicate after user-specified nr of repeats */\r
5020                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5021                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
5022                              return;\r
5023                         }\r
5024                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
5025                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
5026                     }\r
5027                 }\r
5028 \r
5029                 /* Now we test for 50-move draws. Determine ply count */\r
5030                 count = forwardMostMove;\r
5031                 /* look for last irreversble move */\r
5032                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
5033                     count--;\r
5034                 /* if we hit starting position, add initial plies */\r
5035                 if( count == backwardMostMove )\r
5036                     count -= initialRulePlies;\r
5037                 count = forwardMostMove - count; \r
5038                 if( count >= 100)\r
5039                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
5040                          /* this is used to judge if draw claims are legal */\r
5041                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
5042                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5043                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
5044                          return;\r
5045                  }\r
5046             }\r
5047 \r
5048 \r
5049         }\r
5050 #endif\r
5051         if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
5052             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5053 \r
5054             GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
5055 \r
5056             return;\r
5057         }\r
5058 \r
5059         if (gameMode == TwoMachinesPlay) {\r
5060             if (cps->other->sendTime) {\r
5061                 SendTimeRemaining(cps->other,\r
5062                                   cps->other->twoMachinesColor[0] == 'w');\r
5063             }\r
5064             SendMoveToProgram(forwardMostMove-1, cps->other);\r
5065             if (firstMove) {\r
5066                 firstMove = FALSE;\r
5067                 if (cps->other->useColors) {\r
5068                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
5069                 }\r
5070                 SendToProgram("go\n", cps->other);\r
5071             }\r
5072             cps->other->maybeThinking = TRUE;\r
5073         }\r
5074 \r
5075         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5076         \r
5077         if (!pausing && appData.ringBellAfterMoves) {\r
5078             RingBell();\r
5079         }\r
5080 \r
5081         /* \r
5082          * Reenable menu items that were disabled while\r
5083          * machine was thinking\r
5084          */\r
5085         if (gameMode != TwoMachinesPlay)\r
5086             SetUserThinkingEnables();\r
5087 \r
5088         return;\r
5089     }\r
5090 \r
5091     /* Set special modes for chess engines.  Later something general\r
5092      *  could be added here; for now there is just one kludge feature,\r
5093      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
5094      *  when "xboard" is given as an interactive command.\r
5095      */\r
5096     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
5097         cps->useSigint = FALSE;\r
5098         cps->useSigterm = FALSE;\r
5099     }\r
5100 \r
5101     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
5102      * want this, I was asked to put it in, and obliged.\r
5103      */\r
5104     if (!strncmp(message, "setboard ", 9)) {\r
5105         Board initial_position; int i;\r
5106 \r
5107         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
5108 \r
5109         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
5110             DisplayError("Bad FEN received from engine", 0);\r
5111             return ;\r
5112         } else {\r
5113            Reset(FALSE, FALSE);\r
5114            CopyBoard(boards[0], initial_position);\r
5115            initialRulePlies = FENrulePlies;\r
5116            epStatus[0] = FENepStatus;\r
5117            for( i=0; i<nrCastlingRights; i++ )\r
5118                 castlingRights[0][i] = FENcastlingRights[i];\r
5119            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
5120            else gameMode = MachinePlaysBlack;                 \r
5121            DrawPosition(FALSE, boards[currentMove]);\r
5122         }\r
5123         return;\r
5124     }\r
5125 \r
5126     /*\r
5127      * Look for communication commands\r
5128      */\r
5129     if (!strncmp(message, "telluser ", 9)) {\r
5130         DisplayNote(message + 9);\r
5131         return;\r
5132     }\r
5133     if (!strncmp(message, "tellusererror ", 14)) {\r
5134         DisplayError(message + 14, 0);\r
5135         return;\r
5136     }\r
5137     if (!strncmp(message, "tellopponent ", 13)) {\r
5138       if (appData.icsActive) {\r
5139         if (loggedOn) {\r
5140           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
5141           SendToICS(buf1);\r
5142         }\r
5143       } else {\r
5144         DisplayNote(message + 13);\r
5145       }\r
5146       return;\r
5147     }\r
5148     if (!strncmp(message, "tellothers ", 11)) {\r
5149       if (appData.icsActive) {\r
5150         if (loggedOn) {\r
5151           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
5152           SendToICS(buf1);\r
5153         }\r
5154       }\r
5155       return;\r
5156     }\r
5157     if (!strncmp(message, "tellall ", 8)) {\r
5158       if (appData.icsActive) {\r
5159         if (loggedOn) {\r
5160           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
5161           SendToICS(buf1);\r
5162         }\r
5163       } else {\r
5164         DisplayNote(message + 8);\r
5165       }\r
5166       return;\r
5167     }\r
5168     if (strncmp(message, "warning", 7) == 0) {\r
5169         /* Undocumented feature, use tellusererror in new code */\r
5170         DisplayError(message, 0);\r
5171         return;\r
5172     }\r
5173     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
5174         strcpy(realname, cps->tidy);\r
5175         strcat(realname, " query");\r
5176         AskQuestion(realname, buf2, buf1, cps->pr);\r
5177         return;\r
5178     }\r
5179     /* Commands from the engine directly to ICS.  We don't allow these to be \r
5180      *  sent until we are logged on. Crafty kibitzes have been known to \r
5181      *  interfere with the login process.\r
5182      */\r
5183     if (loggedOn) {\r
5184         if (!strncmp(message, "tellics ", 8)) {\r
5185             SendToICS(message + 8);\r
5186             SendToICS("\n");\r
5187             return;\r
5188         }\r
5189         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
5190             SendToICS(ics_prefix);\r
5191             SendToICS(message + 15);\r
5192             SendToICS("\n");\r
5193             return;\r
5194         }\r
5195         /* The following are for backward compatibility only */\r
5196         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
5197             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
5198             SendToICS(ics_prefix);\r
5199             SendToICS(message);\r
5200             SendToICS("\n");\r
5201             return;\r
5202         }\r
5203     }\r
5204     if (strncmp(message, "feature ", 8) == 0) {\r
5205       ParseFeatures(message+8, cps);\r
5206     }\r
5207     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
5208       return;\r
5209     }\r
5210     /*\r
5211      * If the move is illegal, cancel it and redraw the board.\r
5212      * Also deal with other error cases.  Matching is rather loose\r
5213      * here to accommodate engines written before the spec.\r
5214      */\r
5215     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
5216         strncmp(message, "Error", 5) == 0) {\r
5217         if (StrStr(message, "name") || \r
5218             StrStr(message, "rating") || StrStr(message, "?") ||\r
5219             StrStr(message, "result") || StrStr(message, "board") ||\r
5220             StrStr(message, "bk") || StrStr(message, "computer") ||\r
5221             StrStr(message, "variant") || StrStr(message, "hint") ||\r
5222             StrStr(message, "random") || StrStr(message, "depth") ||\r
5223             StrStr(message, "accepted")) {\r
5224             return;\r
5225         }\r
5226         if (StrStr(message, "protover")) {\r
5227           /* Program is responding to input, so it's apparently done\r
5228              initializing, and this error message indicates it is\r
5229              protocol version 1.  So we don't need to wait any longer\r
5230              for it to initialize and send feature commands. */\r
5231           FeatureDone(cps, 1);\r
5232           cps->protocolVersion = 1;\r
5233           return;\r
5234         }\r
5235         cps->maybeThinking = FALSE;\r
5236 \r
5237         if (StrStr(message, "draw")) {\r
5238             /* Program doesn't have "draw" command */\r
5239             cps->sendDrawOffers = 0;\r
5240             return;\r
5241         }\r
5242         if (cps->sendTime != 1 &&\r
5243             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
5244           /* Program apparently doesn't have "time" or "otim" command */\r
5245           cps->sendTime = 0;\r
5246           return;\r
5247         }\r
5248         if (StrStr(message, "analyze")) {\r
5249             cps->analysisSupport = FALSE;\r
5250             cps->analyzing = FALSE;\r
5251             Reset(FALSE, TRUE);\r
5252             sprintf(buf2, "%s does not support analysis", cps->tidy);\r
5253             DisplayError(buf2, 0);\r
5254             return;\r
5255         }\r
5256         if (StrStr(message, "(no matching move)st")) {\r
5257           /* Special kludge for GNU Chess 4 only */\r
5258           cps->stKludge = TRUE;\r
5259           SendTimeControl(cps, movesPerSession, timeControl,\r
5260                           timeIncrement, appData.searchDepth,\r
5261                           searchTime);\r
5262           return;\r
5263         }\r
5264         if (StrStr(message, "(no matching move)sd")) {\r
5265           /* Special kludge for GNU Chess 4 only */\r
5266           cps->sdKludge = TRUE;\r
5267           SendTimeControl(cps, movesPerSession, timeControl,\r
5268                           timeIncrement, appData.searchDepth,\r
5269                           searchTime);\r
5270           return;\r
5271         }\r
5272         if (!StrStr(message, "llegal")) {\r
5273             return;\r
5274         }\r
5275         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
5276             gameMode == IcsIdle) return;\r
5277         if (forwardMostMove <= backwardMostMove) return;\r
5278 #if 0\r
5279         /* Following removed: it caused a bug where a real illegal move\r
5280            message in analyze mored would be ignored. */\r
5281         if (cps == &first && programStats.ok_to_send == 0) {\r
5282             /* Bogus message from Crafty responding to "."  This filtering\r
5283                can miss some of the bad messages, but fortunately the bug \r
5284                is fixed in current Crafty versions, so it doesn't matter. */\r
5285             return;\r
5286         }\r
5287 #endif\r
5288         if (pausing) PauseEvent();\r
5289         if (gameMode == PlayFromGameFile) {\r
5290             /* Stop reading this game file */\r
5291             gameMode = EditGame;\r
5292             ModeHighlight();\r
5293         }\r
5294         currentMove = --forwardMostMove;\r
5295         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
5296         SwitchClocks();\r
5297         DisplayBothClocks();\r
5298         sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",\r
5299                 parseList[currentMove], cps->which);\r
5300         DisplayMoveError(buf1);\r
5301         DrawPosition(FALSE, boards[currentMove]);\r
5302 \r
5303         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
5304         /* only passes fully legal moves                            */\r
5305         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
5306             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
5307                                 "False illegal-move claim", GE_XBOARD );\r
5308         }\r
5309         return;\r
5310     }\r
5311     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
5312         /* Program has a broken "time" command that\r
5313            outputs a string not ending in newline.\r
5314            Don't use it. */\r
5315         cps->sendTime = 0;\r
5316     }\r
5317     \r
5318     /*\r
5319      * If chess program startup fails, exit with an error message.\r
5320      * Attempts to recover here are futile.\r
5321      */\r
5322     if ((StrStr(message, "unknown host") != NULL)\r
5323         || (StrStr(message, "No remote directory") != NULL)\r
5324         || (StrStr(message, "not found") != NULL)\r
5325         || (StrStr(message, "No such file") != NULL)\r
5326         || (StrStr(message, "can't alloc") != NULL)\r
5327         || (StrStr(message, "Permission denied") != NULL)) {\r
5328 \r
5329         cps->maybeThinking = FALSE;\r
5330         sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",\r
5331                 cps->which, cps->program, cps->host, message);\r
5332         RemoveInputSource(cps->isr);\r
5333         DisplayFatalError(buf1, 0, 1);\r
5334         return;\r
5335     }\r
5336     \r
5337     /* \r
5338      * Look for hint output\r
5339      */\r
5340     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
5341         if (cps == &first && hintRequested) {\r
5342             hintRequested = FALSE;\r
5343             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
5344                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5345                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
5346                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
5347                                     fromY, fromX, toY, toX, promoChar, buf1);\r
5348                 sprintf(buf2, "Hint: %s", buf1);\r
5349                 DisplayInformation(buf2);\r
5350             } else {\r
5351                 /* Hint move could not be parsed!? */\r
5352                 sprintf(buf2,\r
5353                         "Illegal hint move \"%s\"\nfrom %s chess program",\r
5354                         buf1, cps->which);\r
5355                 DisplayError(buf2, 0);\r
5356             }\r
5357         } else {\r
5358             strcpy(lastHint, buf1);\r
5359         }\r
5360         return;\r
5361     }\r
5362 \r
5363     /*\r
5364      * Ignore other messages if game is not in progress\r
5365      */\r
5366     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
5367         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
5368 \r
5369     /*\r
5370      * look for win, lose, draw, or draw offer\r
5371      */\r
5372     if (strncmp(message, "1-0", 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         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
5383         return;\r
5384     } else if (strncmp(message, "0-1", 3) == 0) {\r
5385         char *p, *q, *r = "";\r
5386         p = strchr(message, '{');\r
5387         if (p) {\r
5388             q = strchr(p, '}');\r
5389             if (q) {\r
5390                 *q = NULLCHAR;\r
5391                 r = p + 1;\r
5392             }\r
5393         }\r
5394         /* Kludge for Arasan 4.1 bug */\r
5395         if (strcmp(r, "Black resigns") == 0) {\r
5396             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
5397             return;\r
5398         }\r
5399         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
5400         return;\r
5401     } else if (strncmp(message, "1/2", 3) == 0) {\r
5402         char *p, *q, *r = "";\r
5403         p = strchr(message, '{');\r
5404         if (p) {\r
5405             q = strchr(p, '}');\r
5406             if (q) {\r
5407                 *q = NULLCHAR;\r
5408                 r = p + 1;\r
5409             }\r
5410         }\r
5411             \r
5412         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
5413         return;\r
5414 \r
5415     } else if (strncmp(message, "White resign", 12) == 0) {\r
5416         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
5417         return;\r
5418     } else if (strncmp(message, "Black resign", 12) == 0) {\r
5419         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
5420         return;\r
5421     } else if (strncmp(message, "White", 5) == 0 &&\r
5422                message[5] != '(' &&\r
5423                StrStr(message, "Black") == NULL) {\r
5424         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5425         return;\r
5426     } else if (strncmp(message, "Black", 5) == 0 &&\r
5427                message[5] != '(') {\r
5428         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5429         return;\r
5430     } else if (strcmp(message, "resign") == 0 ||\r
5431                strcmp(message, "computer resigns") == 0) {\r
5432         switch (gameMode) {\r
5433           case MachinePlaysBlack:\r
5434           case IcsPlayingBlack:\r
5435             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
5436             break;\r
5437           case MachinePlaysWhite:\r
5438           case IcsPlayingWhite:\r
5439             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
5440             break;\r
5441           case TwoMachinesPlay:\r
5442             if (cps->twoMachinesColor[0] == 'w')\r
5443               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
5444             else\r
5445               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
5446             break;\r
5447           default:\r
5448             /* can't happen */\r
5449             break;\r
5450         }\r
5451         return;\r
5452     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
5453         switch (gameMode) {\r
5454           case MachinePlaysBlack:\r
5455           case IcsPlayingBlack:\r
5456             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
5457             break;\r
5458           case MachinePlaysWhite:\r
5459           case IcsPlayingWhite:\r
5460             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
5461             break;\r
5462           case TwoMachinesPlay:\r
5463             if (cps->twoMachinesColor[0] == 'w')\r
5464               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5465             else\r
5466               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5467             break;\r
5468           default:\r
5469             /* can't happen */\r
5470             break;\r
5471         }\r
5472         return;\r
5473     } else if (strncmp(message, "computer mates", 14) == 0) {\r
5474         switch (gameMode) {\r
5475           case MachinePlaysBlack:\r
5476           case IcsPlayingBlack:\r
5477             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
5478             break;\r
5479           case MachinePlaysWhite:\r
5480           case IcsPlayingWhite:\r
5481             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
5482             break;\r
5483           case TwoMachinesPlay:\r
5484             if (cps->twoMachinesColor[0] == 'w')\r
5485               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5486             else\r
5487               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5488             break;\r
5489           default:\r
5490             /* can't happen */\r
5491             break;\r
5492         }\r
5493         return;\r
5494     } else if (strncmp(message, "checkmate", 9) == 0) {\r
5495         if (WhiteOnMove(forwardMostMove)) {\r
5496             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5497         } else {\r
5498             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5499         }\r
5500         return;\r
5501     } else if (strstr(message, "Draw") != NULL ||\r
5502                strstr(message, "game is a draw") != NULL) {\r
5503         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
5504         return;\r
5505     } else if (strstr(message, "offer") != NULL &&\r
5506                strstr(message, "draw") != NULL) {\r
5507 #if ZIPPY\r
5508         if (appData.zippyPlay && first.initDone) {\r
5509             /* Relay offer to ICS */\r
5510             SendToICS(ics_prefix);\r
5511             SendToICS("draw\n");\r
5512         }\r
5513 #endif\r
5514         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
5515         if (gameMode == TwoMachinesPlay) {\r
5516             if (cps->other->offeredDraw) {\r
5517                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
5518             } else {\r
5519                 if (cps->other->sendDrawOffers) {\r
5520                     SendToProgram("draw\n", cps->other);\r
5521                 }\r
5522             }\r
5523         } else if (gameMode == MachinePlaysWhite ||\r
5524                    gameMode == MachinePlaysBlack) {\r
5525           if (userOfferedDraw) {\r
5526             DisplayInformation("Machine accepts your draw offer");\r
5527             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
5528           } else {\r
5529             DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");\r
5530           }\r
5531         }\r
5532     }\r
5533 \r
5534     \r
5535     /*\r
5536      * Look for thinking output\r
5537      */\r
5538     if ( appData.showThinking) {\r
5539         int plylev, mvleft, mvtot, curscore, time;\r
5540         char mvname[MOVE_LEN];\r
5541         unsigned long nodes;\r
5542         char plyext;\r
5543         int ignore = FALSE;\r
5544         int prefixHint = FALSE;\r
5545         mvname[0] = NULLCHAR;\r
5546 \r
5547         switch (gameMode) {\r
5548           case MachinePlaysBlack:\r
5549           case IcsPlayingBlack:\r
5550             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
5551             break;\r
5552           case MachinePlaysWhite:\r
5553           case IcsPlayingWhite:\r
5554             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
5555             break;\r
5556           case AnalyzeMode:\r
5557           case AnalyzeFile:\r
5558             break;\r
5559           case TwoMachinesPlay:\r
5560             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
5561                 ignore = TRUE;\r
5562             }\r
5563             break;\r
5564           default:\r
5565             ignore = TRUE;\r
5566             break;\r
5567         }\r
5568 \r
5569         if (!ignore) {\r
5570             buf1[0] = NULLCHAR;\r
5571             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
5572                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
5573 \r
5574                 if (plyext != ' ' && plyext != '\t') {\r
5575                     time *= 100;\r
5576                 }\r
5577 \r
5578                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
5579                 if( cps->scoreIsAbsolute && \r
5580                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
5581                 {\r
5582                     curscore = -curscore;\r
5583                 }\r
5584 \r
5585 \r
5586                 programStats.depth = plylev;\r
5587                 programStats.nodes = nodes;\r
5588                 programStats.time = time;\r
5589                 programStats.score = curscore;\r
5590                 programStats.got_only_move = 0;\r
5591 \r
5592                 /* Buffer overflow protection */\r
5593                 if (buf1[0] != NULLCHAR) {\r
5594                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
5595                         && appData.debugMode) {\r
5596                         fprintf(debugFP,\r
5597                                 "PV is too long; using the first %d bytes.\n",\r
5598                                 sizeof(programStats.movelist) - 1);\r
5599                     }\r
5600 \r
5601                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
5602                 } else {\r
5603                     sprintf(programStats.movelist, " no PV\n");\r
5604                 }\r
5605 \r
5606                 if (programStats.seen_stat) {\r
5607                     programStats.ok_to_send = 1;\r
5608                 }\r
5609 \r
5610                 if (strchr(programStats.movelist, '(') != NULL) {\r
5611                     programStats.line_is_book = 1;\r
5612                     programStats.nr_moves = 0;\r
5613                     programStats.moves_left = 0;\r
5614                 } else {\r
5615                     programStats.line_is_book = 0;\r
5616                 }\r
5617 \r
5618                 SendProgramStatsToFrontend( cps, &programStats );\r
5619 \r
5620                 /* \r
5621                     [AS] Protect the thinkOutput buffer from overflow... this\r
5622                     is only useful if buf1 hasn't overflowed first!\r
5623                 */\r
5624                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
5625                         plylev, \r
5626                         (gameMode == TwoMachinesPlay ?\r
5627                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
5628                         ((double) curscore) / 100.0,\r
5629                         prefixHint ? lastHint : "",\r
5630                         prefixHint ? " " : "" );\r
5631 \r
5632                 if( buf1[0] != NULLCHAR ) {\r
5633                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
5634 \r
5635                     if( strlen(buf1) > max_len ) {\r
5636                         if( appData.debugMode) {\r
5637                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
5638                         }\r
5639                         buf1[max_len+1] = '\0';\r
5640                     }\r
5641 \r
5642                     strcat( thinkOutput, buf1 );\r
5643                 }\r
5644 \r
5645                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
5646                     DisplayMove(currentMove - 1);\r
5647                     DisplayAnalysis();\r
5648                 }\r
5649                 return;\r
5650 \r
5651             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
5652                 /* crafty (9.25+) says "(only move) <move>"\r
5653                  * if there is only 1 legal move\r
5654                  */\r
5655                 sscanf(p, "(only move) %s", buf1);\r
5656                 sprintf(thinkOutput, "%s (only move)", buf1);\r
5657                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
5658                 programStats.depth = 1;\r
5659                 programStats.nr_moves = 1;\r
5660                 programStats.moves_left = 1;\r
5661                 programStats.nodes = 1;\r
5662                 programStats.time = 1;\r
5663                 programStats.got_only_move = 1;\r
5664 \r
5665                 /* Not really, but we also use this member to\r
5666                    mean "line isn't going to change" (Crafty\r
5667                    isn't searching, so stats won't change) */\r
5668                 programStats.line_is_book = 1;\r
5669 \r
5670                 SendProgramStatsToFrontend( cps, &programStats );\r
5671                 \r
5672                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
5673                     DisplayMove(currentMove - 1);\r
5674                     DisplayAnalysis();\r
5675                 }\r
5676                 return;\r
5677             } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",\r
5678                               &time, &nodes, &plylev, &mvleft,\r
5679                               &mvtot, mvname) >= 5) {\r
5680                 /* The stat01: line is from Crafty (9.29+) in response\r
5681                    to the "." command */\r
5682                 programStats.seen_stat = 1;\r
5683                 cps->maybeThinking = TRUE;\r
5684 \r
5685                 if (programStats.got_only_move || !appData.periodicUpdates)\r
5686                   return;\r
5687 \r
5688                 programStats.depth = plylev;\r
5689                 programStats.time = time;\r
5690                 programStats.nodes = nodes;\r
5691                 programStats.moves_left = mvleft;\r
5692                 programStats.nr_moves = mvtot;\r
5693                 strcpy(programStats.move_name, mvname);\r
5694                 programStats.ok_to_send = 1;\r
5695                 programStats.movelist[0] = '\0';\r
5696 \r
5697                 SendProgramStatsToFrontend( cps, &programStats );\r
5698 \r
5699                 DisplayAnalysis();\r
5700                 return;\r
5701 \r
5702             } else if (strncmp(message,"++",2) == 0) {\r
5703                 /* Crafty 9.29+ outputs this */\r
5704                 programStats.got_fail = 2;\r
5705                 return;\r
5706 \r
5707             } else if (strncmp(message,"--",2) == 0) {\r
5708                 /* Crafty 9.29+ outputs this */\r
5709                 programStats.got_fail = 1;\r
5710                 return;\r
5711 \r
5712             } else if (thinkOutput[0] != NULLCHAR &&\r
5713                        strncmp(message, "    ", 4) == 0) {\r
5714                 unsigned message_len;\r
5715 \r
5716                 p = message;\r
5717                 while (*p && *p == ' ') p++;\r
5718 \r
5719                 message_len = strlen( p );\r
5720 \r
5721                 /* [AS] Avoid buffer overflow */\r
5722                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
5723                     strcat(thinkOutput, " ");\r
5724                     strcat(thinkOutput, p);\r
5725                 }\r
5726 \r
5727                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
5728                     strcat(programStats.movelist, " ");\r
5729                     strcat(programStats.movelist, p);\r
5730                 }\r
5731 \r
5732                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
5733                     DisplayMove(currentMove - 1);\r
5734                     DisplayAnalysis();\r
5735                 }\r
5736                 return;\r
5737             }\r
5738         }\r
5739         else {\r
5740             buf1[0] = NULLCHAR;\r
5741 \r
5742             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
5743                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
5744             {\r
5745                 ChessProgramStats cpstats;\r
5746 \r
5747                 if (plyext != ' ' && plyext != '\t') {\r
5748                     time *= 100;\r
5749                 }\r
5750 \r
5751                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
5752                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
5753                     curscore = -curscore;\r
5754                 }\r
5755 \r
5756                 cpstats.depth = plylev;\r
5757                 cpstats.nodes = nodes;\r
5758                 cpstats.time = time;\r
5759                 cpstats.score = curscore;\r
5760                 cpstats.got_only_move = 0;\r
5761                 cpstats.movelist[0] = '\0';\r
5762 \r
5763                 if (buf1[0] != NULLCHAR) {\r
5764                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
5765                 }\r
5766 \r
5767                 cpstats.ok_to_send = 0;\r
5768                 cpstats.line_is_book = 0;\r
5769                 cpstats.nr_moves = 0;\r
5770                 cpstats.moves_left = 0;\r
5771 \r
5772                 SendProgramStatsToFrontend( cps, &cpstats );\r
5773             }\r
5774         }\r
5775     }\r
5776 }\r
5777 \r
5778 \r
5779 /* Parse a game score from the character string "game", and\r
5780    record it as the history of the current game.  The game\r
5781    score is NOT assumed to start from the standard position. \r
5782    The display is not updated in any way.\r
5783    */\r
5784 void\r
5785 ParseGameHistory(game)\r
5786      char *game;\r
5787 {\r
5788     ChessMove moveType;\r
5789     int fromX, fromY, toX, toY, boardIndex;\r
5790     char promoChar;\r
5791     char *p, *q;\r
5792     char buf[MSG_SIZ];\r
5793 \r
5794     if (appData.debugMode)\r
5795       fprintf(debugFP, "Parsing game history: %s\n", game);\r
5796 \r
5797     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
5798     gameInfo.site = StrSave(appData.icsHost);\r
5799     gameInfo.date = PGNDate();\r
5800     gameInfo.round = StrSave("-");\r
5801 \r
5802     /* Parse out names of players */\r
5803     while (*game == ' ') game++;\r
5804     p = buf;\r
5805     while (*game != ' ') *p++ = *game++;\r
5806     *p = NULLCHAR;\r
5807     gameInfo.white = StrSave(buf);\r
5808     while (*game == ' ') game++;\r
5809     p = buf;\r
5810     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
5811     *p = NULLCHAR;\r
5812     gameInfo.black = StrSave(buf);\r
5813 \r
5814     /* Parse moves */\r
5815     boardIndex = blackPlaysFirst ? 1 : 0;\r
5816     yynewstr(game);\r
5817     for (;;) {\r
5818         yyboardindex = boardIndex;\r
5819         moveType = (ChessMove) yylex();\r
5820         switch (moveType) {\r
5821 #ifdef FAIRY\r
5822           case WhitePromotionChancellor:\r
5823           case BlackPromotionChancellor:\r
5824           case WhitePromotionArchbishop:\r
5825           case BlackPromotionArchbishop:\r
5826 #endif\r
5827           case WhitePromotionQueen:\r
5828           case BlackPromotionQueen:\r
5829           case WhitePromotionRook:\r
5830           case BlackPromotionRook:\r
5831           case WhitePromotionBishop:\r
5832           case BlackPromotionBishop:\r
5833           case WhitePromotionKnight:\r
5834           case BlackPromotionKnight:\r
5835           case WhitePromotionKing:\r
5836           case BlackPromotionKing:\r
5837           case NormalMove:\r
5838           case WhiteCapturesEnPassant:\r
5839           case BlackCapturesEnPassant:\r
5840           case WhiteKingSideCastle:\r
5841           case WhiteQueenSideCastle:\r
5842           case BlackKingSideCastle:\r
5843           case BlackQueenSideCastle:\r
5844           case WhiteKingSideCastleWild:\r
5845           case WhiteQueenSideCastleWild:\r
5846           case BlackKingSideCastleWild:\r
5847           case BlackQueenSideCastleWild:\r
5848           /* PUSH Fabien */\r
5849           case WhiteHSideCastleFR:\r
5850           case WhiteASideCastleFR:\r
5851           case BlackHSideCastleFR:\r
5852           case BlackASideCastleFR:\r
5853           /* POP Fabien */\r
5854           case IllegalMove:             /* maybe suicide chess, etc. */\r
5855             fromX = currentMoveString[0] - AAA;\r
5856             fromY = currentMoveString[1] - ONE;\r
5857             toX = currentMoveString[2] - AAA;\r
5858             toY = currentMoveString[3] - ONE;\r
5859             promoChar = currentMoveString[4];\r
5860             break;\r
5861           case WhiteDrop:\r
5862           case BlackDrop:\r
5863             fromX = moveType == WhiteDrop ?\r
5864               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
5865             (int) CharToPiece(ToLower(currentMoveString[0]));\r
5866             fromY = DROP_RANK;\r
5867             toX = currentMoveString[2] - AAA;\r
5868             toY = currentMoveString[3] - ONE;\r
5869             promoChar = NULLCHAR;\r
5870             break;\r
5871           case AmbiguousMove:\r
5872             /* bug? */\r
5873             sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);\r
5874             DisplayError(buf, 0);\r
5875             return;\r
5876           case ImpossibleMove:\r
5877             /* bug? */\r
5878             sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);\r
5879             DisplayError(buf, 0);\r
5880             return;\r
5881           case (ChessMove) 0:   /* end of file */\r
5882             if (boardIndex < backwardMostMove) {\r
5883                 /* Oops, gap.  How did that happen? */\r
5884                 DisplayError("Gap in move list", 0);\r
5885                 return;\r
5886             }\r
5887             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
5888             if (boardIndex > forwardMostMove) {\r
5889                 forwardMostMove = boardIndex;\r
5890             }\r
5891             return;\r
5892           case ElapsedTime:\r
5893             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
5894                 strcat(parseList[boardIndex-1], " ");\r
5895                 strcat(parseList[boardIndex-1], yy_text);\r
5896             }\r
5897             continue;\r
5898           case Comment:\r
5899           case PGNTag:\r
5900           case NAG:\r
5901           default:\r
5902             /* ignore */\r
5903             continue;\r
5904           case WhiteWins:\r
5905           case BlackWins:\r
5906           case GameIsDrawn:\r
5907           case GameUnfinished:\r
5908             if (gameMode == IcsExamining) {\r
5909                 if (boardIndex < backwardMostMove) {\r
5910                     /* Oops, gap.  How did that happen? */\r
5911                     return;\r
5912                 }\r
5913                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
5914                 return;\r
5915             }\r
5916             gameInfo.result = moveType;\r
5917             p = strchr(yy_text, '{');\r
5918             if (p == NULL) p = strchr(yy_text, '(');\r
5919             if (p == NULL) {\r
5920                 p = yy_text;\r
5921                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
5922             } else {\r
5923                 q = strchr(p, *p == '{' ? '}' : ')');\r
5924                 if (q != NULL) *q = NULLCHAR;\r
5925                 p++;\r
5926             }\r
5927             gameInfo.resultDetails = StrSave(p);\r
5928             continue;\r
5929         }\r
5930         if (boardIndex >= forwardMostMove &&\r
5931             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
5932             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
5933             return;\r
5934         }\r
5935         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
5936                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
5937                                  parseList[boardIndex]);\r
5938         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
5939         /* currentMoveString is set as a side-effect of yylex */\r
5940         strcpy(moveList[boardIndex], currentMoveString);\r
5941         strcat(moveList[boardIndex], "\n");\r
5942         boardIndex++;\r
5943         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
5944         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
5945                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
5946           case MT_NONE:\r
5947           case MT_STALEMATE:\r
5948           default:\r
5949             break;\r
5950           case MT_CHECK:\r
5951             if(gameInfo.variant != VariantShogi)\r
5952                 strcat(parseList[boardIndex - 1], "+");\r
5953             break;\r
5954           case MT_CHECKMATE:\r
5955             strcat(parseList[boardIndex - 1], "#");\r
5956             break;\r
5957         }\r
5958     }\r
5959 }\r
5960 \r
5961 \r
5962 /* Apply a move to the given board  */\r
5963 void\r
5964 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
5965      int fromX, fromY, toX, toY;\r
5966      int promoChar;\r
5967      Board board;\r
5968 {\r
5969   ChessSquare captured = board[toY][toX], piece; int p;\r
5970 \r
5971   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
5972   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
5973        && promoChar != 0) promoChar = 'F';\r
5974          \r
5975   if (fromX == toX && fromY == toY) return;\r
5976 \r
5977   if (fromY == DROP_RANK) {\r
5978         /* must be first */\r
5979         board[toY][toX] = (ChessSquare) fromX;\r
5980   } else {\r
5981      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
5982 \r
5983     /* Code added by Tord: */\r
5984     /* FRC castling assumed when king captures friendly rook. */\r
5985     if (board[fromY][fromX] == WhiteKing &&\r
5986              board[toY][toX] == WhiteRook) {\r
5987       board[fromY][fromX] = EmptySquare;\r
5988       board[toY][toX] = EmptySquare;\r
5989       if(toX > fromX) {\r
5990         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
5991       } else {\r
5992         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
5993       }\r
5994     } else if (board[fromY][fromX] == BlackKing &&\r
5995                board[toY][toX] == BlackRook) {\r
5996       board[fromY][fromX] = EmptySquare;\r
5997       board[toY][toX] = EmptySquare;\r
5998       if(toX > fromX) {\r
5999         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
6000       } else {\r
6001         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
6002       }\r
6003     /* End of code added by Tord */\r
6004 \r
6005     } else if (initialPosition[fromY][fromX] == WhiteKing\r
6006         && board[fromY][fromX] == WhiteKing\r
6007         && toY == fromY && toX > fromX+1) {\r
6008         board[fromY][fromX] = EmptySquare;\r
6009         board[toY][toX] = WhiteKing;\r
6010         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
6011         board[toY][toX-1] = WhiteRook;\r
6012     } else if (initialPosition[fromY][fromX] == WhiteKing\r
6013                && board[fromY][fromX] == WhiteKing\r
6014                && toY == fromY && toX < fromX-1) {\r
6015         board[fromY][fromX] = EmptySquare;\r
6016         board[toY][toX] = WhiteKing;\r
6017         board[fromY][BOARD_LEFT] = EmptySquare;\r
6018         board[toY][toX+1] = WhiteRook;\r
6019     } else if (fromY == 0 && fromX == 3\r
6020                && board[fromY][fromX] == WhiteKing\r
6021                && toY == 0 && toX == 5) {\r
6022         board[fromY][fromX] = EmptySquare;\r
6023         board[toY][toX] = WhiteKing;\r
6024         board[fromY][7] = EmptySquare;\r
6025         board[toY][4] = WhiteRook;\r
6026     } else if (fromY == 0 && fromX == 3\r
6027                && board[fromY][fromX] == WhiteKing\r
6028                && toY == 0 && toX == 1) {\r
6029         board[fromY][fromX] = EmptySquare;\r
6030         board[toY][toX] = WhiteKing;\r
6031         board[fromY][0] = EmptySquare;\r
6032         board[toY][2] = WhiteRook;\r
6033     } else if (board[fromY][fromX] == WhitePawn\r
6034                && toY == BOARD_HEIGHT-1\r
6035                && gameInfo.variant != VariantXiangqi\r
6036                ) {\r
6037         /* white pawn promotion */\r
6038         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
6039         if (board[toY][toX] == EmptySquare) {\r
6040             board[toY][toX] = WhiteQueen;\r
6041         }\r
6042         if(gameInfo.variant==VariantBughouse ||\r
6043            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
6044             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
6045         board[fromY][fromX] = EmptySquare;\r
6046     } else if ((fromY == BOARD_HEIGHT-4)\r
6047                && (toX != fromX)\r
6048                && (board[fromY][fromX] == WhitePawn)\r
6049                && (board[toY][toX] == EmptySquare)) {\r
6050         board[fromY][fromX] = EmptySquare;\r
6051         board[toY][toX] = WhitePawn;\r
6052         captured = board[toY - 1][toX];\r
6053         board[toY - 1][toX] = EmptySquare;\r
6054     } else if (initialPosition[fromY][fromX] == BlackKing\r
6055                && board[fromY][fromX] == BlackKing\r
6056                && toY == fromY && toX > fromX+1) {\r
6057         board[fromY][fromX] = EmptySquare;\r
6058         board[toY][toX] = BlackKing;\r
6059         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
6060         board[toY][toX-1] = BlackRook;\r
6061     } else if (initialPosition[fromY][fromX] == BlackKing\r
6062                && board[fromY][fromX] == BlackKing\r
6063                && toY == fromY && toX < fromX-1) {\r
6064         board[fromY][fromX] = EmptySquare;\r
6065         board[toY][toX] = BlackKing;\r
6066         board[fromY][BOARD_LEFT] = EmptySquare;\r
6067         board[toY][toX+1] = BlackRook;\r
6068     } else if (fromY == 7 && fromX == 3\r
6069                && board[fromY][fromX] == BlackKing\r
6070                && toY == 7 && toX == 5) {\r
6071         board[fromY][fromX] = EmptySquare;\r
6072         board[toY][toX] = BlackKing;\r
6073         board[fromY][7] = EmptySquare;\r
6074         board[toY][4] = BlackRook;\r
6075     } else if (fromY == 7 && fromX == 3\r
6076                && board[fromY][fromX] == BlackKing\r
6077                && toY == 7 && toX == 1) {\r
6078         board[fromY][fromX] = EmptySquare;\r
6079         board[toY][toX] = BlackKing;\r
6080         board[fromY][0] = EmptySquare;\r
6081         board[toY][2] = BlackRook;\r
6082     } else if (board[fromY][fromX] == BlackPawn\r
6083                && toY == 0\r
6084                && gameInfo.variant != VariantXiangqi\r
6085                ) {\r
6086         /* black pawn promotion */\r
6087         board[0][toX] = CharToPiece(ToLower(promoChar));\r
6088         if (board[0][toX] == EmptySquare) {\r
6089             board[0][toX] = BlackQueen;\r
6090         }\r
6091         if(gameInfo.variant==VariantBughouse ||\r
6092            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
6093             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
6094         board[fromY][fromX] = EmptySquare;\r
6095     } else if ((fromY == 3)\r
6096                && (toX != fromX)\r
6097                && (board[fromY][fromX] == BlackPawn)\r
6098                && (board[toY][toX] == EmptySquare)) {\r
6099         board[fromY][fromX] = EmptySquare;\r
6100         board[toY][toX] = BlackPawn;\r
6101         captured = board[toY + 1][toX];\r
6102         board[toY + 1][toX] = EmptySquare;\r
6103     } else {\r
6104         board[toY][toX] = board[fromY][fromX];\r
6105         board[fromY][fromX] = EmptySquare;\r
6106     }\r
6107 \r
6108     /* [HGM] now we promote for Shogi, if needed */\r
6109     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
6110         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
6111   }\r
6112 \r
6113     if (gameInfo.holdingsWidth != 0) {\r
6114 \r
6115       /* !!A lot more code needs to be written to support holdings  */\r
6116       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
6117       /* penultimate board files, so they are automaticlly stored   */\r
6118       /* in the game history.                                       */\r
6119       if (fromY == DROP_RANK) {\r
6120         /* Delete from holdings, by decreasing count */\r
6121         /* and erasing image if necessary            */\r
6122         p = (int) fromX;\r
6123         if(p < (int) BlackPawn) { /* white drop */\r
6124              p -= (int)WhitePawn;\r
6125              if(p >= gameInfo.holdingsSize) p = 0;\r
6126              if(--board[p][BOARD_WIDTH-2] == 0)\r
6127                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
6128         } else {                  /* black drop */\r
6129              p -= (int)BlackPawn;\r
6130              if(p >= gameInfo.holdingsSize) p = 0;\r
6131              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
6132                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
6133         }\r
6134       }\r
6135       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
6136           && gameInfo.variant != VariantBughouse        ) {\r
6137         /* Add to holdings, if holdings exist */\r
6138         p = (int) captured;\r
6139         if (p >= (int) BlackPawn) {\r
6140           p -= (int)BlackPawn;\r
6141           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
6142                   /* in Shogi restore piece to its original  first */\r
6143                   captured = (ChessSquare) (DEMOTED captured);\r
6144                   p = DEMOTED p;\r
6145           }\r
6146           p = PieceToNumber((ChessSquare)p);\r
6147           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
6148           board[p][BOARD_WIDTH-2]++;\r
6149           board[p][BOARD_WIDTH-1] =\r
6150                                    BLACK_TO_WHITE captured;\r
6151         } else {\r
6152           p -= (int)WhitePawn;\r
6153           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
6154                   captured = (ChessSquare) (DEMOTED captured);\r
6155                   p = DEMOTED p;\r
6156           }\r
6157           p = PieceToNumber((ChessSquare)p);\r
6158           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
6159           board[BOARD_HEIGHT-1-p][1]++;\r
6160           board[BOARD_HEIGHT-1-p][0] =\r
6161                                   WHITE_TO_BLACK captured;\r
6162         }\r
6163       }\r
6164 \r
6165     } else if (gameInfo.variant == VariantAtomic) {\r
6166       if (captured != EmptySquare) {\r
6167         int y, x;\r
6168         for (y = toY-1; y <= toY+1; y++) {\r
6169           for (x = toX-1; x <= toX+1; x++) {\r
6170             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
6171                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
6172               board[y][x] = EmptySquare;\r
6173             }\r
6174           }\r
6175         }\r
6176         board[toY][toX] = EmptySquare;\r
6177       }\r
6178     }\r
6179     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
6180         /* [HGM] Shogi promotions */\r
6181         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
6182     }\r
6183 \r
6184 }\r
6185 \r
6186 /* Updates forwardMostMove */\r
6187 void\r
6188 MakeMove(fromX, fromY, toX, toY, promoChar)\r
6189      int fromX, fromY, toX, toY;\r
6190      int promoChar;\r
6191 {\r
6192     forwardMostMove++;\r
6193 \r
6194     if (forwardMostMove >= MAX_MOVES) {\r
6195       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
6196                         0, 1);\r
6197       return;\r
6198     }\r
6199     SwitchClocks();\r
6200     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
6201     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
6202     if (commentList[forwardMostMove] != NULL) {\r
6203         free(commentList[forwardMostMove]);\r
6204         commentList[forwardMostMove] = NULL;\r
6205     }\r
6206     CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);\r
6207     /* [HGM] compute & store e.p. status and castling rights for new position */\r
6208     { int i, j;\r
6209 \r
6210       epStatus[forwardMostMove] = EP_NONE;\r
6211 \r
6212       if( boards[forwardMostMove][toY][toX] != EmptySquare ) \r
6213            epStatus[forwardMostMove] = EP_CAPTURE;  \r
6214 \r
6215       if( boards[forwardMostMove][fromY][fromX] == WhitePawn ) {\r
6216            epStatus[forwardMostMove] = EP_PAWN_MOVE; \r
6217            if( toY-fromY==2 &&\r
6218                (toX>BOARD_LEFT+1 && boards[forwardMostMove][toY][toX-1] == BlackPawn ||\r
6219                 toX<BOARD_RGHT-1 && boards[forwardMostMove][toY][toX+1] == BlackPawn ) )\r
6220               epStatus[forwardMostMove] = toX;\r
6221       } else \r
6222       if( boards[forwardMostMove][fromY][fromX] == BlackPawn ) {\r
6223            epStatus[forwardMostMove] = EP_PAWN_MOVE; \r
6224            if( toY-fromY== -2 &&\r
6225                (toX>BOARD_LEFT+1 && boards[forwardMostMove][toY][toX-1] == WhitePawn ||\r
6226                 toX<BOARD_RGHT-1 && boards[forwardMostMove][toY][toX+1] == WhitePawn ) )\r
6227               epStatus[forwardMostMove] = toX;\r
6228        }\r
6229 \r
6230        for(i=0; i<nrCastlingRights; i++) {\r
6231            castlingRights[forwardMostMove][i] = castlingRights[forwardMostMove-1][i];\r
6232            if(castlingRights[forwardMostMove][i] == fromX && castlingRank[i] == fromY ||\r
6233               castlingRights[forwardMostMove][i] == toX   && castlingRank[i] == toY   \r
6234              ) castlingRights[forwardMostMove][i] = -1; // revoke for moved or captured piece\r
6235 \r
6236        }\r
6237 \r
6238     }\r
6239     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);\r
6240     gameInfo.result = GameUnfinished;\r
6241     if (gameInfo.resultDetails != NULL) {\r
6242         free(gameInfo.resultDetails);\r
6243         gameInfo.resultDetails = NULL;\r
6244     }\r
6245     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
6246                               moveList[forwardMostMove - 1]);\r
6247     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
6248                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
6249                              fromY, fromX, toY, toX, promoChar,\r
6250                              parseList[forwardMostMove - 1]);\r
6251     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
6252                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
6253                             castlingRights[forwardMostMove]) ) {\r
6254       case MT_NONE:\r
6255       case MT_STALEMATE:\r
6256       default:\r
6257         break;\r
6258       case MT_CHECK:\r
6259         if(gameInfo.variant != VariantShogi)\r
6260             strcat(parseList[forwardMostMove - 1], "+");\r
6261         break;\r
6262       case MT_CHECKMATE:\r
6263         strcat(parseList[forwardMostMove - 1], "#");\r
6264         break;\r
6265     }\r
6266     if (appData.debugMode) {\r
6267         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
6268     }\r
6269 \r
6270 }\r
6271 \r
6272 /* Updates currentMove if not pausing */\r
6273 void\r
6274 ShowMove(fromX, fromY, toX, toY)\r
6275 {\r
6276     int instant = (gameMode == PlayFromGameFile) ?\r
6277         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
6278     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
6279         if (!instant) {\r
6280             if (forwardMostMove == currentMove + 1) {\r
6281                 AnimateMove(boards[forwardMostMove - 1],\r
6282                             fromX, fromY, toX, toY);\r
6283             }\r
6284             if (appData.highlightLastMove) {\r
6285                 SetHighlights(fromX, fromY, toX, toY);\r
6286             }\r
6287         }\r
6288         currentMove = forwardMostMove;\r
6289     }\r
6290 \r
6291     if (instant) return;\r
6292 \r
6293     DisplayMove(currentMove - 1);\r
6294     DrawPosition(FALSE, boards[currentMove]);\r
6295     DisplayBothClocks();\r
6296     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
6297 }\r
6298 \r
6299 \r
6300 void\r
6301 InitChessProgram(cps)\r
6302      ChessProgramState *cps;\r
6303 {\r
6304     char buf[MSG_SIZ], *b; int overruled;\r
6305     if (appData.noChessProgram) return;\r
6306     hintRequested = FALSE;\r
6307     bookRequested = FALSE;\r
6308     SendToProgram(cps->initString, cps);\r
6309     if (gameInfo.variant != VariantNormal &&\r
6310         gameInfo.variant != VariantLoadable\r
6311         /* [HGM] also send variant if board size non-standard */\r
6312         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8\r
6313                                             ) {\r
6314       char *v = VariantName(gameInfo.variant);\r
6315       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
6316         /* [HGM] in protocol 1 we have to assume all variants valid */\r
6317         sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);\r
6318         DisplayFatalError(buf, 0, 1);\r
6319         return;\r
6320       }\r
6321       b = buf;\r
6322       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
6323       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
6324       if( gameInfo.variant == VariantXiangqi )\r
6325            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
6326       if( gameInfo.variant == VariantShogi )\r
6327            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
6328       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
6329            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
6330       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantGothic )\r
6331            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
6332       if( gameInfo.variant == VariantCourier )\r
6333            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
6334 \r
6335       if(overruled) {\r
6336            if (cps->protocolVersion != 1 && StrStr(cps->variants, "boardsize") == NULL) {\r
6337              sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
6338                   gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
6339              DisplayFatalError(buf, 0, 1);\r
6340              return;\r
6341            }\r
6342            /* [HGM] here we really should compare with the maximum supported board size */\r
6343            sprintf(buf, "%dx%d+%d_", gameInfo.boardWidth,\r
6344                               gameInfo.boardHeight, gameInfo.holdingsSize );\r
6345            while(*b++ != '_');\r
6346       }\r
6347       sprintf(b, "variant %s\n", VariantName(gameInfo.variant));\r
6348       SendToProgram(buf, cps);\r
6349     }\r
6350     if (cps->sendICS) {\r
6351       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
6352       SendToProgram(buf, cps);\r
6353     }\r
6354     cps->maybeThinking = FALSE;\r
6355     cps->offeredDraw = 0;\r
6356     if (!appData.icsActive) {\r
6357         SendTimeControl(cps, movesPerSession, timeControl,\r
6358                         timeIncrement, appData.searchDepth,\r
6359                         searchTime);\r
6360     }\r
6361     if (appData.showThinking) {\r
6362         SendToProgram("post\n", cps);\r
6363     }\r
6364     SendToProgram("hard\n", cps);\r
6365     if (!appData.ponderNextMove) {\r
6366         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
6367            it without being sure what state we are in first.  "hard"\r
6368            is not a toggle, so that one is OK.\r
6369          */\r
6370         SendToProgram("easy\n", cps);\r
6371     }\r
6372     if (cps->usePing) {\r
6373       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
6374       SendToProgram(buf, cps);\r
6375     }\r
6376     cps->initDone = TRUE;\r
6377 }   \r
6378 \r
6379 \r
6380 void\r
6381 StartChessProgram(cps)\r
6382      ChessProgramState *cps;\r
6383 {\r
6384     char buf[MSG_SIZ];\r
6385     int err;\r
6386 \r
6387     if (appData.noChessProgram) return;\r
6388     cps->initDone = FALSE;\r
6389 \r
6390     if (strcmp(cps->host, "localhost") == 0) {\r
6391         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
6392     } else if (*appData.remoteShell == NULLCHAR) {\r
6393         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
6394     } else {\r
6395         if (*appData.remoteUser == NULLCHAR) {\r
6396             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
6397                     cps->program);\r
6398         } else {\r
6399             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
6400                     cps->host, appData.remoteUser, cps->program);\r
6401         }\r
6402         err = StartChildProcess(buf, "", &cps->pr);\r
6403     }\r
6404     \r
6405     if (err != 0) {\r
6406         sprintf(buf, "Startup failure on '%s'", cps->program);\r
6407         DisplayFatalError(buf, err, 1);\r
6408         cps->pr = NoProc;\r
6409         cps->isr = NULL;\r
6410         return;\r
6411     }\r
6412     \r
6413     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
6414     if (cps->protocolVersion > 1) {\r
6415       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
6416       SendToProgram(buf, cps);\r
6417     } else {\r
6418       SendToProgram("xboard\n", cps);\r
6419     }\r
6420 }\r
6421 \r
6422 \r
6423 void\r
6424 TwoMachinesEventIfReady P((void))\r
6425 {\r
6426   if (first.lastPing != first.lastPong) {\r
6427     DisplayMessage("", "Waiting for first chess program");\r
6428     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
6429     return;\r
6430   }\r
6431   if (second.lastPing != second.lastPong) {\r
6432     DisplayMessage("", "Waiting for second chess program");\r
6433     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
6434     return;\r
6435   }\r
6436   ThawUI();\r
6437   TwoMachinesEvent();\r
6438 }\r
6439 \r
6440 void\r
6441 NextMatchGame P((void))\r
6442 {\r
6443     Reset(FALSE, TRUE);\r
6444     if (*appData.loadGameFile != NULLCHAR) {\r
6445         LoadGameFromFile(appData.loadGameFile,\r
6446                          appData.loadGameIndex,\r
6447                          appData.loadGameFile, FALSE);\r
6448     } else if (*appData.loadPositionFile != NULLCHAR) {\r
6449         LoadPositionFromFile(appData.loadPositionFile,\r
6450                              appData.loadPositionIndex,\r
6451                              appData.loadPositionFile);\r
6452     }\r
6453     TwoMachinesEventIfReady();\r
6454 }\r
6455 \r
6456 void UserAdjudicationEvent( int result )\r
6457 {\r
6458     ChessMove gameResult = GameIsDrawn;\r
6459 \r
6460     if( result > 0 ) {\r
6461         gameResult = WhiteWins;\r
6462     }\r
6463     else if( result < 0 ) {\r
6464         gameResult = BlackWins;\r
6465     }\r
6466 \r
6467     if( gameMode == TwoMachinesPlay ) {\r
6468         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
6469     }\r
6470 }\r
6471 \r
6472 \r
6473 void\r
6474 GameEnds(result, resultDetails, whosays)\r
6475      ChessMove result;\r
6476      char *resultDetails;\r
6477      int whosays;\r
6478 {\r
6479     GameMode nextGameMode;\r
6480     int isIcsGame;\r
6481     char buf[MSG_SIZ];\r
6482 \r
6483     if (appData.debugMode) {\r
6484       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
6485               result, resultDetails ? resultDetails : "(null)", whosays);\r
6486     }\r
6487 \r
6488     if (appData.icsActive && whosays == (GE_ENGINE || whosays >= GE_ENGINE1)) {\r
6489         /* If we are playing on ICS, the server decides when the\r
6490            game is over, but the engine can offer to draw, claim \r
6491            a draw, or resign. \r
6492          */\r
6493 #if ZIPPY\r
6494         if (appData.zippyPlay && first.initDone) {\r
6495             if (result == GameIsDrawn) {\r
6496                 /* In case draw still needs to be claimed */\r
6497                 SendToICS(ics_prefix);\r
6498                 SendToICS("draw\n");\r
6499             } else if (StrCaseStr(resultDetails, "resign")) {\r
6500                 SendToICS(ics_prefix);\r
6501                 SendToICS("resign\n");\r
6502             }\r
6503         }\r
6504 #endif\r
6505         return;\r
6506     }\r
6507 \r
6508     /* If we're loading the game from a file, stop */\r
6509     if (whosays == GE_FILE) {\r
6510       (void) StopLoadGameTimer();\r
6511       gameFileFP = NULL;\r
6512     }\r
6513 \r
6514     /* Cancel draw offers */\r
6515    first.offeredDraw = second.offeredDraw = 0;\r
6516 \r
6517     /* If this is an ICS game, only ICS can really say it's done;\r
6518        if not, anyone can. */\r
6519     isIcsGame = (gameMode == IcsPlayingWhite || \r
6520                  gameMode == IcsPlayingBlack || \r
6521                  gameMode == IcsObserving    || \r
6522                  gameMode == IcsExamining);\r
6523 \r
6524     if (!isIcsGame || whosays == GE_ICS) {\r
6525         /* OK -- not an ICS game, or ICS said it was done */\r
6526         StopClocks();\r
6527     if (appData.debugMode) {\r
6528       fprintf(debugFP, "GameEnds(%d, %s, %d) clock stopped\n",\r
6529               result, resultDetails ? resultDetails : "(null)", whosays);\r
6530     }\r
6531         if (!isIcsGame && !appData.noChessProgram) \r
6532           SetUserThinkingEnables();\r
6533     \r
6534         /* [HGM] if a machine claims the game end we verify this claim */\r
6535         if( appData.testLegality && gameMode == TwoMachinesPlay &&\r
6536             appData.testClaims && whosays >= GE_ENGINE1 ) {\r
6537                 char claimer;\r
6538 \r
6539     if (appData.debugMode) {\r
6540       fprintf(debugFP, "GameEnds(%d, %s, %d) test claims\n",\r
6541               result, resultDetails ? resultDetails : "(null)", whosays);\r
6542     }\r
6543                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
6544                                             first.twoMachinesColor[0] :\r
6545                                             second.twoMachinesColor[0] ;\r
6546                 if( result == WhiteWins && claimer == 'w' ||\r
6547                     result == BlackWins && claimer == 'b' ) {\r
6548                       /* Xboard immediately adjudicates all mates, so win claims must be false */\r
6549                       sprintf(buf, "False win claim: '%s'", resultDetails);\r
6550                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
6551                       resultDetails = buf;\r
6552                 } else\r
6553                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS ) {\r
6554                       /* Draw that was not flagged by Xboard is false */\r
6555                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
6556                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
6557                       resultDetails = buf;\r
6558                 }\r
6559                 /* (Claiming a loss is accepted no questions asked!) */\r
6560         }\r
6561 \r
6562     if (appData.debugMode) {\r
6563       fprintf(debugFP, "GameEnds(%d, %s, %d) after test\n",\r
6564               result, resultDetails ? resultDetails : "(null)", whosays);\r
6565     }\r
6566         if (resultDetails != NULL) {\r
6567             gameInfo.result = result;\r
6568             gameInfo.resultDetails = StrSave(resultDetails);\r
6569 \r
6570             /* Tell program how game ended in case it is learning */\r
6571             if (gameMode == MachinePlaysWhite ||\r
6572                 gameMode == MachinePlaysBlack ||\r
6573                 gameMode == TwoMachinesPlay ||\r
6574                 gameMode == IcsPlayingWhite ||\r
6575                 gameMode == IcsPlayingBlack ||\r
6576                 gameMode == BeginningOfGame) {\r
6577                 char buf[MSG_SIZ];\r
6578                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
6579                         resultDetails);\r
6580                 if (first.pr != NoProc) {\r
6581                     SendToProgram(buf, &first);\r
6582                 }\r
6583                 if (second.pr != NoProc &&\r
6584                     gameMode == TwoMachinesPlay) {\r
6585                     SendToProgram(buf, &second);\r
6586                 }\r
6587             }\r
6588 \r
6589             /* display last move only if game was not loaded from file */\r
6590             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
6591                 DisplayMove(currentMove - 1);\r
6592     \r
6593             if (forwardMostMove != 0) {\r
6594                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
6595                     if (*appData.saveGameFile != NULLCHAR) {\r
6596                         SaveGameToFile(appData.saveGameFile, TRUE);\r
6597                     } else if (appData.autoSaveGames) {\r
6598                         AutoSaveGame();\r
6599                     }\r
6600                     if (*appData.savePositionFile != NULLCHAR) {\r
6601                         SavePositionToFile(appData.savePositionFile);\r
6602                     }\r
6603                 }\r
6604             }\r
6605         }\r
6606 \r
6607         if (appData.icsActive) {\r
6608             if (appData.quietPlay &&\r
6609                 (gameMode == IcsPlayingWhite ||\r
6610                  gameMode == IcsPlayingBlack)) {\r
6611                 SendToICS(ics_prefix);\r
6612                 SendToICS("set shout 1\n");\r
6613             }\r
6614             nextGameMode = IcsIdle;\r
6615             ics_user_moved = FALSE;\r
6616             /* clean up premove.  It's ugly when the game has ended and the\r
6617              * premove highlights are still on the board.\r
6618              */\r
6619             if (gotPremove) {\r
6620               gotPremove = FALSE;\r
6621               ClearPremoveHighlights();\r
6622               DrawPosition(FALSE, boards[currentMove]);\r
6623             }\r
6624             if (whosays == GE_ICS) {\r
6625                 switch (result) {\r
6626                 case WhiteWins:\r
6627                     if (gameMode == IcsPlayingWhite)\r
6628                         PlayIcsWinSound();\r
6629                     else if(gameMode == IcsPlayingBlack)\r
6630                         PlayIcsLossSound();\r
6631                     break;\r
6632                 case BlackWins:\r
6633                     if (gameMode == IcsPlayingBlack)\r
6634                         PlayIcsWinSound();\r
6635                     else if(gameMode == IcsPlayingWhite)\r
6636                         PlayIcsLossSound();\r
6637                     break;\r
6638                 case GameIsDrawn:\r
6639                     PlayIcsDrawSound();\r
6640                     break;\r
6641                 default:\r
6642                     PlayIcsUnfinishedSound();\r
6643                 }\r
6644             }\r
6645         } else if (gameMode == EditGame ||\r
6646                    gameMode == PlayFromGameFile || \r
6647                    gameMode == AnalyzeMode || \r
6648                    gameMode == AnalyzeFile) {\r
6649             nextGameMode = gameMode;\r
6650         } else {\r
6651             nextGameMode = EndOfGame;\r
6652         }\r
6653         pausing = FALSE;\r
6654         ModeHighlight();\r
6655     } else {\r
6656         nextGameMode = gameMode;\r
6657     }\r
6658 \r
6659     if (appData.noChessProgram) {\r
6660         gameMode = nextGameMode;\r
6661         ModeHighlight();\r
6662         return;\r
6663     }\r
6664 \r
6665     if (first.reuse) {\r
6666         /* Put first chess program into idle state */\r
6667         if (first.pr != NoProc &&\r
6668             (gameMode == MachinePlaysWhite ||\r
6669              gameMode == MachinePlaysBlack ||\r
6670              gameMode == TwoMachinesPlay ||\r
6671              gameMode == IcsPlayingWhite ||\r
6672              gameMode == IcsPlayingBlack ||\r
6673              gameMode == BeginningOfGame)) {\r
6674             SendToProgram("force\n", &first);\r
6675             if (first.usePing) {\r
6676               char buf[MSG_SIZ];\r
6677               sprintf(buf, "ping %d\n", ++first.lastPing);\r
6678               SendToProgram(buf, &first);\r
6679             }\r
6680         }\r
6681     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
6682         /* Kill off first chess program */\r
6683         if (first.isr != NULL)\r
6684           RemoveInputSource(first.isr);\r
6685         first.isr = NULL;\r
6686     \r
6687         if (first.pr != NoProc) {\r
6688             ExitAnalyzeMode();\r
6689             DoSleep( appData.delayBeforeQuit );\r
6690             SendToProgram("quit\n", &first);\r
6691             DoSleep( appData.delayAfterQuit );\r
6692             DestroyChildProcess(first.pr, first.useSigterm);\r
6693         }\r
6694         first.pr = NoProc;\r
6695     }\r
6696     if (second.reuse) {\r
6697         /* Put second chess program into idle state */\r
6698         if (second.pr != NoProc &&\r
6699             gameMode == TwoMachinesPlay) {\r
6700             SendToProgram("force\n", &second);\r
6701             if (second.usePing) {\r
6702               char buf[MSG_SIZ];\r
6703               sprintf(buf, "ping %d\n", ++second.lastPing);\r
6704               SendToProgram(buf, &second);\r
6705             }\r
6706         }\r
6707     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
6708         /* Kill off second chess program */\r
6709         if (second.isr != NULL)\r
6710           RemoveInputSource(second.isr);\r
6711         second.isr = NULL;\r
6712     \r
6713         if (second.pr != NoProc) {\r
6714             DoSleep( appData.delayBeforeQuit );\r
6715             SendToProgram("quit\n", &second);\r
6716             DoSleep( appData.delayAfterQuit );\r
6717             DestroyChildProcess(second.pr, second.useSigterm);\r
6718         }\r
6719         second.pr = NoProc;\r
6720     }\r
6721 \r
6722     if (matchMode && gameMode == TwoMachinesPlay) {\r
6723         switch (result) {\r
6724         case WhiteWins:\r
6725           if (first.twoMachinesColor[0] == 'w') {\r
6726             first.matchWins++;\r
6727           } else {\r
6728             second.matchWins++;\r
6729           }\r
6730           break;\r
6731         case BlackWins:\r
6732           if (first.twoMachinesColor[0] == 'b') {\r
6733             first.matchWins++;\r
6734           } else {\r
6735             second.matchWins++;\r
6736           }\r
6737           break;\r
6738         default:\r
6739           break;\r
6740         }\r
6741         if (matchGame < appData.matchGames) {\r
6742             char *tmp;\r
6743             tmp = first.twoMachinesColor;\r
6744             first.twoMachinesColor = second.twoMachinesColor;\r
6745             second.twoMachinesColor = tmp;\r
6746             gameMode = nextGameMode;\r
6747             matchGame++;\r
6748             if(appData.matchPause>10000 || appData.matchPause<10)\r
6749                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
6750             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
6751             return;\r
6752         } else {\r
6753             char buf[MSG_SIZ];\r
6754             gameMode = nextGameMode;\r
6755             sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",\r
6756                     first.tidy, second.tidy,\r
6757                     first.matchWins, second.matchWins,\r
6758                     appData.matchGames - (first.matchWins + second.matchWins));\r
6759             DisplayFatalError(buf, 0, 0);\r
6760         }\r
6761     }\r
6762     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
6763         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
6764       ExitAnalyzeMode();\r
6765     gameMode = nextGameMode;\r
6766     ModeHighlight();\r
6767 }\r
6768 \r
6769 /* Assumes program was just initialized (initString sent).\r
6770    Leaves program in force mode. */\r
6771 void\r
6772 FeedMovesToProgram(cps, upto) \r
6773      ChessProgramState *cps;\r
6774      int upto;\r
6775 {\r
6776     int i;\r
6777     \r
6778     if (appData.debugMode)\r
6779       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
6780               startedFromSetupPosition ? "position and " : "",\r
6781               backwardMostMove, upto, cps->which);\r
6782     SendToProgram("force\n", cps);\r
6783     if (startedFromSetupPosition) {\r
6784         SendBoard(cps, backwardMostMove);\r
6785     }\r
6786     for (i = backwardMostMove; i < upto; i++) {\r
6787         SendMoveToProgram(i, cps);\r
6788     }\r
6789 }\r
6790 \r
6791 \r
6792 void\r
6793 ResurrectChessProgram()\r
6794 {\r
6795      /* The chess program may have exited.\r
6796         If so, restart it and feed it all the moves made so far. */\r
6797 \r
6798     if (appData.noChessProgram || first.pr != NoProc) return;\r
6799     \r
6800     StartChessProgram(&first);\r
6801     InitChessProgram(&first);\r
6802     FeedMovesToProgram(&first, currentMove);\r
6803 \r
6804     if (!first.sendTime) {\r
6805         /* can't tell gnuchess what its clock should read,\r
6806            so we bow to its notion. */\r
6807         ResetClocks();\r
6808         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
6809         timeRemaining[1][currentMove] = blackTimeRemaining;\r
6810     }\r
6811 \r
6812     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
6813         first.analysisSupport) {\r
6814       SendToProgram("analyze\n", &first);\r
6815       first.analyzing = TRUE;\r
6816     }\r
6817 }\r
6818 \r
6819 /*\r
6820  * Button procedures\r
6821  */\r
6822 void\r
6823 Reset(redraw, init)\r
6824      int redraw, init;\r
6825 {\r
6826     int i;\r
6827 \r
6828     if (appData.debugMode) {\r
6829         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
6830                 redraw, init, gameMode);\r
6831     }\r
6832 \r
6833     pausing = pauseExamInvalid = FALSE;\r
6834     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
6835     firstMove = TRUE;\r
6836     whiteFlag = blackFlag = FALSE;\r
6837     userOfferedDraw = FALSE;\r
6838     hintRequested = bookRequested = FALSE;\r
6839     first.maybeThinking = FALSE;\r
6840     second.maybeThinking = FALSE;\r
6841     thinkOutput[0] = NULLCHAR;\r
6842     lastHint[0] = NULLCHAR;\r
6843     ClearGameInfo(&gameInfo);\r
6844     gameInfo.variant = StringToVariant(appData.variant);\r
6845     ics_user_moved = ics_clock_paused = FALSE;\r
6846     ics_getting_history = H_FALSE;\r
6847     ics_gamenum = -1;\r
6848     white_holding[0] = black_holding[0] = NULLCHAR;\r
6849     ClearProgramStats();\r
6850     \r
6851     ResetFrontEnd();\r
6852     ClearHighlights();\r
6853     flipView = appData.flipView;\r
6854     ClearPremoveHighlights();\r
6855     gotPremove = FALSE;\r
6856     alarmSounded = FALSE;\r
6857 \r
6858     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
6859     ExitAnalyzeMode();\r
6860     gameMode = BeginningOfGame;\r
6861     ModeHighlight();\r
6862     InitPosition(redraw);\r
6863     for (i = 0; i < MAX_MOVES; i++) {\r
6864         if (commentList[i] != NULL) {\r
6865             free(commentList[i]);\r
6866             commentList[i] = NULL;\r
6867         }\r
6868     }\r
6869     ResetClocks();\r
6870     timeRemaining[0][0] = whiteTimeRemaining;\r
6871     timeRemaining[1][0] = blackTimeRemaining;\r
6872     if (first.pr == NULL) {\r
6873         StartChessProgram(&first);\r
6874     }\r
6875     if (init) InitChessProgram(&first);\r
6876     DisplayTitle("");\r
6877     DisplayMessage("", "");\r
6878     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
6879 }\r
6880 \r
6881 void\r
6882 AutoPlayGameLoop()\r
6883 {\r
6884     for (;;) {\r
6885         if (!AutoPlayOneMove())\r
6886           return;\r
6887         if (matchMode || appData.timeDelay == 0)\r
6888           continue;\r
6889         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
6890           return;\r
6891         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
6892         break;\r
6893     }\r
6894 }\r
6895 \r
6896 \r
6897 int\r
6898 AutoPlayOneMove()\r
6899 {\r
6900     int fromX, fromY, toX, toY;\r
6901 \r
6902     if (appData.debugMode) {\r
6903       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
6904     }\r
6905 \r
6906     if (gameMode != PlayFromGameFile)\r
6907       return FALSE;\r
6908 \r
6909     if (currentMove >= forwardMostMove) {\r
6910       gameMode = EditGame;\r
6911       ModeHighlight();\r
6912 \r
6913       /* [AS] Clear current move marker at the end of a game */\r
6914       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
6915 \r
6916       return FALSE;\r
6917     }\r
6918     \r
6919     toX = moveList[currentMove][2] - AAA;\r
6920     toY = moveList[currentMove][3] - ONE;\r
6921 \r
6922     if (moveList[currentMove][1] == '@') {\r
6923         if (appData.highlightLastMove) {\r
6924             SetHighlights(-1, -1, toX, toY);\r
6925         }\r
6926     } else {\r
6927         fromX = moveList[currentMove][0] - AAA;\r
6928         fromY = moveList[currentMove][1] - ONE;\r
6929 \r
6930         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
6931 \r
6932         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
6933 \r
6934         if (appData.highlightLastMove) {\r
6935             SetHighlights(fromX, fromY, toX, toY);\r
6936         }\r
6937     }\r
6938     DisplayMove(currentMove);\r
6939     SendMoveToProgram(currentMove++, &first);\r
6940     DisplayBothClocks();\r
6941     DrawPosition(FALSE, boards[currentMove]);\r
6942     if (commentList[currentMove] != NULL) {\r
6943         DisplayComment(currentMove - 1, commentList[currentMove]);\r
6944     }\r
6945     return TRUE;\r
6946 }\r
6947 \r
6948 \r
6949 int\r
6950 LoadGameOneMove(readAhead)\r
6951      ChessMove readAhead;\r
6952 {\r
6953     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
6954     char promoChar = NULLCHAR;\r
6955     ChessMove moveType;\r
6956     char move[MSG_SIZ];\r
6957     char *p, *q;\r
6958     \r
6959     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
6960         gameMode != AnalyzeMode && gameMode != Training) {\r
6961         gameFileFP = NULL;\r
6962         return FALSE;\r
6963     }\r
6964     \r
6965     yyboardindex = forwardMostMove;\r
6966     if (readAhead != (ChessMove)0) {\r
6967       moveType = readAhead;\r
6968     } else {\r
6969       if (gameFileFP == NULL)\r
6970           return FALSE;\r
6971       moveType = (ChessMove) yylex();\r
6972     }\r
6973     \r
6974     done = FALSE;\r
6975     switch (moveType) {\r
6976       case Comment:\r
6977         if (appData.debugMode) \r
6978           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
6979         p = yy_text;\r
6980         if (*p == '{' || *p == '[' || *p == '(') {\r
6981             p[strlen(p) - 1] = NULLCHAR;\r
6982             p++;\r
6983         }\r
6984 \r
6985         /* append the comment but don't display it */\r
6986         while (*p == '\n') p++;\r
6987         AppendComment(currentMove, p);\r
6988         return TRUE;\r
6989 \r
6990       case WhiteCapturesEnPassant:\r
6991       case BlackCapturesEnPassant:\r
6992       case WhitePromotionChancellor:\r
6993       case BlackPromotionChancellor:\r
6994       case WhitePromotionArchbishop:\r
6995       case BlackPromotionArchbishop:\r
6996       case WhitePromotionQueen:\r
6997       case BlackPromotionQueen:\r
6998       case WhitePromotionRook:\r
6999       case BlackPromotionRook:\r
7000       case WhitePromotionBishop:\r
7001       case BlackPromotionBishop:\r
7002       case WhitePromotionKnight:\r
7003       case BlackPromotionKnight:\r
7004       case WhitePromotionKing:\r
7005       case BlackPromotionKing:\r
7006       case NormalMove:\r
7007       case WhiteKingSideCastle:\r
7008       case WhiteQueenSideCastle:\r
7009       case BlackKingSideCastle:\r
7010       case BlackQueenSideCastle:\r
7011       case WhiteKingSideCastleWild:\r
7012       case WhiteQueenSideCastleWild:\r
7013       case BlackKingSideCastleWild:\r
7014       case BlackQueenSideCastleWild:\r
7015       /* PUSH Fabien */\r
7016       case WhiteHSideCastleFR:\r
7017       case WhiteASideCastleFR:\r
7018       case BlackHSideCastleFR:\r
7019       case BlackASideCastleFR:\r
7020       /* POP Fabien */\r
7021         if (appData.debugMode)\r
7022           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
7023         fromX = currentMoveString[0] - AAA;\r
7024         fromY = currentMoveString[1] - ONE;\r
7025         toX = currentMoveString[2] - AAA;\r
7026         toY = currentMoveString[3] - ONE;\r
7027         promoChar = currentMoveString[4];\r
7028         break;\r
7029 \r
7030       case WhiteDrop:\r
7031       case BlackDrop:\r
7032         if (appData.debugMode)\r
7033           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
7034         fromX = moveType == WhiteDrop ?\r
7035           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
7036         (int) CharToPiece(ToLower(currentMoveString[0]));\r
7037         fromY = DROP_RANK;\r
7038         toX = currentMoveString[2] - AAA;\r
7039         toY = currentMoveString[3] - ONE;\r
7040         break;\r
7041 \r
7042       case WhiteWins:\r
7043       case BlackWins:\r
7044       case GameIsDrawn:\r
7045       case GameUnfinished:\r
7046         if (appData.debugMode)\r
7047           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
7048         p = strchr(yy_text, '{');\r
7049         if (p == NULL) p = strchr(yy_text, '(');\r
7050         if (p == NULL) {\r
7051             p = yy_text;\r
7052             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
7053         } else {\r
7054             q = strchr(p, *p == '{' ? '}' : ')');\r
7055             if (q != NULL) *q = NULLCHAR;\r
7056             p++;\r
7057         }\r
7058         GameEnds(moveType, p, GE_FILE);\r
7059         done = TRUE;\r
7060         if (cmailMsgLoaded) {\r
7061             ClearHighlights();\r
7062             flipView = WhiteOnMove(currentMove);\r
7063             if (moveType == GameUnfinished) flipView = !flipView;\r
7064             if (appData.debugMode)\r
7065               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
7066         }\r
7067         break;\r
7068 \r
7069       case (ChessMove) 0:       /* end of file */\r
7070         if (appData.debugMode)\r
7071           fprintf(debugFP, "Parser hit end of file\n");\r
7072         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
7073                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
7074           case MT_NONE:\r
7075           case MT_CHECK:\r
7076             break;\r
7077           case MT_CHECKMATE:\r
7078             if (WhiteOnMove(currentMove)) {\r
7079                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
7080             } else {\r
7081                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
7082             }\r
7083             break;\r
7084           case MT_STALEMATE:\r
7085             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
7086             break;\r
7087         }\r
7088         done = TRUE;\r
7089         break;\r
7090 \r
7091       case MoveNumberOne:\r
7092         if (lastLoadGameStart == GNUChessGame) {\r
7093             /* GNUChessGames have numbers, but they aren't move numbers */\r
7094             if (appData.debugMode)\r
7095               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
7096                       yy_text, (int) moveType);\r
7097             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
7098         }\r
7099         /* else fall thru */\r
7100 \r
7101       case XBoardGame:\r
7102       case GNUChessGame:\r
7103       case PGNTag:\r
7104         /* Reached start of next game in file */\r
7105         if (appData.debugMode)\r
7106           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
7107         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
7108                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
7109           case MT_NONE:\r
7110           case MT_CHECK:\r
7111             break;\r
7112           case MT_CHECKMATE:\r
7113             if (WhiteOnMove(currentMove)) {\r
7114                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
7115             } else {\r
7116                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
7117             }\r
7118             break;\r
7119           case MT_STALEMATE:\r
7120             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
7121             break;\r
7122         }\r
7123         done = TRUE;\r
7124         break;\r
7125 \r
7126       case PositionDiagram:     /* should not happen; ignore */\r
7127       case ElapsedTime:         /* ignore */\r
7128       case NAG:                 /* ignore */\r
7129         if (appData.debugMode)\r
7130           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
7131                   yy_text, (int) moveType);\r
7132         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
7133 \r
7134       case IllegalMove:\r
7135         if (appData.testLegality) {\r
7136             if (appData.debugMode)\r
7137               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
7138             sprintf(move, "Illegal move: %d.%s%s",\r
7139                     (forwardMostMove / 2) + 1,\r
7140                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
7141             DisplayError(move, 0);\r
7142             done = TRUE;\r
7143         } else {\r
7144             if (appData.debugMode)\r
7145               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
7146                       yy_text, currentMoveString);\r
7147             fromX = currentMoveString[0] - AAA;\r
7148             fromY = currentMoveString[1] - ONE;\r
7149             toX = currentMoveString[2] - AAA;\r
7150             toY = currentMoveString[3] - ONE;\r
7151             promoChar = currentMoveString[4];\r
7152         }\r
7153         break;\r
7154 \r
7155       case AmbiguousMove:\r
7156         if (appData.debugMode)\r
7157           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
7158         sprintf(move, "Ambiguous move: %d.%s%s",\r
7159                 (forwardMostMove / 2) + 1,\r
7160                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
7161         DisplayError(move, 0);\r
7162         done = TRUE;\r
7163         break;\r
7164 \r
7165       default:\r
7166       case ImpossibleMove:\r
7167         if (appData.debugMode)\r
7168           fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);\r
7169         sprintf(move, "Illegal move: %d.%s%s",\r
7170                 (forwardMostMove / 2) + 1,\r
7171                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
7172         DisplayError(move, 0);\r
7173         done = TRUE;\r
7174         break;\r
7175     }\r
7176 \r
7177     if (done) {\r
7178         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
7179             DrawPosition(FALSE, boards[currentMove]);\r
7180             DisplayBothClocks();\r
7181             if (!appData.matchMode && commentList[currentMove] != NULL)\r
7182               DisplayComment(currentMove - 1, commentList[currentMove]);\r
7183         }\r
7184         (void) StopLoadGameTimer();\r
7185         gameFileFP = NULL;\r
7186         cmailOldMove = forwardMostMove;\r
7187         return FALSE;\r
7188     } else {\r
7189         /* currentMoveString is set as a side-effect of yylex */\r
7190         strcat(currentMoveString, "\n");\r
7191         strcpy(moveList[forwardMostMove], currentMoveString);\r
7192         \r
7193         thinkOutput[0] = NULLCHAR;\r
7194         MakeMove(fromX, fromY, toX, toY, promoChar);\r
7195         currentMove = forwardMostMove;\r
7196         return TRUE;\r
7197     }\r
7198 }\r
7199 \r
7200 /* Load the nth game from the given file */\r
7201 int\r
7202 LoadGameFromFile(filename, n, title, useList)\r
7203      char *filename;\r
7204      int n;\r
7205      char *title;\r
7206      /*Boolean*/ int useList;\r
7207 {\r
7208     FILE *f;\r
7209     char buf[MSG_SIZ];\r
7210 \r
7211     if (strcmp(filename, "-") == 0) {\r
7212         f = stdin;\r
7213         title = "stdin";\r
7214     } else {\r
7215         f = fopen(filename, "rb");\r
7216         if (f == NULL) {\r
7217             sprintf(buf, "Can't open \"%s\"", filename);\r
7218             DisplayError(buf, errno);\r
7219             return FALSE;\r
7220         }\r
7221     }\r
7222     if (fseek(f, 0, 0) == -1) {\r
7223         /* f is not seekable; probably a pipe */\r
7224         useList = FALSE;\r
7225     }\r
7226     if (useList && n == 0) {\r
7227         int error = GameListBuild(f);\r
7228         if (error) {\r
7229             DisplayError("Cannot build game list", error);\r
7230         } else if (!ListEmpty(&gameList) &&\r
7231                    ((ListGame *) gameList.tailPred)->number > 1) {\r
7232             GameListPopUp(f, title);\r
7233             return TRUE;\r
7234         }\r
7235         GameListDestroy();\r
7236         n = 1;\r
7237     }\r
7238     if (n == 0) n = 1;\r
7239     return LoadGame(f, n, title, FALSE);\r
7240 }\r
7241 \r
7242 \r
7243 void\r
7244 MakeRegisteredMove()\r
7245 {\r
7246     int fromX, fromY, toX, toY;\r
7247     char promoChar;\r
7248     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
7249         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
7250           case CMAIL_MOVE:\r
7251           case CMAIL_DRAW:\r
7252             if (appData.debugMode)\r
7253               fprintf(debugFP, "Restoring %s for game %d\n",\r
7254                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
7255     \r
7256             thinkOutput[0] = NULLCHAR;\r
7257             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
7258             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
7259             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
7260             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
7261             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
7262             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
7263             MakeMove(fromX, fromY, toX, toY, promoChar);\r
7264             ShowMove(fromX, fromY, toX, toY);\r
7265               \r
7266             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
7267                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
7268               case MT_NONE:\r
7269               case MT_CHECK:\r
7270                 break;\r
7271                 \r
7272               case MT_CHECKMATE:\r
7273                 if (WhiteOnMove(currentMove)) {\r
7274                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
7275                 } else {\r
7276                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
7277                 }\r
7278                 break;\r
7279                 \r
7280               case MT_STALEMATE:\r
7281                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
7282                 break;\r
7283             }\r
7284 \r
7285             break;\r
7286             \r
7287           case CMAIL_RESIGN:\r
7288             if (WhiteOnMove(currentMove)) {\r
7289                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
7290             } else {\r
7291                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
7292             }\r
7293             break;\r
7294             \r
7295           case CMAIL_ACCEPT:\r
7296             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
7297             break;\r
7298               \r
7299           default:\r
7300             break;\r
7301         }\r
7302     }\r
7303 \r
7304     return;\r
7305 }\r
7306 \r
7307 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
7308 int\r
7309 CmailLoadGame(f, gameNumber, title, useList)\r
7310      FILE *f;\r
7311      int gameNumber;\r
7312      char *title;\r
7313      int useList;\r
7314 {\r
7315     int retVal;\r
7316 \r
7317     if (gameNumber > nCmailGames) {\r
7318         DisplayError("No more games in this message", 0);\r
7319         return FALSE;\r
7320     }\r
7321     if (f == lastLoadGameFP) {\r
7322         int offset = gameNumber - lastLoadGameNumber;\r
7323         if (offset == 0) {\r
7324             cmailMsg[0] = NULLCHAR;\r
7325             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
7326                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
7327                 nCmailMovesRegistered--;\r
7328             }\r
7329             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
7330             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
7331                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
7332             }\r
7333         } else {\r
7334             if (! RegisterMove()) return FALSE;\r
7335         }\r
7336     }\r
7337 \r
7338     retVal = LoadGame(f, gameNumber, title, useList);\r
7339 \r
7340     /* Make move registered during previous look at this game, if any */\r
7341     MakeRegisteredMove();\r
7342 \r
7343     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
7344         commentList[currentMove]\r
7345           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
7346         DisplayComment(currentMove - 1, commentList[currentMove]);\r
7347     }\r
7348 \r
7349     return retVal;\r
7350 }\r
7351 \r
7352 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
7353 int\r
7354 ReloadGame(offset)\r
7355      int offset;\r
7356 {\r
7357     int gameNumber = lastLoadGameNumber + offset;\r
7358     if (lastLoadGameFP == NULL) {\r
7359         DisplayError("No game has been loaded yet", 0);\r
7360         return FALSE;\r
7361     }\r
7362     if (gameNumber <= 0) {\r
7363         DisplayError("Can't back up any further", 0);\r
7364         return FALSE;\r
7365     }\r
7366     if (cmailMsgLoaded) {\r
7367         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
7368                              lastLoadGameTitle, lastLoadGameUseList);\r
7369     } else {\r
7370         return LoadGame(lastLoadGameFP, gameNumber,\r
7371                         lastLoadGameTitle, lastLoadGameUseList);\r
7372     }\r
7373 }\r
7374 \r
7375 \r
7376 \r
7377 /* Load the nth game from open file f */\r
7378 int\r
7379 LoadGame(f, gameNumber, title, useList)\r
7380      FILE *f;\r
7381      int gameNumber;\r
7382      char *title;\r
7383      int useList;\r
7384 {\r
7385     ChessMove cm;\r
7386     char buf[MSG_SIZ];\r
7387     int gn = gameNumber;\r
7388     ListGame *lg = NULL;\r
7389     int numPGNTags = 0;\r
7390     int err;\r
7391     GameMode oldGameMode;\r
7392 \r
7393     if (appData.debugMode) \r
7394         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
7395 \r
7396     if (gameMode == Training )\r
7397         SetTrainingModeOff();\r
7398 \r
7399     oldGameMode = gameMode;\r
7400     if (gameMode != BeginningOfGame) {\r
7401       Reset(FALSE, TRUE);\r
7402     }\r
7403 \r
7404     gameFileFP = f;\r
7405     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
7406         fclose(lastLoadGameFP);\r
7407     }\r
7408 \r
7409     if (useList) {\r
7410         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
7411         \r
7412         if (lg) {\r
7413             fseek(f, lg->offset, 0);\r
7414             GameListHighlight(gameNumber);\r
7415             gn = 1;\r
7416         }\r
7417         else {\r
7418             DisplayError("Game number out of range", 0);\r
7419             return FALSE;\r
7420         }\r
7421     } else {\r
7422         GameListDestroy();\r
7423         if (fseek(f, 0, 0) == -1) {\r
7424             if (f == lastLoadGameFP ?\r
7425                 gameNumber == lastLoadGameNumber + 1 :\r
7426                 gameNumber == 1) {\r
7427                 gn = 1;\r
7428             } else {\r
7429                 DisplayError("Can't seek on game file", 0);\r
7430                 return FALSE;\r
7431             }\r
7432         }\r
7433     }\r
7434     lastLoadGameFP = f;\r
7435     lastLoadGameNumber = gameNumber;\r
7436     strcpy(lastLoadGameTitle, title);\r
7437     lastLoadGameUseList = useList;\r
7438 \r
7439     yynewfile(f);\r
7440 \r
7441 \r
7442     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
7443         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
7444                 lg->gameInfo.black);\r
7445             DisplayTitle(buf);\r
7446     } else if (*title != NULLCHAR) {\r
7447         if (gameNumber > 1) {\r
7448             sprintf(buf, "%s %d", title, gameNumber);\r
7449             DisplayTitle(buf);\r
7450         } else {\r
7451             DisplayTitle(title);\r
7452         }\r
7453     }\r
7454 \r
7455     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
7456         gameMode = PlayFromGameFile;\r
7457         ModeHighlight();\r
7458     }\r
7459 \r
7460     currentMove = forwardMostMove = backwardMostMove = 0;\r
7461     CopyBoard(boards[0], initialPosition);\r
7462     StopClocks();\r
7463 \r
7464     /*\r
7465      * Skip the first gn-1 games in the file.\r
7466      * Also skip over anything that precedes an identifiable \r
7467      * start of game marker, to avoid being confused by \r
7468      * garbage at the start of the file.  Currently \r
7469      * recognized start of game markers are the move number "1",\r
7470      * the pattern "gnuchess .* game", the pattern\r
7471      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
7472      * A game that starts with one of the latter two patterns\r
7473      * will also have a move number 1, possibly\r
7474      * following a position diagram.\r
7475      * 5-4-02: Let's try being more lenient and allowing a game to\r
7476      * start with an unnumbered move.  Does that break anything?\r
7477      */\r
7478     cm = lastLoadGameStart = (ChessMove) 0;\r
7479     while (gn > 0) {\r
7480         yyboardindex = forwardMostMove;\r
7481         cm = (ChessMove) yylex();\r
7482         switch (cm) {\r
7483           case (ChessMove) 0:\r
7484             if (cmailMsgLoaded) {\r
7485                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
7486             } else {\r
7487                 Reset(TRUE, TRUE);\r
7488                 DisplayError("Game not found in file", 0);\r
7489             }\r
7490             return FALSE;\r
7491 \r
7492           case GNUChessGame:\r
7493           case XBoardGame:\r
7494             gn--;\r
7495             lastLoadGameStart = cm;\r
7496             break;\r
7497             \r
7498           case MoveNumberOne:\r
7499             switch (lastLoadGameStart) {\r
7500               case GNUChessGame:\r
7501               case XBoardGame:\r
7502               case PGNTag:\r
7503                 break;\r
7504               case MoveNumberOne:\r
7505               case (ChessMove) 0:\r
7506                 gn--;           /* count this game */\r
7507                 lastLoadGameStart = cm;\r
7508                 break;\r
7509               default:\r
7510                 /* impossible */\r
7511                 break;\r
7512             }\r
7513             break;\r
7514 \r
7515           case PGNTag:\r
7516             switch (lastLoadGameStart) {\r
7517               case GNUChessGame:\r
7518               case PGNTag:\r
7519               case MoveNumberOne:\r
7520               case (ChessMove) 0:\r
7521                 gn--;           /* count this game */\r
7522                 lastLoadGameStart = cm;\r
7523                 break;\r
7524               case XBoardGame:\r
7525                 lastLoadGameStart = cm; /* game counted already */\r
7526                 break;\r
7527               default:\r
7528                 /* impossible */\r
7529                 break;\r
7530             }\r
7531             if (gn > 0) {\r
7532                 do {\r
7533                     yyboardindex = forwardMostMove;\r
7534                     cm = (ChessMove) yylex();\r
7535                 } while (cm == PGNTag || cm == Comment);\r
7536             }\r
7537             break;\r
7538 \r
7539           case WhiteWins:\r
7540           case BlackWins:\r
7541           case GameIsDrawn:\r
7542             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
7543                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
7544                     != CMAIL_OLD_RESULT) {\r
7545                     nCmailResults ++ ;\r
7546                     cmailResult[  CMAIL_MAX_GAMES\r
7547                                 - gn - 1] = CMAIL_OLD_RESULT;\r
7548                 }\r
7549             }\r
7550             break;\r
7551 \r
7552           case NormalMove:\r
7553             /* Only a NormalMove can be at the start of a game\r
7554              * without a position diagram. */\r
7555             if (lastLoadGameStart == (ChessMove) 0) {\r
7556               gn--;\r
7557               lastLoadGameStart = MoveNumberOne;\r
7558             }\r
7559             break;\r
7560 \r
7561           default:\r
7562             break;\r
7563         }\r
7564     }\r
7565     \r
7566     if (appData.debugMode)\r
7567       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
7568 \r
7569     if (cm == XBoardGame) {\r
7570         /* Skip any header junk before position diagram and/or move 1 */\r
7571         for (;;) {\r
7572             yyboardindex = forwardMostMove;\r
7573             cm = (ChessMove) yylex();\r
7574 \r
7575             if (cm == (ChessMove) 0 ||\r
7576                 cm == GNUChessGame || cm == XBoardGame) {\r
7577                 /* Empty game; pretend end-of-file and handle later */\r
7578                 cm = (ChessMove) 0;\r
7579                 break;\r
7580             }\r
7581 \r
7582             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
7583                 cm == PGNTag || cm == Comment)\r
7584               break;\r
7585         }\r
7586     } else if (cm == GNUChessGame) {\r
7587         if (gameInfo.event != NULL) {\r
7588             free(gameInfo.event);\r
7589         }\r
7590         gameInfo.event = StrSave(yy_text);\r
7591     }   \r
7592 \r
7593     startedFromSetupPosition = FALSE;\r
7594     while (cm == PGNTag) {\r
7595         if (appData.debugMode) \r
7596           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
7597         err = ParsePGNTag(yy_text, &gameInfo);\r
7598         if (!err) numPGNTags++;\r
7599 \r
7600         if (gameInfo.fen != NULL) {\r
7601           Board initial_position;\r
7602           startedFromSetupPosition = TRUE;\r
7603           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
7604             Reset(TRUE, TRUE);\r
7605             DisplayError("Bad FEN position in file", 0);\r
7606             return FALSE;\r
7607           }\r
7608           CopyBoard(boards[0], initial_position);\r
7609           /* [HGM] copy FEN attributes as well */\r
7610           {   int i;\r
7611               initialRulePlies = FENrulePlies;\r
7612               epStatus[0] = FENepStatus;\r
7613               for( i=0; i< nrCastlingRights; i++ )\r
7614                   castlingRights[0][i] = FENcastlingRights[i];\r
7615           }\r
7616           if (blackPlaysFirst) {\r
7617             currentMove = forwardMostMove = backwardMostMove = 1;\r
7618             CopyBoard(boards[1], initial_position);\r
7619             strcpy(moveList[0], "");\r
7620             strcpy(parseList[0], "");\r
7621             timeRemaining[0][1] = whiteTimeRemaining;\r
7622             timeRemaining[1][1] = blackTimeRemaining;\r
7623             if (commentList[0] != NULL) {\r
7624               commentList[1] = commentList[0];\r
7625               commentList[0] = NULL;\r
7626             }\r
7627           } else {\r
7628             currentMove = forwardMostMove = backwardMostMove = 0;\r
7629           }\r
7630           yyboardindex = forwardMostMove;\r
7631           free(gameInfo.fen);\r
7632           gameInfo.fen = NULL;\r
7633         }\r
7634 \r
7635         yyboardindex = forwardMostMove;\r
7636         cm = (ChessMove) yylex();\r
7637 \r
7638         /* Handle comments interspersed among the tags */\r
7639         while (cm == Comment) {\r
7640             char *p;\r
7641             if (appData.debugMode) \r
7642               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
7643             p = yy_text;\r
7644             if (*p == '{' || *p == '[' || *p == '(') {\r
7645                 p[strlen(p) - 1] = NULLCHAR;\r
7646                 p++;\r
7647             }\r
7648             while (*p == '\n') p++;\r
7649             AppendComment(currentMove, p);\r
7650             yyboardindex = forwardMostMove;\r
7651             cm = (ChessMove) yylex();\r
7652         }\r
7653     }\r
7654 \r
7655     /* don't rely on existence of Event tag since if game was\r
7656      * pasted from clipboard the Event tag may not exist\r
7657      */\r
7658     if (numPGNTags > 0){\r
7659         char *tags;\r
7660         if (gameInfo.variant == VariantNormal) {\r
7661           gameInfo.variant = StringToVariant(gameInfo.event);\r
7662         }\r
7663         if (!matchMode) {\r
7664           if( appData.autoDisplayTags ) {\r
7665             tags = PGNTags(&gameInfo);\r
7666             TagsPopUp(tags, CmailMsg());\r
7667             free(tags);\r
7668           }\r
7669         }\r
7670     } else {\r
7671         /* Make something up, but don't display it now */\r
7672         SetGameInfo();\r
7673         TagsPopDown();\r
7674     }\r
7675 \r
7676     if (cm == PositionDiagram) {\r
7677         int i, j;\r
7678         char *p;\r
7679         Board initial_position;\r
7680 \r
7681         if (appData.debugMode)\r
7682           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
7683 \r
7684         if (!startedFromSetupPosition) {\r
7685             p = yy_text;\r
7686             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
7687               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
7688                 switch (*p) {\r
7689                   case '[':\r
7690                   case '-':\r
7691                   case ' ':\r
7692                   case '\t':\r
7693                   case '\n':\r
7694                   case '\r':\r
7695                     break;\r
7696                   default:\r
7697                     initial_position[i][j++] = CharToPiece(*p);\r
7698                     break;\r
7699                 }\r
7700             while (*p == ' ' || *p == '\t' ||\r
7701                    *p == '\n' || *p == '\r') p++;\r
7702         \r
7703             if (strncmp(p, "black", strlen("black"))==0)\r
7704               blackPlaysFirst = TRUE;\r
7705             else\r
7706               blackPlaysFirst = FALSE;\r
7707             startedFromSetupPosition = TRUE;\r
7708         \r
7709             CopyBoard(boards[0], initial_position);\r
7710             if (blackPlaysFirst) {\r
7711                 currentMove = forwardMostMove = backwardMostMove = 1;\r
7712                 CopyBoard(boards[1], initial_position);\r
7713                 strcpy(moveList[0], "");\r
7714                 strcpy(parseList[0], "");\r
7715                 timeRemaining[0][1] = whiteTimeRemaining;\r
7716                 timeRemaining[1][1] = blackTimeRemaining;\r
7717                 if (commentList[0] != NULL) {\r
7718                     commentList[1] = commentList[0];\r
7719                     commentList[0] = NULL;\r
7720                 }\r
7721             } else {\r
7722                 currentMove = forwardMostMove = backwardMostMove = 0;\r
7723             }\r
7724         }\r
7725         yyboardindex = forwardMostMove;\r
7726         cm = (ChessMove) yylex();\r
7727     }\r
7728 \r
7729     if (first.pr == NoProc) {\r
7730         StartChessProgram(&first);\r
7731     }\r
7732     InitChessProgram(&first);\r
7733     SendToProgram("force\n", &first);\r
7734     if (startedFromSetupPosition) {\r
7735         SendBoard(&first, forwardMostMove);\r
7736         DisplayBothClocks();\r
7737     }      \r
7738 \r
7739     while (cm == Comment) {\r
7740         char *p;\r
7741         if (appData.debugMode) \r
7742           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
7743         p = yy_text;\r
7744         if (*p == '{' || *p == '[' || *p == '(') {\r
7745             p[strlen(p) - 1] = NULLCHAR;\r
7746             p++;\r
7747         }\r
7748         while (*p == '\n') p++;\r
7749         AppendComment(currentMove, p);\r
7750         yyboardindex = forwardMostMove;\r
7751         cm = (ChessMove) yylex();\r
7752     }\r
7753 \r
7754     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
7755         cm == WhiteWins || cm == BlackWins ||\r
7756         cm == GameIsDrawn || cm == GameUnfinished) {\r
7757         DisplayMessage("", "No moves in game");\r
7758         if (cmailMsgLoaded) {\r
7759             if (appData.debugMode)\r
7760               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
7761             ClearHighlights();\r
7762             flipView = FALSE;\r
7763         }\r
7764         DrawPosition(FALSE, boards[currentMove]);\r
7765         DisplayBothClocks();\r
7766         gameMode = EditGame;\r
7767         ModeHighlight();\r
7768         gameFileFP = NULL;\r
7769         cmailOldMove = 0;\r
7770         return TRUE;\r
7771     }\r
7772 \r
7773     if (commentList[currentMove] != NULL) {\r
7774       if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
7775         DisplayComment(currentMove - 1, commentList[currentMove]);\r
7776       }\r
7777     }\r
7778     if (!matchMode && appData.timeDelay != 0) \r
7779       DrawPosition(FALSE, boards[currentMove]);\r
7780 \r
7781     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
7782       programStats.ok_to_send = 1;\r
7783     }\r
7784 \r
7785     /* if the first token after the PGN tags is a move\r
7786      * and not move number 1, retrieve it from the parser \r
7787      */\r
7788     if (cm != MoveNumberOne)\r
7789         LoadGameOneMove(cm);\r
7790 \r
7791     /* load the remaining moves from the file */\r
7792     while (LoadGameOneMove((ChessMove)0)) {\r
7793       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
7794       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
7795     }\r
7796 \r
7797     /* rewind to the start of the game */\r
7798     currentMove = backwardMostMove;\r
7799 \r
7800     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
7801 \r
7802     if (oldGameMode == AnalyzeFile ||\r
7803         oldGameMode == AnalyzeMode) {\r
7804       AnalyzeFileEvent();\r
7805     }\r
7806 \r
7807     if (matchMode || appData.timeDelay == 0) {\r
7808       ToEndEvent();\r
7809       gameMode = EditGame;\r
7810       ModeHighlight();\r
7811     } else if (appData.timeDelay > 0) {\r
7812       AutoPlayGameLoop();\r
7813     }\r
7814 \r
7815     if (appData.debugMode) \r
7816         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
7817     return TRUE;\r
7818 }\r
7819 \r
7820 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
7821 int\r
7822 ReloadPosition(offset)\r
7823      int offset;\r
7824 {\r
7825     int positionNumber = lastLoadPositionNumber + offset;\r
7826     if (lastLoadPositionFP == NULL) {\r
7827         DisplayError("No position has been loaded yet", 0);\r
7828         return FALSE;\r
7829     }\r
7830     if (positionNumber <= 0) {\r
7831         DisplayError("Can't back up any further", 0);\r
7832         return FALSE;\r
7833     }\r
7834     return LoadPosition(lastLoadPositionFP, positionNumber,\r
7835                         lastLoadPositionTitle);\r
7836 }\r
7837 \r
7838 /* Load the nth position from the given file */\r
7839 int\r
7840 LoadPositionFromFile(filename, n, title)\r
7841      char *filename;\r
7842      int n;\r
7843      char *title;\r
7844 {\r
7845     FILE *f;\r
7846     char buf[MSG_SIZ];\r
7847 \r
7848     if (strcmp(filename, "-") == 0) {\r
7849         return LoadPosition(stdin, n, "stdin");\r
7850     } else {\r
7851         f = fopen(filename, "rb");\r
7852         if (f == NULL) {\r
7853             sprintf(buf, "Can't open \"%s\"", filename);\r
7854             DisplayError(buf, errno);\r
7855             return FALSE;\r
7856         } else {\r
7857             return LoadPosition(f, n, title);\r
7858         }\r
7859     }\r
7860 }\r
7861 \r
7862 /* Load the nth position from the given open file, and close it */\r
7863 int\r
7864 LoadPosition(f, positionNumber, title)\r
7865      FILE *f;\r
7866      int positionNumber;\r
7867      char *title;\r
7868 {\r
7869     char *p, line[MSG_SIZ];\r
7870     Board initial_position;\r
7871     int i, j, fenMode, pn;\r
7872     \r
7873     if (gameMode == Training )\r
7874         SetTrainingModeOff();\r
7875 \r
7876     if (gameMode != BeginningOfGame) {\r
7877         Reset(FALSE, TRUE);\r
7878     }\r
7879     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
7880         fclose(lastLoadPositionFP);\r
7881     }\r
7882     if (positionNumber == 0) positionNumber = 1;\r
7883     lastLoadPositionFP = f;\r
7884     lastLoadPositionNumber = positionNumber;\r
7885     strcpy(lastLoadPositionTitle, title);\r
7886     if (first.pr == NoProc) {\r
7887       StartChessProgram(&first);\r
7888       InitChessProgram(&first);\r
7889     }    \r
7890     pn = positionNumber;\r
7891     if (positionNumber < 0) {\r
7892         /* Negative position number means to seek to that byte offset */\r
7893         if (fseek(f, -positionNumber, 0) == -1) {\r
7894             DisplayError("Can't seek on position file", 0);\r
7895             return FALSE;\r
7896         };\r
7897         pn = 1;\r
7898     } else {\r
7899         if (fseek(f, 0, 0) == -1) {\r
7900             if (f == lastLoadPositionFP ?\r
7901                 positionNumber == lastLoadPositionNumber + 1 :\r
7902                 positionNumber == 1) {\r
7903                 pn = 1;\r
7904             } else {\r
7905                 DisplayError("Can't seek on position file", 0);\r
7906                 return FALSE;\r
7907             }\r
7908         }\r
7909     }\r
7910     /* See if this file is FEN or old-style xboard */\r
7911     if (fgets(line, MSG_SIZ, f) == NULL) {\r
7912         DisplayError("Position not found in file", 0);\r
7913         return FALSE;\r
7914     }\r
7915     switch (line[0]) {\r
7916       case '#':  case 'x':\r
7917       default:\r
7918         fenMode = FALSE;\r
7919         break;\r
7920       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
7921       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
7922       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
7923       case '7':  case '8':  case '9':\r
7924 #ifdef FAIRY\r
7925       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
7926       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
7927       case 'C':  case 'W':             case 'c':  case 'w': \r
7928 #endif\r
7929         fenMode = TRUE;\r
7930         break;\r
7931     }\r
7932 \r
7933     if (pn >= 2) {\r
7934         if (fenMode || line[0] == '#') pn--;\r
7935         while (pn > 0) {\r
7936             /* skip postions before number pn */\r
7937             if (fgets(line, MSG_SIZ, f) == NULL) {\r
7938                 Reset(TRUE, TRUE);\r
7939                 DisplayError("Position not found in file", 0);\r
7940                 return FALSE;\r
7941             }\r
7942             if (fenMode || line[0] == '#') pn--;\r
7943         }\r
7944     }\r
7945 \r
7946     if (fenMode) {\r
7947         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
7948             DisplayError("Bad FEN position in file", 0);\r
7949             return FALSE;\r
7950         }\r
7951     } else {\r
7952         (void) fgets(line, MSG_SIZ, f);\r
7953         (void) fgets(line, MSG_SIZ, f);\r
7954     \r
7955         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
7956             (void) fgets(line, MSG_SIZ, f);\r
7957             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
7958                 if (*p == ' ')\r
7959                   continue;\r
7960                 initial_position[i][j++] = CharToPiece(*p);\r
7961             }\r
7962         }\r
7963     \r
7964         blackPlaysFirst = FALSE;\r
7965         if (!feof(f)) {\r
7966             (void) fgets(line, MSG_SIZ, f);\r
7967             if (strncmp(line, "black", strlen("black"))==0)\r
7968               blackPlaysFirst = TRUE;\r
7969         }\r
7970     }\r
7971     startedFromSetupPosition = TRUE;\r
7972     \r
7973     SendToProgram("force\n", &first);\r
7974     CopyBoard(boards[0], initial_position);\r
7975           /* [HGM] copy FEN attributes as well */\r
7976           {   int i;\r
7977               initialRulePlies = FENrulePlies;\r
7978               epStatus[0] = FENepStatus;\r
7979               for( i=0; i< nrCastlingRights; i++ )\r
7980                   castlingRights[0][i] = FENcastlingRights[i];\r
7981           }\r
7982     if (blackPlaysFirst) {\r
7983         currentMove = forwardMostMove = backwardMostMove = 1;\r
7984         strcpy(moveList[0], "");\r
7985         strcpy(parseList[0], "");\r
7986         CopyBoard(boards[1], initial_position);\r
7987         DisplayMessage("", "Black to play");\r
7988     } else {\r
7989         currentMove = forwardMostMove = backwardMostMove = 0;\r
7990         DisplayMessage("", "White to play");\r
7991     }\r
7992     SendBoard(&first, forwardMostMove);\r
7993 \r
7994     if (positionNumber > 1) {\r
7995         sprintf(line, "%s %d", title, positionNumber);\r
7996         DisplayTitle(line);\r
7997     } else {\r
7998         DisplayTitle(title);\r
7999     }\r
8000     gameMode = EditGame;\r
8001     ModeHighlight();\r
8002     ResetClocks();\r
8003     timeRemaining[0][1] = whiteTimeRemaining;\r
8004     timeRemaining[1][1] = blackTimeRemaining;\r
8005     DrawPosition(FALSE, boards[currentMove]);\r
8006    \r
8007     return TRUE;\r
8008 }\r
8009 \r
8010 \r
8011 void\r
8012 CopyPlayerNameIntoFileName(dest, src)\r
8013      char **dest, *src;\r
8014 {\r
8015     while (*src != NULLCHAR && *src != ',') {\r
8016         if (*src == ' ') {\r
8017             *(*dest)++ = '_';\r
8018             src++;\r
8019         } else {\r
8020             *(*dest)++ = *src++;\r
8021         }\r
8022     }\r
8023 }\r
8024 \r
8025 char *DefaultFileName(ext)\r
8026      char *ext;\r
8027 {\r
8028     static char def[MSG_SIZ];\r
8029     char *p;\r
8030 \r
8031     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
8032         p = def;\r
8033         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
8034         *p++ = '-';\r
8035         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
8036         *p++ = '.';\r
8037         strcpy(p, ext);\r
8038     } else {\r
8039         def[0] = NULLCHAR;\r
8040     }\r
8041     return def;\r
8042 }\r
8043 \r
8044 /* Save the current game to the given file */\r
8045 int\r
8046 SaveGameToFile(filename, append)\r
8047      char *filename;\r
8048      int append;\r
8049 {\r
8050     FILE *f;\r
8051     char buf[MSG_SIZ];\r
8052 \r
8053     if (strcmp(filename, "-") == 0) {\r
8054         return SaveGame(stdout, 0, NULL);\r
8055     } else {\r
8056         f = fopen(filename, append ? "a" : "w");\r
8057         if (f == NULL) {\r
8058             sprintf(buf, "Can't open \"%s\"", filename);\r
8059             DisplayError(buf, errno);\r
8060             return FALSE;\r
8061         } else {\r
8062             return SaveGame(f, 0, NULL);\r
8063         }\r
8064     }\r
8065 }\r
8066 \r
8067 char *\r
8068 SavePart(str)\r
8069      char *str;\r
8070 {\r
8071     static char buf[MSG_SIZ];\r
8072     char *p;\r
8073     \r
8074     p = strchr(str, ' ');\r
8075     if (p == NULL) return str;\r
8076     strncpy(buf, str, p - str);\r
8077     buf[p - str] = NULLCHAR;\r
8078     return buf;\r
8079 }\r
8080 \r
8081 #define PGN_MAX_LINE 75\r
8082 \r
8083 #define PGN_SIDE_WHITE  0\r
8084 #define PGN_SIDE_BLACK  1\r
8085 \r
8086 /* [AS] */\r
8087 static int FindFirstMoveOutOfBook( int side )\r
8088 {\r
8089     int result = -1;\r
8090 \r
8091     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
8092         int index = backwardMostMove;\r
8093         int has_book_hit = 0;\r
8094 \r
8095         if( (index % 2) != side ) {\r
8096             index++;\r
8097         }\r
8098 \r
8099         while( index < forwardMostMove ) {\r
8100             /* Check to see if engine is in book */\r
8101             int depth = pvInfoList[index].depth;\r
8102             int score = pvInfoList[index].score;\r
8103             int in_book = 0;\r
8104 \r
8105             if( depth <= 2 ) {\r
8106                 in_book = 1;\r
8107             }\r
8108             else if( score == 0 && depth == 63 ) {\r
8109                 in_book = 1; /* Zappa */\r
8110             }\r
8111             else if( score == 2 && depth == 99 ) {\r
8112                 in_book = 1; /* Abrok */\r
8113             }\r
8114 \r
8115             has_book_hit += in_book;\r
8116 \r
8117             if( ! in_book ) {\r
8118                 result = index;\r
8119 \r
8120                 break;\r
8121             }\r
8122 \r
8123             index += 2;\r
8124         }\r
8125     }\r
8126 \r
8127     return result;\r
8128 }\r
8129 \r
8130 /* [AS] */\r
8131 void GetOutOfBookInfo( char * buf )\r
8132 {\r
8133     int oob[2];\r
8134     int i;\r
8135     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
8136 \r
8137     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
8138     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
8139 \r
8140     *buf = '\0';\r
8141 \r
8142     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
8143         for( i=0; i<2; i++ ) {\r
8144             int idx = oob[i];\r
8145 \r
8146             if( idx >= 0 ) {\r
8147                 if( i > 0 && oob[0] >= 0 ) {\r
8148                     strcat( buf, "   " );\r
8149                 }\r
8150 \r
8151                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
8152                 sprintf( buf+strlen(buf), "%s%.2f", \r
8153                     pvInfoList[idx].score >= 0 ? "+" : "",\r
8154                     pvInfoList[idx].score / 100.0 );\r
8155             }\r
8156         }\r
8157     }\r
8158 }\r
8159 \r
8160 /* Save game in PGN style and close the file */\r
8161 int\r
8162 SaveGamePGN(f)\r
8163      FILE *f;\r
8164 {\r
8165     int i, offset, linelen, newblock;\r
8166     time_t tm;\r
8167     char *movetext;\r
8168     char numtext[32];\r
8169     int movelen, numlen, blank;\r
8170     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
8171 \r
8172     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
8173     \r
8174     tm = time((time_t *) NULL);\r
8175     \r
8176     PrintPGNTags(f, &gameInfo);\r
8177     \r
8178     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
8179         char *fen = PositionToFEN(backwardMostMove, 1);\r
8180         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
8181         fprintf(f, "\n{--------------\n");\r
8182         PrintPosition(f, backwardMostMove);\r
8183         fprintf(f, "--------------}\n");\r
8184         free(fen);\r
8185     }\r
8186     else {\r
8187         /* [AS] Out of book annotation */\r
8188         if( appData.saveOutOfBookInfo ) {\r
8189             char buf[64];\r
8190 \r
8191             GetOutOfBookInfo( buf );\r
8192 \r
8193             if( buf[0] != '\0' ) {\r
8194                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
8195             }\r
8196         }\r
8197 \r
8198         fprintf(f, "\n");\r
8199     }\r
8200 \r
8201     i = backwardMostMove;\r
8202     linelen = 0;\r
8203     newblock = TRUE;\r
8204 \r
8205     while (i < forwardMostMove) {\r
8206         /* Print comments preceding this move */\r
8207         if (commentList[i] != NULL) {\r
8208             if (linelen > 0) fprintf(f, "\n");\r
8209             fprintf(f, "{\n%s}\n", commentList[i]);\r
8210             linelen = 0;\r
8211             newblock = TRUE;\r
8212         }\r
8213 \r
8214         /* Format move number */\r
8215         if ((i % 2) == 0) {\r
8216             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
8217         } else {\r
8218             if (newblock) {\r
8219                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
8220             } else {\r
8221                 numtext[0] = NULLCHAR;\r
8222             }\r
8223         }\r
8224         numlen = strlen(numtext);\r
8225         newblock = FALSE;\r
8226 \r
8227         /* Print move number */\r
8228         blank = linelen > 0 && numlen > 0;\r
8229         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
8230             fprintf(f, "\n");\r
8231             linelen = 0;\r
8232             blank = 0;\r
8233         }\r
8234         if (blank) {\r
8235             fprintf(f, " ");\r
8236             linelen++;\r
8237         }\r
8238         fprintf(f, numtext);\r
8239         linelen += numlen;\r
8240 \r
8241         /* Get move */\r
8242         movetext = SavePart(parseList[i]);\r
8243 \r
8244         /* [AS] Add PV info if present */\r
8245         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
8246             /* [HGM] add time */\r
8247             char buf[MSG_SIZ]; int seconds = 0;\r
8248 \r
8249             if(i >= backwardMostMove) {\r
8250                 /* take the time that changed */\r
8251                 seconds = timeRemaining[0][i] - timeRemaining[0][i+1];\r
8252                 if(seconds <= 0)\r
8253                     seconds = timeRemaining[1][i] - timeRemaining[1][i+1];\r
8254             }\r
8255             seconds /= 1000;\r
8256     if (appData.debugMode) {\r
8257         fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",\r
8258                 timeRemaining[0][i+1], timeRemaining[0][i],\r
8259                      timeRemaining[1][i+1], timeRemaining[1][i], seconds\r
8260         );\r
8261     }\r
8262 \r
8263             if( seconds < 0 ) buf[0] = 0; else\r
8264             if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0);\r
8265             else    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
8266 \r
8267             sprintf( move_buffer, "%s {%s%.2f/%d%s}", \r
8268                 movetext, \r
8269                 pvInfoList[i].score >= 0 ? "+" : "",\r
8270                 pvInfoList[i].score / 100.0,\r
8271                 pvInfoList[i].depth,\r
8272                 buf );\r
8273             movetext = move_buffer;\r
8274         }\r
8275 \r
8276         movelen = strlen(movetext);\r
8277 \r
8278         /* Print move */\r
8279         blank = linelen > 0 && movelen > 0;\r
8280         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
8281             fprintf(f, "\n");\r
8282             linelen = 0;\r
8283             blank = 0;\r
8284         }\r
8285         if (blank) {\r
8286             fprintf(f, " ");\r
8287             linelen++;\r
8288         }\r
8289         fprintf(f, movetext);\r
8290         linelen += movelen;\r
8291 \r
8292         i++;\r
8293     }\r
8294     \r
8295     /* Start a new line */\r
8296     if (linelen > 0) fprintf(f, "\n");\r
8297 \r
8298     /* Print comments after last move */\r
8299     if (commentList[i] != NULL) {\r
8300         fprintf(f, "{\n%s}\n", commentList[i]);\r
8301     }\r
8302 \r
8303     /* Print result */\r
8304     if (gameInfo.resultDetails != NULL &&\r
8305         gameInfo.resultDetails[0] != NULLCHAR) {\r
8306         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
8307                 PGNResult(gameInfo.result));\r
8308     } else {\r
8309         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
8310     }\r
8311 \r
8312     fclose(f);\r
8313     return TRUE;\r
8314 }\r
8315 \r
8316 /* Save game in old style and close the file */\r
8317 int\r
8318 SaveGameOldStyle(f)\r
8319      FILE *f;\r
8320 {\r
8321     int i, offset;\r
8322     time_t tm;\r
8323     \r
8324     tm = time((time_t *) NULL);\r
8325     \r
8326     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
8327     PrintOpponents(f);\r
8328     \r
8329     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
8330         fprintf(f, "\n[--------------\n");\r
8331         PrintPosition(f, backwardMostMove);\r
8332         fprintf(f, "--------------]\n");\r
8333     } else {\r
8334         fprintf(f, "\n");\r
8335     }\r
8336 \r
8337     i = backwardMostMove;\r
8338     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
8339 \r
8340     while (i < forwardMostMove) {\r
8341         if (commentList[i] != NULL) {\r
8342             fprintf(f, "[%s]\n", commentList[i]);\r
8343         }\r
8344 \r
8345         if ((i % 2) == 1) {\r
8346             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
8347             i++;\r
8348         } else {\r
8349             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
8350             i++;\r
8351             if (commentList[i] != NULL) {\r
8352                 fprintf(f, "\n");\r
8353                 continue;\r
8354             }\r
8355             if (i >= forwardMostMove) {\r
8356                 fprintf(f, "\n");\r
8357                 break;\r
8358             }\r
8359             fprintf(f, "%s\n", parseList[i]);\r
8360             i++;\r
8361         }\r
8362     }\r
8363     \r
8364     if (commentList[i] != NULL) {\r
8365         fprintf(f, "[%s]\n", commentList[i]);\r
8366     }\r
8367 \r
8368     /* This isn't really the old style, but it's close enough */\r
8369     if (gameInfo.resultDetails != NULL &&\r
8370         gameInfo.resultDetails[0] != NULLCHAR) {\r
8371         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
8372                 gameInfo.resultDetails);\r
8373     } else {\r
8374         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
8375     }\r
8376 \r
8377     fclose(f);\r
8378     return TRUE;\r
8379 }\r
8380 \r
8381 /* Save the current game to open file f and close the file */\r
8382 int\r
8383 SaveGame(f, dummy, dummy2)\r
8384      FILE *f;\r
8385      int dummy;\r
8386      char *dummy2;\r
8387 {\r
8388     if (gameMode == EditPosition) EditPositionDone();\r
8389     if (appData.oldSaveStyle)\r
8390       return SaveGameOldStyle(f);\r
8391     else\r
8392       return SaveGamePGN(f);\r
8393 }\r
8394 \r
8395 /* Save the current position to the given file */\r
8396 int\r
8397 SavePositionToFile(filename)\r
8398      char *filename;\r
8399 {\r
8400     FILE *f;\r
8401     char buf[MSG_SIZ];\r
8402 \r
8403     if (strcmp(filename, "-") == 0) {\r
8404         return SavePosition(stdout, 0, NULL);\r
8405     } else {\r
8406         f = fopen(filename, "a");\r
8407         if (f == NULL) {\r
8408             sprintf(buf, "Can't open \"%s\"", filename);\r
8409             DisplayError(buf, errno);\r
8410             return FALSE;\r
8411         } else {\r
8412             SavePosition(f, 0, NULL);\r
8413             return TRUE;\r
8414         }\r
8415     }\r
8416 }\r
8417 \r
8418 /* Save the current position to the given open file and close the file */\r
8419 int\r
8420 SavePosition(f, dummy, dummy2)\r
8421      FILE *f;\r
8422      int dummy;\r
8423      char *dummy2;\r
8424 {\r
8425     time_t tm;\r
8426     char *fen;\r
8427     \r
8428     if (appData.oldSaveStyle) {\r
8429         tm = time((time_t *) NULL);\r
8430     \r
8431         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
8432         PrintOpponents(f);\r
8433         fprintf(f, "[--------------\n");\r
8434         PrintPosition(f, currentMove);\r
8435         fprintf(f, "--------------]\n");\r
8436     } else {\r
8437         fen = PositionToFEN(currentMove, 1);\r
8438         fprintf(f, "%s\n", fen);\r
8439         free(fen);\r
8440     }\r
8441     fclose(f);\r
8442     return TRUE;\r
8443 }\r
8444 \r
8445 void\r
8446 ReloadCmailMsgEvent(unregister)\r
8447      int unregister;\r
8448 {\r
8449 #if !WIN32\r
8450     static char *inFilename = NULL;\r
8451     static char *outFilename;\r
8452     int i;\r
8453     struct stat inbuf, outbuf;\r
8454     int status;\r
8455     \r
8456     /* Any registered moves are unregistered if unregister is set, */\r
8457     /* i.e. invoked by the signal handler */\r
8458     if (unregister) {\r
8459         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
8460             cmailMoveRegistered[i] = FALSE;\r
8461             if (cmailCommentList[i] != NULL) {\r
8462                 free(cmailCommentList[i]);\r
8463                 cmailCommentList[i] = NULL;\r
8464             }\r
8465         }\r
8466         nCmailMovesRegistered = 0;\r
8467     }\r
8468 \r
8469     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
8470         cmailResult[i] = CMAIL_NOT_RESULT;\r
8471     }\r
8472     nCmailResults = 0;\r
8473 \r
8474     if (inFilename == NULL) {\r
8475         /* Because the filenames are static they only get malloced once  */\r
8476         /* and they never get freed                                      */\r
8477         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
8478         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
8479 \r
8480         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
8481         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
8482     }\r
8483     \r
8484     status = stat(outFilename, &outbuf);\r
8485     if (status < 0) {\r
8486         cmailMailedMove = FALSE;\r
8487     } else {\r
8488         status = stat(inFilename, &inbuf);\r
8489         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
8490     }\r
8491     \r
8492     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
8493        counts the games, notes how each one terminated, etc.\r
8494        \r
8495        It would be nice to remove this kludge and instead gather all\r
8496        the information while building the game list.  (And to keep it\r
8497        in the game list nodes instead of having a bunch of fixed-size\r
8498        parallel arrays.)  Note this will require getting each game's\r
8499        termination from the PGN tags, as the game list builder does\r
8500        not process the game moves.  --mann\r
8501        */\r
8502     cmailMsgLoaded = TRUE;\r
8503     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
8504     \r
8505     /* Load first game in the file or popup game menu */\r
8506     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
8507 \r
8508 #endif /* !WIN32 */\r
8509     return;\r
8510 }\r
8511 \r
8512 int\r
8513 RegisterMove()\r
8514 {\r
8515     FILE *f;\r
8516     char string[MSG_SIZ];\r
8517 \r
8518     if (   cmailMailedMove\r
8519         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
8520         return TRUE;            /* Allow free viewing  */\r
8521     }\r
8522 \r
8523     /* Unregister move to ensure that we don't leave RegisterMove        */\r
8524     /* with the move registered when the conditions for registering no   */\r
8525     /* longer hold                                                       */\r
8526     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8527         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8528         nCmailMovesRegistered --;\r
8529 \r
8530         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
8531           {\r
8532               free(cmailCommentList[lastLoadGameNumber - 1]);\r
8533               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
8534           }\r
8535     }\r
8536 \r
8537     if (cmailOldMove == -1) {\r
8538         DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);\r
8539         return FALSE;\r
8540     }\r
8541 \r
8542     if (currentMove > cmailOldMove + 1) {\r
8543         DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);\r
8544         return FALSE;\r
8545     }\r
8546 \r
8547     if (currentMove < cmailOldMove) {\r
8548         DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);\r
8549         return FALSE;\r
8550     }\r
8551 \r
8552     if (forwardMostMove > currentMove) {\r
8553         /* Silently truncate extra moves */\r
8554         TruncateGame();\r
8555     }\r
8556 \r
8557     if (   (currentMove == cmailOldMove + 1)\r
8558         || (   (currentMove == cmailOldMove)\r
8559             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
8560                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
8561         if (gameInfo.result != GameUnfinished) {\r
8562             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
8563         }\r
8564 \r
8565         if (commentList[currentMove] != NULL) {\r
8566             cmailCommentList[lastLoadGameNumber - 1]\r
8567               = StrSave(commentList[currentMove]);\r
8568         }\r
8569         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
8570 \r
8571         if (appData.debugMode)\r
8572           fprintf(debugFP, "Saving %s for game %d\n",\r
8573                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8574 \r
8575         sprintf(string,\r
8576                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
8577         \r
8578         f = fopen(string, "w");\r
8579         if (appData.oldSaveStyle) {\r
8580             SaveGameOldStyle(f); /* also closes the file */\r
8581             \r
8582             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
8583             f = fopen(string, "w");\r
8584             SavePosition(f, 0, NULL); /* also closes the file */\r
8585         } else {\r
8586             fprintf(f, "{--------------\n");\r
8587             PrintPosition(f, currentMove);\r
8588             fprintf(f, "--------------}\n\n");\r
8589             \r
8590             SaveGame(f, 0, NULL); /* also closes the file*/\r
8591         }\r
8592         \r
8593         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
8594         nCmailMovesRegistered ++;\r
8595     } else if (nCmailGames == 1) {\r
8596         DisplayError("You have not made a move yet", 0);\r
8597         return FALSE;\r
8598     }\r
8599 \r
8600     return TRUE;\r
8601 }\r
8602 \r
8603 void\r
8604 MailMoveEvent()\r
8605 {\r
8606 #if !WIN32\r
8607     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
8608     FILE *commandOutput;\r
8609     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
8610     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
8611     int nBuffers;\r
8612     int i;\r
8613     int archived;\r
8614     char *arcDir;\r
8615 \r
8616     if (! cmailMsgLoaded) {\r
8617         DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);\r
8618         return;\r
8619     }\r
8620 \r
8621     if (nCmailGames == nCmailResults) {\r
8622         DisplayError("No unfinished games", 0);\r
8623         return;\r
8624     }\r
8625 \r
8626 #if CMAIL_PROHIBIT_REMAIL\r
8627     if (cmailMailedMove) {\r
8628         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
8629         DisplayError(msg, 0);\r
8630         return;\r
8631     }\r
8632 #endif\r
8633 \r
8634     if (! (cmailMailedMove || RegisterMove())) return;\r
8635     \r
8636     if (   cmailMailedMove\r
8637         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
8638         sprintf(string, partCommandString,\r
8639                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
8640         commandOutput = popen(string, "rb");\r
8641 \r
8642         if (commandOutput == NULL) {\r
8643             DisplayError("Failed to invoke cmail", 0);\r
8644         } else {\r
8645             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
8646                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
8647             }\r
8648             if (nBuffers > 1) {\r
8649                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
8650                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
8651                 nBytes = MSG_SIZ - 1;\r
8652             } else {\r
8653                 (void) memcpy(msg, buffer, nBytes);\r
8654             }\r
8655             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
8656 \r
8657             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
8658                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
8659 \r
8660                 archived = TRUE;\r
8661                 for (i = 0; i < nCmailGames; i ++) {\r
8662                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
8663                         archived = FALSE;\r
8664                     }\r
8665                 }\r
8666                 if (   archived\r
8667                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
8668                         != NULL)) {\r
8669                     sprintf(buffer, "%s/%s.%s.archive",\r
8670                             arcDir,\r
8671                             appData.cmailGameName,\r
8672                             gameInfo.date);\r
8673                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
8674                     cmailMsgLoaded = FALSE;\r
8675                 }\r
8676             }\r
8677 \r
8678             DisplayInformation(msg);\r
8679             pclose(commandOutput);\r
8680         }\r
8681     } else {\r
8682         if ((*cmailMsg) != '\0') {\r
8683             DisplayInformation(cmailMsg);\r
8684         }\r
8685     }\r
8686 \r
8687     return;\r
8688 #endif /* !WIN32 */\r
8689 }\r
8690 \r
8691 char *\r
8692 CmailMsg()\r
8693 {\r
8694 #if WIN32\r
8695     return NULL;\r
8696 #else\r
8697     int  prependComma = 0;\r
8698     char number[5];\r
8699     char string[MSG_SIZ];       /* Space for game-list */\r
8700     int  i;\r
8701     \r
8702     if (!cmailMsgLoaded) return "";\r
8703 \r
8704     if (cmailMailedMove) {\r
8705         sprintf(cmailMsg, "Waiting for reply from opponent\n");\r
8706     } else {\r
8707         /* Create a list of games left */\r
8708         sprintf(string, "[");\r
8709         for (i = 0; i < nCmailGames; i ++) {\r
8710             if (! (   cmailMoveRegistered[i]\r
8711                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
8712                 if (prependComma) {\r
8713                     sprintf(number, ",%d", i + 1);\r
8714                 } else {\r
8715                     sprintf(number, "%d", i + 1);\r
8716                     prependComma = 1;\r
8717                 }\r
8718                 \r
8719                 strcat(string, number);\r
8720             }\r
8721         }\r
8722         strcat(string, "]");\r
8723 \r
8724         if (nCmailMovesRegistered + nCmailResults == 0) {\r
8725             switch (nCmailGames) {\r
8726               case 1:\r
8727                 sprintf(cmailMsg,\r
8728                         "Still need to make move for game\n");\r
8729                 break;\r
8730                 \r
8731               case 2:\r
8732                 sprintf(cmailMsg,\r
8733                         "Still need to make moves for both games\n");\r
8734                 break;\r
8735                 \r
8736               default:\r
8737                 sprintf(cmailMsg,\r
8738                         "Still need to make moves for all %d games\n",\r
8739                         nCmailGames);\r
8740                 break;\r
8741             }\r
8742         } else {\r
8743             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
8744               case 1:\r
8745                 sprintf(cmailMsg,\r
8746                         "Still need to make a move for game %s\n",\r
8747                         string);\r
8748                 break;\r
8749                 \r
8750               case 0:\r
8751                 if (nCmailResults == nCmailGames) {\r
8752                     sprintf(cmailMsg, "No unfinished games\n");\r
8753                 } else {\r
8754                     sprintf(cmailMsg, "Ready to send mail\n");\r
8755                 }\r
8756                 break;\r
8757                 \r
8758               default:\r
8759                 sprintf(cmailMsg,\r
8760                         "Still need to make moves for games %s\n",\r
8761                         string);\r
8762             }\r
8763         }\r
8764     }\r
8765     return cmailMsg;\r
8766 #endif /* WIN32 */\r
8767 }\r
8768 \r
8769 void\r
8770 ResetGameEvent()\r
8771 {\r
8772     if (gameMode == Training)\r
8773       SetTrainingModeOff();\r
8774 \r
8775     Reset(TRUE, TRUE);\r
8776     cmailMsgLoaded = FALSE;\r
8777     if (appData.icsActive) {\r
8778       SendToICS(ics_prefix);\r
8779       SendToICS("refresh\n");\r
8780     }\r
8781 }\r
8782 \r
8783 static int exiting = 0;\r
8784 \r
8785 void\r
8786 ExitEvent(status)\r
8787      int status;\r
8788 {\r
8789     exiting++;\r
8790     if (exiting > 2) {\r
8791       /* Give up on clean exit */\r
8792       exit(status);\r
8793     }\r
8794     if (exiting > 1) {\r
8795       /* Keep trying for clean exit */\r
8796       return;\r
8797     }\r
8798 \r
8799     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
8800 \r
8801     if (telnetISR != NULL) {\r
8802       RemoveInputSource(telnetISR);\r
8803     }\r
8804     if (icsPR != NoProc) {\r
8805       DestroyChildProcess(icsPR, TRUE);\r
8806     }\r
8807     /* Save game if resource set and not already saved by GameEnds() */\r
8808     if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {\r
8809       if (*appData.saveGameFile != NULLCHAR) {\r
8810         SaveGameToFile(appData.saveGameFile, TRUE);\r
8811       } else if (appData.autoSaveGames) {\r
8812         AutoSaveGame();\r
8813       }\r
8814       if (*appData.savePositionFile != NULLCHAR) {\r
8815         SavePositionToFile(appData.savePositionFile);\r
8816       }\r
8817     }\r
8818     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8819 \r
8820     /* Kill off chess programs */\r
8821     if (first.pr != NoProc) {\r
8822         ExitAnalyzeMode();\r
8823         \r
8824         DoSleep( appData.delayBeforeQuit );\r
8825         SendToProgram("quit\n", &first);\r
8826         DoSleep( appData.delayAfterQuit );\r
8827         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
8828     }\r
8829     if (second.pr != NoProc) {\r
8830         DoSleep( appData.delayBeforeQuit );\r
8831         SendToProgram("quit\n", &second);\r
8832         DoSleep( appData.delayAfterQuit );\r
8833         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
8834     }\r
8835     if (first.isr != NULL) {\r
8836         RemoveInputSource(first.isr);\r
8837     }\r
8838     if (second.isr != NULL) {\r
8839         RemoveInputSource(second.isr);\r
8840     }\r
8841 \r
8842     ShutDownFrontEnd();\r
8843     exit(status);\r
8844 }\r
8845 \r
8846 void\r
8847 PauseEvent()\r
8848 {\r
8849     if (appData.debugMode)\r
8850         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
8851     if (pausing) {\r
8852         pausing = FALSE;\r
8853         ModeHighlight();\r
8854         if (gameMode == MachinePlaysWhite ||\r
8855             gameMode == MachinePlaysBlack) {\r
8856             StartClocks();\r
8857         } else {\r
8858             DisplayBothClocks();\r
8859         }\r
8860         if (gameMode == PlayFromGameFile) {\r
8861             if (appData.timeDelay >= 0) \r
8862                 AutoPlayGameLoop();\r
8863         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
8864             Reset(FALSE, TRUE);\r
8865             SendToICS(ics_prefix);\r
8866             SendToICS("refresh\n");\r
8867         } else if (currentMove < forwardMostMove) {\r
8868             ForwardInner(forwardMostMove);\r
8869         }\r
8870         pauseExamInvalid = FALSE;\r
8871     } else {\r
8872         switch (gameMode) {\r
8873           default:\r
8874             return;\r
8875           case IcsExamining:\r
8876             pauseExamForwardMostMove = forwardMostMove;\r
8877             pauseExamInvalid = FALSE;\r
8878             /* fall through */\r
8879           case IcsObserving:\r
8880           case IcsPlayingWhite:\r
8881           case IcsPlayingBlack:\r
8882             pausing = TRUE;\r
8883             ModeHighlight();\r
8884             return;\r
8885           case PlayFromGameFile:\r
8886             (void) StopLoadGameTimer();\r
8887             pausing = TRUE;\r
8888             ModeHighlight();\r
8889             break;\r
8890           case BeginningOfGame:\r
8891             if (appData.icsActive) return;\r
8892             /* else fall through */\r
8893           case MachinePlaysWhite:\r
8894           case MachinePlaysBlack:\r
8895           case TwoMachinesPlay:\r
8896             if (forwardMostMove == 0)\r
8897               return;           /* don't pause if no one has moved */\r
8898             if ((gameMode == MachinePlaysWhite &&\r
8899                  !WhiteOnMove(forwardMostMove)) ||\r
8900                 (gameMode == MachinePlaysBlack &&\r
8901                  WhiteOnMove(forwardMostMove))) {\r
8902                 StopClocks();\r
8903             }\r
8904             pausing = TRUE;\r
8905             ModeHighlight();\r
8906             break;\r
8907         }\r
8908     }\r
8909 }\r
8910 \r
8911 void\r
8912 EditCommentEvent()\r
8913 {\r
8914     char title[MSG_SIZ];\r
8915 \r
8916     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
8917         strcpy(title, "Edit comment");\r
8918     } else {\r
8919         sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,\r
8920                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
8921                 parseList[currentMove - 1]);\r
8922     }\r
8923 \r
8924     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
8925 }\r
8926 \r
8927 \r
8928 void\r
8929 EditTagsEvent()\r
8930 {\r
8931     char *tags = PGNTags(&gameInfo);\r
8932     EditTagsPopUp(tags);\r
8933     free(tags);\r
8934 }\r
8935 \r
8936 void\r
8937 AnalyzeModeEvent()\r
8938 {\r
8939     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
8940       return;\r
8941 \r
8942     if (gameMode != AnalyzeFile) {\r
8943         EditGameEvent();\r
8944         if (gameMode != EditGame) return;\r
8945         ResurrectChessProgram();\r
8946         SendToProgram("analyze\n", &first);\r
8947         first.analyzing = TRUE;\r
8948         /*first.maybeThinking = TRUE;*/\r
8949         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
8950         AnalysisPopUp("Analysis",\r
8951                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
8952     }\r
8953     gameMode = AnalyzeMode;\r
8954     pausing = FALSE;\r
8955     ModeHighlight();\r
8956     SetGameInfo();\r
8957 \r
8958     StartAnalysisClock();\r
8959     GetTimeMark(&lastNodeCountTime);\r
8960     lastNodeCount = 0;\r
8961 }\r
8962 \r
8963 void\r
8964 AnalyzeFileEvent()\r
8965 {\r
8966     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
8967       return;\r
8968 \r
8969     if (gameMode != AnalyzeMode) {\r
8970         EditGameEvent();\r
8971         if (gameMode != EditGame) return;\r
8972         ResurrectChessProgram();\r
8973         SendToProgram("analyze\n", &first);\r
8974         first.analyzing = TRUE;\r
8975         /*first.maybeThinking = TRUE;*/\r
8976         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
8977         AnalysisPopUp("Analysis",\r
8978                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
8979     }\r
8980     gameMode = AnalyzeFile;\r
8981     pausing = FALSE;\r
8982     ModeHighlight();\r
8983     SetGameInfo();\r
8984 \r
8985     StartAnalysisClock();\r
8986     GetTimeMark(&lastNodeCountTime);\r
8987     lastNodeCount = 0;\r
8988 }\r
8989 \r
8990 void\r
8991 MachineWhiteEvent()\r
8992 {\r
8993     char buf[MSG_SIZ];\r
8994 \r
8995     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
8996       return;\r
8997 \r
8998 \r
8999     if (gameMode == PlayFromGameFile || \r
9000         gameMode == TwoMachinesPlay  || \r
9001         gameMode == Training         || \r
9002         gameMode == AnalyzeMode      || \r
9003         gameMode == EndOfGame)\r
9004         EditGameEvent();\r
9005 \r
9006     if (gameMode == EditPosition) \r
9007         EditPositionDone();\r
9008 \r
9009     if (!WhiteOnMove(currentMove)) {\r
9010         DisplayError("It is not White's turn", 0);\r
9011         return;\r
9012     }\r
9013   \r
9014     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
9015       ExitAnalyzeMode();\r
9016 \r
9017     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
9018         gameMode == AnalyzeFile)\r
9019         TruncateGame();\r
9020 \r
9021     ResurrectChessProgram();    /* in case it isn't running */\r
9022     gameMode = MachinePlaysWhite;\r
9023     pausing = FALSE;\r
9024     ModeHighlight();\r
9025     SetGameInfo();\r
9026     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
9027     DisplayTitle(buf);\r
9028     if (first.sendName) {\r
9029       sprintf(buf, "name %s\n", gameInfo.black);\r
9030       SendToProgram(buf, &first);\r
9031     }\r
9032     if (first.sendTime) {\r
9033       if (first.useColors) {\r
9034         SendToProgram("black\n", &first); /*gnu kludge*/\r
9035       }\r
9036       SendTimeRemaining(&first, TRUE);\r
9037     }\r
9038     if (first.useColors) {\r
9039       SendToProgram("white\ngo\n", &first);\r
9040     } else {\r
9041       SendToProgram("go\n", &first);\r
9042     }\r
9043     SetMachineThinkingEnables();\r
9044     first.maybeThinking = TRUE;\r
9045     StartClocks();\r
9046 \r
9047     if (appData.autoFlipView && !flipView) {\r
9048       flipView = !flipView;\r
9049       DrawPosition(FALSE, NULL);\r
9050     }\r
9051 }\r
9052 \r
9053 void\r
9054 MachineBlackEvent()\r
9055 {\r
9056     char buf[MSG_SIZ];\r
9057 \r
9058     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
9059         return;\r
9060 \r
9061 \r
9062     if (gameMode == PlayFromGameFile || \r
9063         gameMode == TwoMachinesPlay  || \r
9064         gameMode == Training         || \r
9065         gameMode == AnalyzeMode      || \r
9066         gameMode == EndOfGame)\r
9067         EditGameEvent();\r
9068 \r
9069     if (gameMode == EditPosition) \r
9070         EditPositionDone();\r
9071 \r
9072     if (WhiteOnMove(currentMove)) {\r
9073         DisplayError("It is not Black's turn", 0);\r
9074         return;\r
9075     }\r
9076     \r
9077     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
9078       ExitAnalyzeMode();\r
9079 \r
9080     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
9081         gameMode == AnalyzeFile)\r
9082         TruncateGame();\r
9083 \r
9084     ResurrectChessProgram();    /* in case it isn't running */\r
9085     gameMode = MachinePlaysBlack;\r
9086     pausing = FALSE;\r
9087     ModeHighlight();\r
9088     SetGameInfo();\r
9089     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
9090     DisplayTitle(buf);\r
9091     if (first.sendName) {\r
9092       sprintf(buf, "name %s\n", gameInfo.white);\r
9093       SendToProgram(buf, &first);\r
9094     }\r
9095     if (first.sendTime) {\r
9096       if (first.useColors) {\r
9097         SendToProgram("white\n", &first); /*gnu kludge*/\r
9098       }\r
9099       SendTimeRemaining(&first, FALSE);\r
9100     }\r
9101     if (first.useColors) {\r
9102       SendToProgram("black\ngo\n", &first);\r
9103     } else {\r
9104       SendToProgram("go\n", &first);\r
9105     }\r
9106     SetMachineThinkingEnables();\r
9107     first.maybeThinking = TRUE;\r
9108     StartClocks();\r
9109 \r
9110     if (appData.autoFlipView && flipView) {\r
9111       flipView = !flipView;\r
9112       DrawPosition(FALSE, NULL);\r
9113     }\r
9114 }\r
9115 \r
9116 \r
9117 void\r
9118 DisplayTwoMachinesTitle()\r
9119 {\r
9120     char buf[MSG_SIZ];\r
9121     if (appData.matchGames > 0) {\r
9122         if (first.twoMachinesColor[0] == 'w') {\r
9123             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
9124                     gameInfo.white, gameInfo.black,\r
9125                     first.matchWins, second.matchWins,\r
9126                     matchGame - 1 - (first.matchWins + second.matchWins));\r
9127         } else {\r
9128             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
9129                     gameInfo.white, gameInfo.black,\r
9130                     second.matchWins, first.matchWins,\r
9131                     matchGame - 1 - (first.matchWins + second.matchWins));\r
9132         }\r
9133     } else {\r
9134         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
9135     }\r
9136     DisplayTitle(buf);\r
9137 }\r
9138 \r
9139 void\r
9140 TwoMachinesEvent P((void))\r
9141 {\r
9142     int i;\r
9143     char buf[MSG_SIZ];\r
9144     ChessProgramState *onmove;\r
9145     \r
9146     if (appData.noChessProgram) return;\r
9147 \r
9148     switch (gameMode) {\r
9149       case TwoMachinesPlay:\r
9150         return;\r
9151       case MachinePlaysWhite:\r
9152       case MachinePlaysBlack:\r
9153         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
9154             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
9155             return;\r
9156         }\r
9157         /* fall through */\r
9158       case BeginningOfGame:\r
9159       case PlayFromGameFile:\r
9160       case EndOfGame:\r
9161         EditGameEvent();\r
9162         if (gameMode != EditGame) return;\r
9163         break;\r
9164       case EditPosition:\r
9165         EditPositionDone();\r
9166         break;\r
9167       case AnalyzeMode:\r
9168       case AnalyzeFile:\r
9169         ExitAnalyzeMode();\r
9170         break;\r
9171       case EditGame:\r
9172       default:\r
9173         break;\r
9174     }\r
9175 \r
9176     forwardMostMove = currentMove;\r
9177     ResurrectChessProgram();    /* in case first program isn't running */\r
9178 \r
9179     if (second.pr == NULL) {\r
9180         StartChessProgram(&second);\r
9181         if (second.protocolVersion == 1) {\r
9182           TwoMachinesEventIfReady();\r
9183         } else {\r
9184           /* kludge: allow timeout for initial "feature" command */\r
9185           FreezeUI();\r
9186           DisplayMessage("", "Starting second chess program");\r
9187           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
9188         }\r
9189         return;\r
9190     }\r
9191     DisplayMessage("", "");\r
9192     InitChessProgram(&second);\r
9193     SendToProgram("force\n", &second);\r
9194     if (startedFromSetupPosition) {\r
9195         SendBoard(&second, backwardMostMove);\r
9196     }\r
9197     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
9198         SendMoveToProgram(i, &second);\r
9199     }\r
9200 \r
9201     gameMode = TwoMachinesPlay;\r
9202     pausing = FALSE;\r
9203     ModeHighlight();\r
9204     SetGameInfo();\r
9205     DisplayTwoMachinesTitle();\r
9206     firstMove = TRUE;\r
9207     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
9208         onmove = &first;\r
9209     } else {\r
9210         onmove = &second;\r
9211     }\r
9212 \r
9213     SendToProgram(first.computerString, &first);\r
9214     if (first.sendName) {\r
9215       sprintf(buf, "name %s\n", second.tidy);\r
9216       SendToProgram(buf, &first);\r
9217     }\r
9218     SendToProgram(second.computerString, &second);\r
9219     if (second.sendName) {\r
9220       sprintf(buf, "name %s\n", first.tidy);\r
9221       SendToProgram(buf, &second);\r
9222     }\r
9223 \r
9224     if (!first.sendTime || !second.sendTime) {\r
9225         ResetClocks();\r
9226         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9227         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9228     }\r
9229     if (onmove->sendTime) {\r
9230       if (onmove->useColors) {\r
9231         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
9232       }\r
9233       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
9234     }\r
9235     if (onmove->useColors) {\r
9236       SendToProgram(onmove->twoMachinesColor, onmove);\r
9237     }\r
9238     SendToProgram("go\n", onmove);\r
9239     onmove->maybeThinking = TRUE;\r
9240     SetMachineThinkingEnables();\r
9241 \r
9242     StartClocks();\r
9243 }\r
9244 \r
9245 void\r
9246 TrainingEvent()\r
9247 {\r
9248     if (gameMode == Training) {\r
9249       SetTrainingModeOff();\r
9250       gameMode = PlayFromGameFile;\r
9251       DisplayMessage("", "Training mode off");\r
9252     } else {\r
9253       gameMode = Training;\r
9254       animateTraining = appData.animate;\r
9255 \r
9256       /* make sure we are not already at the end of the game */\r
9257       if (currentMove < forwardMostMove) {\r
9258         SetTrainingModeOn();\r
9259         DisplayMessage("", "Training mode on");\r
9260       } else {\r
9261         gameMode = PlayFromGameFile;\r
9262         DisplayError("Already at end of game", 0);\r
9263       }\r
9264     }\r
9265     ModeHighlight();\r
9266 }\r
9267 \r
9268 void\r
9269 IcsClientEvent()\r
9270 {\r
9271     if (!appData.icsActive) return;\r
9272     switch (gameMode) {\r
9273       case IcsPlayingWhite:\r
9274       case IcsPlayingBlack:\r
9275       case IcsObserving:\r
9276       case IcsIdle:\r
9277       case BeginningOfGame:\r
9278       case IcsExamining:\r
9279         return;\r
9280 \r
9281       case EditGame:\r
9282         break;\r
9283 \r
9284       case EditPosition:\r
9285         EditPositionDone();\r
9286         break;\r
9287 \r
9288       case AnalyzeMode:\r
9289       case AnalyzeFile:\r
9290         ExitAnalyzeMode();\r
9291         break;\r
9292         \r
9293       default:\r
9294         EditGameEvent();\r
9295         break;\r
9296     }\r
9297 \r
9298     gameMode = IcsIdle;\r
9299     ModeHighlight();\r
9300     return;\r
9301 }\r
9302 \r
9303 \r
9304 void\r
9305 EditGameEvent()\r
9306 {\r
9307     int i;\r
9308 \r
9309     switch (gameMode) {\r
9310       case Training:\r
9311         SetTrainingModeOff();\r
9312         break;\r
9313       case MachinePlaysWhite:\r
9314       case MachinePlaysBlack:\r
9315       case BeginningOfGame:\r
9316         SendToProgram("force\n", &first);\r
9317         SetUserThinkingEnables();\r
9318         break;\r
9319       case PlayFromGameFile:\r
9320         (void) StopLoadGameTimer();\r
9321         if (gameFileFP != NULL) {\r
9322             gameFileFP = NULL;\r
9323         }\r
9324         break;\r
9325       case EditPosition:\r
9326         EditPositionDone();\r
9327         break;\r
9328       case AnalyzeMode:\r
9329       case AnalyzeFile:\r
9330         ExitAnalyzeMode();\r
9331         SendToProgram("force\n", &first);\r
9332         break;\r
9333       case TwoMachinesPlay:\r
9334         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
9335         ResurrectChessProgram();\r
9336         SetUserThinkingEnables();\r
9337         break;\r
9338       case EndOfGame:\r
9339         ResurrectChessProgram();\r
9340         break;\r
9341       case IcsPlayingBlack:\r
9342       case IcsPlayingWhite:\r
9343         DisplayError("Warning: You are still playing a game", 0);\r
9344         break;\r
9345       case IcsObserving:\r
9346         DisplayError("Warning: You are still observing a game", 0);\r
9347         break;\r
9348       case IcsExamining:\r
9349         DisplayError("Warning: You are still examining a game", 0);\r
9350         break;\r
9351       case IcsIdle:\r
9352         break;\r
9353       case EditGame:\r
9354       default:\r
9355         return;\r
9356     }\r
9357     \r
9358     pausing = FALSE;\r
9359     StopClocks();\r
9360     first.offeredDraw = second.offeredDraw = 0;\r
9361 \r
9362     if (gameMode == PlayFromGameFile) {\r
9363         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9364         blackTimeRemaining = timeRemaining[1][currentMove];\r
9365         DisplayTitle("");\r
9366     }\r
9367 \r
9368     if (gameMode == MachinePlaysWhite ||\r
9369         gameMode == MachinePlaysBlack ||\r
9370         gameMode == TwoMachinesPlay ||\r
9371         gameMode == EndOfGame) {\r
9372         i = forwardMostMove;\r
9373         while (i > currentMove) {\r
9374             SendToProgram("undo\n", &first);\r
9375             i--;\r
9376         }\r
9377         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9378         blackTimeRemaining = timeRemaining[1][currentMove];\r
9379         DisplayBothClocks();\r
9380         if (whiteFlag || blackFlag) {\r
9381             whiteFlag = blackFlag = 0;\r
9382         }\r
9383         DisplayTitle("");\r
9384     }           \r
9385     \r
9386     gameMode = EditGame;\r
9387     ModeHighlight();\r
9388     SetGameInfo();\r
9389 }\r
9390 \r
9391 \r
9392 void\r
9393 EditPositionEvent()\r
9394 {\r
9395     if (gameMode == EditPosition) {\r
9396         EditGameEvent();\r
9397         return;\r
9398     }\r
9399     \r
9400     EditGameEvent();\r
9401     if (gameMode != EditGame) return;\r
9402     \r
9403     gameMode = EditPosition;\r
9404     ModeHighlight();\r
9405     SetGameInfo();\r
9406     if (currentMove > 0)\r
9407       CopyBoard(boards[0], boards[currentMove]);\r
9408     \r
9409     blackPlaysFirst = !WhiteOnMove(currentMove);\r
9410     ResetClocks();\r
9411     currentMove = forwardMostMove = backwardMostMove = 0;\r
9412     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9413     DisplayMove(-1);\r
9414 }\r
9415 \r
9416 void\r
9417 ExitAnalyzeMode()\r
9418 {\r
9419     if (first.analysisSupport && first.analyzing) {\r
9420       SendToProgram("exit\n", &first);\r
9421       first.analyzing = FALSE;\r
9422     }\r
9423     AnalysisPopDown();\r
9424     thinkOutput[0] = NULLCHAR;\r
9425 }\r
9426 \r
9427 void\r
9428 EditPositionDone()\r
9429 {\r
9430     startedFromSetupPosition = TRUE;\r
9431     InitChessProgram(&first);\r
9432     SendToProgram("force\n", &first);\r
9433     if (blackPlaysFirst) {\r
9434         strcpy(moveList[0], "");\r
9435         strcpy(parseList[0], "");\r
9436         currentMove = forwardMostMove = backwardMostMove = 1;\r
9437         CopyBoard(boards[1], boards[0]);\r
9438     } else {\r
9439         currentMove = forwardMostMove = backwardMostMove = 0;\r
9440     }\r
9441     SendBoard(&first, forwardMostMove);\r
9442     DisplayTitle("");\r
9443     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9444     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9445     gameMode = EditGame;\r
9446     ModeHighlight();\r
9447     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9448     ClearHighlights(); /* [AS] */\r
9449 }\r
9450 \r
9451 /* Pause for `ms' milliseconds */\r
9452 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
9453 void\r
9454 TimeDelay(ms)\r
9455      long ms;\r
9456 {\r
9457     TimeMark m1, m2;\r
9458 \r
9459     GetTimeMark(&m1);\r
9460     do {\r
9461         GetTimeMark(&m2);\r
9462     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
9463 }\r
9464 \r
9465 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
9466 void\r
9467 SendMultiLineToICS(buf)\r
9468      char *buf;\r
9469 {\r
9470     char temp[MSG_SIZ+1], *p;\r
9471     int len;\r
9472 \r
9473     len = strlen(buf);\r
9474     if (len > MSG_SIZ)\r
9475       len = MSG_SIZ;\r
9476   \r
9477     strncpy(temp, buf, len);\r
9478     temp[len] = 0;\r
9479 \r
9480     p = temp;\r
9481     while (*p) {\r
9482         if (*p == '\n' || *p == '\r')\r
9483           *p = ' ';\r
9484         ++p;\r
9485     }\r
9486 \r
9487     strcat(temp, "\n");\r
9488     SendToICS(temp);\r
9489     SendToPlayer(temp, strlen(temp));\r
9490 }\r
9491 \r
9492 void\r
9493 SetWhiteToPlayEvent()\r
9494 {\r
9495     if (gameMode == EditPosition) {\r
9496         blackPlaysFirst = FALSE;\r
9497         DisplayBothClocks();    /* works because currentMove is 0 */\r
9498     } else if (gameMode == IcsExamining) {\r
9499         SendToICS(ics_prefix);\r
9500         SendToICS("tomove white\n");\r
9501     }\r
9502 }\r
9503 \r
9504 void\r
9505 SetBlackToPlayEvent()\r
9506 {\r
9507     if (gameMode == EditPosition) {\r
9508         blackPlaysFirst = TRUE;\r
9509         currentMove = 1;        /* kludge */\r
9510         DisplayBothClocks();\r
9511         currentMove = 0;\r
9512     } else if (gameMode == IcsExamining) {\r
9513         SendToICS(ics_prefix);\r
9514         SendToICS("tomove black\n");\r
9515     }\r
9516 }\r
9517 \r
9518 void\r
9519 EditPositionMenuEvent(selection, x, y)\r
9520      ChessSquare selection;\r
9521      int x, y;\r
9522 {\r
9523     char buf[MSG_SIZ];\r
9524     ChessSquare piece = boards[0][y][x];\r
9525 \r
9526     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
9527 \r
9528     switch (selection) {\r
9529       case ClearBoard:\r
9530         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
9531             SendToICS(ics_prefix);\r
9532             SendToICS("bsetup clear\n");\r
9533         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
9534             SendToICS(ics_prefix);\r
9535             SendToICS("clearboard\n");\r
9536         } else {\r
9537             for (x = 0; x < BOARD_WIDTH; x++) {\r
9538                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
9539                     if (gameMode == IcsExamining) {\r
9540                         if (boards[currentMove][y][x] != EmptySquare) {\r
9541                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
9542                                     AAA + x, ONE + y);\r
9543                             SendToICS(buf);\r
9544                         }\r
9545                     } else {\r
9546                         boards[0][y][x] = EmptySquare;\r
9547                     }\r
9548                 }\r
9549             }\r
9550         }\r
9551         if (gameMode == EditPosition) {\r
9552             DrawPosition(FALSE, boards[0]);\r
9553         }\r
9554         break;\r
9555 \r
9556       case WhitePlay:\r
9557         SetWhiteToPlayEvent();\r
9558         break;\r
9559 \r
9560       case BlackPlay:\r
9561         SetBlackToPlayEvent();\r
9562         break;\r
9563 \r
9564       case EmptySquare:\r
9565         if (gameMode == IcsExamining) {\r
9566             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
9567             SendToICS(buf);\r
9568         } else {\r
9569             boards[0][y][x] = EmptySquare;\r
9570             DrawPosition(FALSE, boards[0]);\r
9571         }\r
9572         break;\r
9573 \r
9574       case PromotePiece:\r
9575         if(piece >= (int)WhitePawn && piece < (int)WhiteWazir ||\r
9576            piece >= (int)BlackPawn && piece < (int)BlackWazir   ) {\r
9577             selection = (ChessSquare) (PROMOTED piece);\r
9578         } else if(piece == EmptySquare) selection = WhiteWazir;\r
9579         else selection = (ChessSquare)((int)piece - 1);\r
9580         goto defaultlabel;\r
9581 \r
9582       case DemotePiece:\r
9583         if(piece >= (int)WhiteUnicorn && piece < (int)WhiteKing ||\r
9584            piece >= (int)BlackUnicorn && piece < (int)BlackKing   ) {\r
9585             selection = (ChessSquare) (DEMOTED piece);\r
9586         } else if( piece == WhiteKing || piece == BlackKing )\r
9587             selection = (ChessSquare)((int)piece - (int)WhiteKing + (int)WhiteMan);\r
9588         else if(piece == EmptySquare) selection = BlackWazir;\r
9589         else selection = (ChessSquare)((int)piece + 1);       \r
9590         goto defaultlabel;\r
9591 \r
9592       case WhiteQueen:\r
9593       case BlackQueen:\r
9594         if(gameInfo.variant == VariantShatranj ||\r
9595            gameInfo.variant == VariantXiangqi  ||\r
9596            gameInfo.variant == VariantCourier    )\r
9597             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
9598         goto defaultlabel;\r
9599 \r
9600       case WhiteKing:\r
9601       case BlackKing:\r
9602         if(gameInfo.variant == VariantXiangqi)\r
9603             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
9604         if(gameInfo.variant == VariantKnightmate)\r
9605             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
9606       default:\r
9607         defaultlabel:\r
9608         if (gameMode == IcsExamining) {\r
9609             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
9610                     PieceToChar(selection), AAA + x, ONE + y);\r
9611             SendToICS(buf);\r
9612         } else {\r
9613             boards[0][y][x] = selection;\r
9614             DrawPosition(FALSE, boards[0]);\r
9615         }\r
9616         break;\r
9617     }\r
9618 }\r
9619 \r
9620 \r
9621 void\r
9622 DropMenuEvent(selection, x, y)\r
9623      ChessSquare selection;\r
9624      int x, y;\r
9625 {\r
9626     ChessMove moveType;\r
9627 \r
9628     switch (gameMode) {\r
9629       case IcsPlayingWhite:\r
9630       case MachinePlaysBlack:\r
9631         if (!WhiteOnMove(currentMove)) {\r
9632             DisplayMoveError("It is Black's turn");\r
9633             return;\r
9634         }\r
9635         moveType = WhiteDrop;\r
9636         break;\r
9637       case IcsPlayingBlack:\r
9638       case MachinePlaysWhite:\r
9639         if (WhiteOnMove(currentMove)) {\r
9640             DisplayMoveError("It is White's turn");\r
9641             return;\r
9642         }\r
9643         moveType = BlackDrop;\r
9644         break;\r
9645       case EditGame:\r
9646         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
9647         break;\r
9648       default:\r
9649         return;\r
9650     }\r
9651 \r
9652     if (moveType == BlackDrop && selection < BlackPawn) {\r
9653       selection = (ChessSquare) ((int) selection\r
9654                                  + (int) BlackPawn - (int) WhitePawn);\r
9655     }\r
9656     if (boards[currentMove][y][x] != EmptySquare) {\r
9657         DisplayMoveError("That square is occupied");\r
9658         return;\r
9659     }\r
9660 \r
9661     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
9662 }\r
9663 \r
9664 void\r
9665 AcceptEvent()\r
9666 {\r
9667     /* Accept a pending offer of any kind from opponent */\r
9668     \r
9669     if (appData.icsActive) {\r
9670         SendToICS(ics_prefix);\r
9671         SendToICS("accept\n");\r
9672     } else if (cmailMsgLoaded) {\r
9673         if (currentMove == cmailOldMove &&\r
9674             commentList[cmailOldMove] != NULL &&\r
9675             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
9676                    "Black offers a draw" : "White offers a draw")) {\r
9677             TruncateGame();\r
9678             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
9679             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
9680         } else {\r
9681             DisplayError("There is no pending offer on this move", 0);\r
9682             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
9683         }\r
9684     } else {\r
9685         /* Not used for offers from chess program */\r
9686     }\r
9687 }\r
9688 \r
9689 void\r
9690 DeclineEvent()\r
9691 {\r
9692     /* Decline a pending offer of any kind from opponent */\r
9693     \r
9694     if (appData.icsActive) {\r
9695         SendToICS(ics_prefix);\r
9696         SendToICS("decline\n");\r
9697     } else if (cmailMsgLoaded) {\r
9698         if (currentMove == cmailOldMove &&\r
9699             commentList[cmailOldMove] != NULL &&\r
9700             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
9701                    "Black offers a draw" : "White offers a draw")) {\r
9702 #ifdef NOTDEF\r
9703             AppendComment(cmailOldMove, "Draw declined");\r
9704             DisplayComment(cmailOldMove - 1, "Draw declined");\r
9705 #endif /*NOTDEF*/\r
9706         } else {\r
9707             DisplayError("There is no pending offer on this move", 0);\r
9708         }\r
9709     } else {\r
9710         /* Not used for offers from chess program */\r
9711     }\r
9712 }\r
9713 \r
9714 void\r
9715 RematchEvent()\r
9716 {\r
9717     /* Issue ICS rematch command */\r
9718     if (appData.icsActive) {\r
9719         SendToICS(ics_prefix);\r
9720         SendToICS("rematch\n");\r
9721     }\r
9722 }\r
9723 \r
9724 void\r
9725 CallFlagEvent()\r
9726 {\r
9727     /* Call your opponent's flag (claim a win on time) */\r
9728     if (appData.icsActive) {\r
9729         SendToICS(ics_prefix);\r
9730         SendToICS("flag\n");\r
9731     } else {\r
9732         switch (gameMode) {\r
9733           default:\r
9734             return;\r
9735           case MachinePlaysWhite:\r
9736             if (whiteFlag) {\r
9737                 if (blackFlag)\r
9738                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
9739                            GE_PLAYER);\r
9740                 else\r
9741                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
9742             } else {\r
9743                 DisplayError("Your opponent is not out of time", 0);\r
9744             }\r
9745             break;\r
9746           case MachinePlaysBlack:\r
9747             if (blackFlag) {\r
9748                 if (whiteFlag)\r
9749                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
9750                            GE_PLAYER);\r
9751                 else\r
9752                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
9753             } else {\r
9754                 DisplayError("Your opponent is not out of time", 0);\r
9755             }\r
9756             break;\r
9757         }\r
9758     }\r
9759 }\r
9760 \r
9761 void\r
9762 DrawEvent()\r
9763 {\r
9764     /* Offer draw or accept pending draw offer from opponent */\r
9765     \r
9766     if (appData.icsActive) {\r
9767         /* Note: tournament rules require draw offers to be\r
9768            made after you make your move but before you punch\r
9769            your clock.  Currently ICS doesn't let you do that;\r
9770            instead, you immediately punch your clock after making\r
9771            a move, but you can offer a draw at any time. */\r
9772         \r
9773         SendToICS(ics_prefix);\r
9774         SendToICS("draw\n");\r
9775     } else if (cmailMsgLoaded) {\r
9776         if (currentMove == cmailOldMove &&\r
9777             commentList[cmailOldMove] != NULL &&\r
9778             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
9779                    "Black offers a draw" : "White offers a draw")) {\r
9780             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
9781             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
9782         } else if (currentMove == cmailOldMove + 1) {\r
9783             char *offer = WhiteOnMove(cmailOldMove) ?\r
9784               "White offers a draw" : "Black offers a draw";\r
9785             AppendComment(currentMove, offer);\r
9786             DisplayComment(currentMove - 1, offer);\r
9787             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
9788         } else {\r
9789             DisplayError("You must make your move before offering a draw", 0);\r
9790             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
9791         }\r
9792     } else if (first.offeredDraw) {\r
9793         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
9794     } else {\r
9795         if (first.sendDrawOffers) {\r
9796             SendToProgram("draw\n", &first);\r
9797             userOfferedDraw = TRUE;\r
9798         }\r
9799     }\r
9800 }\r
9801 \r
9802 void\r
9803 AdjournEvent()\r
9804 {\r
9805     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
9806     \r
9807     if (appData.icsActive) {\r
9808         SendToICS(ics_prefix);\r
9809         SendToICS("adjourn\n");\r
9810     } else {\r
9811         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
9812     }\r
9813 }\r
9814 \r
9815 \r
9816 void\r
9817 AbortEvent()\r
9818 {\r
9819     /* Offer Abort or accept pending Abort offer from opponent */\r
9820     \r
9821     if (appData.icsActive) {\r
9822         SendToICS(ics_prefix);\r
9823         SendToICS("abort\n");\r
9824     } else {\r
9825         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
9826     }\r
9827 }\r
9828 \r
9829 void\r
9830 ResignEvent()\r
9831 {\r
9832     /* Resign.  You can do this even if it's not your turn. */\r
9833     \r
9834     if (appData.icsActive) {\r
9835         SendToICS(ics_prefix);\r
9836         SendToICS("resign\n");\r
9837     } else {\r
9838         switch (gameMode) {\r
9839           case MachinePlaysWhite:\r
9840             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
9841             break;\r
9842           case MachinePlaysBlack:\r
9843             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
9844             break;\r
9845           case EditGame:\r
9846             if (cmailMsgLoaded) {\r
9847                 TruncateGame();\r
9848                 if (WhiteOnMove(cmailOldMove)) {\r
9849                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
9850                 } else {\r
9851                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
9852                 }\r
9853                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
9854             }\r
9855             break;\r
9856           default:\r
9857             break;\r
9858         }\r
9859     }\r
9860 }\r
9861 \r
9862 \r
9863 void\r
9864 StopObservingEvent()\r
9865 {\r
9866     /* Stop observing current games */\r
9867     SendToICS(ics_prefix);\r
9868     SendToICS("unobserve\n");\r
9869 }\r
9870 \r
9871 void\r
9872 StopExaminingEvent()\r
9873 {\r
9874     /* Stop observing current game */\r
9875     SendToICS(ics_prefix);\r
9876     SendToICS("unexamine\n");\r
9877 }\r
9878 \r
9879 void\r
9880 ForwardInner(target)\r
9881      int target;\r
9882 {\r
9883     int limit;\r
9884 \r
9885     if (appData.debugMode)\r
9886         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
9887                 target, currentMove, forwardMostMove);\r
9888 \r
9889     if (gameMode == EditPosition)\r
9890       return;\r
9891 \r
9892     if (gameMode == PlayFromGameFile && !pausing)\r
9893       PauseEvent();\r
9894     \r
9895     if (gameMode == IcsExamining && pausing)\r
9896       limit = pauseExamForwardMostMove;\r
9897     else\r
9898       limit = forwardMostMove;\r
9899     \r
9900     if (target > limit) target = limit;\r
9901 \r
9902     if (target > 0 && moveList[target - 1][0]) {\r
9903         int fromX, fromY, toX, toY;\r
9904         toX = moveList[target - 1][2] - AAA;\r
9905         toY = moveList[target - 1][3] - ONE;\r
9906         if (moveList[target - 1][1] == '@') {\r
9907             if (appData.highlightLastMove) {\r
9908                 SetHighlights(-1, -1, toX, toY);\r
9909             }\r
9910         } else {\r
9911             fromX = moveList[target - 1][0] - AAA;\r
9912             fromY = moveList[target - 1][1] - ONE;\r
9913             if (target == currentMove + 1) {\r
9914                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
9915             }\r
9916             if (appData.highlightLastMove) {\r
9917                 SetHighlights(fromX, fromY, toX, toY);\r
9918             }\r
9919         }\r
9920     }\r
9921     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
9922         gameMode == Training || gameMode == PlayFromGameFile || \r
9923         gameMode == AnalyzeFile) {\r
9924         while (currentMove < target) {\r
9925             SendMoveToProgram(currentMove++, &first);\r
9926         }\r
9927     } else {\r
9928         currentMove = target;\r
9929     }\r
9930     \r
9931     if (gameMode == EditGame || gameMode == EndOfGame) {\r
9932         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9933         blackTimeRemaining = timeRemaining[1][currentMove];\r
9934     }\r
9935     DisplayBothClocks();\r
9936     DisplayMove(currentMove - 1);\r
9937     DrawPosition(FALSE, boards[currentMove]);\r
9938     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
9939     if (commentList[currentMove] && !matchMode && gameMode != Training) {\r
9940         DisplayComment(currentMove - 1, commentList[currentMove]);\r
9941     }\r
9942 }\r
9943 \r
9944 \r
9945 void\r
9946 ForwardEvent()\r
9947 {\r
9948     if (gameMode == IcsExamining && !pausing) {\r
9949         SendToICS(ics_prefix);\r
9950         SendToICS("forward\n");\r
9951     } else {\r
9952         ForwardInner(currentMove + 1);\r
9953     }\r
9954 }\r
9955 \r
9956 void\r
9957 ToEndEvent()\r
9958 {\r
9959     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
9960         /* to optimze, we temporarily turn off analysis mode while we feed\r
9961          * the remaining moves to the engine. Otherwise we get analysis output\r
9962          * after each move.\r
9963          */ \r
9964         if (first.analysisSupport) {\r
9965           SendToProgram("exit\nforce\n", &first);\r
9966           first.analyzing = FALSE;\r
9967         }\r
9968     }\r
9969         \r
9970     if (gameMode == IcsExamining && !pausing) {\r
9971         SendToICS(ics_prefix);\r
9972         SendToICS("forward 999999\n");\r
9973     } else {\r
9974         ForwardInner(forwardMostMove);\r
9975     }\r
9976 \r
9977     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
9978         /* we have fed all the moves, so reactivate analysis mode */\r
9979         SendToProgram("analyze\n", &first);\r
9980         first.analyzing = TRUE;\r
9981         /*first.maybeThinking = TRUE;*/\r
9982         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
9983     }\r
9984 }\r
9985 \r
9986 void\r
9987 BackwardInner(target)\r
9988      int target;\r
9989 {\r
9990     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
9991 \r
9992     if (appData.debugMode)\r
9993         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
9994                 target, currentMove, forwardMostMove);\r
9995 \r
9996     if (gameMode == EditPosition) return;\r
9997     if (currentMove <= backwardMostMove) {\r
9998         ClearHighlights();\r
9999         DrawPosition(full_redraw, boards[currentMove]);\r
10000         return;\r
10001     }\r
10002     if (gameMode == PlayFromGameFile && !pausing)\r
10003       PauseEvent();\r
10004     \r
10005     if (moveList[target][0]) {\r
10006         int fromX, fromY, toX, toY;\r
10007         toX = moveList[target][2] - AAA;\r
10008         toY = moveList[target][3] - ONE;\r
10009         if (moveList[target][1] == '@') {\r
10010             if (appData.highlightLastMove) {\r
10011                 SetHighlights(-1, -1, toX, toY);\r
10012             }\r
10013         } else {\r
10014             fromX = moveList[target][0] - AAA;\r
10015             fromY = moveList[target][1] - ONE;\r
10016             if (target == currentMove - 1) {\r
10017                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
10018             }\r
10019             if (appData.highlightLastMove) {\r
10020                 SetHighlights(fromX, fromY, toX, toY);\r
10021             }\r
10022         }\r
10023     }\r
10024     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
10025         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
10026         while (currentMove > target) {\r
10027             SendToProgram("undo\n", &first);\r
10028             currentMove--;\r
10029         }\r
10030     } else {\r
10031         currentMove = target;\r
10032     }\r
10033     \r
10034     if (gameMode == EditGame || gameMode == EndOfGame) {\r
10035         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10036         blackTimeRemaining = timeRemaining[1][currentMove];\r
10037     }\r
10038     DisplayBothClocks();\r
10039     DisplayMove(currentMove - 1);\r
10040     DrawPosition(full_redraw, boards[currentMove]);\r
10041     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
10042     if (commentList[currentMove] != NULL) {\r
10043         DisplayComment(currentMove - 1, commentList[currentMove]);\r
10044     }\r
10045 }\r
10046 \r
10047 void\r
10048 BackwardEvent()\r
10049 {\r
10050     if (gameMode == IcsExamining && !pausing) {\r
10051         SendToICS(ics_prefix);\r
10052         SendToICS("backward\n");\r
10053     } else {\r
10054         BackwardInner(currentMove - 1);\r
10055     }\r
10056 }\r
10057 \r
10058 void\r
10059 ToStartEvent()\r
10060 {\r
10061     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10062         /* to optimze, we temporarily turn off analysis mode while we undo\r
10063          * all the moves. Otherwise we get analysis output after each undo.\r
10064          */ \r
10065         if (first.analysisSupport) {\r
10066           SendToProgram("exit\nforce\n", &first);\r
10067           first.analyzing = FALSE;\r
10068         }\r
10069     }\r
10070 \r
10071     if (gameMode == IcsExamining && !pausing) {\r
10072         SendToICS(ics_prefix);\r
10073         SendToICS("backward 999999\n");\r
10074     } else {\r
10075         BackwardInner(backwardMostMove);\r
10076     }\r
10077 \r
10078     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10079         /* we have fed all the moves, so reactivate analysis mode */\r
10080         SendToProgram("analyze\n", &first);\r
10081         first.analyzing = TRUE;\r
10082         /*first.maybeThinking = TRUE;*/\r
10083         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10084     }\r
10085 }\r
10086 \r
10087 void\r
10088 ToNrEvent(int to)\r
10089 {\r
10090   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
10091   if (to >= forwardMostMove) to = forwardMostMove;\r
10092   if (to <= backwardMostMove) to = backwardMostMove;\r
10093   if (to < currentMove) {\r
10094     BackwardInner(to);\r
10095   } else {\r
10096     ForwardInner(to);\r
10097   }\r
10098 }\r
10099 \r
10100 void\r
10101 RevertEvent()\r
10102 {\r
10103     if (gameMode != IcsExamining) {\r
10104         DisplayError("You are not examining a game", 0);\r
10105         return;\r
10106     }\r
10107     if (pausing) {\r
10108         DisplayError("You can't revert while pausing", 0);\r
10109         return;\r
10110     }\r
10111     SendToICS(ics_prefix);\r
10112     SendToICS("revert\n");\r
10113 }\r
10114 \r
10115 void\r
10116 RetractMoveEvent()\r
10117 {\r
10118     switch (gameMode) {\r
10119       case MachinePlaysWhite:\r
10120       case MachinePlaysBlack:\r
10121         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10122             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
10123             return;\r
10124         }\r
10125         if (forwardMostMove < 2) return;\r
10126         currentMove = forwardMostMove = forwardMostMove - 2;\r
10127         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10128         blackTimeRemaining = timeRemaining[1][currentMove];\r
10129         DisplayBothClocks();\r
10130         DisplayMove(currentMove - 1);\r
10131         ClearHighlights();/*!! could figure this out*/\r
10132         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
10133         SendToProgram("remove\n", &first);\r
10134         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
10135         break;\r
10136 \r
10137       case BeginningOfGame:\r
10138       default:\r
10139         break;\r
10140 \r
10141       case IcsPlayingWhite:\r
10142       case IcsPlayingBlack:\r
10143         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
10144             SendToICS(ics_prefix);\r
10145             SendToICS("takeback 2\n");\r
10146         } else {\r
10147             SendToICS(ics_prefix);\r
10148             SendToICS("takeback 1\n");\r
10149         }\r
10150         break;\r
10151     }\r
10152 }\r
10153 \r
10154 void\r
10155 MoveNowEvent()\r
10156 {\r
10157     ChessProgramState *cps;\r
10158 \r
10159     switch (gameMode) {\r
10160       case MachinePlaysWhite:\r
10161         if (!WhiteOnMove(forwardMostMove)) {\r
10162             DisplayError("It is your turn", 0);\r
10163             return;\r
10164         }\r
10165         cps = &first;\r
10166         break;\r
10167       case MachinePlaysBlack:\r
10168         if (WhiteOnMove(forwardMostMove)) {\r
10169             DisplayError("It is your turn", 0);\r
10170             return;\r
10171         }\r
10172         cps = &first;\r
10173         break;\r
10174       case TwoMachinesPlay:\r
10175         if (WhiteOnMove(forwardMostMove) ==\r
10176             (first.twoMachinesColor[0] == 'w')) {\r
10177             cps = &first;\r
10178         } else {\r
10179             cps = &second;\r
10180         }\r
10181         break;\r
10182       case BeginningOfGame:\r
10183       default:\r
10184         return;\r
10185     }\r
10186     SendToProgram("?\n", cps);\r
10187 }\r
10188 \r
10189 void\r
10190 TruncateGameEvent()\r
10191 {\r
10192     EditGameEvent();\r
10193     if (gameMode != EditGame) return;\r
10194     TruncateGame();\r
10195 }\r
10196 \r
10197 void\r
10198 TruncateGame()\r
10199 {\r
10200     if (forwardMostMove > currentMove) {\r
10201         if (gameInfo.resultDetails != NULL) {\r
10202             free(gameInfo.resultDetails);\r
10203             gameInfo.resultDetails = NULL;\r
10204             gameInfo.result = GameUnfinished;\r
10205         }\r
10206         forwardMostMove = currentMove;\r
10207         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
10208                    currentMove-1);\r
10209     }\r
10210 }\r
10211 \r
10212 void\r
10213 HintEvent()\r
10214 {\r
10215     if (appData.noChessProgram) return;\r
10216     switch (gameMode) {\r
10217       case MachinePlaysWhite:\r
10218         if (WhiteOnMove(forwardMostMove)) {\r
10219             DisplayError("Wait until your turn", 0);\r
10220             return;\r
10221         }\r
10222         break;\r
10223       case BeginningOfGame:\r
10224       case MachinePlaysBlack:\r
10225         if (!WhiteOnMove(forwardMostMove)) {\r
10226             DisplayError("Wait until your turn", 0);\r
10227             return;\r
10228         }\r
10229         break;\r
10230       default:\r
10231         DisplayError("No hint available", 0);\r
10232         return;\r
10233     }\r
10234     SendToProgram("hint\n", &first);\r
10235     hintRequested = TRUE;\r
10236 }\r
10237 \r
10238 void\r
10239 BookEvent()\r
10240 {\r
10241     if (appData.noChessProgram) return;\r
10242     switch (gameMode) {\r
10243       case MachinePlaysWhite:\r
10244         if (WhiteOnMove(forwardMostMove)) {\r
10245             DisplayError("Wait until your turn", 0);\r
10246             return;\r
10247         }\r
10248         break;\r
10249       case BeginningOfGame:\r
10250       case MachinePlaysBlack:\r
10251         if (!WhiteOnMove(forwardMostMove)) {\r
10252             DisplayError("Wait until your turn", 0);\r
10253             return;\r
10254         }\r
10255         break;\r
10256       case EditPosition:\r
10257         EditPositionDone();\r
10258         break;\r
10259       case TwoMachinesPlay:\r
10260         return;\r
10261       default:\r
10262         break;\r
10263     }\r
10264     SendToProgram("bk\n", &first);\r
10265     bookOutput[0] = NULLCHAR;\r
10266     bookRequested = TRUE;\r
10267 }\r
10268 \r
10269 void\r
10270 AboutGameEvent()\r
10271 {\r
10272     char *tags = PGNTags(&gameInfo);\r
10273     TagsPopUp(tags, CmailMsg());\r
10274     free(tags);\r
10275 }\r
10276 \r
10277 /* end button procedures */\r
10278 \r
10279 void\r
10280 PrintPosition(fp, move)\r
10281      FILE *fp;\r
10282      int move;\r
10283 {\r
10284     int i, j;\r
10285     \r
10286     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
10287         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
10288             char c = PieceToChar(boards[move][i][j]);\r
10289             fputc(c == 'x' ? '.' : c, fp);\r
10290             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
10291         }\r
10292     }\r
10293     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
10294       fprintf(fp, "white to play\n");\r
10295     else\r
10296       fprintf(fp, "black to play\n");\r
10297 }\r
10298 \r
10299 void\r
10300 PrintOpponents(fp)\r
10301      FILE *fp;\r
10302 {\r
10303     if (gameInfo.white != NULL) {\r
10304         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
10305     } else {\r
10306         fprintf(fp, "\n");\r
10307     }\r
10308 }\r
10309 \r
10310 /* Find last component of program's own name, using some heuristics */\r
10311 void\r
10312 TidyProgramName(prog, host, buf)\r
10313      char *prog, *host, buf[MSG_SIZ];\r
10314 {\r
10315     char *p, *q;\r
10316     int local = (strcmp(host, "localhost") == 0);\r
10317     while (!local && (p = strchr(prog, ';')) != NULL) {\r
10318         p++;\r
10319         while (*p == ' ') p++;\r
10320         prog = p;\r
10321     }\r
10322     if (*prog == '"' || *prog == '\'') {\r
10323         q = strchr(prog + 1, *prog);\r
10324     } else {\r
10325         q = strchr(prog, ' ');\r
10326     }\r
10327     if (q == NULL) q = prog + strlen(prog);\r
10328     p = q;\r
10329     while (p >= prog && *p != '/' && *p != '\\') p--;\r
10330     p++;\r
10331     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
10332     memcpy(buf, p, q - p);\r
10333     buf[q - p] = NULLCHAR;\r
10334     if (!local) {\r
10335         strcat(buf, "@");\r
10336         strcat(buf, host);\r
10337     }\r
10338 }\r
10339 \r
10340 char *\r
10341 TimeControlTagValue()\r
10342 {\r
10343     char buf[MSG_SIZ];\r
10344     if (!appData.clockMode) {\r
10345         strcpy(buf, "-");\r
10346     } else if (movesPerSession > 0) {\r
10347         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
10348     } else if (timeIncrement == 0) {\r
10349         sprintf(buf, "%ld", timeControl/1000);\r
10350     } else {\r
10351         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
10352     }\r
10353     return StrSave(buf);\r
10354 }\r
10355 \r
10356 void\r
10357 SetGameInfo()\r
10358 {\r
10359     /* This routine is used only for certain modes */\r
10360     VariantClass v = gameInfo.variant;\r
10361     ClearGameInfo(&gameInfo);\r
10362     gameInfo.variant = v;\r
10363 \r
10364     switch (gameMode) {\r
10365       case MachinePlaysWhite:\r
10366         gameInfo.event = StrSave( appData.pgnEventHeader );\r
10367         gameInfo.site = StrSave(HostName());\r
10368         gameInfo.date = PGNDate();\r
10369         gameInfo.round = StrSave("-");\r
10370         gameInfo.white = StrSave(first.tidy);\r
10371         gameInfo.black = StrSave(UserName());\r
10372         gameInfo.timeControl = TimeControlTagValue();\r
10373         break;\r
10374 \r
10375       case MachinePlaysBlack:\r
10376         gameInfo.event = StrSave( appData.pgnEventHeader );\r
10377         gameInfo.site = StrSave(HostName());\r
10378         gameInfo.date = PGNDate();\r
10379         gameInfo.round = StrSave("-");\r
10380         gameInfo.white = StrSave(UserName());\r
10381         gameInfo.black = StrSave(first.tidy);\r
10382         gameInfo.timeControl = TimeControlTagValue();\r
10383         break;\r
10384 \r
10385       case TwoMachinesPlay:\r
10386         gameInfo.event = StrSave( appData.pgnEventHeader );\r
10387         gameInfo.site = StrSave(HostName());\r
10388         gameInfo.date = PGNDate();\r
10389         if (matchGame > 0) {\r
10390             char buf[MSG_SIZ];\r
10391             sprintf(buf, "%d", matchGame);\r
10392             gameInfo.round = StrSave(buf);\r
10393         } else {\r
10394             gameInfo.round = StrSave("-");\r
10395         }\r
10396         if (first.twoMachinesColor[0] == 'w') {\r
10397             gameInfo.white = StrSave(first.tidy);\r
10398             gameInfo.black = StrSave(second.tidy);\r
10399         } else {\r
10400             gameInfo.white = StrSave(second.tidy);\r
10401             gameInfo.black = StrSave(first.tidy);\r
10402         }\r
10403         gameInfo.timeControl = TimeControlTagValue();\r
10404         break;\r
10405 \r
10406       case EditGame:\r
10407         gameInfo.event = StrSave("Edited game");\r
10408         gameInfo.site = StrSave(HostName());\r
10409         gameInfo.date = PGNDate();\r
10410         gameInfo.round = StrSave("-");\r
10411         gameInfo.white = StrSave("-");\r
10412         gameInfo.black = StrSave("-");\r
10413         break;\r
10414 \r
10415       case EditPosition:\r
10416         gameInfo.event = StrSave("Edited position");\r
10417         gameInfo.site = StrSave(HostName());\r
10418         gameInfo.date = PGNDate();\r
10419         gameInfo.round = StrSave("-");\r
10420         gameInfo.white = StrSave("-");\r
10421         gameInfo.black = StrSave("-");\r
10422         break;\r
10423 \r
10424       case IcsPlayingWhite:\r
10425       case IcsPlayingBlack:\r
10426       case IcsObserving:\r
10427       case IcsExamining:\r
10428         break;\r
10429 \r
10430       case PlayFromGameFile:\r
10431         gameInfo.event = StrSave("Game from non-PGN file");\r
10432         gameInfo.site = StrSave(HostName());\r
10433         gameInfo.date = PGNDate();\r
10434         gameInfo.round = StrSave("-");\r
10435         gameInfo.white = StrSave("?");\r
10436         gameInfo.black = StrSave("?");\r
10437         break;\r
10438 \r
10439       default:\r
10440         break;\r
10441     }\r
10442 }\r
10443 \r
10444 void\r
10445 ReplaceComment(index, text)\r
10446      int index;\r
10447      char *text;\r
10448 {\r
10449     int len;\r
10450 \r
10451     while (*text == '\n') text++;\r
10452     len = strlen(text);\r
10453     while (len > 0 && text[len - 1] == '\n') len--;\r
10454 \r
10455     if (commentList[index] != NULL)\r
10456       free(commentList[index]);\r
10457 \r
10458     if (len == 0) {\r
10459         commentList[index] = NULL;\r
10460         return;\r
10461     }\r
10462     commentList[index] = (char *) malloc(len + 2);\r
10463     strncpy(commentList[index], text, len);\r
10464     commentList[index][len] = '\n';\r
10465     commentList[index][len + 1] = NULLCHAR;\r
10466 }\r
10467 \r
10468 void\r
10469 CrushCRs(text)\r
10470      char *text;\r
10471 {\r
10472   char *p = text;\r
10473   char *q = text;\r
10474   char ch;\r
10475 \r
10476   do {\r
10477     ch = *p++;\r
10478     if (ch == '\r') continue;\r
10479     *q++ = ch;\r
10480   } while (ch != '\0');\r
10481 }\r
10482 \r
10483 void\r
10484 AppendComment(index, text)\r
10485      int index;\r
10486      char *text;\r
10487 {\r
10488     int oldlen, len;\r
10489     char *old;\r
10490 \r
10491     GetInfoFromComment( index, text );\r
10492 \r
10493     CrushCRs(text);\r
10494     while (*text == '\n') text++;\r
10495     len = strlen(text);\r
10496     while (len > 0 && text[len - 1] == '\n') len--;\r
10497 \r
10498     if (len == 0) return;\r
10499 \r
10500     if (commentList[index] != NULL) {\r
10501         old = commentList[index];\r
10502         oldlen = strlen(old);\r
10503         commentList[index] = (char *) malloc(oldlen + len + 2);\r
10504         strcpy(commentList[index], old);\r
10505         free(old);\r
10506         strncpy(&commentList[index][oldlen], text, len);\r
10507         commentList[index][oldlen + len] = '\n';\r
10508         commentList[index][oldlen + len + 1] = NULLCHAR;\r
10509     } else {\r
10510         commentList[index] = (char *) malloc(len + 2);\r
10511         strncpy(commentList[index], text, len);\r
10512         commentList[index][len] = '\n';\r
10513         commentList[index][len + 1] = NULLCHAR;\r
10514     }\r
10515 }\r
10516 \r
10517 static char * FindStr( char * text, char * sub_text )\r
10518 {\r
10519     char * result = strstr( text, sub_text );\r
10520 \r
10521     if( result != NULL ) {\r
10522         result += strlen( sub_text );\r
10523     }\r
10524 \r
10525     return result;\r
10526 }\r
10527 \r
10528 /* [AS] Try to extract PV info from PGN comment */\r
10529 void GetInfoFromComment( int index, char * text )\r
10530 {\r
10531     if( text != NULL && index > 0 ) {\r
10532         int score = 0;\r
10533         int depth = 0;\r
10534         int time = -1;\r
10535         char * s_eval = FindStr( text, "[%eval " );\r
10536         char * s_emt = FindStr( text, "[%emt " );\r
10537 \r
10538         if( s_eval != NULL || s_emt != NULL ) {\r
10539             /* New style */\r
10540             char delim;\r
10541 \r
10542             if( s_eval != NULL ) {\r
10543                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
10544                     return;\r
10545                 }\r
10546 \r
10547                 if( delim != ']' ) {\r
10548                     return;\r
10549                 }\r
10550             }\r
10551 \r
10552             if( s_emt != NULL ) {\r
10553             }\r
10554         }\r
10555         else {\r
10556             /* We expect something like: [+|-]nnn.nn/dd */\r
10557             char * sep = strchr( text, '/' );\r
10558             int score_lo = 0;\r
10559 \r
10560             if( sep == NULL || sep < (text+4) ) {\r
10561                 return;\r
10562             }\r
10563 \r
10564             if( sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {\r
10565                 return;\r
10566             }\r
10567 \r
10568             if( score_lo < 0 || score_lo >= 100 ) {\r
10569                 return;\r
10570             }\r
10571 \r
10572             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
10573         }\r
10574 \r
10575         if( depth <= 0 ) {\r
10576             return;\r
10577         }\r
10578 \r
10579         if( time < 0 ) {\r
10580             time = -1;\r
10581         }\r
10582 \r
10583         pvInfoList[index-1].depth = depth;\r
10584         pvInfoList[index-1].score = score;\r
10585         pvInfoList[index-1].time = time;\r
10586     }\r
10587 }\r
10588 \r
10589 void\r
10590 SendToProgram(message, cps)\r
10591      char *message;\r
10592      ChessProgramState *cps;\r
10593 {\r
10594     int count, outCount, error;\r
10595     char buf[MSG_SIZ];\r
10596 \r
10597     if (cps->pr == NULL) return;\r
10598     Attention(cps);\r
10599     \r
10600     if (appData.debugMode) {\r
10601         TimeMark now;\r
10602         GetTimeMark(&now);\r
10603         fprintf(debugFP, "%ld >%-6s: %s", \r
10604                 SubtractTimeMarks(&now, &programStartTime),\r
10605                 cps->which, message);\r
10606     }\r
10607     \r
10608     count = strlen(message);\r
10609     outCount = OutputToProcess(cps->pr, message, count, &error);\r
10610     if (outCount < count && !exiting) {\r
10611         sprintf(buf, "Error writing to %s chess program", cps->which);\r
10612         DisplayFatalError(buf, error, 1);\r
10613     }\r
10614 }\r
10615 \r
10616 void\r
10617 ReceiveFromProgram(isr, closure, message, count, error)\r
10618      InputSourceRef isr;\r
10619      VOIDSTAR closure;\r
10620      char *message;\r
10621      int count;\r
10622      int error;\r
10623 {\r
10624     char *end_str;\r
10625     char buf[MSG_SIZ];\r
10626     ChessProgramState *cps = (ChessProgramState *)closure;\r
10627 \r
10628     if (isr != cps->isr) return; /* Killed intentionally */\r
10629     if (count <= 0) {\r
10630         if (count == 0) {\r
10631             sprintf(buf,\r
10632                     "Error: %s chess program (%s) exited unexpectedly",\r
10633                     cps->which, cps->program);\r
10634             RemoveInputSource(cps->isr);\r
10635             DisplayFatalError(buf, 0, 1);\r
10636         } else {\r
10637             sprintf(buf,\r
10638                     "Error reading from %s chess program (%s)",\r
10639                     cps->which, cps->program);\r
10640             RemoveInputSource(cps->isr);\r
10641 \r
10642             /* [AS] Program is misbehaving badly... kill it */\r
10643             if( count == -2 ) {\r
10644                 DestroyChildProcess( cps->pr, 9 );\r
10645                 cps->pr = NoProc;\r
10646             }\r
10647 \r
10648             DisplayFatalError(buf, error, 1);\r
10649         }\r
10650         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10651         return;\r
10652     }\r
10653     \r
10654     if ((end_str = strchr(message, '\r')) != NULL)\r
10655       *end_str = NULLCHAR;\r
10656     if ((end_str = strchr(message, '\n')) != NULL)\r
10657       *end_str = NULLCHAR;\r
10658     \r
10659     if (appData.debugMode) {\r
10660         TimeMark now;\r
10661         GetTimeMark(&now);\r
10662         fprintf(debugFP, "%ld <%-6s: %s\n", \r
10663                 SubtractTimeMarks(&now, &programStartTime),\r
10664                 cps->which, message);\r
10665     }\r
10666     HandleMachineMove(message, cps);\r
10667 }\r
10668 \r
10669 \r
10670 void\r
10671 SendTimeControl(cps, mps, tc, inc, sd, st)\r
10672      ChessProgramState *cps;\r
10673      int mps, inc, sd, st;\r
10674      long tc;\r
10675 {\r
10676     char buf[MSG_SIZ];\r
10677     int seconds = (tc / 1000) % 60;\r
10678 \r
10679     if( timeControl_2 > 0 ) {\r
10680         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
10681             tc = timeControl_2;\r
10682         }\r
10683     }\r
10684 \r
10685     if (st > 0) {\r
10686       /* Set exact time per move, normally using st command */\r
10687       if (cps->stKludge) {\r
10688         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
10689         seconds = st % 60;\r
10690         if (seconds == 0) {\r
10691           sprintf(buf, "level 1 %d\n", st/60);\r
10692         } else {\r
10693           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
10694         }\r
10695       } else {\r
10696         sprintf(buf, "st %d\n", st);\r
10697       }\r
10698     } else {\r
10699       /* Set conventional or incremental time control, using level command */\r
10700       if (seconds == 0) {\r
10701         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
10702            Fixed in later versions, but still avoid :seconds\r
10703            when seconds is 0. */\r
10704         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
10705       } else {\r
10706         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
10707                 seconds, inc/1000);\r
10708       }\r
10709     }\r
10710     SendToProgram(buf, cps);\r
10711 \r
10712     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
10713     /* Orthogonally, limit search to given depth */\r
10714     if (sd > 0) {\r
10715       if (cps->sdKludge) {\r
10716         sprintf(buf, "depth\n%d\n", sd);\r
10717       } else {\r
10718         sprintf(buf, "sd %d\n", sd);\r
10719       }\r
10720       SendToProgram(buf, cps);\r
10721     }\r
10722 }\r
10723 \r
10724 void\r
10725 SendTimeRemaining(cps, machineWhite)\r
10726      ChessProgramState *cps;\r
10727      int /*boolean*/ machineWhite;\r
10728 {\r
10729     char message[MSG_SIZ];\r
10730     long time, otime;\r
10731 \r
10732     /* Note: this routine must be called when the clocks are stopped\r
10733        or when they have *just* been set or switched; otherwise\r
10734        it will be off by the time since the current tick started.\r
10735     */\r
10736     if (machineWhite) {\r
10737         time = whiteTimeRemaining / 10;\r
10738         otime = blackTimeRemaining / 10;\r
10739     } else {\r
10740         time = blackTimeRemaining / 10;\r
10741         otime = whiteTimeRemaining / 10;\r
10742     }\r
10743     if (time <= 0) time = 1;\r
10744     if (otime <= 0) otime = 1;\r
10745     \r
10746     sprintf(message, "time %ld\n", time);\r
10747     SendToProgram(message, cps);\r
10748 \r
10749     sprintf(message, "otim %ld\n", otime);\r
10750     SendToProgram(message, cps);\r
10751 }\r
10752 \r
10753 int\r
10754 BoolFeature(p, name, loc, cps)\r
10755      char **p;\r
10756      char *name;\r
10757      int *loc;\r
10758      ChessProgramState *cps;\r
10759 {\r
10760   char buf[MSG_SIZ];\r
10761   int len = strlen(name);\r
10762   int val;\r
10763   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
10764     (*p) += len + 1;\r
10765     sscanf(*p, "%d", &val);\r
10766     *loc = (val != 0);\r
10767     while (**p && **p != ' ') (*p)++;\r
10768     sprintf(buf, "accepted %s\n", name);\r
10769     SendToProgram(buf, cps);\r
10770     return TRUE;\r
10771   }\r
10772   return FALSE;\r
10773 }\r
10774 \r
10775 int\r
10776 IntFeature(p, name, loc, cps)\r
10777      char **p;\r
10778      char *name;\r
10779      int *loc;\r
10780      ChessProgramState *cps;\r
10781 {\r
10782   char buf[MSG_SIZ];\r
10783   int len = strlen(name);\r
10784   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
10785     (*p) += len + 1;\r
10786     sscanf(*p, "%d", loc);\r
10787     while (**p && **p != ' ') (*p)++;\r
10788     sprintf(buf, "accepted %s\n", name);\r
10789     SendToProgram(buf, cps);\r
10790     return TRUE;\r
10791   }\r
10792   return FALSE;\r
10793 }\r
10794 \r
10795 int\r
10796 StringFeature(p, name, loc, cps)\r
10797      char **p;\r
10798      char *name;\r
10799      char loc[];\r
10800      ChessProgramState *cps;\r
10801 {\r
10802   char buf[MSG_SIZ];\r
10803   int len = strlen(name);\r
10804   if (strncmp((*p), name, len) == 0\r
10805       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
10806     (*p) += len + 2;\r
10807     sscanf(*p, "%[^\"]", loc);\r
10808     while (**p && **p != '\"') (*p)++;\r
10809     if (**p == '\"') (*p)++;\r
10810     sprintf(buf, "accepted %s\n", name);\r
10811     SendToProgram(buf, cps);\r
10812     return TRUE;\r
10813   }\r
10814   return FALSE;\r
10815 }\r
10816 \r
10817 void\r
10818 FeatureDone(cps, val)\r
10819      ChessProgramState* cps;\r
10820      int val;\r
10821 {\r
10822   DelayedEventCallback cb = GetDelayedEvent();\r
10823   if ((cb == InitBackEnd3 && cps == &first) ||\r
10824       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
10825     CancelDelayedEvent();\r
10826     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
10827   }\r
10828   cps->initDone = val;\r
10829 }\r
10830 \r
10831 /* Parse feature command from engine */\r
10832 void\r
10833 ParseFeatures(args, cps)\r
10834      char* args;\r
10835      ChessProgramState *cps;  \r
10836 {\r
10837   char *p = args;\r
10838   char *q;\r
10839   int val;\r
10840   char buf[MSG_SIZ];\r
10841 \r
10842   for (;;) {\r
10843     while (*p == ' ') p++;\r
10844     if (*p == NULLCHAR) return;\r
10845 \r
10846     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
10847     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
10848     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
10849     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
10850     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
10851     if (BoolFeature(&p, "reuse", &val, cps)) {\r
10852       /* Engine can disable reuse, but can't enable it if user said no */\r
10853       if (!val) cps->reuse = FALSE;\r
10854       continue;\r
10855     }\r
10856     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
10857     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
10858       if (gameMode == TwoMachinesPlay) {\r
10859         DisplayTwoMachinesTitle();\r
10860       } else {\r
10861         DisplayTitle("");\r
10862       }\r
10863       continue;\r
10864     }\r
10865     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
10866     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
10867     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
10868     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
10869     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
10870     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
10871     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
10872     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
10873     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
10874     if (IntFeature(&p, "done", &val, cps)) {\r
10875       FeatureDone(cps, val);\r
10876       continue;\r
10877     }\r
10878     /* Added by Tord: */\r
10879     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
10880     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
10881     /* End of additions by Tord */\r
10882 \r
10883     /* unknown feature: complain and skip */\r
10884     q = p;\r
10885     while (*q && *q != '=') q++;\r
10886     sprintf(buf, "rejected %.*s\n", q-p, p);\r
10887     SendToProgram(buf, cps);\r
10888     p = q;\r
10889     if (*p == '=') {\r
10890       p++;\r
10891       if (*p == '\"') {\r
10892         p++;\r
10893         while (*p && *p != '\"') p++;\r
10894         if (*p == '\"') p++;\r
10895       } else {\r
10896         while (*p && *p != ' ') p++;\r
10897       }\r
10898     }\r
10899   }\r
10900 \r
10901 }\r
10902 \r
10903 void\r
10904 PeriodicUpdatesEvent(newState)\r
10905      int newState;\r
10906 {\r
10907     if (newState == appData.periodicUpdates)\r
10908       return;\r
10909 \r
10910     appData.periodicUpdates=newState;\r
10911 \r
10912     /* Display type changes, so update it now */\r
10913     DisplayAnalysis();\r
10914 \r
10915     /* Get the ball rolling again... */\r
10916     if (newState) {\r
10917         AnalysisPeriodicEvent(1);\r
10918         StartAnalysisClock();\r
10919     }\r
10920 }\r
10921 \r
10922 void\r
10923 PonderNextMoveEvent(newState)\r
10924      int newState;\r
10925 {\r
10926     if (newState == appData.ponderNextMove) return;\r
10927     if (gameMode == EditPosition) EditPositionDone();\r
10928     if (newState) {\r
10929         SendToProgram("hard\n", &first);\r
10930         if (gameMode == TwoMachinesPlay) {\r
10931             SendToProgram("hard\n", &second);\r
10932         }\r
10933     } else {\r
10934         SendToProgram("easy\n", &first);\r
10935         thinkOutput[0] = NULLCHAR;\r
10936         if (gameMode == TwoMachinesPlay) {\r
10937             SendToProgram("easy\n", &second);\r
10938         }\r
10939     }\r
10940     appData.ponderNextMove = newState;\r
10941 }\r
10942 \r
10943 void\r
10944 ShowThinkingEvent(newState)\r
10945      int newState;\r
10946 {\r
10947     if (newState == appData.showThinking) return;\r
10948     if (gameMode == EditPosition) EditPositionDone();\r
10949     if (newState) {\r
10950         SendToProgram("post\n", &first);\r
10951         if (gameMode == TwoMachinesPlay) {\r
10952             SendToProgram("post\n", &second);\r
10953         }\r
10954     } else {\r
10955         SendToProgram("nopost\n", &first);\r
10956         thinkOutput[0] = NULLCHAR;\r
10957         if (gameMode == TwoMachinesPlay) {\r
10958             SendToProgram("nopost\n", &second);\r
10959         }\r
10960     }\r
10961     appData.showThinking = newState;\r
10962 }\r
10963 \r
10964 void\r
10965 AskQuestionEvent(title, question, replyPrefix, which)\r
10966      char *title; char *question; char *replyPrefix; char *which;\r
10967 {\r
10968   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
10969   if (pr == NoProc) return;\r
10970   AskQuestion(title, question, replyPrefix, pr);\r
10971 }\r
10972 \r
10973 void\r
10974 DisplayMove(moveNumber)\r
10975      int moveNumber;\r
10976 {\r
10977     char message[MSG_SIZ];\r
10978     char res[MSG_SIZ];\r
10979     char cpThinkOutput[MSG_SIZ];\r
10980 \r
10981     if (moveNumber == forwardMostMove - 1 || \r
10982         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10983 \r
10984         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
10985 \r
10986         if (strchr(cpThinkOutput, '\n')) {\r
10987             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
10988         }\r
10989     } else {\r
10990         *cpThinkOutput = NULLCHAR;\r
10991     }\r
10992 \r
10993     /* [AS] Hide thinking from human user */\r
10994     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
10995         *cpThinkOutput = NULLCHAR;\r
10996         if( thinkOutput[0] != NULLCHAR ) {\r
10997             int i;\r
10998 \r
10999             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
11000                 cpThinkOutput[i] = '.';\r
11001             }\r
11002             cpThinkOutput[i] = NULLCHAR;\r
11003             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
11004         }\r
11005     }\r
11006 \r
11007     if (moveNumber == forwardMostMove - 1 &&\r
11008         gameInfo.resultDetails != NULL) {\r
11009         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
11010             sprintf(res, " %s", PGNResult(gameInfo.result));\r
11011         } else {\r
11012             sprintf(res, " {%s} %s",\r
11013                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
11014         }\r
11015     } else {\r
11016         res[0] = NULLCHAR;\r
11017     }\r
11018     \r
11019     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
11020         DisplayMessage(res, cpThinkOutput);\r
11021     } else {\r
11022         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
11023                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
11024                 parseList[moveNumber], res);\r
11025         DisplayMessage(message, cpThinkOutput);\r
11026     }\r
11027 }\r
11028 \r
11029 void\r
11030 DisplayAnalysisText(text)\r
11031      char *text;\r
11032 {\r
11033     char buf[MSG_SIZ];\r
11034 \r
11035     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11036         sprintf(buf, "Analysis (%s)", first.tidy);\r
11037         AnalysisPopUp(buf, text);\r
11038     }\r
11039 }\r
11040 \r
11041 static int\r
11042 only_one_move(str)\r
11043      char *str;\r
11044 {\r
11045     while (*str && isspace(*str)) ++str;\r
11046     while (*str && !isspace(*str)) ++str;\r
11047     if (!*str) return 1;\r
11048     while (*str && isspace(*str)) ++str;\r
11049     if (!*str) return 1;\r
11050     return 0;\r
11051 }\r
11052 \r
11053 void\r
11054 DisplayAnalysis()\r
11055 {\r
11056     char buf[MSG_SIZ];\r
11057     char lst[MSG_SIZ / 2];\r
11058     double nps;\r
11059     static char *xtra[] = { "", " (--)", " (++)" };\r
11060     int h, m, s, cs;\r
11061   \r
11062     if (programStats.time == 0) {\r
11063         programStats.time = 1;\r
11064     }\r
11065   \r
11066     if (programStats.got_only_move) {\r
11067         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
11068     } else {\r
11069         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
11070 \r
11071         nps = (((double)programStats.nodes) /\r
11072                (((double)programStats.time)/100.0));\r
11073 \r
11074         cs = programStats.time % 100;\r
11075         s = programStats.time / 100;\r
11076         h = (s / (60*60));\r
11077         s = s - h*60*60;\r
11078         m = (s/60);\r
11079         s = s - m*60;\r
11080 \r
11081         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
11082           if (programStats.move_name[0] != NULLCHAR) {\r
11083             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
11084                     programStats.depth,\r
11085                     programStats.nr_moves-programStats.moves_left,\r
11086                     programStats.nr_moves, programStats.move_name,\r
11087                     ((float)programStats.score)/100.0, lst,\r
11088                     only_one_move(lst)?\r
11089                     xtra[programStats.got_fail] : "",\r
11090                     programStats.nodes, (int)nps, h, m, s, cs);\r
11091           } else {\r
11092             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
11093                     programStats.depth,\r
11094                     programStats.nr_moves-programStats.moves_left,\r
11095                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
11096                     lst,\r
11097                     only_one_move(lst)?\r
11098                     xtra[programStats.got_fail] : "",\r
11099                     programStats.nodes, (int)nps, h, m, s, cs);\r
11100           }\r
11101         } else {\r
11102             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
11103                     programStats.depth,\r
11104                     ((float)programStats.score)/100.0,\r
11105                     lst,\r
11106                     only_one_move(lst)?\r
11107                     xtra[programStats.got_fail] : "",\r
11108                     programStats.nodes, (int)nps, h, m, s, cs);\r
11109         }\r
11110     }\r
11111     DisplayAnalysisText(buf);\r
11112 }\r
11113 \r
11114 void\r
11115 DisplayComment(moveNumber, text)\r
11116      int moveNumber;\r
11117      char *text;\r
11118 {\r
11119     char title[MSG_SIZ];\r
11120 \r
11121     if( appData.autoDisplayComment ) {\r
11122         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
11123             strcpy(title, "Comment");\r
11124         } else {\r
11125             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
11126                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
11127                     parseList[moveNumber]);\r
11128         }\r
11129 \r
11130         CommentPopUp(title, text);\r
11131     }\r
11132 }\r
11133 \r
11134 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
11135  * might be busy thinking or pondering.  It can be omitted if your\r
11136  * gnuchess is configured to stop thinking immediately on any user\r
11137  * input.  However, that gnuchess feature depends on the FIONREAD\r
11138  * ioctl, which does not work properly on some flavors of Unix.\r
11139  */\r
11140 void\r
11141 Attention(cps)\r
11142      ChessProgramState *cps;\r
11143 {\r
11144 #if ATTENTION\r
11145     if (!cps->useSigint) return;\r
11146     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
11147     switch (gameMode) {\r
11148       case MachinePlaysWhite:\r
11149       case MachinePlaysBlack:\r
11150       case TwoMachinesPlay:\r
11151       case IcsPlayingWhite:\r
11152       case IcsPlayingBlack:\r
11153       case AnalyzeMode:\r
11154       case AnalyzeFile:\r
11155         /* Skip if we know it isn't thinking */\r
11156         if (!cps->maybeThinking) return;\r
11157         if (appData.debugMode)\r
11158           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
11159         InterruptChildProcess(cps->pr);\r
11160         cps->maybeThinking = FALSE;\r
11161         break;\r
11162       default:\r
11163         break;\r
11164     }\r
11165 #endif /*ATTENTION*/\r
11166 }\r
11167 \r
11168 int\r
11169 CheckFlags()\r
11170 {\r
11171     if (whiteTimeRemaining <= 0) {\r
11172         if (!whiteFlag) {\r
11173             whiteFlag = TRUE;\r
11174             if (appData.icsActive) {\r
11175                 if (appData.autoCallFlag &&\r
11176                     gameMode == IcsPlayingBlack && !blackFlag) {\r
11177                   SendToICS(ics_prefix);\r
11178                   SendToICS("flag\n");\r
11179                 }\r
11180             } else {\r
11181                 if (blackFlag) {\r
11182                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
11183                 } else {\r
11184                     if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell");\r
11185                     if (appData.autoCallFlag) {\r
11186                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
11187                         return TRUE;\r
11188                     }\r
11189                 }\r
11190             }\r
11191         }\r
11192     }\r
11193     if (blackTimeRemaining <= 0) {\r
11194         if (!blackFlag) {\r
11195             blackFlag = TRUE;\r
11196             if (appData.icsActive) {\r
11197                 if (appData.autoCallFlag &&\r
11198                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
11199                   SendToICS(ics_prefix);\r
11200                   SendToICS("flag\n");\r
11201                 }\r
11202             } else {\r
11203                 if (whiteFlag) {\r
11204                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
11205                 } else {\r
11206                     if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell");\r
11207                     if (appData.autoCallFlag) {\r
11208                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
11209                         return TRUE;\r
11210                     }\r
11211                 }\r
11212             }\r
11213         }\r
11214     }\r
11215     return FALSE;\r
11216 }\r
11217 \r
11218 void\r
11219 CheckTimeControl()\r
11220 {\r
11221     if (!appData.clockMode || appData.icsActive ||\r
11222         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
11223 \r
11224     if (timeIncrement >= 0) {\r
11225         if (WhiteOnMove(forwardMostMove)) {\r
11226             blackTimeRemaining += timeIncrement;\r
11227         } else {\r
11228             whiteTimeRemaining += timeIncrement;\r
11229         }\r
11230     }\r
11231     /*\r
11232      * add time to clocks when time control is achieved\r
11233      */\r
11234     if (movesPerSession) {\r
11235       switch ((forwardMostMove + 1) % (movesPerSession * 2)) {\r
11236       case 0:\r
11237         /* White made time control */\r
11238         whiteTimeRemaining += GetTimeControlForWhite();\r
11239         break;\r
11240       case 1:\r
11241         /* Black made time control */\r
11242         blackTimeRemaining += GetTimeControlForBlack();\r
11243         break;\r
11244       default:\r
11245         break;\r
11246       }\r
11247     }\r
11248 }\r
11249 \r
11250 void\r
11251 DisplayBothClocks()\r
11252 {\r
11253     int wom = gameMode == EditPosition ?\r
11254       !blackPlaysFirst : WhiteOnMove(currentMove);\r
11255     DisplayWhiteClock(whiteTimeRemaining, wom);\r
11256     DisplayBlackClock(blackTimeRemaining, !wom);\r
11257 }\r
11258 \r
11259 \r
11260 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
11261    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
11262    to use other calls if you don't.  Clocks will be less accurate if\r
11263    you have neither ftime nor gettimeofday.\r
11264 */\r
11265 \r
11266 /* Get the current time as a TimeMark */\r
11267 void\r
11268 GetTimeMark(tm)\r
11269      TimeMark *tm;\r
11270 {\r
11271 #if HAVE_GETTIMEOFDAY\r
11272 \r
11273     struct timeval timeVal;\r
11274     struct timezone timeZone;\r
11275 \r
11276     gettimeofday(&timeVal, &timeZone);\r
11277     tm->sec = (long) timeVal.tv_sec; \r
11278     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
11279 \r
11280 #else /*!HAVE_GETTIMEOFDAY*/\r
11281 #if HAVE_FTIME\r
11282 \r
11283 #include <sys/timeb.h>\r
11284     struct timeb timeB;\r
11285 \r
11286     ftime(&timeB);\r
11287     tm->sec = (long) timeB.time;\r
11288     tm->ms = (int) timeB.millitm;\r
11289 \r
11290 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
11291     tm->sec = (long) time(NULL);\r
11292     tm->ms = 0;\r
11293 #endif\r
11294 #endif\r
11295 }\r
11296 \r
11297 /* Return the difference in milliseconds between two\r
11298    time marks.  We assume the difference will fit in a long!\r
11299 */\r
11300 long\r
11301 SubtractTimeMarks(tm2, tm1)\r
11302      TimeMark *tm2, *tm1;\r
11303 {\r
11304     return 1000L*(tm2->sec - tm1->sec) +\r
11305            (long) (tm2->ms - tm1->ms);\r
11306 }\r
11307 \r
11308 \r
11309 /*\r
11310  * Code to manage the game clocks.\r
11311  *\r
11312  * In tournament play, black starts the clock and then white makes a move.\r
11313  * We give the human user a slight advantage if he is playing white---the\r
11314  * clocks don't run until he makes his first move, so it takes zero time.\r
11315  * Also, we don't account for network lag, so we could get out of sync\r
11316  * with GNU Chess's clock -- but then, referees are always right.  \r
11317  */\r
11318 \r
11319 static TimeMark tickStartTM;\r
11320 static long intendedTickLength;\r
11321 \r
11322 long\r
11323 NextTickLength(timeRemaining)\r
11324      long timeRemaining;\r
11325 {\r
11326     long nominalTickLength, nextTickLength;\r
11327 \r
11328     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
11329       nominalTickLength = 100L;\r
11330     else\r
11331       nominalTickLength = 1000L;\r
11332     nextTickLength = timeRemaining % nominalTickLength;\r
11333     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
11334 \r
11335     return nextTickLength;\r
11336 }\r
11337 \r
11338 /* Adjust clock one minute up or down */\r
11339 void\r
11340 AdjustClock(Boolean which, int dir)\r
11341 {\r
11342     if(which) blackTimeRemaining += 60000*dir;\r
11343     else      whiteTimeRemaining += 60000*dir;\r
11344     DisplayBothClocks();\r
11345 }\r
11346 \r
11347 /* Stop clocks and reset to a fresh time control */\r
11348 void\r
11349 ResetClocks() \r
11350 {\r
11351     (void) StopClockTimer();\r
11352     if (appData.icsActive) {\r
11353         whiteTimeRemaining = blackTimeRemaining = 0;\r
11354     } else {\r
11355         whiteTimeRemaining = GetTimeControlForWhite();\r
11356         blackTimeRemaining = GetTimeControlForBlack();\r
11357     }\r
11358     if (whiteFlag || blackFlag) {\r
11359         DisplayTitle("");\r
11360         whiteFlag = blackFlag = FALSE;\r
11361     }\r
11362     DisplayBothClocks();\r
11363 }\r
11364 \r
11365 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
11366 \r
11367 /* Decrement running clock by amount of time that has passed */\r
11368 void\r
11369 DecrementClocks()\r
11370 {\r
11371     long timeRemaining;\r
11372     long lastTickLength, fudge;\r
11373     TimeMark now;\r
11374 \r
11375     if (!appData.clockMode) return;\r
11376     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
11377         \r
11378     GetTimeMark(&now);\r
11379 \r
11380     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
11381 \r
11382     /* Fudge if we woke up a little too soon */\r
11383     fudge = intendedTickLength - lastTickLength;\r
11384     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
11385 \r
11386     if (WhiteOnMove(forwardMostMove)) {\r
11387         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
11388         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
11389                           WhiteOnMove(currentMove));\r
11390     } else {\r
11391         timeRemaining = blackTimeRemaining -= lastTickLength;\r
11392         DisplayBlackClock(blackTimeRemaining - fudge,\r
11393                           !WhiteOnMove(currentMove));\r
11394     }\r
11395 \r
11396     if (CheckFlags()) return;\r
11397         \r
11398     tickStartTM = now;\r
11399     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
11400     StartClockTimer(intendedTickLength);\r
11401 \r
11402     /* if the time remaining has fallen below the alarm threshold, sound the\r
11403      * alarm. if the alarm has sounded and (due to a takeback or time control\r
11404      * with increment) the time remaining has increased to a level above the\r
11405      * threshold, reset the alarm so it can sound again. \r
11406      */\r
11407     \r
11408     if (appData.icsActive && appData.icsAlarm) {\r
11409 \r
11410         /* make sure we are dealing with the user's clock */\r
11411         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
11412                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
11413            )) return;\r
11414 \r
11415         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
11416             alarmSounded = FALSE;\r
11417         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
11418             PlayAlarmSound();\r
11419             alarmSounded = TRUE;\r
11420         }\r
11421     }\r
11422 }\r
11423 \r
11424 \r
11425 /* A player has just moved, so stop the previously running\r
11426    clock and (if in clock mode) start the other one.\r
11427    We redisplay both clocks in case we're in ICS mode, because\r
11428    ICS gives us an update to both clocks after every move.\r
11429    Note that this routine is called *after* forwardMostMove\r
11430    is updated, so the last fractional tick must be subtracted\r
11431    from the color that is *not* on move now.\r
11432 */\r
11433 void\r
11434 SwitchClocks()\r
11435 {\r
11436     long lastTickLength;\r
11437     TimeMark now;\r
11438     int flagged = FALSE;\r
11439 \r
11440     GetTimeMark(&now);\r
11441 \r
11442     if (StopClockTimer() && appData.clockMode) {\r
11443         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
11444         if (WhiteOnMove(forwardMostMove)) {\r
11445             blackTimeRemaining -= lastTickLength;\r
11446         } else {\r
11447             whiteTimeRemaining -= lastTickLength;\r
11448         }\r
11449         /* [HGM] save time for PGN file if engine did not give it */\r
11450         if(pvInfoList[forwardMostMove-1].time == -1)\r
11451              pvInfoList[forwardMostMove-1].time = lastTickLength/100;\r
11452         flagged = CheckFlags();\r
11453     }\r
11454     CheckTimeControl();\r
11455 \r
11456     if (flagged || !appData.clockMode) return;\r
11457 \r
11458     switch (gameMode) {\r
11459       case MachinePlaysBlack:\r
11460       case MachinePlaysWhite:\r
11461       case BeginningOfGame:\r
11462         if (pausing) return;\r
11463         break;\r
11464 \r
11465       case EditGame:\r
11466       case PlayFromGameFile:\r
11467       case IcsExamining:\r
11468         return;\r
11469 \r
11470       default:\r
11471         break;\r
11472     }\r
11473 \r
11474     tickStartTM = now;\r
11475     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
11476       whiteTimeRemaining : blackTimeRemaining);\r
11477     StartClockTimer(intendedTickLength);\r
11478 }\r
11479         \r
11480 \r
11481 /* Stop both clocks */\r
11482 void\r
11483 StopClocks()\r
11484 {       \r
11485     long lastTickLength;\r
11486     TimeMark now;\r
11487 \r
11488     if (!StopClockTimer()) return;\r
11489     if (!appData.clockMode) return;\r
11490 \r
11491     GetTimeMark(&now);\r
11492 \r
11493     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
11494     if (WhiteOnMove(forwardMostMove)) {\r
11495         whiteTimeRemaining -= lastTickLength;\r
11496         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
11497     } else {\r
11498         blackTimeRemaining -= lastTickLength;\r
11499         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
11500     }\r
11501     CheckFlags();\r
11502 }\r
11503         \r
11504 /* Start clock of player on move.  Time may have been reset, so\r
11505    if clock is already running, stop and restart it. */\r
11506 void\r
11507 StartClocks()\r
11508 {\r
11509     (void) StopClockTimer(); /* in case it was running already */\r
11510     DisplayBothClocks();\r
11511     if (CheckFlags()) return;\r
11512 \r
11513     if (!appData.clockMode) return;\r
11514     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
11515 \r
11516     GetTimeMark(&tickStartTM);\r
11517     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
11518       whiteTimeRemaining : blackTimeRemaining);\r
11519     StartClockTimer(intendedTickLength);\r
11520 }\r
11521 \r
11522 char *\r
11523 TimeString(ms)\r
11524      long ms;\r
11525 {\r
11526     long second, minute, hour, day;\r
11527     char *sign = "";\r
11528     static char buf[32];\r
11529     \r
11530     if (ms > 0 && ms <= 9900) {\r
11531       /* convert milliseconds to tenths, rounding up */\r
11532       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
11533 \r
11534       sprintf(buf, " %03.1f ", tenths/10.0);\r
11535       return buf;\r
11536     }\r
11537 \r
11538     /* convert milliseconds to seconds, rounding up */\r
11539     /* use floating point to avoid strangeness of integer division\r
11540        with negative dividends on many machines */\r
11541     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
11542 \r
11543     if (second < 0) {\r
11544         sign = "-";\r
11545         second = -second;\r
11546     }\r
11547     \r
11548     day = second / (60 * 60 * 24);\r
11549     second = second % (60 * 60 * 24);\r
11550     hour = second / (60 * 60);\r
11551     second = second % (60 * 60);\r
11552     minute = second / 60;\r
11553     second = second % 60;\r
11554     \r
11555     if (day > 0)\r
11556       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
11557               sign, day, hour, minute, second);\r
11558     else if (hour > 0)\r
11559       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
11560     else\r
11561       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
11562     \r
11563     return buf;\r
11564 }\r
11565 \r
11566 \r
11567 /*\r
11568  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
11569  */\r
11570 char *\r
11571 StrStr(string, match)\r
11572      char *string, *match;\r
11573 {\r
11574     int i, length;\r
11575     \r
11576     length = strlen(match);\r
11577     \r
11578     for (i = strlen(string) - length; i >= 0; i--, string++)\r
11579       if (!strncmp(match, string, length))\r
11580         return string;\r
11581     \r
11582     return NULL;\r
11583 }\r
11584 \r
11585 char *\r
11586 StrCaseStr(string, match)\r
11587      char *string, *match;\r
11588 {\r
11589     int i, j, length;\r
11590     \r
11591     length = strlen(match);\r
11592     \r
11593     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
11594         for (j = 0; j < length; j++) {\r
11595             if (ToLower(match[j]) != ToLower(string[j]))\r
11596               break;\r
11597         }\r
11598         if (j == length) return string;\r
11599     }\r
11600 \r
11601     return NULL;\r
11602 }\r
11603 \r
11604 #ifndef _amigados\r
11605 int\r
11606 StrCaseCmp(s1, s2)\r
11607      char *s1, *s2;\r
11608 {\r
11609     char c1, c2;\r
11610     \r
11611     for (;;) {\r
11612         c1 = ToLower(*s1++);\r
11613         c2 = ToLower(*s2++);\r
11614         if (c1 > c2) return 1;\r
11615         if (c1 < c2) return -1;\r
11616         if (c1 == NULLCHAR) return 0;\r
11617     }\r
11618 }\r
11619 \r
11620 \r
11621 int\r
11622 ToLower(c)\r
11623      int c;\r
11624 {\r
11625     return isupper(c) ? tolower(c) : c;\r
11626 }\r
11627 \r
11628 \r
11629 int\r
11630 ToUpper(c)\r
11631      int c;\r
11632 {\r
11633     return islower(c) ? toupper(c) : c;\r
11634 }\r
11635 #endif /* !_amigados    */\r
11636 \r
11637 char *\r
11638 StrSave(s)\r
11639      char *s;\r
11640 {\r
11641     char *ret;\r
11642 \r
11643     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
11644         strcpy(ret, s);\r
11645     }\r
11646     return ret;\r
11647 }\r
11648 \r
11649 char *\r
11650 StrSavePtr(s, savePtr)\r
11651      char *s, **savePtr;\r
11652 {\r
11653     if (*savePtr) {\r
11654         free(*savePtr);\r
11655     }\r
11656     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
11657         strcpy(*savePtr, s);\r
11658     }\r
11659     return(*savePtr);\r
11660 }\r
11661 \r
11662 char *\r
11663 PGNDate()\r
11664 {\r
11665     time_t clock;\r
11666     struct tm *tm;\r
11667     char buf[MSG_SIZ];\r
11668 \r
11669     clock = time((time_t *)NULL);\r
11670     tm = localtime(&clock);\r
11671     sprintf(buf, "%04d.%02d.%02d",\r
11672             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
11673     return StrSave(buf);\r
11674 }\r
11675 \r
11676 \r
11677 char *\r
11678 PositionToFEN(move, useFEN960)\r
11679      int move;\r
11680      int useFEN960;\r
11681 {\r
11682     int i, j, fromX, fromY, toX, toY;\r
11683     int whiteToPlay;\r
11684     char buf[128];\r
11685     char *p, *q;\r
11686     int emptycount;\r
11687     ChessSquare piece;\r
11688 \r
11689     whiteToPlay = (gameMode == EditPosition) ?\r
11690       !blackPlaysFirst : (move % 2 == 0);\r
11691     p = buf;\r
11692 \r
11693     /* Piece placement data */\r
11694     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11695         emptycount = 0;\r
11696         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11697             if (boards[move][i][j] == EmptySquare) {\r
11698                 emptycount++;\r
11699             } else { ChessSquare piece = boards[move][i][j];\r
11700                 if (emptycount > 0) {\r
11701                     if(emptycount<10) /* [HGM] can be >= 10 */\r
11702                         *p++ = '0' + emptycount;\r
11703                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
11704                     emptycount = 0;\r
11705                 }\r
11706                 if(gameInfo.variant == VariantShogi) {\r
11707                     /* [HGM] write Shogi promoted pieces as +<unpromoted> */\r
11708                     if( (int)piece > (int) WhiteCannon && (int)piece < (int) WhiteKing ||\r
11709                         (int)piece > (int) BlackCannon && (int)piece < (int) BlackKing ) {\r
11710                         *p++ = '+';\r
11711                         piece = (ChessSquare)(DEMOTED piece);\r
11712                     }\r
11713                 } \r
11714                 *p++ = PieceToChar(piece);\r
11715                 if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantBughouse) {\r
11716                     /* [HGM] flag Crazyhouse promoted pieces */\r
11717                     if( (int)piece > (int) WhiteQueen && (int)piece < (int) WhiteKing ||\r
11718                         (int)piece > (int) BlackQueen && (int)piece < (int) BlackKing ) {\r
11719                         p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
11720                         *p++ = '~';\r
11721                     }\r
11722                 }\r
11723             }\r
11724         }\r
11725         if (emptycount > 0) {\r
11726             if(emptycount<10) /* [HGM] can be >= 10 */\r
11727                 *p++ = '0' + emptycount;\r
11728             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
11729             emptycount = 0;\r
11730         }\r
11731         *p++ = '/';\r
11732     }\r
11733     *(p - 1) = ' ';\r
11734 \r
11735     /* Active color */\r
11736     *p++ = whiteToPlay ? 'w' : 'b';\r
11737     *p++ = ' ';\r
11738 \r
11739     /* HACK: we don't keep track of castling availability, so fake it! */\r
11740     /* Tord! please fix with the aid of castlingRights[move][...] */\r
11741 \r
11742     /* PUSH Fabien & Tord */\r
11743 \r
11744     /* Declare all potential FRC castling rights (conservative) */\r
11745     /* outermost rook on each side of the king */\r
11746 \r
11747     if( gameInfo.variant == VariantFischeRandom ) {\r
11748        int fk, fr;\r
11749 \r
11750        q = p;\r
11751 \r
11752        /* White castling rights */\r
11753 \r
11754        for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) {\r
11755 \r
11756           if (boards[move][0][fk] == WhiteKing) {\r
11757 \r
11758              for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */\r
11759                 if (boards[move][0][fr] == WhiteRook) {\r
11760                    *p++ = useFEN960 ? 'A' + fr : 'K';\r
11761                    break;\r
11762                 }\r
11763              }\r
11764 \r
11765              for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */\r
11766                 if (boards[move][0][fr] == WhiteRook) {\r
11767                    *p++ = useFEN960 ? 'A' + fr : 'Q';\r
11768                    break;\r
11769                 }\r
11770              }\r
11771           }\r
11772        }\r
11773 \r
11774        /* Black castling rights */\r
11775 \r
11776        for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) {\r
11777 \r
11778           if (boards[move][BOARD_HEIGHT-1][fk] == BlackKing) {\r
11779 \r
11780              for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */\r
11781                 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {\r
11782                    *p++ = useFEN960 ? 'a' + fr : 'k';\r
11783                    break;\r
11784                 }\r
11785              }\r
11786 \r
11787              for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */\r
11788                 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {\r
11789                    *p++ = useFEN960 ? 'a' + fr : 'q';\r
11790                    break;\r
11791                 }\r
11792              }\r
11793           }\r
11794        }\r
11795 \r
11796        if (q == p) *p++ = '-'; /* No castling rights */\r
11797        *p++ = ' ';\r
11798     }\r
11799     else {\r
11800         q = p;\r
11801 \r
11802 #ifdef OLDCASTLINGCODE\r
11803         if (boards[move][0][BOARD_WIDTH>>1] == WhiteKing) {\r
11804             if (boards[move][0][BOARD_RGHT-1] == WhiteRook) *p++ = 'K';\r
11805             if (boards[move][0][BOARD_LEFT] == WhiteRook) *p++ = 'Q';\r
11806         }\r
11807         if (boards[move][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == BlackKing) {\r
11808             if (boards[move][BOARD_HEIGHT-1][BOARD_HEIGHT-1] == BlackRook) *p++ = 'k';\r
11809             if (boards[move][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook) *p++ = 'q';\r
11810         }           \r
11811 #else\r
11812         /* [HGM] write true castling rights */\r
11813         if( nrCastlingRights == 6 ) {\r
11814             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
11815                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
11816             if(castlingRights[move][1] == BOARD_LEFT &&\r
11817                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
11818             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
11819                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
11820             if(castlingRights[move][4] == BOARD_LEFT &&\r
11821                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
11822         }\r
11823 #endif\r
11824         if (q == p) *p++ = '-';\r
11825         *p++ = ' ';\r
11826     }\r
11827 \r
11828     /* POP Fabien & Tord */\r
11829 \r
11830     /* En passant target square */\r
11831     if (move > backwardMostMove) {\r
11832         fromX = moveList[move - 1][0] - AAA;\r
11833         fromY = moveList[move - 1][1] - ONE;\r
11834         toX = moveList[move - 1][2] - AAA;\r
11835         toY = moveList[move - 1][3] - ONE;\r
11836         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
11837             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
11838             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
11839             fromX == toX) {\r
11840             /* 2-square pawn move just happened */\r
11841             *p++ = toX + AAA;\r
11842             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
11843         } else {\r
11844             *p++ = '-';\r
11845         }\r
11846     } else {\r
11847         *p++ = '-';\r
11848     }\r
11849 \r
11850     /* [HGM] print Crazyhouse or Shogi holdings */\r
11851     if( gameInfo.holdingsWidth ) {\r
11852         *p++ = ' '; q = p;\r
11853         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
11854             piece = boards[move][i][BOARD_WIDTH-1];\r
11855             if( piece != EmptySquare )\r
11856               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
11857                   *p++ = PieceToChar(piece);\r
11858         }\r
11859         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
11860             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
11861             if( piece != EmptySquare )\r
11862               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
11863                   *p++ = PieceToChar(piece);\r
11864         }\r
11865 \r
11866         if( q == p ) *p++ = '-';\r
11867         *p++ = ' ';\r
11868     }\r
11869 \r
11870     /* [HGM] find reversible plies */\r
11871     {   int i = 0, j=move;\r
11872 \r
11873     if (appData.debugMode) { int k;\r
11874         fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
11875         for(k=backwardMostMove; k<=forwardMostMove; k++)\r
11876             fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
11877 \r
11878     }\r
11879 \r
11880         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
11881         if( j == backwardMostMove ) i += initialRulePlies;\r
11882         sprintf(p, " %d", i);\r
11883       p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
11884     }\r
11885     /* Fullmove number */\r
11886     sprintf(p, " %d", (move / 2) + 1);\r
11887     \r
11888     return StrSave(buf);\r
11889 }\r
11890 \r
11891 Boolean\r
11892 ParseFEN(board, blackPlaysFirst, fen)\r
11893     Board board;\r
11894      int *blackPlaysFirst;\r
11895      char *fen;\r
11896 {\r
11897     int i, j;\r
11898     char *p;\r
11899     int emptycount;\r
11900     ChessSquare piece;\r
11901 \r
11902     p = fen;\r
11903 \r
11904     /* [HGM] by default clear Crazyhouse holdings, if present */\r
11905    if(gameInfo.holdingsWidth) {\r
11906        for(i=0; i<BOARD_HEIGHT; i++) {\r
11907            board[i][0]             = EmptySquare; /* black holdings */\r
11908            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
11909            board[i][1]             = (ChessSquare) 0; /* black counts */\r
11910            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
11911        }\r
11912    }\r
11913 \r
11914     /* Piece placement data */\r
11915     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11916         j = 0;\r
11917         for (;;) {\r
11918             if (*p == '/' || *p == ' ') {\r
11919                 if (*p == '/') p++;\r
11920                 emptycount = gameInfo.boardWidth - j;\r
11921                 while (emptycount--)\r
11922                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
11923                 break;\r
11924 #if(BOARD_SIZE >= 10)\r
11925             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
11926                 p++; emptycount=10;\r
11927                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
11928                 while (emptycount--)\r
11929                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
11930 #endif\r
11931             } else if (isdigit(*p)) {\r
11932                 emptycount = *p++ - '0';\r
11933                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
11934                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
11935                 while (emptycount--)\r
11936                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
11937             } else if (*p == '+' || isalpha(*p)) {\r
11938                 if (j >= gameInfo.boardWidth) return FALSE;\r
11939                 if(*p=='+') { piece = (ChessSquare) (PROMOTED CharToPiece(*++p) ); p++; }\r
11940                 else piece = CharToPiece(*p++);\r
11941                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
11942                     piece = (ChessSquare) (PROMOTED piece);\r
11943                     p++;\r
11944                 }\r
11945                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
11946             } else {\r
11947                 return FALSE;\r
11948             }\r
11949         }\r
11950     }\r
11951     while (*p == '/' || *p == ' ') p++;\r
11952 \r
11953     /* Active color */\r
11954     switch (*p++) {\r
11955       case 'w':\r
11956         *blackPlaysFirst = FALSE;\r
11957         break;\r
11958       case 'b': \r
11959         *blackPlaysFirst = TRUE;\r
11960         break;\r
11961       default:\r
11962         return FALSE;\r
11963     }\r
11964 \r
11965     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
11966     /* return the extra info in global variiables             */\r
11967   {\r
11968     /* set defaults in case FEN is incomplete */\r
11969     FENepStatus = EP_UNKNOWN;\r
11970     for(i=0; i<nrCastlingRights; i++ ) {\r
11971         FENcastlingRights[i] = initialRights[i];\r
11972     }   /* assume possible unless obviously impossible */\r
11973     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
11974     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
11975     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
11976     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
11977     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
11978     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
11979     FENrulePlies = 0;\r
11980 \r
11981     while(*p==' ') p++;\r
11982 \r
11983     if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
11984               /* castling indicator present, so default is no castlings */\r
11985               for(i=0; i<nrCastlingRights; i++ ) {\r
11986                      FENcastlingRights[i] = -1;\r
11987               }\r
11988     }\r
11989     while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
11990         switch(*p++) {\r
11991           case'K':\r
11992               FENcastlingRights[0] = BOARD_RGHT-1;\r
11993               FENcastlingRights[2] = BOARD_WIDTH>>1;\r
11994               break;\r
11995           case'Q':\r
11996               FENcastlingRights[1] = BOARD_LEFT;\r
11997               FENcastlingRights[2] = BOARD_WIDTH>>1;\r
11998               break;\r
11999           case'k':\r
12000               FENcastlingRights[3] = BOARD_RGHT-1;\r
12001               FENcastlingRights[5] = BOARD_WIDTH>>1;\r
12002               break;\r
12003           case'q':\r
12004               FENcastlingRights[4] = BOARD_LEFT;\r
12005               FENcastlingRights[5] = BOARD_WIDTH>>1;\r
12006               break;\r
12007           /* Tord! FRC! */\r
12008         }\r
12009     }\r
12010 \r
12011     while(*p==' ') p++;\r
12012 \r
12013 \r
12014     if(*p=='-') {\r
12015         p++; FENepStatus = EP_NONE;\r
12016     } else {\r
12017        char c = *p++ - AAA;\r
12018 \r
12019        if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
12020        if(*p >= '0' && *p <='9') *p++;\r
12021        FENepStatus = c;\r
12022     }\r
12023 \r
12024     /* [HGM] look for Crazyhouse holdings here */\r
12025     while(*p==' ') p++;\r
12026     if( !isdigit(*p) ) {\r
12027         if(*p == '-' ) *p++; /* empty holdings */ else {\r
12028             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
12029             /* if we would allow FEN reading to set board size, we would   */\r
12030             /* have to add holdings and shift the board read so far here   */\r
12031             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
12032                 *p++;\r
12033                 if((int) piece >= (int) BlackPawn ) {\r
12034                     i = (int)piece - (int)BlackPawn;\r
12035                     if( i >= BOARD_HEIGHT ) return FALSE;\r
12036                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
12037                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
12038                 } else {\r
12039                     i = (int)piece - (int)WhitePawn;\r
12040                     if( i >= BOARD_HEIGHT ) return FALSE;\r
12041                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
12042                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
12043                 }\r
12044             }\r
12045         }\r
12046     }\r
12047 \r
12048 \r
12049 \r
12050     if(sscanf(p, "%d", &i) == 1) {\r
12051         FENrulePlies = i; /* 50-move ply counter */\r
12052         /* (The move number is still ignored)    */\r
12053     }\r
12054  }\r
12055     return TRUE;\r
12056 }\r
12057       \r
12058 void\r
12059 EditPositionPasteFEN(char *fen)\r
12060 {\r
12061   if (fen != NULL) {\r
12062     Board initial_position;\r
12063 \r
12064     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
12065       DisplayError("Bad FEN position in clipboard", 0);\r
12066       return ;\r
12067     } else {\r
12068       int savedBlackPlaysFirst = blackPlaysFirst;\r
12069       EditPositionEvent();\r
12070       blackPlaysFirst = savedBlackPlaysFirst;\r
12071       CopyBoard(boards[0], initial_position);\r
12072           /* [HGM] copy FEN attributes as well */\r
12073           {   int i;\r
12074               initialRulePlies = FENrulePlies;\r
12075               epStatus[0] = FENepStatus;\r
12076               for( i=0; i<nrCastlingRights; i++ )\r
12077                   castlingRights[0][i] = FENcastlingRights[i];\r
12078           }\r
12079       EditPositionDone();\r
12080       DisplayBothClocks();\r
12081       DrawPosition(FALSE, boards[currentMove]);\r
12082     }\r
12083   }\r
12084 }\r