changes from H.G. Muller; version 4.3.12
[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 \r
1793         if(board != boards[0]) InitPosition(FALSE);\r
1794 \r
1795     } else gameInfo.variant = newVariant;\r
1796 }\r
1797 \r
1798 static int loggedOn = FALSE;\r
1799 \r
1800 /*-- Game start info cache: --*/\r
1801 int gs_gamenum;\r
1802 char gs_kind[MSG_SIZ];\r
1803 static char player1Name[128] = "";\r
1804 static char player2Name[128] = "";\r
1805 static int player1Rating = -1;\r
1806 static int player2Rating = -1;\r
1807 /*----------------------------*/\r
1808 \r
1809 ColorClass curColor = ColorNormal;\r
1810 \r
1811 void\r
1812 read_from_ics(isr, closure, data, count, error)\r
1813      InputSourceRef isr;\r
1814      VOIDSTAR closure;\r
1815      char *data;\r
1816      int count;\r
1817      int error;\r
1818 {\r
1819 #define BUF_SIZE 8192\r
1820 #define STARTED_NONE 0\r
1821 #define STARTED_MOVES 1\r
1822 #define STARTED_BOARD 2\r
1823 #define STARTED_OBSERVE 3\r
1824 #define STARTED_HOLDINGS 4\r
1825 #define STARTED_CHATTER 5\r
1826 #define STARTED_COMMENT 6\r
1827 #define STARTED_MOVES_NOHIDE 7\r
1828     \r
1829     static int started = STARTED_NONE;\r
1830     static char parse[20000];\r
1831     static int parse_pos = 0;\r
1832     static char buf[BUF_SIZE + 1];\r
1833     static int firstTime = TRUE, intfSet = FALSE;\r
1834     static ColorClass prevColor = ColorNormal;\r
1835     static int savingComment = FALSE;\r
1836     char str[500];\r
1837     int i, oldi;\r
1838     int buf_len;\r
1839     int next_out;\r
1840     int tkind;\r
1841     char *p;\r
1842 \r
1843 #ifdef WIN32\r
1844     if (appData.debugMode) {\r
1845       if (!error) {\r
1846         fprintf(debugFP, "<ICS: ");\r
1847         show_bytes(debugFP, data, count);\r
1848         fprintf(debugFP, "\n");\r
1849       }\r
1850     }\r
1851 #endif\r
1852 \r
1853     if (count > 0) {\r
1854         /* If last read ended with a partial line that we couldn't parse,\r
1855            prepend it to the new read and try again. */\r
1856         if (leftover_len > 0) {\r
1857             for (i=0; i<leftover_len; i++)\r
1858               buf[i] = buf[leftover_start + i];\r
1859         }\r
1860 \r
1861         /* Copy in new characters, removing nulls and \r's */\r
1862         buf_len = leftover_len;\r
1863         for (i = 0; i < count; i++) {\r
1864             if (data[i] != NULLCHAR && data[i] != '\r')\r
1865               buf[buf_len++] = data[i];\r
1866         }\r
1867 \r
1868         buf[buf_len] = NULLCHAR;\r
1869         next_out = leftover_len;\r
1870         leftover_start = 0;\r
1871         \r
1872         i = 0;\r
1873         while (i < buf_len) {\r
1874             /* Deal with part of the TELNET option negotiation\r
1875                protocol.  We refuse to do anything beyond the\r
1876                defaults, except that we allow the WILL ECHO option,\r
1877                which ICS uses to turn off password echoing when we are\r
1878                directly connected to it.  We reject this option\r
1879                if localLineEditing mode is on (always on in xboard)\r
1880                and we are talking to port 23, which might be a real\r
1881                telnet server that will try to keep WILL ECHO on permanently.\r
1882              */\r
1883             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
1884                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
1885                 unsigned char option;\r
1886                 oldi = i;\r
1887                 switch ((unsigned char) buf[++i]) {\r
1888                   case TN_WILL:\r
1889                     if (appData.debugMode)\r
1890                       fprintf(debugFP, "\n<WILL ");\r
1891                     switch (option = (unsigned char) buf[++i]) {\r
1892                       case TN_ECHO:\r
1893                         if (appData.debugMode)\r
1894                           fprintf(debugFP, "ECHO ");\r
1895                         /* Reply only if this is a change, according\r
1896                            to the protocol rules. */\r
1897                         if (remoteEchoOption) break;\r
1898                         if (appData.localLineEditing &&\r
1899                             atoi(appData.icsPort) == TN_PORT) {\r
1900                             TelnetRequest(TN_DONT, TN_ECHO);\r
1901                         } else {\r
1902                             EchoOff();\r
1903                             TelnetRequest(TN_DO, TN_ECHO);\r
1904                             remoteEchoOption = TRUE;\r
1905                         }\r
1906                         break;\r
1907                       default:\r
1908                         if (appData.debugMode)\r
1909                           fprintf(debugFP, "%d ", option);\r
1910                         /* Whatever this is, we don't want it. */\r
1911                         TelnetRequest(TN_DONT, option);\r
1912                         break;\r
1913                     }\r
1914                     break;\r
1915                   case TN_WONT:\r
1916                     if (appData.debugMode)\r
1917                       fprintf(debugFP, "\n<WONT ");\r
1918                     switch (option = (unsigned char) buf[++i]) {\r
1919                       case TN_ECHO:\r
1920                         if (appData.debugMode)\r
1921                           fprintf(debugFP, "ECHO ");\r
1922                         /* Reply only if this is a change, according\r
1923                            to the protocol rules. */\r
1924                         if (!remoteEchoOption) break;\r
1925                         EchoOn();\r
1926                         TelnetRequest(TN_DONT, TN_ECHO);\r
1927                         remoteEchoOption = FALSE;\r
1928                         break;\r
1929                       default:\r
1930                         if (appData.debugMode)\r
1931                           fprintf(debugFP, "%d ", (unsigned char) option);\r
1932                         /* Whatever this is, it must already be turned\r
1933                            off, because we never agree to turn on\r
1934                            anything non-default, so according to the\r
1935                            protocol rules, we don't reply. */\r
1936                         break;\r
1937                     }\r
1938                     break;\r
1939                   case TN_DO:\r
1940                     if (appData.debugMode)\r
1941                       fprintf(debugFP, "\n<DO ");\r
1942                     switch (option = (unsigned char) buf[++i]) {\r
1943                       default:\r
1944                         /* Whatever this is, we refuse to do it. */\r
1945                         if (appData.debugMode)\r
1946                           fprintf(debugFP, "%d ", option);\r
1947                         TelnetRequest(TN_WONT, option);\r
1948                         break;\r
1949                     }\r
1950                     break;\r
1951                   case TN_DONT:\r
1952                     if (appData.debugMode)\r
1953                       fprintf(debugFP, "\n<DONT ");\r
1954                     switch (option = (unsigned char) buf[++i]) {\r
1955                       default:\r
1956                         if (appData.debugMode)\r
1957                           fprintf(debugFP, "%d ", option);\r
1958                         /* Whatever this is, we are already not doing\r
1959                            it, because we never agree to do anything\r
1960                            non-default, so according to the protocol\r
1961                            rules, we don't reply. */\r
1962                         break;\r
1963                     }\r
1964                     break;\r
1965                   case TN_IAC:\r
1966                     if (appData.debugMode)\r
1967                       fprintf(debugFP, "\n<IAC ");\r
1968                     /* Doubled IAC; pass it through */\r
1969                     i--;\r
1970                     break;\r
1971                   default:\r
1972                     if (appData.debugMode)\r
1973                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
1974                     /* Drop all other telnet commands on the floor */\r
1975                     break;\r
1976                 }\r
1977                 if (oldi > next_out)\r
1978                   SendToPlayer(&buf[next_out], oldi - next_out);\r
1979                 if (++i > next_out)\r
1980                   next_out = i;\r
1981                 continue;\r
1982             }\r
1983                 \r
1984             /* OK, this at least will *usually* work */\r
1985             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
1986                 loggedOn = TRUE;\r
1987             }\r
1988             \r
1989             if (loggedOn && !intfSet) {\r
1990                 if (ics_type == ICS_ICC) {\r
1991                   sprintf(str,\r
1992                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
1993                           programVersion);\r
1994 \r
1995                 } else if (ics_type == ICS_CHESSNET) {\r
1996                   sprintf(str, "/style 12\n");\r
1997                 } else {\r
1998                   strcpy(str, "alias $ @\n$set interface ");\r
1999                   strcat(str, programVersion);\r
2000                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2001 #ifdef WIN32\r
2002                   strcat(str, "$iset nohighlight 1\n");\r
2003 #endif\r
2004                   strcat(str, "$iset lock 1\n$style 12\n");\r
2005                 }\r
2006                 SendToICS(str);\r
2007                 intfSet = TRUE;\r
2008             }\r
2009 \r
2010             if (started == STARTED_COMMENT) {\r
2011                 /* Accumulate characters in comment */\r
2012                 parse[parse_pos++] = buf[i];\r
2013                 if (buf[i] == '\n') {\r
2014                     parse[parse_pos] = NULLCHAR;\r
2015                     AppendComment(forwardMostMove, StripHighlight(parse));\r
2016                     started = STARTED_NONE;\r
2017                 } else {\r
2018                     /* Don't match patterns against characters in chatter */\r
2019                     i++;\r
2020                     continue;\r
2021                 }\r
2022             }\r
2023             if (started == STARTED_CHATTER) {\r
2024                 if (buf[i] != '\n') {\r
2025                     /* Don't match patterns against characters in chatter */\r
2026                     i++;\r
2027                     continue;\r
2028                 }\r
2029                 started = STARTED_NONE;\r
2030             }\r
2031 \r
2032             /* Kludge to deal with rcmd protocol */\r
2033             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2034                 DisplayFatalError(&buf[1], 0, 1);\r
2035                 continue;\r
2036             } else {\r
2037                 firstTime = FALSE;\r
2038             }\r
2039 \r
2040             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2041                 ics_type = ICS_ICC;\r
2042                 ics_prefix = "/";\r
2043                 if (appData.debugMode)\r
2044                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2045                 continue;\r
2046             }\r
2047             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2048                 ics_type = ICS_FICS;\r
2049                 ics_prefix = "$";\r
2050                 if (appData.debugMode)\r
2051                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2052                 continue;\r
2053             }\r
2054             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2055                 ics_type = ICS_CHESSNET;\r
2056                 ics_prefix = "/";\r
2057                 if (appData.debugMode)\r
2058                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2059                 continue;\r
2060             }\r
2061 \r
2062             if (!loggedOn &&\r
2063                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2064                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2065                  looking_at(buf, &i, "will be \"*\""))) {\r
2066               strcpy(ics_handle, star_match[0]);\r
2067               continue;\r
2068             }\r
2069 \r
2070             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2071               char buf[MSG_SIZ];\r
2072               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2073               DisplayIcsInteractionTitle(buf);\r
2074               have_set_title = TRUE;\r
2075             }\r
2076 \r
2077             /* skip finger notes */\r
2078             if (started == STARTED_NONE &&\r
2079                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2080                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2081                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2082               started = STARTED_CHATTER;\r
2083               i += 3;\r
2084               continue;\r
2085             }\r
2086 \r
2087             /* skip formula vars */\r
2088             if (started == STARTED_NONE &&\r
2089                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2090               started = STARTED_CHATTER;\r
2091               i += 3;\r
2092               continue;\r
2093             }\r
2094 \r
2095             oldi = i;\r
2096             if (appData.zippyTalk || appData.zippyPlay) {\r
2097 #if ZIPPY\r
2098                 if (ZippyControl(buf, &i) ||\r
2099                     ZippyConverse(buf, &i) ||\r
2100                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2101                     loggedOn = TRUE;\r
2102                     continue;\r
2103                 }\r
2104 #endif\r
2105             } else {\r
2106                 if (/* Don't color "message" or "messages" output */\r
2107                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2108                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2109                     looking_at(buf, &i, "--* (*:*): ") ||\r
2110                     /* Regular tells and says */\r
2111                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2112                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2113                     looking_at(buf, &i, "* says: ") ||\r
2114                     /* Message notifications (same color as tells) */\r
2115                     looking_at(buf, &i, "* has left a message ") ||\r
2116                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2117                     /* Whispers and kibitzes */\r
2118                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2119                     looking_at(buf, &i, "* kibitzes: ") ||\r
2120                     /* Channel tells */\r
2121                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2122 \r
2123                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2124                       /* Avoid "tells you:" spoofs in channels */\r
2125                      tkind = 3;\r
2126                   }\r
2127                   if (star_match[0][0] == NULLCHAR ||\r
2128                       strchr(star_match[0], ' ') ||\r
2129                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2130                     /* Reject bogus matches */\r
2131                     i = oldi;\r
2132                   } else {\r
2133                     if (appData.colorize) {\r
2134                       if (oldi > next_out) {\r
2135                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2136                         next_out = oldi;\r
2137                       }\r
2138                       switch (tkind) {\r
2139                       case 1:\r
2140                         Colorize(ColorTell, FALSE);\r
2141                         curColor = ColorTell;\r
2142                         break;\r
2143                       case 2:\r
2144                         Colorize(ColorKibitz, FALSE);\r
2145                         curColor = ColorKibitz;\r
2146                         break;\r
2147                       case 3:\r
2148                         p = strrchr(star_match[1], '(');\r
2149                         if (p == NULL) {\r
2150                           p = star_match[1];\r
2151                         } else {\r
2152                           p++;\r
2153                         }\r
2154                         if (atoi(p) == 1) {\r
2155                           Colorize(ColorChannel1, FALSE);\r
2156                           curColor = ColorChannel1;\r
2157                         } else {\r
2158                           Colorize(ColorChannel, FALSE);\r
2159                           curColor = ColorChannel;\r
2160                         }\r
2161                         break;\r
2162                       case 5:\r
2163                         curColor = ColorNormal;\r
2164                         break;\r
2165                       }\r
2166                     }\r
2167                     if (started == STARTED_NONE && appData.autoComment &&\r
2168                         (gameMode == IcsObserving ||\r
2169                          gameMode == IcsPlayingWhite ||\r
2170                          gameMode == IcsPlayingBlack)) {\r
2171                       parse_pos = i - oldi;\r
2172                       memcpy(parse, &buf[oldi], parse_pos);\r
2173                       parse[parse_pos] = NULLCHAR;\r
2174                       started = STARTED_COMMENT;\r
2175                       savingComment = TRUE;\r
2176                     } else {\r
2177                       started = STARTED_CHATTER;\r
2178                       savingComment = FALSE;\r
2179                     }\r
2180                     loggedOn = TRUE;\r
2181                     continue;\r
2182                   }\r
2183                 }\r
2184 \r
2185                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2186                     looking_at(buf, &i, "* c-shouts: ")) {\r
2187                     if (appData.colorize) {\r
2188                         if (oldi > next_out) {\r
2189                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2190                             next_out = oldi;\r
2191                         }\r
2192                         Colorize(ColorSShout, FALSE);\r
2193                         curColor = ColorSShout;\r
2194                     }\r
2195                     loggedOn = TRUE;\r
2196                     started = STARTED_CHATTER;\r
2197                     continue;\r
2198                 }\r
2199 \r
2200                 if (looking_at(buf, &i, "--->")) {\r
2201                     loggedOn = TRUE;\r
2202                     continue;\r
2203                 }\r
2204 \r
2205                 if (looking_at(buf, &i, "* shouts: ") ||\r
2206                     looking_at(buf, &i, "--> ")) {\r
2207                     if (appData.colorize) {\r
2208                         if (oldi > next_out) {\r
2209                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2210                             next_out = oldi;\r
2211                         }\r
2212                         Colorize(ColorShout, FALSE);\r
2213                         curColor = ColorShout;\r
2214                     }\r
2215                     loggedOn = TRUE;\r
2216                     started = STARTED_CHATTER;\r
2217                     continue;\r
2218                 }\r
2219 \r
2220                 if (looking_at( buf, &i, "Challenge:")) {\r
2221                     if (appData.colorize) {\r
2222                         if (oldi > next_out) {\r
2223                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2224                             next_out = oldi;\r
2225                         }\r
2226                         Colorize(ColorChallenge, FALSE);\r
2227                         curColor = ColorChallenge;\r
2228                     }\r
2229                     loggedOn = TRUE;\r
2230                     continue;\r
2231                 }\r
2232 \r
2233                 if (looking_at(buf, &i, "* offers you") ||\r
2234                     looking_at(buf, &i, "* offers to be") ||\r
2235                     looking_at(buf, &i, "* would like to") ||\r
2236                     looking_at(buf, &i, "* requests to") ||\r
2237                     looking_at(buf, &i, "Your opponent offers") ||\r
2238                     looking_at(buf, &i, "Your opponent requests")) {\r
2239 \r
2240                     if (appData.colorize) {\r
2241                         if (oldi > next_out) {\r
2242                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2243                             next_out = oldi;\r
2244                         }\r
2245                         Colorize(ColorRequest, FALSE);\r
2246                         curColor = ColorRequest;\r
2247                     }\r
2248                     continue;\r
2249                 }\r
2250 \r
2251                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2252                     if (appData.colorize) {\r
2253                         if (oldi > next_out) {\r
2254                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2255                             next_out = oldi;\r
2256                         }\r
2257                         Colorize(ColorSeek, FALSE);\r
2258                         curColor = ColorSeek;\r
2259                     }\r
2260                     continue;\r
2261                 }\r
2262             }\r
2263 \r
2264             if (looking_at(buf, &i, "\\   ")) {\r
2265                 if (prevColor != ColorNormal) {\r
2266                     if (oldi > next_out) {\r
2267                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2268                         next_out = oldi;\r
2269                     }\r
2270                     Colorize(prevColor, TRUE);\r
2271                     curColor = prevColor;\r
2272                 }\r
2273                 if (savingComment) {\r
2274                     parse_pos = i - oldi;\r
2275                     memcpy(parse, &buf[oldi], parse_pos);\r
2276                     parse[parse_pos] = NULLCHAR;\r
2277                     started = STARTED_COMMENT;\r
2278                 } else {\r
2279                     started = STARTED_CHATTER;\r
2280                 }\r
2281                 continue;\r
2282             }\r
2283 \r
2284             if (looking_at(buf, &i, "Black Strength :") ||\r
2285                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2286                 looking_at(buf, &i, "<10>") ||\r
2287                 looking_at(buf, &i, "#@#")) {\r
2288                 /* Wrong board style */\r
2289                 loggedOn = TRUE;\r
2290                 SendToICS(ics_prefix);\r
2291                 SendToICS("set style 12\n");\r
2292                 SendToICS(ics_prefix);\r
2293                 SendToICS("refresh\n");\r
2294                 continue;\r
2295             }\r
2296             \r
2297             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2298                 ICSInitScript();\r
2299                 have_sent_ICS_logon = 1;\r
2300                 continue;\r
2301             }\r
2302               \r
2303             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2304                 (looking_at(buf, &i, "\n<12> ") ||\r
2305                  looking_at(buf, &i, "<12> "))) {\r
2306                 loggedOn = TRUE;\r
2307                 if (oldi > next_out) {\r
2308                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2309                 }\r
2310                 next_out = i;\r
2311                 started = STARTED_BOARD;\r
2312                 parse_pos = 0;\r
2313                 continue;\r
2314             }\r
2315 \r
2316             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2317                 looking_at(buf, &i, "<b1> ")) {\r
2318                 if (oldi > next_out) {\r
2319                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2320                 }\r
2321                 next_out = i;\r
2322                 started = STARTED_HOLDINGS;\r
2323                 parse_pos = 0;\r
2324                 continue;\r
2325             }\r
2326 \r
2327             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2328                 loggedOn = TRUE;\r
2329                 /* Header for a move list -- first line */\r
2330 \r
2331                 switch (ics_getting_history) {\r
2332                   case H_FALSE:\r
2333                     switch (gameMode) {\r
2334                       case IcsIdle:\r
2335                       case BeginningOfGame:\r
2336                         /* User typed "moves" or "oldmoves" while we\r
2337                            were idle.  Pretend we asked for these\r
2338                            moves and soak them up so user can step\r
2339                            through them and/or save them.\r
2340                            */\r
2341                         Reset(FALSE, TRUE);\r
2342                         gameMode = IcsObserving;\r
2343                         ModeHighlight();\r
2344                         ics_gamenum = -1;\r
2345                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2346                         break;\r
2347                       case EditGame: /*?*/\r
2348                       case EditPosition: /*?*/\r
2349                         /* Should above feature work in these modes too? */\r
2350                         /* For now it doesn't */\r
2351                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2352                         break;\r
2353                       default:\r
2354                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2355                         break;\r
2356                     }\r
2357                     break;\r
2358                   case H_REQUESTED:\r
2359                     /* Is this the right one? */\r
2360                     if (gameInfo.white && gameInfo.black &&\r
2361                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2362                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2363                         /* All is well */\r
2364                         ics_getting_history = H_GOT_REQ_HEADER;\r
2365                     }\r
2366                     break;\r
2367                   case H_GOT_REQ_HEADER:\r
2368                   case H_GOT_UNREQ_HEADER:\r
2369                   case H_GOT_UNWANTED_HEADER:\r
2370                   case H_GETTING_MOVES:\r
2371                     /* Should not happen */\r
2372                     DisplayError("Error gathering move list: two headers", 0);\r
2373                     ics_getting_history = H_FALSE;\r
2374                     break;\r
2375                 }\r
2376 \r
2377                 /* Save player ratings into gameInfo if needed */\r
2378                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2379                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2380                     (gameInfo.whiteRating == -1 ||\r
2381                      gameInfo.blackRating == -1)) {\r
2382 \r
2383                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2384                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2385                     if (appData.debugMode)\r
2386                       fprintf(debugFP, "Ratings from header: W %d, B %d\n", \r
2387                               gameInfo.whiteRating, gameInfo.blackRating);\r
2388                 }\r
2389                 continue;\r
2390             }\r
2391 \r
2392             if (looking_at(buf, &i,\r
2393               "* * match, initial time: * minute*, increment: * second")) {\r
2394                 /* Header for a move list -- second line */\r
2395                 /* Initial board will follow if this is a wild game */\r
2396                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2397                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2398                 gameInfo.event = StrSave(str);\r
2399                 /* [HGM] we switched variant. Translate boards if needed. */\r
2400                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2401                 continue;\r
2402             }\r
2403 \r
2404             if (looking_at(buf, &i, "Move  ")) {\r
2405                 /* Beginning of a move list */\r
2406                 switch (ics_getting_history) {\r
2407                   case H_FALSE:\r
2408                     /* Normally should not happen */\r
2409                     /* Maybe user hit reset while we were parsing */\r
2410                     break;\r
2411                   case H_REQUESTED:\r
2412                     /* Happens if we are ignoring a move list that is not\r
2413                      * the one we just requested.  Common if the user\r
2414                      * tries to observe two games without turning off\r
2415                      * getMoveList */\r
2416                     break;\r
2417                   case H_GETTING_MOVES:\r
2418                     /* Should not happen */\r
2419                     DisplayError("Error gathering move list: nested", 0);\r
2420                     ics_getting_history = H_FALSE;\r
2421                     break;\r
2422                   case H_GOT_REQ_HEADER:\r
2423                     ics_getting_history = H_GETTING_MOVES;\r
2424                     started = STARTED_MOVES;\r
2425                     parse_pos = 0;\r
2426                     if (oldi > next_out) {\r
2427                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2428                     }\r
2429                     break;\r
2430                   case H_GOT_UNREQ_HEADER:\r
2431                     ics_getting_history = H_GETTING_MOVES;\r
2432                     started = STARTED_MOVES_NOHIDE;\r
2433                     parse_pos = 0;\r
2434                     break;\r
2435                   case H_GOT_UNWANTED_HEADER:\r
2436                     ics_getting_history = H_FALSE;\r
2437                     break;\r
2438                 }\r
2439                 continue;\r
2440             }                           \r
2441             \r
2442             if (looking_at(buf, &i, "% ") ||\r
2443                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2444                  && looking_at(buf, &i, "}*"))) {\r
2445                 savingComment = FALSE;\r
2446                 switch (started) {\r
2447                   case STARTED_MOVES:\r
2448                   case STARTED_MOVES_NOHIDE:\r
2449                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2450                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2451                     ParseGameHistory(parse);\r
2452 #if ZIPPY\r
2453                     if (appData.zippyPlay && first.initDone) {\r
2454                         FeedMovesToProgram(&first, forwardMostMove);\r
2455                         if (gameMode == IcsPlayingWhite) {\r
2456                             if (WhiteOnMove(forwardMostMove)) {\r
2457                                 if (first.sendTime) {\r
2458                                   if (first.useColors) {\r
2459                                     SendToProgram("black\n", &first); \r
2460                                   }\r
2461                                   SendTimeRemaining(&first, TRUE);\r
2462                                 }\r
2463                                 if (first.useColors) {\r
2464                                   SendToProgram("white\ngo\n", &first);\r
2465                                 } else {\r
2466                                   SendToProgram("go\n", &first);\r
2467                                 }\r
2468                                 first.maybeThinking = TRUE;\r
2469                             } else {\r
2470                                 if (first.usePlayother) {\r
2471                                   if (first.sendTime) {\r
2472                                     SendTimeRemaining(&first, TRUE);\r
2473                                   }\r
2474                                   SendToProgram("playother\n", &first);\r
2475                                   firstMove = FALSE;\r
2476                                 } else {\r
2477                                   firstMove = TRUE;\r
2478                                 }\r
2479                             }\r
2480                         } else if (gameMode == IcsPlayingBlack) {\r
2481                             if (!WhiteOnMove(forwardMostMove)) {\r
2482                                 if (first.sendTime) {\r
2483                                   if (first.useColors) {\r
2484                                     SendToProgram("white\n", &first);\r
2485                                   }\r
2486                                   SendTimeRemaining(&first, FALSE);\r
2487                                 }\r
2488                                 if (first.useColors) {\r
2489                                   SendToProgram("black\ngo\n", &first);\r
2490                                 } else {\r
2491                                   SendToProgram("go\n", &first);\r
2492                                 }\r
2493                                 first.maybeThinking = TRUE;\r
2494                             } else {\r
2495                                 if (first.usePlayother) {\r
2496                                   if (first.sendTime) {\r
2497                                     SendTimeRemaining(&first, FALSE);\r
2498                                   }\r
2499                                   SendToProgram("playother\n", &first);\r
2500                                   firstMove = FALSE;\r
2501                                 } else {\r
2502                                   firstMove = TRUE;\r
2503                                 }\r
2504                             }\r
2505                         }                       \r
2506                     }\r
2507 #endif\r
2508                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2509                         /* Moves came from oldmoves or moves command\r
2510                            while we weren't doing anything else.\r
2511                            */\r
2512                         currentMove = forwardMostMove;\r
2513                         ClearHighlights();/*!!could figure this out*/\r
2514                         flipView = appData.flipView;\r
2515                         DrawPosition(FALSE, boards[currentMove]);\r
2516                         DisplayBothClocks();\r
2517                         sprintf(str, "%s vs. %s",\r
2518                                 gameInfo.white, gameInfo.black);\r
2519                         DisplayTitle(str);\r
2520                         gameMode = IcsIdle;\r
2521                     } else {\r
2522                         /* Moves were history of an active game */\r
2523                         if (gameInfo.resultDetails != NULL) {\r
2524                             free(gameInfo.resultDetails);\r
2525                             gameInfo.resultDetails = NULL;\r
2526                         }\r
2527                     }\r
2528                     HistorySet(parseList, backwardMostMove,\r
2529                                forwardMostMove, currentMove-1);\r
2530                     DisplayMove(currentMove - 1);\r
2531                     if (started == STARTED_MOVES) next_out = i;\r
2532                     started = STARTED_NONE;\r
2533                     ics_getting_history = H_FALSE;\r
2534                     break;\r
2535 \r
2536                   case STARTED_OBSERVE:\r
2537                     started = STARTED_NONE;\r
2538                     SendToICS(ics_prefix);\r
2539                     SendToICS("refresh\n");\r
2540                     break;\r
2541 \r
2542                   default:\r
2543                     break;\r
2544                 }\r
2545                 continue;\r
2546             }\r
2547             \r
2548             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2549                  started == STARTED_HOLDINGS ||\r
2550                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2551                 /* Accumulate characters in move list or board */\r
2552                 parse[parse_pos++] = buf[i];\r
2553             }\r
2554             \r
2555             /* Start of game messages.  Mostly we detect start of game\r
2556                when the first board image arrives.  On some versions\r
2557                of the ICS, though, we need to do a "refresh" after starting\r
2558                to observe in order to get the current board right away. */\r
2559             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2560                 started = STARTED_OBSERVE;\r
2561                 continue;\r
2562             }\r
2563 \r
2564             /* Handle auto-observe */\r
2565             if (appData.autoObserve &&\r
2566                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2567                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2568                 char *player;\r
2569                 /* Choose the player that was highlighted, if any. */\r
2570                 if (star_match[0][0] == '\033' ||\r
2571                     star_match[1][0] != '\033') {\r
2572                     player = star_match[0];\r
2573                 } else {\r
2574                     player = star_match[2];\r
2575                 }\r
2576                 sprintf(str, "%sobserve %s\n",\r
2577                         ics_prefix, StripHighlightAndTitle(player));\r
2578                 SendToICS(str);\r
2579 \r
2580                 /* Save ratings from notify string */\r
2581                 strcpy(player1Name, star_match[0]);\r
2582                 player1Rating = string_to_rating(star_match[1]);\r
2583                 strcpy(player2Name, star_match[2]);\r
2584                 player2Rating = string_to_rating(star_match[3]);\r
2585 \r
2586                 if (appData.debugMode)\r
2587                   fprintf(debugFP, \r
2588                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2589                           player1Name, player1Rating,\r
2590                           player2Name, player2Rating);\r
2591 \r
2592                 continue;\r
2593             }\r
2594 \r
2595             /* Deal with automatic examine mode after a game,\r
2596                and with IcsObserving -> IcsExamining transition */\r
2597             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2598                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2599 \r
2600                 int gamenum = atoi(star_match[0]);\r
2601                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2602                     gamenum == ics_gamenum) {\r
2603                     /* We were already playing or observing this game;\r
2604                        no need to refetch history */\r
2605                     gameMode = IcsExamining;\r
2606                     if (pausing) {\r
2607                         pauseExamForwardMostMove = forwardMostMove;\r
2608                     } else if (currentMove < forwardMostMove) {\r
2609                         ForwardInner(forwardMostMove);\r
2610                     }\r
2611                 } else {\r
2612                     /* I don't think this case really can happen */\r
2613                     SendToICS(ics_prefix);\r
2614                     SendToICS("refresh\n");\r
2615                 }\r
2616                 continue;\r
2617             }    \r
2618             \r
2619             /* Error messages */\r
2620             if (ics_user_moved) {\r
2621                 if (looking_at(buf, &i, "Illegal move") ||\r
2622                     looking_at(buf, &i, "Not a legal move") ||\r
2623                     looking_at(buf, &i, "Your king is in check") ||\r
2624                     looking_at(buf, &i, "It isn't your turn") ||\r
2625                     looking_at(buf, &i, "It is not your move")) {\r
2626                     /* Illegal move */\r
2627                     ics_user_moved = 0;\r
2628                     if (forwardMostMove > backwardMostMove) {\r
2629                         currentMove = --forwardMostMove;\r
2630                         DisplayMove(currentMove - 1); /* before DMError */\r
2631                         DisplayMoveError("Illegal move (rejected by ICS)");\r
2632                         DrawPosition(FALSE, boards[currentMove]);\r
2633                         SwitchClocks();\r
2634                         DisplayBothClocks();\r
2635                     }\r
2636                     continue;\r
2637                 }\r
2638             }\r
2639 \r
2640             if (looking_at(buf, &i, "still have time") ||\r
2641                 looking_at(buf, &i, "not out of time") ||\r
2642                 looking_at(buf, &i, "either player is out of time") ||\r
2643                 looking_at(buf, &i, "has timeseal; checking")) {\r
2644                 /* We must have called his flag a little too soon */\r
2645                 whiteFlag = blackFlag = FALSE;\r
2646                 continue;\r
2647             }\r
2648 \r
2649             if (looking_at(buf, &i, "added * seconds to") ||\r
2650                 looking_at(buf, &i, "seconds were added to")) {\r
2651                 /* Update the clocks */\r
2652                 SendToICS(ics_prefix);\r
2653                 SendToICS("refresh\n");\r
2654                 continue;\r
2655             }\r
2656 \r
2657             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2658                 ics_clock_paused = TRUE;\r
2659                 StopClocks();\r
2660                 continue;\r
2661             }\r
2662 \r
2663             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2664                 ics_clock_paused = FALSE;\r
2665                 StartClocks();\r
2666                 continue;\r
2667             }\r
2668 \r
2669             /* Grab player ratings from the Creating: message.\r
2670                Note we have to check for the special case when\r
2671                the ICS inserts things like [white] or [black]. */\r
2672             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
2673                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
2674                 /* star_matches:\r
2675                    0    player 1 name (not necessarily white)\r
2676                    1    player 1 rating\r
2677                    2    empty, white, or black (IGNORED)\r
2678                    3    player 2 name (not necessarily black)\r
2679                    4    player 2 rating\r
2680                    \r
2681                    The names/ratings are sorted out when the game\r
2682                    actually starts (below).\r
2683                 */\r
2684                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
2685                 player1Rating = string_to_rating(star_match[1]);\r
2686                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
2687                 player2Rating = string_to_rating(star_match[4]);\r
2688 \r
2689                 if (appData.debugMode)\r
2690                   fprintf(debugFP, \r
2691                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
2692                           player1Name, player1Rating,\r
2693                           player2Name, player2Rating);\r
2694 \r
2695                 continue;\r
2696             }\r
2697             \r
2698             /* Improved generic start/end-of-game messages */\r
2699             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
2700                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
2701                 /* If tkind == 0: */\r
2702                 /* star_match[0] is the game number */\r
2703                 /*           [1] is the white player's name */\r
2704                 /*           [2] is the black player's name */\r
2705                 /* For end-of-game: */\r
2706                 /*           [3] is the reason for the game end */\r
2707                 /*           [4] is a PGN end game-token, preceded by " " */\r
2708                 /* For start-of-game: */\r
2709                 /*           [3] begins with "Creating" or "Continuing" */\r
2710                 /*           [4] is " *" or empty (don't care). */\r
2711                 int gamenum = atoi(star_match[0]);\r
2712                 char *whitename, *blackname, *why, *endtoken;\r
2713                 ChessMove endtype = (ChessMove) 0;\r
2714 \r
2715                 if (tkind == 0) {\r
2716                   whitename = star_match[1];\r
2717                   blackname = star_match[2];\r
2718                   why = star_match[3];\r
2719                   endtoken = star_match[4];\r
2720                 } else {\r
2721                   whitename = star_match[1];\r
2722                   blackname = star_match[3];\r
2723                   why = star_match[5];\r
2724                   endtoken = star_match[6];\r
2725                 }\r
2726 \r
2727                 /* Game start messages */\r
2728                 if (strncmp(why, "Creating ", 9) == 0 ||\r
2729                     strncmp(why, "Continuing ", 11) == 0) {\r
2730                     gs_gamenum = gamenum;\r
2731                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
2732 #if ZIPPY\r
2733                     if (appData.zippyPlay) {\r
2734                         ZippyGameStart(whitename, blackname);\r
2735                     }\r
2736 #endif /*ZIPPY*/\r
2737                     continue;\r
2738                 }\r
2739 \r
2740                 /* Game end messages */\r
2741                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
2742                     ics_gamenum != gamenum) {\r
2743                     continue;\r
2744                 }\r
2745                 while (endtoken[0] == ' ') endtoken++;\r
2746                 switch (endtoken[0]) {\r
2747                   case '*':\r
2748                   default:\r
2749                     endtype = GameUnfinished;\r
2750                     break;\r
2751                   case '0':\r
2752                     endtype = BlackWins;\r
2753                     break;\r
2754                   case '1':\r
2755                     if (endtoken[1] == '/')\r
2756                       endtype = GameIsDrawn;\r
2757                     else\r
2758                       endtype = WhiteWins;\r
2759                     break;\r
2760                 }\r
2761                 GameEnds(endtype, why, GE_ICS);\r
2762 #if ZIPPY\r
2763                 if (appData.zippyPlay && first.initDone) {\r
2764                     ZippyGameEnd(endtype, why);\r
2765                     if (first.pr == NULL) {\r
2766                       /* Start the next process early so that we'll\r
2767                          be ready for the next challenge */\r
2768                       StartChessProgram(&first);\r
2769                     }\r
2770                     /* Send "new" early, in case this command takes\r
2771                        a long time to finish, so that we'll be ready\r
2772                        for the next challenge. */\r
2773                     Reset(TRUE, TRUE);\r
2774                 }\r
2775 #endif /*ZIPPY*/\r
2776                 continue;\r
2777             }\r
2778 \r
2779             if (looking_at(buf, &i, "Removing game * from observation") ||\r
2780                 looking_at(buf, &i, "no longer observing game *") ||\r
2781                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
2782                 if (gameMode == IcsObserving &&\r
2783                     atoi(star_match[0]) == ics_gamenum)\r
2784                   {\r
2785                       StopClocks();\r
2786                       gameMode = IcsIdle;\r
2787                       ics_gamenum = -1;\r
2788                       ics_user_moved = FALSE;\r
2789                   }\r
2790                 continue;\r
2791             }\r
2792 \r
2793             if (looking_at(buf, &i, "no longer examining game *")) {\r
2794                 if (gameMode == IcsExamining &&\r
2795                     atoi(star_match[0]) == ics_gamenum)\r
2796                   {\r
2797                       gameMode = IcsIdle;\r
2798                       ics_gamenum = -1;\r
2799                       ics_user_moved = FALSE;\r
2800                   }\r
2801                 continue;\r
2802             }\r
2803 \r
2804             /* Advance leftover_start past any newlines we find,\r
2805                so only partial lines can get reparsed */\r
2806             if (looking_at(buf, &i, "\n")) {\r
2807                 prevColor = curColor;\r
2808                 if (curColor != ColorNormal) {\r
2809                     if (oldi > next_out) {\r
2810                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2811                         next_out = oldi;\r
2812                     }\r
2813                     Colorize(ColorNormal, FALSE);\r
2814                     curColor = ColorNormal;\r
2815                 }\r
2816                 if (started == STARTED_BOARD) {\r
2817                     started = STARTED_NONE;\r
2818                     parse[parse_pos] = NULLCHAR;\r
2819                     ParseBoard12(parse);\r
2820                     ics_user_moved = 0;\r
2821 \r
2822                     /* Send premove here */\r
2823                     if (appData.premove) {\r
2824                       char str[MSG_SIZ];\r
2825                       if (currentMove == 0 &&\r
2826                           gameMode == IcsPlayingWhite &&\r
2827                           appData.premoveWhite) {\r
2828                         sprintf(str, "%s%s\n", ics_prefix,\r
2829                                 appData.premoveWhiteText);\r
2830                         if (appData.debugMode)\r
2831                           fprintf(debugFP, "Sending premove:\n");\r
2832                         SendToICS(str);\r
2833                       } else if (currentMove == 1 &&\r
2834                                  gameMode == IcsPlayingBlack &&\r
2835                                  appData.premoveBlack) {\r
2836                         sprintf(str, "%s%s\n", ics_prefix,\r
2837                                 appData.premoveBlackText);\r
2838                         if (appData.debugMode)\r
2839                           fprintf(debugFP, "Sending premove:\n");\r
2840                         SendToICS(str);\r
2841                       } else if (gotPremove) {\r
2842                         gotPremove = 0;\r
2843                         ClearPremoveHighlights();\r
2844                         if (appData.debugMode)\r
2845                           fprintf(debugFP, "Sending premove:\n");\r
2846                           UserMoveEvent(premoveFromX, premoveFromY, \r
2847                                         premoveToX, premoveToY, \r
2848                                         premovePromoChar);\r
2849                       }\r
2850                     }\r
2851 \r
2852                     /* Usually suppress following prompt */\r
2853                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
2854                         if (looking_at(buf, &i, "*% ")) {\r
2855                             savingComment = FALSE;\r
2856                         }\r
2857                     }\r
2858                     next_out = i;\r
2859                 } else if (started == STARTED_HOLDINGS) {\r
2860                     int gamenum;\r
2861                     char new_piece[MSG_SIZ];\r
2862                     started = STARTED_NONE;\r
2863                     parse[parse_pos] = NULLCHAR;\r
2864                     if (appData.debugMode)\r
2865                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
2866                                                         parse, currentMove);\r
2867                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
2868                         gamenum == ics_gamenum) {\r
2869                         if (gameInfo.variant == VariantNormal) {\r
2870                           /* [HGM] We seem to switch variant during a game!\r
2871                            * Presumably no holdings were displayed, so we have\r
2872                            * to move the position two files to the right to\r
2873                            * create room for them!\r
2874                            */\r
2875                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
2876                           /* Get a move list just to see the header, which\r
2877                              will tell us whether this is really bug or zh */\r
2878                           if (ics_getting_history == H_FALSE) {\r
2879                             ics_getting_history = H_REQUESTED;\r
2880                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
2881                             SendToICS(str);\r
2882                           }\r
2883                         }\r
2884                         new_piece[0] = NULLCHAR;\r
2885                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
2886                                &gamenum, white_holding, black_holding,\r
2887                                new_piece);\r
2888                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
2889                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
2890                         /* [HGM] copy holdings to board holdings area */\r
2891                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
2892                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
2893 #if ZIPPY\r
2894                         if (appData.zippyPlay && first.initDone) {\r
2895                             ZippyHoldings(white_holding, black_holding,\r
2896                                           new_piece);\r
2897                         }\r
2898 #endif /*ZIPPY*/\r
2899                         if (tinyLayout || smallLayout) {\r
2900                             char wh[16], bh[16];\r
2901                             PackHolding(wh, white_holding);\r
2902                             PackHolding(bh, black_holding);\r
2903                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
2904                                     gameInfo.white, gameInfo.black);\r
2905                         } else {\r
2906                             sprintf(str, "%s [%s] vs. %s [%s]",\r
2907                                     gameInfo.white, white_holding,\r
2908                                     gameInfo.black, black_holding);\r
2909                         }\r
2910 \r
2911                         DrawPosition(FALSE, boards[currentMove]);\r
2912                         DisplayTitle(str);\r
2913                     }\r
2914                     /* Suppress following prompt */\r
2915                     if (looking_at(buf, &i, "*% ")) {\r
2916                         savingComment = FALSE;\r
2917                     }\r
2918                     next_out = i;\r
2919                 }\r
2920                 continue;\r
2921             }\r
2922 \r
2923             i++;                /* skip unparsed character and loop back */\r
2924         }\r
2925         \r
2926         if (started != STARTED_MOVES && started != STARTED_BOARD &&\r
2927             started != STARTED_HOLDINGS && i > next_out) {\r
2928             SendToPlayer(&buf[next_out], i - next_out);\r
2929             next_out = i;\r
2930         }\r
2931         \r
2932         leftover_len = buf_len - leftover_start;\r
2933         /* if buffer ends with something we couldn't parse,\r
2934            reparse it after appending the next read */\r
2935         \r
2936     } else if (count == 0) {\r
2937         RemoveInputSource(isr);\r
2938         DisplayFatalError("Connection closed by ICS", 0, 0);\r
2939     } else {\r
2940         DisplayFatalError("Error reading from ICS", error, 1);\r
2941     }\r
2942 }\r
2943 \r
2944 \r
2945 /* Board style 12 looks like this:\r
2946    \r
2947    <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
2948    \r
2949  * The "<12> " is stripped before it gets to this routine.  The two\r
2950  * trailing 0's (flip state and clock ticking) are later addition, and\r
2951  * some chess servers may not have them, or may have only the first.\r
2952  * Additional trailing fields may be added in the future.  \r
2953  */\r
2954 \r
2955 #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
2956 \r
2957 #define RELATION_OBSERVING_PLAYED    0\r
2958 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
2959 #define RELATION_PLAYING_MYMOVE      1\r
2960 #define RELATION_PLAYING_NOTMYMOVE  -1\r
2961 #define RELATION_EXAMINING           2\r
2962 #define RELATION_ISOLATED_BOARD     -3\r
2963 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
2964 \r
2965 void\r
2966 ParseBoard12(string)\r
2967      char *string;\r
2968\r
2969     GameMode newGameMode;\r
2970     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;\r
2971     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;\r
2972     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
2973     char to_play, board_chars[72];\r
2974     char move_str[500], str[500], elapsed_time[500];\r
2975     char black[32], white[32];\r
2976     Board board;\r
2977     int prevMove = currentMove;\r
2978     int ticking = 2;\r
2979     ChessMove moveType;\r
2980     int fromX, fromY, toX, toY;\r
2981     char promoChar;\r
2982 \r
2983     fromX = fromY = toX = toY = -1;\r
2984     \r
2985     newGame = FALSE;\r
2986 \r
2987     if (appData.debugMode)\r
2988       fprintf(debugFP, "Parsing board: %s\n", string);\r
2989 \r
2990     move_str[0] = NULLCHAR;\r
2991     elapsed_time[0] = NULLCHAR;\r
2992     n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,\r
2993                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
2994                &gamenum, white, black, &relation, &basetime, &increment,\r
2995                &white_stren, &black_stren, &white_time, &black_time,\r
2996                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
2997                &ticking);\r
2998 \r
2999     if (n < 22) {\r
3000         sprintf(str, "Failed to parse board string:\n\"%s\"", string);\r
3001         DisplayError(str, 0);\r
3002         return;\r
3003     }\r
3004 \r
3005     /* Convert the move number to internal form */\r
3006     moveNum = (moveNum - 1) * 2;\r
3007     if (to_play == 'B') moveNum++;\r
3008     if (moveNum >= MAX_MOVES) {\r
3009       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
3010                         0, 1);\r
3011       return;\r
3012     }\r
3013     \r
3014     switch (relation) {\r
3015       case RELATION_OBSERVING_PLAYED:\r
3016       case RELATION_OBSERVING_STATIC:\r
3017         if (gamenum == -1) {\r
3018             /* Old ICC buglet */\r
3019             relation = RELATION_OBSERVING_STATIC;\r
3020         }\r
3021         newGameMode = IcsObserving;\r
3022         break;\r
3023       case RELATION_PLAYING_MYMOVE:\r
3024       case RELATION_PLAYING_NOTMYMOVE:\r
3025         newGameMode =\r
3026           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3027             IcsPlayingWhite : IcsPlayingBlack;\r
3028         break;\r
3029       case RELATION_EXAMINING:\r
3030         newGameMode = IcsExamining;\r
3031         break;\r
3032       case RELATION_ISOLATED_BOARD:\r
3033       default:\r
3034         /* Just display this board.  If user was doing something else,\r
3035            we will forget about it until the next board comes. */ \r
3036         newGameMode = IcsIdle;\r
3037         break;\r
3038       case RELATION_STARTING_POSITION:\r
3039         newGameMode = gameMode;\r
3040         break;\r
3041     }\r
3042     \r
3043     /* Modify behavior for initial board display on move listing\r
3044        of wild games.\r
3045        */\r
3046     switch (ics_getting_history) {\r
3047       case H_FALSE:\r
3048       case H_REQUESTED:\r
3049         break;\r
3050       case H_GOT_REQ_HEADER:\r
3051       case H_GOT_UNREQ_HEADER:\r
3052         /* This is the initial position of the current game */\r
3053         gamenum = ics_gamenum;\r
3054         moveNum = 0;            /* old ICS bug workaround */\r
3055         if (to_play == 'B') {\r
3056           startedFromSetupPosition = TRUE;\r
3057           blackPlaysFirst = TRUE;\r
3058           moveNum = 1;\r
3059           if (forwardMostMove == 0) forwardMostMove = 1;\r
3060           if (backwardMostMove == 0) backwardMostMove = 1;\r
3061           if (currentMove == 0) currentMove = 1;\r
3062         }\r
3063         newGameMode = gameMode;\r
3064         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3065         break;\r
3066       case H_GOT_UNWANTED_HEADER:\r
3067         /* This is an initial board that we don't want */\r
3068         return;\r
3069       case H_GETTING_MOVES:\r
3070         /* Should not happen */\r
3071         DisplayError("Error gathering move list: extra board", 0);\r
3072         ics_getting_history = H_FALSE;\r
3073         return;\r
3074     }\r
3075     \r
3076     /* Take action if this is the first board of a new game, or of a\r
3077        different game than is currently being displayed.  */\r
3078     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3079         relation == RELATION_ISOLATED_BOARD) {\r
3080         \r
3081         /* Forget the old game and get the history (if any) of the new one */\r
3082         if (gameMode != BeginningOfGame) {\r
3083           Reset(FALSE, TRUE);\r
3084         }\r
3085         newGame = TRUE;\r
3086         if (appData.autoRaiseBoard) BoardToTop();\r
3087         prevMove = -3;\r
3088         if (gamenum == -1) {\r
3089             newGameMode = IcsIdle;\r
3090         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3091                    appData.getMoveList) {\r
3092             /* Need to get game history */\r
3093             ics_getting_history = H_REQUESTED;\r
3094             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3095             SendToICS(str);\r
3096         }\r
3097         \r
3098         /* Initially flip the board to have black on the bottom if playing\r
3099            black or if the ICS flip flag is set, but let the user change\r
3100            it with the Flip View button. */\r
3101         flipView = appData.autoFlipView ? \r
3102           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3103           appData.flipView;\r
3104         \r
3105         /* Done with values from previous mode; copy in new ones */\r
3106         gameMode = newGameMode;\r
3107         ModeHighlight();\r
3108         ics_gamenum = gamenum;\r
3109         if (gamenum == gs_gamenum) {\r
3110             int klen = strlen(gs_kind);\r
3111             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3112             sprintf(str, "ICS %s", gs_kind);\r
3113             gameInfo.event = StrSave(str);\r
3114         } else {\r
3115             gameInfo.event = StrSave("ICS game");\r
3116         }\r
3117         gameInfo.site = StrSave(appData.icsHost);\r
3118         gameInfo.date = PGNDate();\r
3119         gameInfo.round = StrSave("-");\r
3120         gameInfo.white = StrSave(white);\r
3121         gameInfo.black = StrSave(black);\r
3122         timeControl = basetime * 60 * 1000;\r
3123         timeControl_2 = 0;\r
3124         timeIncrement = increment * 1000;\r
3125         movesPerSession = 0;\r
3126         gameInfo.timeControl = TimeControlTagValue();\r
3127         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3128   if (appData.debugMode) {\r
3129     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3130     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3131     setbuf(debugFP, NULL);\r
3132   }\r
3133 \r
3134         gameInfo.outOfBook = NULL;\r
3135         \r
3136         /* Do we have the ratings? */\r
3137         if (strcmp(player1Name, white) == 0 &&\r
3138             strcmp(player2Name, black) == 0) {\r
3139             if (appData.debugMode)\r
3140               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3141                       player1Rating, player2Rating);\r
3142             gameInfo.whiteRating = player1Rating;\r
3143             gameInfo.blackRating = player2Rating;\r
3144         } else if (strcmp(player2Name, white) == 0 &&\r
3145                    strcmp(player1Name, black) == 0) {\r
3146             if (appData.debugMode)\r
3147               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3148                       player2Rating, player1Rating);\r
3149             gameInfo.whiteRating = player2Rating;\r
3150             gameInfo.blackRating = player1Rating;\r
3151         }\r
3152         player1Name[0] = player2Name[0] = NULLCHAR;\r
3153 \r
3154         /* Silence shouts if requested */\r
3155         if (appData.quietPlay &&\r
3156             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3157             SendToICS(ics_prefix);\r
3158             SendToICS("set shout 0\n");\r
3159         }\r
3160     }\r
3161     \r
3162     /* Deal with midgame name changes */\r
3163     if (!newGame) {\r
3164         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3165             if (gameInfo.white) free(gameInfo.white);\r
3166             gameInfo.white = StrSave(white);\r
3167         }\r
3168         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3169             if (gameInfo.black) free(gameInfo.black);\r
3170             gameInfo.black = StrSave(black);\r
3171         }\r
3172     }\r
3173     \r
3174     /* Throw away game result if anything actually changes in examine mode */\r
3175     if (gameMode == IcsExamining && !newGame) {\r
3176         gameInfo.result = GameUnfinished;\r
3177         if (gameInfo.resultDetails != NULL) {\r
3178             free(gameInfo.resultDetails);\r
3179             gameInfo.resultDetails = NULL;\r
3180         }\r
3181     }\r
3182     \r
3183     /* In pausing && IcsExamining mode, we ignore boards coming\r
3184        in if they are in a different variation than we are. */\r
3185     if (pauseExamInvalid) return;\r
3186     if (pausing && gameMode == IcsExamining) {\r
3187         if (moveNum <= pauseExamForwardMostMove) {\r
3188             pauseExamInvalid = TRUE;\r
3189             forwardMostMove = pauseExamForwardMostMove;\r
3190             return;\r
3191         }\r
3192     }\r
3193     \r
3194     /* Parse the board */\r
3195     for (k = 0; k < 8; k++) {\r
3196       for (j = 0; j < 8; j++)\r
3197         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(7-k)*9 + j]);\r
3198       if(gameInfo.holdingsWidth > 1) {\r
3199            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3200            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3201       }\r
3202     }\r
3203     CopyBoard(boards[moveNum], board);\r
3204     if (moveNum == 0) {\r
3205         startedFromSetupPosition =\r
3206           !CompareBoards(board, initialPosition);\r
3207     }\r
3208     \r
3209     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3210         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3211         /* This was an initial position from a move list, not\r
3212            the current position */\r
3213         return;\r
3214     }\r
3215     \r
3216     /* Update currentMove and known move number limits */\r
3217     newMove = newGame || moveNum > forwardMostMove;\r
3218     if (newGame) {\r
3219         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3220         if (gameMode == IcsExamining && moveNum == 0) {\r
3221           /* Workaround for ICS limitation: we are not told the wild\r
3222              type when starting to examine a game.  But if we ask for\r
3223              the move list, the move list header will tell us */\r
3224             ics_getting_history = H_REQUESTED;\r
3225             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3226             SendToICS(str);\r
3227         }\r
3228     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3229                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3230         forwardMostMove = moveNum;\r
3231         if (!pausing || currentMove > forwardMostMove)\r
3232           currentMove = forwardMostMove;\r
3233     } else {\r
3234         /* New part of history that is not contiguous with old part */ \r
3235         if (pausing && gameMode == IcsExamining) {\r
3236             pauseExamInvalid = TRUE;\r
3237             forwardMostMove = pauseExamForwardMostMove;\r
3238             return;\r
3239         }\r
3240         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3241         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3242             ics_getting_history = H_REQUESTED;\r
3243             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3244             SendToICS(str);\r
3245         }\r
3246     }\r
3247     \r
3248     /* Update the clocks */\r
3249     if (strchr(elapsed_time, '.')) {\r
3250       /* Time is in ms */\r
3251       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3252       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3253     } else {\r
3254       /* Time is in seconds */\r
3255       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3256       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3257     }\r
3258       \r
3259 \r
3260 #if ZIPPY\r
3261     if (appData.zippyPlay && newGame &&\r
3262         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3263         gameMode != IcsExamining)\r
3264       ZippyFirstBoard(moveNum, basetime, increment);\r
3265 #endif\r
3266     \r
3267     /* Put the move on the move list, first converting\r
3268        to canonical algebraic form. */\r
3269     if (moveNum > 0) {\r
3270   if (appData.debugMode) {\r
3271     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3272     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3273     setbuf(debugFP, NULL);\r
3274   }\r
3275         if (moveNum <= backwardMostMove) {\r
3276             /* We don't know what the board looked like before\r
3277                this move.  Punt. */\r
3278             strcpy(parseList[moveNum - 1], move_str);\r
3279             strcat(parseList[moveNum - 1], " ");\r
3280             strcat(parseList[moveNum - 1], elapsed_time);\r
3281             moveList[moveNum - 1][0] = NULLCHAR;\r
3282         } else if (ParseOneMove(move_str, moveNum - 1, &moveType,\r
3283                                 &fromX, &fromY, &toX, &toY, &promoChar)) {\r
3284             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3285                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3286                                      fromY, fromX, toY, toX, promoChar,\r
3287                                      parseList[moveNum-1]);\r
3288             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3289                              castlingRights[moveNum]) ) {\r
3290               case MT_NONE:\r
3291               case MT_STALEMATE:\r
3292               default:\r
3293                 break;\r
3294               case MT_CHECK:\r
3295                 if(gameInfo.variant != VariantShogi)\r
3296                     strcat(parseList[moveNum - 1], "+");\r
3297                 break;\r
3298               case MT_CHECKMATE:\r
3299                 strcat(parseList[moveNum - 1], "#");\r
3300                 break;\r
3301             }\r
3302             strcat(parseList[moveNum - 1], " ");\r
3303             strcat(parseList[moveNum - 1], elapsed_time);\r
3304             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3305             strcpy(moveList[moveNum - 1], currentMoveString);\r
3306             strcat(moveList[moveNum - 1], "\n");\r
3307         } else if (strcmp(move_str, "none") == 0) {\r
3308             /* Again, we don't know what the board looked like;\r
3309                this is really the start of the game. */\r
3310             parseList[moveNum - 1][0] = NULLCHAR;\r
3311             moveList[moveNum - 1][0] = NULLCHAR;\r
3312             backwardMostMove = moveNum;\r
3313             startedFromSetupPosition = TRUE;\r
3314             fromX = fromY = toX = toY = -1;\r
3315         } else {\r
3316             /* Move from ICS was illegal!?  Punt. */\r
3317   if (appData.debugMode) {\r
3318     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3319     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3320   }\r
3321 #if 0\r
3322             if (appData.testLegality && appData.debugMode) {\r
3323                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3324                 DisplayError(str, 0);\r
3325             }\r
3326 #endif\r
3327             strcpy(parseList[moveNum - 1], move_str);\r
3328             strcat(parseList[moveNum - 1], " ");\r
3329             strcat(parseList[moveNum - 1], elapsed_time);\r
3330             moveList[moveNum - 1][0] = NULLCHAR;\r
3331             fromX = fromY = toX = toY = -1;\r
3332         }\r
3333   if (appData.debugMode) {\r
3334     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3335     setbuf(debugFP, NULL);\r
3336   }\r
3337 \r
3338 #if ZIPPY\r
3339         /* Send move to chess program (BEFORE animating it). */\r
3340         if (appData.zippyPlay && !newGame && newMove && \r
3341            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3342 \r
3343             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3344                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3345                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3346                     sprintf(str, "Couldn't parse move \"%s\" from ICS",\r
3347                             move_str);\r
3348                     DisplayError(str, 0);\r
3349                 } else {\r
3350                     if (first.sendTime) {\r
3351                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3352                     }\r
3353                     SendMoveToProgram(moveNum - 1, &first);\r
3354                     if (firstMove) {\r
3355                         firstMove = FALSE;\r
3356                         if (first.useColors) {\r
3357                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3358                                         "white\ngo\n" :\r
3359                                         "black\ngo\n", &first);\r
3360                         } else {\r
3361                           SendToProgram("go\n", &first);\r
3362                         }\r
3363                         first.maybeThinking = TRUE;\r
3364                     }\r
3365                 }\r
3366             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3367               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3368                 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);\r
3369                 DisplayError(str, 0);\r
3370               } else {\r
3371                 SendMoveToProgram(moveNum - 1, &first);\r
3372               }\r
3373             }\r
3374         }\r
3375 #endif\r
3376     }\r
3377 \r
3378     if (moveNum > 0 && !gotPremove) {\r
3379         /* If move comes from a remote source, animate it.  If it\r
3380            isn't remote, it will have already been animated. */\r
3381         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3382             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3383         }\r
3384         if (!pausing && appData.highlightLastMove) {\r
3385             SetHighlights(fromX, fromY, toX, toY);\r
3386         }\r
3387     }\r
3388     \r
3389     /* Start the clocks */\r
3390     whiteFlag = blackFlag = FALSE;\r
3391     appData.clockMode = !(basetime == 0 && increment == 0);\r
3392     if (ticking == 0) {\r
3393       ics_clock_paused = TRUE;\r
3394       StopClocks();\r
3395     } else if (ticking == 1) {\r
3396       ics_clock_paused = FALSE;\r
3397     }\r
3398     if (gameMode == IcsIdle ||\r
3399         relation == RELATION_OBSERVING_STATIC ||\r
3400         relation == RELATION_EXAMINING ||\r
3401         ics_clock_paused)\r
3402       DisplayBothClocks();\r
3403     else\r
3404       StartClocks();\r
3405     \r
3406     /* Display opponents and material strengths */\r
3407     if (gameInfo.variant != VariantBughouse &&\r
3408         gameInfo.variant != VariantCrazyhouse) {\r
3409         if (tinyLayout || smallLayout) {\r
3410             sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3411                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3412                     basetime, increment);\r
3413         } else {\r
3414             sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3415                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3416                     basetime, increment);\r
3417         }\r
3418         DisplayTitle(str);\r
3419     }\r
3420 \r
3421    \r
3422     /* Display the board */\r
3423     if (!pausing) {\r
3424       \r
3425       if (appData.premove)\r
3426           if (!gotPremove || \r
3427              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3428              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3429               ClearPremoveHighlights();\r
3430 \r
3431       DrawPosition(FALSE, boards[currentMove]);\r
3432       DisplayMove(moveNum - 1);\r
3433       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3434         RingBell();\r
3435     }\r
3436 \r
3437     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3438 }\r
3439 \r
3440 void\r
3441 GetMoveListEvent()\r
3442 {\r
3443     char buf[MSG_SIZ];\r
3444     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3445         ics_getting_history = H_REQUESTED;\r
3446         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3447         SendToICS(buf);\r
3448     }\r
3449 }\r
3450 \r
3451 void\r
3452 AnalysisPeriodicEvent(force)\r
3453      int force;\r
3454 {\r
3455     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3456          && !force) || !appData.periodicUpdates)\r
3457       return;\r
3458 \r
3459     /* Send . command to Crafty to collect stats */\r
3460     SendToProgram(".\n", &first);\r
3461 \r
3462     /* Don't send another until we get a response (this makes\r
3463        us stop sending to old Crafty's which don't understand\r
3464        the "." command (sending illegal cmds resets node count & time,\r
3465        which looks bad)) */\r
3466     programStats.ok_to_send = 0;\r
3467 }\r
3468 \r
3469 void\r
3470 SendMoveToProgram(moveNum, cps)\r
3471      int moveNum;\r
3472      ChessProgramState *cps;\r
3473 {\r
3474     char buf[MSG_SIZ];\r
3475     if (cps->useUsermove) {\r
3476       SendToProgram("usermove ", cps);\r
3477     }\r
3478     if (cps->useSAN) {\r
3479       char *space;\r
3480       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3481         int len = space - parseList[moveNum];\r
3482         memcpy(buf, parseList[moveNum], len);\r
3483         buf[len++] = '\n';\r
3484         buf[len] = NULLCHAR;\r
3485       } else {\r
3486         sprintf(buf, "%s\n", parseList[moveNum]);\r
3487       }\r
3488       SendToProgram(buf, cps);\r
3489     } else {\r
3490       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3491        * the engine. It would be nice to have a better way to identify castle \r
3492        * moves here. */\r
3493       if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {\r
3494         int fromX = moveList[moveNum][0] - AAA; \r
3495         int fromY = moveList[moveNum][1] - ONE;\r
3496         int toX = moveList[moveNum][2] - AAA; \r
3497         int toY = moveList[moveNum][3] - ONE;\r
3498         if((boards[currentMove][fromY][fromX] == WhiteKing \r
3499             && boards[currentMove][toY][toX] == WhiteRook)\r
3500            || (boards[currentMove][fromY][fromX] == BlackKing \r
3501                && boards[currentMove][toY][toX] == BlackRook)) {\r
3502           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3503           else SendToProgram("O-O-O\n", cps);\r
3504         }\r
3505         else SendToProgram(moveList[moveNum], cps);\r
3506       }\r
3507       else SendToProgram(moveList[moveNum], cps);\r
3508       /* End of additions by Tord */\r
3509     }\r
3510 }\r
3511 \r
3512 void\r
3513 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
3514      ChessMove moveType;\r
3515      int fromX, fromY, toX, toY;\r
3516 {\r
3517     char user_move[MSG_SIZ];\r
3518 \r
3519     switch (moveType) {\r
3520       default:\r
3521         sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",\r
3522                 (int)moveType, fromX, fromY, toX, toY);\r
3523         DisplayError(user_move + strlen("say "), 0);\r
3524         break;\r
3525       case WhiteKingSideCastle:\r
3526       case BlackKingSideCastle:\r
3527       case WhiteQueenSideCastleWild:\r
3528       case BlackQueenSideCastleWild:\r
3529       /* PUSH Fabien */\r
3530       case WhiteHSideCastleFR:\r
3531       case BlackHSideCastleFR:\r
3532       /* POP Fabien */\r
3533         sprintf(user_move, "o-o\n");\r
3534         break;\r
3535       case WhiteQueenSideCastle:\r
3536       case BlackQueenSideCastle:\r
3537       case WhiteKingSideCastleWild:\r
3538       case BlackKingSideCastleWild:\r
3539       /* PUSH Fabien */\r
3540       case WhiteASideCastleFR:\r
3541       case BlackASideCastleFR:\r
3542       /* POP Fabien */\r
3543         sprintf(user_move, "o-o-o\n");\r
3544         break;\r
3545       case WhitePromotionQueen:\r
3546       case BlackPromotionQueen:\r
3547       case WhitePromotionRook:\r
3548       case BlackPromotionRook:\r
3549       case WhitePromotionBishop:\r
3550       case BlackPromotionBishop:\r
3551       case WhitePromotionKnight:\r
3552       case BlackPromotionKnight:\r
3553       case WhitePromotionKing:\r
3554       case BlackPromotionKing:\r
3555 #ifdef FAIRY\r
3556       case WhitePromotionChancellor:\r
3557       case BlackPromotionChancellor:\r
3558       case WhitePromotionArchbishop:\r
3559       case BlackPromotionArchbishop:\r
3560 #endif\r
3561         sprintf(user_move, "%c%c%c%c=%c\n",\r
3562                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
3563                 PieceToChar(PromoPiece(moveType)));\r
3564         break;\r
3565       case WhiteDrop:\r
3566       case BlackDrop:\r
3567         sprintf(user_move, "%c@%c%c\n",\r
3568                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
3569                 AAA + toX, ONE + toY);\r
3570         break;\r
3571       case NormalMove:\r
3572       case WhiteCapturesEnPassant:\r
3573       case BlackCapturesEnPassant:\r
3574       case IllegalMove:  /* could be a variant we don't quite understand */\r
3575         sprintf(user_move, "%c%c%c%c\n",\r
3576                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
3577         break;\r
3578     }\r
3579     SendToICS(user_move);\r
3580 }\r
3581 \r
3582 void\r
3583 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
3584      int rf, ff, rt, ft;\r
3585      char promoChar;\r
3586      char move[7];\r
3587 {\r
3588     if (rf == DROP_RANK) {\r
3589         sprintf(move, "%c@%c%c\n",\r
3590                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
3591     } else {\r
3592         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
3593             sprintf(move, "%c%c%c%c\n",\r
3594                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
3595         } else {\r
3596             sprintf(move, "%c%c%c%c%c\n",\r
3597                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
3598         }\r
3599     }\r
3600     AlphaRank(move, 4);\r
3601 }\r
3602 \r
3603 void\r
3604 ProcessICSInitScript(f)\r
3605      FILE *f;\r
3606 {\r
3607     char buf[MSG_SIZ];\r
3608 \r
3609     while (fgets(buf, MSG_SIZ, f)) {\r
3610         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
3611     }\r
3612 \r
3613     fclose(f);\r
3614 }\r
3615 \r
3616 \r
3617 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
3618 void\r
3619 AlphaRank(char *move, int n)\r
3620 {\r
3621     char *p = move, c; int x, y;\r
3622 \r
3623     if( !appData.alphaRank ) return;\r
3624 \r
3625     if (appData.debugMode) {\r
3626         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
3627     }\r
3628 \r
3629     if(move[1]=='*' && \r
3630        move[2]>='0' && move[2]<='9' &&\r
3631        move[3]>='a' && move[3]<='x'    ) {\r
3632         move[2] = (move[2]-'1')+BOARD_LEFT + AAA;\r
3633         move[3] = (move[3]-'a') + ONE;\r
3634     } else\r
3635     if(move[0]>='0' && move[0]<='9' &&\r
3636        move[1]>='a' && move[1]<='x' &&\r
3637        move[2]>='0' && move[2]<='9' &&\r
3638        move[3]>='a' && move[3]<='x'    ) {\r
3639         /* input move, Shogi -> normal */\r
3640 /*\r
3641         move[0] = BOARD_RGHT  -1-(move[0]-'1') + AAA;\r
3642         move[1] = BOARD_HEIGHT-1-(move[1]-'a') + ONE;\r
3643         move[2] = BOARD_RGHT  -1-(move[2]-'1') + AAA;\r
3644         move[3] = BOARD_HEIGHT-1-(move[3]-'a') + ONE;\r
3645 */\r
3646         move[0] = (move[0]-'1')+BOARD_LEFT + AAA;\r
3647         move[1] = (move[1]-'a') + ONE;\r
3648         move[2] = (move[2]-'1')+BOARD_LEFT + AAA;\r
3649         move[3] = (move[3]-'a') + ONE;\r
3650     } else\r
3651     if(move[1]=='@' &&\r
3652        move[3]>='0' && move[3]<='9' &&\r
3653        move[2]>='a' && move[2]<='x'    ) {\r
3654         move[1] = '*';\r
3655         move[2] = (move[2]-AAA)-BOARD_LEFT + '1';\r
3656         move[3] = (move[3]-ONE) + 'a';\r
3657     } else\r
3658     if(\r
3659        move[0]>='a' && move[0]<='x' &&\r
3660        move[3]>='0' && move[3]<='9' &&\r
3661        move[2]>='a' && move[2]<='x'    ) {\r
3662          /* output move, normal -> Shogi */\r
3663 /*\r
3664         move[0] = BOARD_RGHT  -1-(move[0]-AAA) + '1';\r
3665         move[1] = BOARD_HEIGHT-1-(move[1]-ONE) + 'a';\r
3666         move[2] = BOARD_RGHT  -1-(move[2]-AAA) + '1';\r
3667         move[3] = BOARD_HEIGHT-1-(move[3]-ONE) + 'a';\r
3668 */\r
3669         move[0] = (move[0]-AAA)-BOARD_LEFT + '1';\r
3670         move[1] = (move[1]-ONE) + 'a';\r
3671         move[2] = (move[2]-AAA)-BOARD_LEFT + '1';\r
3672         move[3] = (move[3]-ONE) + 'a';\r
3673         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
3674     }\r
3675     if (appData.debugMode) {\r
3676         fprintf(debugFP, "   out = '%s'\n", move);\r
3677     }\r
3678 }\r
3679 \r
3680 /* Parser for moves from gnuchess, ICS, or user typein box */\r
3681 Boolean\r
3682 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
3683      char *move;\r
3684      int moveNum;\r
3685      ChessMove *moveType;\r
3686      int *fromX, *fromY, *toX, *toY;\r
3687      char *promoChar;\r
3688 {       \r
3689     if (appData.debugMode) {\r
3690         fprintf(debugFP, "move to parse: %s\n", move);\r
3691     }\r
3692     *moveType = yylexstr(moveNum, move);\r
3693 \r
3694     switch (*moveType) {\r
3695       case WhitePromotionChancellor:\r
3696       case BlackPromotionChancellor:\r
3697       case WhitePromotionArchbishop:\r
3698       case BlackPromotionArchbishop:\r
3699       case WhitePromotionQueen:\r
3700       case BlackPromotionQueen:\r
3701       case WhitePromotionRook:\r
3702       case BlackPromotionRook:\r
3703       case WhitePromotionBishop:\r
3704       case BlackPromotionBishop:\r
3705       case WhitePromotionKnight:\r
3706       case BlackPromotionKnight:\r
3707       case WhitePromotionKing:\r
3708       case BlackPromotionKing:\r
3709       case NormalMove:\r
3710       case WhiteCapturesEnPassant:\r
3711       case BlackCapturesEnPassant:\r
3712       case WhiteKingSideCastle:\r
3713       case WhiteQueenSideCastle:\r
3714       case BlackKingSideCastle:\r
3715       case BlackQueenSideCastle:\r
3716       case WhiteKingSideCastleWild:\r
3717       case WhiteQueenSideCastleWild:\r
3718       case BlackKingSideCastleWild:\r
3719       case BlackQueenSideCastleWild:\r
3720       /* Code added by Tord: */\r
3721       case WhiteHSideCastleFR:\r
3722       case WhiteASideCastleFR:\r
3723       case BlackHSideCastleFR:\r
3724       case BlackASideCastleFR:\r
3725       /* End of code added by Tord */\r
3726       case IllegalMove:         /* bug or odd chess variant */\r
3727         *fromX = currentMoveString[0] - AAA;\r
3728         *fromY = currentMoveString[1] - ONE;\r
3729         *toX = currentMoveString[2] - AAA;\r
3730         *toY = currentMoveString[3] - ONE;\r
3731         *promoChar = currentMoveString[4];\r
3732         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
3733             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
3734     if (appData.debugMode) {\r
3735         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
3736     }\r
3737             *fromX = *fromY = *toX = *toY = 0;\r
3738             return FALSE;\r
3739         }\r
3740         if (appData.testLegality) {\r
3741           return (*moveType != IllegalMove);\r
3742         } else {\r
3743           return !(fromX == fromY && toX == toY);\r
3744         }\r
3745 \r
3746       case WhiteDrop:\r
3747       case BlackDrop:\r
3748         *fromX = *moveType == WhiteDrop ?\r
3749           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
3750         (int) CharToPiece(ToLower(currentMoveString[0]));\r
3751         *fromY = DROP_RANK;\r
3752         *toX = currentMoveString[2] - AAA;\r
3753         *toY = currentMoveString[3] - ONE;\r
3754         *promoChar = NULLCHAR;\r
3755         return TRUE;\r
3756 \r
3757       case AmbiguousMove:\r
3758       case ImpossibleMove:\r
3759       case (ChessMove) 0:       /* end of file */\r
3760       case ElapsedTime:\r
3761       case Comment:\r
3762       case PGNTag:\r
3763       case NAG:\r
3764       case WhiteWins:\r
3765       case BlackWins:\r
3766       case GameIsDrawn:\r
3767       default:\r
3768     if (appData.debugMode) {\r
3769         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
3770     }\r
3771         /* bug? */\r
3772         *fromX = *fromY = *toX = *toY = 0;\r
3773         *promoChar = NULLCHAR;\r
3774         return FALSE;\r
3775     }\r
3776 }\r
3777 \r
3778 /* [AS] FRC game initialization */\r
3779 static int FindEmptySquare( Board board, int n )\r
3780 {\r
3781     int i = 0;\r
3782 \r
3783     while( 1 ) {\r
3784         while( board[0][i] != EmptySquare ) i++;\r
3785         if( n == 0 )\r
3786             break;\r
3787         n--;\r
3788         i++;\r
3789     }\r
3790 \r
3791     return i;\r
3792 }\r
3793 \r
3794 static void ShuffleFRC( Board board )\r
3795 {\r
3796     int i;\r
3797 \r
3798     srand( time(0) );\r
3799     \r
3800     for( i=0; i<8; i++ ) {\r
3801         board[0][i] = EmptySquare;\r
3802     }\r
3803 \r
3804     board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */\r
3805     board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */\r
3806     board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;\r
3807     board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;\r
3808     board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;\r
3809     board[0][FindEmptySquare(board, 0)] = WhiteRook;\r
3810     board[0][FindEmptySquare(board, 0)] = WhiteKing;\r
3811     board[0][FindEmptySquare(board, 0)] = WhiteRook;\r
3812 \r
3813     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
3814         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
3815     }\r
3816 }\r
3817 \r
3818 static unsigned char FRC_KnightTable[10] = {\r
3819     0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33\r
3820 };\r
3821 \r
3822 static void SetupFRC( Board board, int pos_index )\r
3823 {\r
3824     int i;\r
3825     unsigned char knights;\r
3826 \r
3827     /* Bring the position index into a safe range (just in case...) */\r
3828     if( pos_index < 0 ) pos_index = 0;\r
3829 \r
3830     pos_index %= 960;\r
3831 \r
3832     /* Clear the board */\r
3833     for( i=0; i<8; i++ ) {\r
3834         board[0][i] = EmptySquare;\r
3835     }\r
3836 \r
3837     /* Place bishops and queen */\r
3838     board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */\r
3839     pos_index /= 4;\r
3840     \r
3841     board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */\r
3842     pos_index /= 4;\r
3843 \r
3844     board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;\r
3845     pos_index /= 6;\r
3846 \r
3847     /* Place knigths */\r
3848     knights = FRC_KnightTable[ pos_index ];\r
3849 \r
3850     board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;\r
3851     board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;\r
3852 \r
3853     /* Place rooks and king */\r
3854     board[0][ FindEmptySquare(board, 0) ] = WhiteRook;\r
3855     board[0][ FindEmptySquare(board, 0) ] = WhiteKing;\r
3856     board[0][ FindEmptySquare(board, 0) ] = WhiteRook;\r
3857 \r
3858     /* Mirror piece placement for black */\r
3859     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
3860         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
3861     }\r
3862 }\r
3863 \r
3864 BOOL SetCharTable( char *table, const char * map )\r
3865 /* [HGM] moved here from winboard.c because of its general usefulness */\r
3866 /*       Basically a safe strcpy that uses the last character as King */\r
3867 {\r
3868     BOOL result = FALSE; int NrPieces;\r
3869 \r
3870     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
3871                     && NrPieces >= 12 && !(NrPieces&1)) {\r
3872         int i; /* [HGM] Accept even length from 12 to 34 */\r
3873 \r
3874         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
3875         for( i=0; i<NrPieces/2-1; i++ ) {\r
3876             table[i] = map[i];\r
3877             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
3878         }\r
3879         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
3880         table[(int) BlackKing]  = map[NrPieces-1];\r
3881 \r
3882         result = TRUE;\r
3883     }\r
3884 \r
3885     return result;\r
3886 }\r
3887 \r
3888 void\r
3889 InitPosition(redraw)\r
3890      int redraw;\r
3891 {\r
3892     ChessSquare (* pieces)[BOARD_SIZE];\r
3893     int i, j, pawnRow, overrule,\r
3894     oldx = gameInfo.boardWidth,\r
3895     oldy = gameInfo.boardHeight,\r
3896     oldh = gameInfo.holdingsWidth,\r
3897     oldv = gameInfo.variant;\r
3898 \r
3899     currentMove = forwardMostMove = backwardMostMove = 0;\r
3900 \r
3901     /* [AS] Initialize pv info list [HGM] and game status */\r
3902     {\r
3903         for( i=0; i<MAX_MOVES; i++ ) {\r
3904             pvInfoList[i].depth = 0;\r
3905             epStatus[i]=EP_NONE;\r
3906             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
3907         }\r
3908 \r
3909         initialRulePlies = 0; /* 50-move counter start */\r
3910 \r
3911         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
3912         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
3913     }\r
3914 \r
3915     \r
3916     /* [HGM] logic here is completely changed. In stead of full positions */\r
3917     /* the initialized data only consist of the two backranks. The switch */\r
3918     /* selects which one we will use, which is than copied to the Board   */\r
3919     /* initialPosition, which for the rest is initialized by Pawns and    */\r
3920     /* empty squares. This initial position is then copied to boards[0],  */\r
3921     /* possibly after shuffling, so that it remains available.            */\r
3922 \r
3923     gameInfo.holdingsWidth = 0; /* default board sizes */\r
3924     gameInfo.boardWidth    = 8;\r
3925     gameInfo.boardHeight   = 8;\r
3926     gameInfo.holdingsSize  = 0;\r
3927     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
3928     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
3929     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
3930 \r
3931     switch (gameInfo.variant) {\r
3932     default:\r
3933       pieces = FIDEArray;\r
3934       break;\r
3935     case VariantShatranj:\r
3936       pieces = ShatranjArray;\r
3937       nrCastlingRights = 0;\r
3938       break;\r
3939     case VariantTwoKings:\r
3940       pieces = twoKingsArray;\r
3941       nrCastlingRights = 8;                 /* add rights for second King */\r
3942       castlingRights[0][6] = initialRights[2] = 5;\r
3943       castlingRights[0][7] = initialRights[5] = 5;\r
3944       castlingRank[6] = 0;\r
3945       castlingRank[7] = BOARD_HEIGHT-1;\r
3946       startedFromSetupPosition = TRUE;\r
3947       break;\r
3948     case VariantCapablanca:\r
3949       pieces = CapablancaArray;\r
3950       gameInfo.boardWidth = 10;\r
3951       SetCharTable(pieceToChar, "PNBRQ.......AC..Kpnbrq.......ac..k"); \r
3952       break;\r
3953     case VariantGothic:\r
3954       pieces = GothicArray;\r
3955       gameInfo.boardWidth = 10;\r
3956       SetCharTable(pieceToChar, "PNBRQ.......AC..Kpnbrq.......ac..k"); \r
3957       break;\r
3958     case VariantXiangqi:\r
3959       pieces = XiangqiArray;\r
3960       gameInfo.boardWidth  = 9;\r
3961       gameInfo.boardHeight = 10;\r
3962       nrCastlingRights = 0;\r
3963       SetCharTable(pieceToChar, "PH.R.AKE.C.......ph.r.ake.c......."); \r
3964       break;\r
3965     case VariantShogi:\r
3966       pieces = ShogiArray;\r
3967       gameInfo.boardWidth  = 9;\r
3968       gameInfo.boardHeight = 9;\r
3969       gameInfo.holdingsSize = 7;\r
3970       nrCastlingRights = 0;\r
3971       SetCharTable(pieceToChar, "PNBRLSG...++++++Kpnbrlsg...++++++k"); \r
3972       break;\r
3973     case VariantShowgi:\r
3974       pieces = ShogiArray;\r
3975       gameInfo.boardWidth  = 9;\r
3976       gameInfo.boardHeight = 9;\r
3977       gameInfo.holdingsSize = 7;\r
3978       nrCastlingRights = 0;\r
3979       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
3980       SetCharTable(pieceToChar, "PNBRQFWEMOUHACG.Kpnbrlsgpnbrls...k"); \r
3981       break;\r
3982     case VariantCourier:\r
3983       pieces = CourierArray;\r
3984       gameInfo.boardWidth  = 12;\r
3985       nrCastlingRights = 0;\r
3986       SetCharTable(pieceToChar, "PNBR.FWEM.......Kpnbr.fwem.......k"); \r
3987       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
3988       break;\r
3989     case VariantKnightmate:\r
3990       pieces = KnightmateArray;\r
3991       SetCharTable(pieceToChar, "P.BRQ...M.K......p.brq...m.k......"); \r
3992       break;\r
3993     case VariantFairy:\r
3994       pieces = fairyArray;\r
3995       SetCharTable(pieceToChar, "PNBRQFWEMOUHACGSKpnbrqfwemouhacgsk"); \r
3996       startedFromSetupPosition = TRUE;\r
3997       break;\r
3998     case VariantCrazyhouse:\r
3999     case VariantBughouse:\r
4000       pieces = FIDEArray;\r
4001       SetCharTable(pieceToChar, "PNBRQ......~~~~.Kpnbrq......~~~~.k"); \r
4002       gameInfo.holdingsSize = 5;\r
4003       break;\r
4004     case VariantWildCastle:\r
4005       pieces = FIDEArray;\r
4006       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4007       break;\r
4008     case VariantNoCastle:\r
4009       pieces = FIDEArray;\r
4010       nrCastlingRights = 0;\r
4011       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4012       /* !!?unconstrained back-rank shuffle */\r
4013       break;\r
4014     }\r
4015 \r
4016     overrule = 0;\r
4017     if(appData.NrFiles >= 0) {\r
4018         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4019         gameInfo.boardWidth = appData.NrFiles;\r
4020     }\r
4021     if(appData.NrRanks >= 0) {\r
4022         gameInfo.boardHeight = appData.NrRanks;\r
4023     }\r
4024     if(appData.holdingsSize >= 0) {\r
4025         i = appData.holdingsSize;\r
4026         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4027         gameInfo.holdingsSize = i;\r
4028     }\r
4029     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4030     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4031         DisplayFatalError("Recompile to support this BOARD_SIZE!", 0, 2);\r
4032 \r
4033     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4034     if(pawnRow < 1) pawnRow = 1;\r
4035 \r
4036     /* User pieceToChar list overrules defaults */\r
4037     if(appData.pieceToCharTable != NULL)\r
4038         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4039 \r
4040     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4041 \r
4042         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4043             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4044         for( i=0; i<BOARD_HEIGHT; i++ )\r
4045             initialPosition[i][j] = s;\r
4046 \r
4047         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4048         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4049         initialPosition[pawnRow][j] = WhitePawn;\r
4050         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4051         if(gameInfo.variant == VariantXiangqi) {\r
4052             if(j&1) {\r
4053                 initialPosition[pawnRow][j] = \r
4054                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4055                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4056                    initialPosition[2][j] = WhiteCannon;\r
4057                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4058                 }\r
4059             }\r
4060         }\r
4061         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4062     }\r
4063     if( (gameInfo.variant == VariantShogi\r
4064        ||gameInfo.variant == VariantShowgi\r
4065                                          ) && !overrule ) {\r
4066             j=BOARD_LEFT+1;\r
4067             initialPosition[1][j] = WhiteBishop;\r
4068             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4069             j=BOARD_RGHT-2;\r
4070             initialPosition[1][j] = WhiteRook;\r
4071             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4072     }\r
4073 \r
4074     if( nrCastlingRights == -1) {\r
4075         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4076         /*       This sets default castling rights from none to normal corners   */\r
4077         /* Variants with other castling rights must set them themselves above    */\r
4078         nrCastlingRights = 6;\r
4079        \r
4080         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4081         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4082         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4083         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4084         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4085         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4086      }\r
4087 \r
4088     if(gameInfo.variant == VariantFischeRandom) {\r
4089       if( appData.defaultFrcPosition < 0 ) {\r
4090         ShuffleFRC( initialPosition );\r
4091       }\r
4092       else {\r
4093         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4094       }\r
4095     }\r
4096 \r
4097     CopyBoard(boards[0], initialPosition);\r
4098 \r
4099     if(oldx != gameInfo.boardWidth ||\r
4100        oldy != gameInfo.boardHeight ||\r
4101        oldh != gameInfo.holdingsWidth\r
4102 #ifdef GOTHIC\r
4103        || oldv == VariantGothic ||\r
4104        gameInfo.variant == VariantGothic\r
4105 #endif\r
4106                                          )\r
4107             InitDrawingSizes(-2 ,0);\r
4108 \r
4109     if (redraw)\r
4110       DrawPosition(TRUE, boards[currentMove]);\r
4111 }\r
4112 \r
4113 void\r
4114 SendBoard(cps, moveNum)\r
4115      ChessProgramState *cps;\r
4116      int moveNum;\r
4117 {\r
4118     char message[MSG_SIZ];\r
4119     \r
4120     if (cps->useSetboard) {\r
4121       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
4122       sprintf(message, "setboard %s\n", fen);\r
4123       SendToProgram(message, cps);\r
4124       free(fen);\r
4125 \r
4126     } else {\r
4127       ChessSquare *bp;\r
4128       int i, j;\r
4129       /* Kludge to set black to move, avoiding the troublesome and now\r
4130        * deprecated "black" command.\r
4131        */\r
4132       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4133 \r
4134       SendToProgram("edit\n", cps);\r
4135       SendToProgram("#\n", cps);\r
4136       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4137         bp = &boards[moveNum][i][0];\r
4138         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4139           if ((int) *bp < (int) BlackPawn) {\r
4140             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4141                     AAA + j, ONE + i);\r
4142             SendToProgram(message, cps);\r
4143           }\r
4144         }\r
4145       }\r
4146     \r
4147       SendToProgram("c\n", cps);\r
4148       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4149         bp = &boards[moveNum][i][0];\r
4150         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4151           if (((int) *bp != (int) EmptySquare)\r
4152               && ((int) *bp >= (int) BlackPawn)) {\r
4153             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4154                     AAA + j, ONE + i);\r
4155             SendToProgram(message, cps);\r
4156           }\r
4157         }\r
4158       }\r
4159     \r
4160       SendToProgram(".\n", cps);\r
4161     }\r
4162 }\r
4163 \r
4164 int\r
4165 IsPromotion(fromX, fromY, toX, toY)\r
4166      int fromX, fromY, toX, toY;\r
4167 {\r
4168     /* [HGM] add Shogi promotions */\r
4169     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4170     ChessSquare piece;\r
4171 \r
4172     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4173       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4174    /* [HGM] Note to self: line above also weeds out drops */\r
4175     piece = boards[currentMove][fromY][fromX];\r
4176     if(gameInfo.variant == VariantShogi) {\r
4177         promotionZoneSize = 3;\r
4178         highestPromotingPiece = (int)WhiteKing;\r
4179         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4180            and if in normal chess we then allow promotion to King, why not\r
4181            allow promotion of other piece in Shogi?                         */\r
4182     }\r
4183     if((int)piece >= BlackPawn) {\r
4184         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4185              return FALSE;\r
4186         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4187     } else {\r
4188         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4189            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4190     }\r
4191     return ( (int)piece <= highestPromotingPiece );\r
4192 }\r
4193 \r
4194 int\r
4195 InPalace(row, column)\r
4196      int row, column;\r
4197 {   /* [HGM] for Xiangqi */\r
4198     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4199          column < (BOARD_WIDTH + 4)/2 &&\r
4200          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4201     return FALSE;\r
4202 }\r
4203 \r
4204 int\r
4205 PieceForSquare (x, y)\r
4206      int x;\r
4207      int y;\r
4208 {\r
4209   if (x < BOARD_LEFT || x >= BOARD_RGHT || y < 0 || y >= BOARD_HEIGHT)\r
4210      return -1;\r
4211   else\r
4212      return boards[currentMove][y][x];\r
4213 }\r
4214 \r
4215 int\r
4216 OKToStartUserMove(x, y)\r
4217      int x, y;\r
4218 {\r
4219     ChessSquare from_piece;\r
4220     int white_piece;\r
4221 \r
4222     if (matchMode) return FALSE;\r
4223     if (gameMode == EditPosition) return TRUE;\r
4224 \r
4225     if (x >= 0 && y >= 0)\r
4226       from_piece = boards[currentMove][y][x];\r
4227     else\r
4228       from_piece = EmptySquare;\r
4229 \r
4230     if (from_piece == EmptySquare) return FALSE;\r
4231 \r
4232     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4233       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4234 \r
4235     switch (gameMode) {\r
4236       case PlayFromGameFile:\r
4237       case AnalyzeFile:\r
4238       case TwoMachinesPlay:\r
4239       case EndOfGame:\r
4240         return FALSE;\r
4241 \r
4242       case IcsObserving:\r
4243       case IcsIdle:\r
4244         return FALSE;\r
4245 \r
4246       case MachinePlaysWhite:\r
4247       case IcsPlayingBlack:\r
4248         if (appData.zippyPlay) return FALSE;\r
4249         if (white_piece) {\r
4250             DisplayMoveError("You are playing Black");\r
4251             return FALSE;\r
4252         }\r
4253         break;\r
4254 \r
4255       case MachinePlaysBlack:\r
4256       case IcsPlayingWhite:\r
4257         if (appData.zippyPlay) return FALSE;\r
4258         if (!white_piece) {\r
4259             DisplayMoveError("You are playing White");\r
4260             return FALSE;\r
4261         }\r
4262         break;\r
4263 \r
4264       case EditGame:\r
4265         if (!white_piece && WhiteOnMove(currentMove)) {\r
4266             DisplayMoveError("It is White's turn");\r
4267             return FALSE;\r
4268         }           \r
4269         if (white_piece && !WhiteOnMove(currentMove)) {\r
4270             DisplayMoveError("It is Black's turn");\r
4271             return FALSE;\r
4272         }           \r
4273         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
4274             /* Editing correspondence game history */\r
4275             /* Could disallow this or prompt for confirmation */\r
4276             cmailOldMove = -1;\r
4277         }\r
4278         if (currentMove < forwardMostMove) {\r
4279             /* Discarding moves */\r
4280             /* Could prompt for confirmation here,\r
4281                but I don't think that's such a good idea */\r
4282             forwardMostMove = currentMove;\r
4283         }\r
4284         break;\r
4285 \r
4286       case BeginningOfGame:\r
4287         if (appData.icsActive) return FALSE;\r
4288         if (!appData.noChessProgram) {\r
4289             if (!white_piece) {\r
4290                 DisplayMoveError("You are playing White");\r
4291                 return FALSE;\r
4292             }\r
4293         }\r
4294         break;\r
4295         \r
4296       case Training:\r
4297         if (!white_piece && WhiteOnMove(currentMove)) {\r
4298             DisplayMoveError("It is White's turn");\r
4299             return FALSE;\r
4300         }           \r
4301         if (white_piece && !WhiteOnMove(currentMove)) {\r
4302             DisplayMoveError("It is Black's turn");\r
4303             return FALSE;\r
4304         }           \r
4305         break;\r
4306 \r
4307       default:\r
4308       case IcsExamining:\r
4309         break;\r
4310     }\r
4311     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
4312         && gameMode != AnalyzeFile && gameMode != Training) {\r
4313         DisplayMoveError("Displayed position is not current");\r
4314         return FALSE;\r
4315     }\r
4316     return TRUE;\r
4317 }\r
4318 \r
4319 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
4320 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
4321 int lastLoadGameUseList = FALSE;\r
4322 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
4323 ChessMove lastLoadGameStart = (ChessMove) 0;\r
4324 \r
4325 \r
4326 ChessMove\r
4327 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
4328      int fromX, fromY, toX, toY;\r
4329      int promoChar;\r
4330 {\r
4331     ChessMove moveType;\r
4332     ChessSquare pdown, pup;\r
4333 \r
4334     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
4335     if ((fromX == toX) && (fromY == toY)) {\r
4336         return ImpossibleMove;\r
4337     }\r
4338 \r
4339     /* [HGM] suppress all moves into holdings area and guard band */\r
4340     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
4341             return ImpossibleMove;\r
4342 \r
4343     /* [HGM] <sameColor> moved to here from winboard.c */\r
4344     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
4345     pdown = boards[currentMove][fromY][fromX];\r
4346     pup = boards[currentMove][toY][toX];\r
4347     if (    gameMode != EditPosition &&\r
4348             (WhitePawn <= pdown && pdown < BlackPawn &&\r
4349              WhitePawn <= pup && pup < BlackPawn  ||\r
4350              BlackPawn <= pdown && pdown < EmptySquare &&\r
4351              BlackPawn <= pup && pup < EmptySquare)      )\r
4352          return ImpossibleMove;\r
4353 \r
4354     /* Check if the user is playing in turn.  This is complicated because we\r
4355        let the user "pick up" a piece before it is his turn.  So the piece he\r
4356        tried to pick up may have been captured by the time he puts it down!\r
4357        Therefore we use the color the user is supposed to be playing in this\r
4358        test, not the color of the piece that is currently on the starting\r
4359        square---except in EditGame mode, where the user is playing both\r
4360        sides; fortunately there the capture race can't happen.  (It can\r
4361        now happen in IcsExamining mode, but that's just too bad.  The user\r
4362        will get a somewhat confusing message in that case.)\r
4363        */\r
4364 \r
4365     switch (gameMode) {\r
4366       case PlayFromGameFile:\r
4367       case AnalyzeFile:\r
4368       case TwoMachinesPlay:\r
4369       case EndOfGame:\r
4370       case IcsObserving:\r
4371       case IcsIdle:\r
4372         /* We switched into a game mode where moves are not accepted,\r
4373            perhaps while the mouse button was down. */\r
4374         return ImpossibleMove;\r
4375 \r
4376       case MachinePlaysWhite:\r
4377         /* User is moving for Black */\r
4378         if (WhiteOnMove(currentMove)) {\r
4379             DisplayMoveError("It is White's turn");\r
4380             return ImpossibleMove;\r
4381         }\r
4382         break;\r
4383 \r
4384       case MachinePlaysBlack:\r
4385         /* User is moving for White */\r
4386         if (!WhiteOnMove(currentMove)) {\r
4387             DisplayMoveError("It is Black's turn");\r
4388             return ImpossibleMove;\r
4389         }\r
4390         break;\r
4391 \r
4392       case EditGame:\r
4393       case IcsExamining:\r
4394       case BeginningOfGame:\r
4395       case AnalyzeMode:\r
4396       case Training:\r
4397         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
4398             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
4399             /* User is moving for Black */\r
4400             if (WhiteOnMove(currentMove)) {\r
4401                 DisplayMoveError("It is White's turn");\r
4402                 return ImpossibleMove;\r
4403             }\r
4404         } else {\r
4405             /* User is moving for White */\r
4406             if (!WhiteOnMove(currentMove)) {\r
4407                 DisplayMoveError("It is Black's turn");\r
4408                 return ImpossibleMove;\r
4409             }\r
4410         }\r
4411         break;\r
4412 \r
4413       case IcsPlayingBlack:\r
4414         /* User is moving for Black */\r
4415         if (WhiteOnMove(currentMove)) {\r
4416             if (!appData.premove) {\r
4417                 DisplayMoveError("It is White's turn");\r
4418             } else if (toX >= 0 && toY >= 0) {\r
4419                 premoveToX = toX;\r
4420                 premoveToY = toY;\r
4421                 premoveFromX = fromX;\r
4422                 premoveFromY = fromY;\r
4423                 premovePromoChar = promoChar;\r
4424                 gotPremove = 1;\r
4425                 if (appData.debugMode) \r
4426                     fprintf(debugFP, "Got premove: fromX %d,"\r
4427                             "fromY %d, toX %d, toY %d\n",\r
4428                             fromX, fromY, toX, toY);\r
4429             }\r
4430             return ImpossibleMove;\r
4431         }\r
4432         break;\r
4433 \r
4434       case IcsPlayingWhite:\r
4435         /* User is moving for White */\r
4436         if (!WhiteOnMove(currentMove)) {\r
4437             if (!appData.premove) {\r
4438                 DisplayMoveError("It is Black's turn");\r
4439             } else if (toX >= 0 && toY >= 0) {\r
4440                 premoveToX = toX;\r
4441                 premoveToY = toY;\r
4442                 premoveFromX = fromX;\r
4443                 premoveFromY = fromY;\r
4444                 premovePromoChar = promoChar;\r
4445                 gotPremove = 1;\r
4446                 if (appData.debugMode) \r
4447                     fprintf(debugFP, "Got premove: fromX %d,"\r
4448                             "fromY %d, toX %d, toY %d\n",\r
4449                             fromX, fromY, toX, toY);\r
4450             }\r
4451             return ImpossibleMove;\r
4452         }\r
4453         break;\r
4454 \r
4455       default:\r
4456         break;\r
4457 \r
4458       case EditPosition:\r
4459         /* EditPosition, empty square, or different color piece;\r
4460            click-click move is possible */\r
4461         if (toX == -2 || toY == -2) {\r
4462             boards[0][fromY][fromX] = EmptySquare;\r
4463             DrawPosition(FALSE, boards[currentMove]);\r
4464         } else if (toX >= 0 && toY >= 0) {\r
4465             boards[0][toY][toX] = boards[0][fromY][fromX];\r
4466             boards[0][fromY][fromX] = EmptySquare;\r
4467             DrawPosition(FALSE, boards[currentMove]);\r
4468         }\r
4469         return ImpossibleMove;\r
4470     }\r
4471 \r
4472     /* [HGM] If move started in holdings, it means a drop */\r
4473     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
4474          if( pup != EmptySquare ) return ImpossibleMove;\r
4475          if(appData.testLegality) {\r
4476              /* it would be more logical if LegalityTest() also figured out\r
4477               * which drops are legal. For now we forbid pawns on back rank.\r
4478               * Shogi is on its own here...\r
4479               */\r
4480              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
4481                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
4482                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
4483          }\r
4484          return WhiteDrop; /* Not needed to specify white or black yet */\r
4485     }\r
4486 \r
4487     userOfferedDraw = FALSE;\r
4488         \r
4489     /* [HGM] always test for legality, to get promotion info */\r
4490     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
4491                           epStatus[currentMove], castlingRights[currentMove],\r
4492                                          fromY, fromX, toY, toX, promoChar);\r
4493 \r
4494     /* [HGM] but possibly ignore an IllegalMove result */\r
4495     if (appData.testLegality) {\r
4496         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
4497             DisplayMoveError("Illegal move");\r
4498             return ImpossibleMove;\r
4499         }\r
4500     }\r
4501 \r
4502     return moveType;\r
4503     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
4504        function is made into one that returns an OK move type if FinishMove\r
4505        should be called. This to give the calling driver routine the\r
4506        opportunity to finish the userMove input with a promotion popup,\r
4507        without bothering the user with this for invalid or illegal moves */\r
4508 \r
4509 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
4510 }\r
4511 \r
4512 /* Common tail of UserMoveEvent and DropMenuEvent */\r
4513 void\r
4514 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
4515      ChessMove moveType;\r
4516      int fromX, fromY, toX, toY;\r
4517      /*char*/int promoChar;\r
4518 {\r
4519     /* [HGM] <popupFix> kludge to avoid having know the exact promotion\r
4520        move type in caller when we know the move is a legal promotion */\r
4521     if(moveType == NormalMove)\r
4522         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
4523 \r
4524     /* [HGM] convert drag-and-drop piece drops to standard form */\r
4525     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
4526          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
4527          fromX = boards[currentMove][fromY][fromX];\r
4528          fromY = DROP_RANK;\r
4529     }\r
4530 \r
4531     /* [HGM] <popupFix> The following if has been moved here from\r
4532        UserMoveEnevt(). Because it seemed to belon here (why not allow\r
4533        piece drops in training games?), and because it can only be\r
4534        performed after it is known to what we promote. */\r
4535     if (gameMode == Training) {\r
4536       /* compare the move played on the board to the next move in the\r
4537        * game. If they match, display the move and the opponent's response. \r
4538        * If they don't match, display an error message.\r
4539        */\r
4540       int saveAnimate;\r
4541       Board testBoard;\r
4542       CopyBoard(testBoard, boards[currentMove]);\r
4543       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
4544 \r
4545       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
4546         ForwardInner(currentMove+1);\r
4547 \r
4548         /* Autoplay the opponent's response.\r
4549          * if appData.animate was TRUE when Training mode was entered,\r
4550          * the response will be animated.\r
4551          */\r
4552         saveAnimate = appData.animate;\r
4553         appData.animate = animateTraining;\r
4554         ForwardInner(currentMove+1);\r
4555         appData.animate = saveAnimate;\r
4556 \r
4557         /* check for the end of the game */\r
4558         if (currentMove >= forwardMostMove) {\r
4559           gameMode = PlayFromGameFile;\r
4560           ModeHighlight();\r
4561           SetTrainingModeOff();\r
4562           DisplayInformation("End of game");\r
4563         }\r
4564       } else {\r
4565         DisplayError("Incorrect move", 0);\r
4566       }\r
4567       return;\r
4568     }\r
4569 \r
4570   /* Ok, now we know that the move is good, so we can kill\r
4571      the previous line in Analysis Mode */\r
4572   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
4573     forwardMostMove = currentMove;\r
4574   }\r
4575 \r
4576   /* If we need the chess program but it's dead, restart it */\r
4577   ResurrectChessProgram();\r
4578 \r
4579   /* A user move restarts a paused game*/\r
4580   if (pausing)\r
4581     PauseEvent();\r
4582 \r
4583   thinkOutput[0] = NULLCHAR;\r
4584 \r
4585   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
4586 \r
4587   if (gameMode == BeginningOfGame) {\r
4588     if (appData.noChessProgram) {\r
4589       gameMode = EditGame;\r
4590       SetGameInfo();\r
4591     } else {\r
4592       char buf[MSG_SIZ];\r
4593       gameMode = MachinePlaysBlack;\r
4594       SetGameInfo();\r
4595       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
4596       DisplayTitle(buf);\r
4597       if (first.sendName) {\r
4598         sprintf(buf, "name %s\n", gameInfo.white);\r
4599         SendToProgram(buf, &first);\r
4600       }\r
4601     }\r
4602     ModeHighlight();\r
4603   }\r
4604 \r
4605   /* Relay move to ICS or chess engine */\r
4606   if (appData.icsActive) {\r
4607     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
4608         gameMode == IcsExamining) {\r
4609       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
4610       ics_user_moved = 1;\r
4611     }\r
4612   } else {\r
4613     if (first.sendTime && (gameMode == BeginningOfGame ||\r
4614                            gameMode == MachinePlaysWhite ||\r
4615                            gameMode == MachinePlaysBlack)) {\r
4616       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
4617     }\r
4618     SendMoveToProgram(forwardMostMove-1, &first);\r
4619     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
4620       first.maybeThinking = TRUE;\r
4621     }\r
4622     if (currentMove == cmailOldMove + 1) {\r
4623       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
4624     }\r
4625   }\r
4626 \r
4627   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4628 \r
4629   switch (gameMode) {\r
4630   case EditGame:\r
4631     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
4632                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
4633     case MT_NONE:\r
4634     case MT_CHECK:\r
4635       break;\r
4636     case MT_CHECKMATE:\r
4637       if (WhiteOnMove(currentMove)) {\r
4638         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
4639       } else {\r
4640         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
4641       }\r
4642       break;\r
4643     case MT_STALEMATE:\r
4644       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
4645       break;\r
4646     }\r
4647     break;\r
4648     \r
4649   case MachinePlaysBlack:\r
4650   case MachinePlaysWhite:\r
4651     /* disable certain menu options while machine is thinking */\r
4652     SetMachineThinkingEnables();\r
4653     break;\r
4654 \r
4655   default:\r
4656     break;\r
4657   }\r
4658 }\r
4659 \r
4660 void\r
4661 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
4662      int fromX, fromY, toX, toY;\r
4663      int promoChar;\r
4664 {\r
4665     /* [HGM] This routine was added to allow calling of its two logical\r
4666        parts from other modules in the old way. Before, UserMoveEvent()\r
4667        automatically called FinishMove() if the move was OK, and returned\r
4668        otherwise. I separated the two, in order to make it possible to\r
4669        slip a promotion popup in between. But that it always needs two\r
4670        calls, to the first part, (now called UserMoveTest() ), and to\r
4671        FinishMove if the first part succeeded. Calls that do not need\r
4672        to do anything in between, can call this routine the old way. \r
4673     */\r
4674     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
4675 \r
4676     if(moveType != ImpossibleMove)\r
4677         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
4678 }\r
4679 \r
4680 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
4681 {\r
4682     char * hint = lastHint;\r
4683     FrontEndProgramStats stats;\r
4684 \r
4685     stats.which = cps == &first ? 0 : 1;\r
4686     stats.depth = cpstats->depth;\r
4687     stats.nodes = cpstats->nodes;\r
4688     stats.score = cpstats->score;\r
4689     stats.time = cpstats->time;\r
4690     stats.pv = cpstats->movelist;\r
4691     stats.hint = lastHint;\r
4692     stats.an_move_index = 0;\r
4693     stats.an_move_count = 0;\r
4694 \r
4695     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
4696         stats.hint = cpstats->move_name;\r
4697         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
4698         stats.an_move_count = cpstats->nr_moves;\r
4699     }\r
4700 \r
4701     SetProgramStats( &stats );\r
4702 }\r
4703 \r
4704 void\r
4705 HandleMachineMove(message, cps)\r
4706      char *message;\r
4707      ChessProgramState *cps;\r
4708 {\r
4709     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
4710     char realname[MSG_SIZ];\r
4711     int fromX, fromY, toX, toY;\r
4712     ChessMove moveType;\r
4713     char promoChar;\r
4714     char *p;\r
4715     int machineWhite;\r
4716 \r
4717     /*\r
4718      * Kludge to ignore BEL characters\r
4719      */\r
4720     while (*message == '\007') message++;\r
4721 \r
4722     /*\r
4723      * Look for book output\r
4724      */\r
4725     if (cps == &first && bookRequested) {\r
4726         if (message[0] == '\t' || message[0] == ' ') {\r
4727             /* Part of the book output is here; append it */\r
4728             strcat(bookOutput, message);\r
4729             strcat(bookOutput, "  \n");\r
4730             return;\r
4731         } else if (bookOutput[0] != NULLCHAR) {\r
4732             /* All of book output has arrived; display it */\r
4733             char *p = bookOutput;\r
4734             while (*p != NULLCHAR) {\r
4735                 if (*p == '\t') *p = ' ';\r
4736                 p++;\r
4737             }\r
4738             DisplayInformation(bookOutput);\r
4739             bookRequested = FALSE;\r
4740             /* Fall through to parse the current output */\r
4741         }\r
4742     }\r
4743 \r
4744     /*\r
4745      * Look for machine move.\r
4746      */\r
4747     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
4748         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
4749     {\r
4750         /* This method is only useful on engines that support ping */\r
4751         if (cps->lastPing != cps->lastPong) {\r
4752           if (gameMode == BeginningOfGame) {\r
4753             /* Extra move from before last new; ignore */\r
4754             if (appData.debugMode) {\r
4755                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
4756             }\r
4757           } else {\r
4758             if (appData.debugMode) {\r
4759                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
4760                         cps->which, gameMode);\r
4761             }\r
4762 \r
4763             SendToProgram("undo\n", cps);\r
4764           }\r
4765           return;\r
4766         }\r
4767 \r
4768         switch (gameMode) {\r
4769           case BeginningOfGame:\r
4770             /* Extra move from before last reset; ignore */\r
4771             if (appData.debugMode) {\r
4772                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
4773             }\r
4774             return;\r
4775 \r
4776           case EndOfGame:\r
4777           case IcsIdle:\r
4778           default:\r
4779             /* Extra move after we tried to stop.  The mode test is\r
4780                not a reliable way of detecting this problem, but it's\r
4781                the best we can do on engines that don't support ping.\r
4782             */\r
4783             if (appData.debugMode) {\r
4784                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
4785                         cps->which, gameMode);\r
4786             }\r
4787             SendToProgram("undo\n", cps);\r
4788             return;\r
4789 \r
4790           case MachinePlaysWhite:\r
4791           case IcsPlayingWhite:\r
4792             machineWhite = TRUE;\r
4793             break;\r
4794 \r
4795           case MachinePlaysBlack:\r
4796           case IcsPlayingBlack:\r
4797             machineWhite = FALSE;\r
4798             break;\r
4799 \r
4800           case TwoMachinesPlay:\r
4801             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
4802             break;\r
4803         }\r
4804         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
4805             if (appData.debugMode) {\r
4806                 fprintf(debugFP,\r
4807                         "Ignoring move out of turn by %s, gameMode %d"\r
4808                         ", forwardMost %d\n",\r
4809                         cps->which, gameMode, forwardMostMove);\r
4810             }\r
4811             return;\r
4812         }\r
4813 \r
4814         AlphaRank(machineMove, 4);\r
4815         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
4816                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
4817             /* Machine move could not be parsed; ignore it. */\r
4818             sprintf(buf1, "Illegal move \"%s\" from %s machine",\r
4819                     machineMove, cps->which);\r
4820             DisplayError(buf1, 0);\r
4821             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",\r
4822                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
4823             if (gameMode == TwoMachinesPlay) {\r
4824               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
4825                        buf1, GE_XBOARD);\r
4826             }\r
4827             return;\r
4828         }\r
4829 \r
4830         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
4831         /* So we have to redo legality test with true e.p. status here,  */\r
4832         /* to make sure an illegal e.p. capture does not slip through,   */\r
4833         /* to cause a forfeit on a justified illegal-move complaint      */\r
4834         /* of the opponent.                                              */\r
4835         if( gameMode==TwoMachinesPlay && appData.testLegality\r
4836             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
4837                                                               ) {\r
4838            ChessMove moveType;\r
4839            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
4840                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
4841                              fromY, fromX, toY, toX, promoChar);\r
4842             if (appData.debugMode) {\r
4843                 int i;\r
4844                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
4845                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
4846                 fprintf(debugFP, "castling rights\n");\r
4847             }\r
4848             if(moveType == IllegalMove) {\r
4849                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
4850                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
4851                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
4852                            buf1, GE_XBOARD);\r
4853            } else if(gameInfo.variant != VariantFischeRandom)\r
4854            /* [HGM] Kludge to handle engines that send FRC-style castling\r
4855               when they shouldn't (like TSCP-Gothic) */\r
4856            switch(moveType) {\r
4857              case WhiteASideCastleFR:\r
4858              case BlackASideCastleFR:\r
4859                toY++;\r
4860                currentMoveString[2]++;\r
4861                break;\r
4862              case WhiteHSideCastleFR:\r
4863              case BlackHSideCastleFR:\r
4864                toY--;\r
4865                currentMoveString[2]--;\r
4866                break;\r
4867            }\r
4868         }\r
4869         hintRequested = FALSE;\r
4870         lastHint[0] = NULLCHAR;\r
4871         bookRequested = FALSE;\r
4872         /* Program may be pondering now */\r
4873         cps->maybeThinking = TRUE;\r
4874         if (cps->sendTime == 2) cps->sendTime = 1;\r
4875         if (cps->offeredDraw) cps->offeredDraw--;\r
4876 \r
4877 #if ZIPPY\r
4878         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
4879             first.initDone) {\r
4880           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
4881           ics_user_moved = 1;\r
4882         }\r
4883 #endif\r
4884         /* currentMoveString is set as a side-effect of ParseOneMove */\r
4885         strcpy(machineMove, currentMoveString);\r
4886         strcat(machineMove, "\n");\r
4887         strcpy(moveList[forwardMostMove], machineMove);\r
4888 \r
4889         /* [AS] Save move info and clear stats for next move */\r
4890         pvInfoList[ forwardMostMove ].score = programStats.score;\r
4891         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
4892         pvInfoList[ forwardMostMove ].time = -1;\r
4893         ClearProgramStats();\r
4894         thinkOutput[0] = NULLCHAR;\r
4895         hiddenThinkOutputState = 0;\r
4896 \r
4897         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
4898 \r
4899         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
4900         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
4901             int count = 0;\r
4902 \r
4903             while( count < adjudicateLossPlies ) {\r
4904                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
4905 \r
4906                 if( count & 1 ) {\r
4907                     score = -score; /* Flip score for winning side */\r
4908                 }\r
4909 \r
4910                 if( score > adjudicateLossThreshold ) {\r
4911                     break;\r
4912                 }\r
4913 \r
4914                 count++;\r
4915             }\r
4916 \r
4917             if( count >= adjudicateLossPlies ) {\r
4918                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4919 \r
4920                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
4921                     "Xboard adjudication", \r
4922                     GE_XBOARD );\r
4923 \r
4924                 return;\r
4925             }\r
4926         }\r
4927 \r
4928 #ifdef ADJUDICATE // [HGM] some adjudications useful with buggy engines\r
4929 \r
4930         if( gameMode == TwoMachinesPlay && gameInfo.holdingsSize == 0) {\r
4931             int count = 0, epFile = epStatus[forwardMostMove];\r
4932 \r
4933             if(appData.testLegality && appData.checkMates) \r
4934             // don't wait for engine to announce game end if we can judge ourselves\r
4935             switch (MateTest(boards[forwardMostMove],\r
4936                                  PosFlags(forwardMostMove), epFile,\r
4937                                        castlingRights[forwardMostMove]) ) {\r
4938               case MT_NONE:\r
4939               case MT_CHECK:\r
4940               default:\r
4941                 break;\r
4942               case MT_STALEMATE:\r
4943                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4944                 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
4945                     GE_XBOARD );\r
4946                 break;\r
4947               case MT_CHECKMATE:\r
4948                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4949                 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
4950                     "Xboard adjudication: Checkmate", \r
4951                     GE_XBOARD );\r
4952                 break;\r
4953             }\r
4954 \r
4955             if( appData.testLegality )\r
4956             {   /* [HGM] Some more adjudications for obstinate engines */\r
4957                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
4958                     NrWQ=0, NrBQ=0,\r
4959                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;\r
4960                 static int moveCount;\r
4961 \r
4962                 /* First absolutely insufficient mating material. Count what is on board. */\r
4963                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
4964                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
4965                     int m=i;\r
4966 \r
4967                     switch((int) p)\r
4968                     {   /* count B,N,R and other of each side */\r
4969                         case WhiteKnight:\r
4970                              NrWN++; break;\r
4971                         case WhiteBishop:\r
4972                              NrWB++; break;\r
4973                         case BlackKnight:\r
4974                              NrWN++; break;\r
4975                         case BlackBishop:\r
4976                              NrBB++; break;\r
4977                         case WhiteRook:\r
4978                              NrWR++; break;\r
4979                         case BlackRook:\r
4980                              NrBR++; break;\r
4981                         case WhiteQueen:\r
4982                              NrWR++; break;\r
4983                         case BlackQueen:\r
4984                              NrBR++; break;\r
4985                         case EmptySquare: \r
4986                              break;\r
4987                         case BlackPawn:\r
4988                              m = 7-i;\r
4989                         case WhitePawn:\r
4990                              PawnAdvance += m; NrPawns++;\r
4991                     }\r
4992                     NrPieces += (p != EmptySquare);\r
4993                 }\r
4994 \r
4995                 if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2 )\r
4996                 {    /* KBK, KNK or KK */\r
4997 \r
4998                      /* always flag draws, for judging claims */\r
4999                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5000 \r
5001                      if(appData.materialDraws) {\r
5002                          /* but only adjudicate them if adjudication enabled */\r
5003                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5004                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5005                          return;\r
5006                      }\r
5007                 }\r
5008 \r
5009                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5010                 if(NrPieces == 4 && \r
5011                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5012                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5013                    || NrWN==2 || NrBN==2     /* KNNK */\r
5014                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5015                   ) ) {\r
5016                      if(--moveCount < 0 && appData.trivialDraws)\r
5017                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5018                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5019                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5020                           return;\r
5021                      }\r
5022                 } else moveCount = 6;\r
5023 \r
5024     if (appData.debugMode) { int i;\r
5025       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5026               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5027               appData.drawRepeats);\r
5028       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5029            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5030 \r
5031     }\r
5032                 /* Check for rep-draws */\r
5033                 count = 0;\r
5034                 for(k = forwardMostMove-2;\r
5035                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5036                         epStatus[k] < EP_UNKNOWN &&\r
5037                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5038                     k-=2)\r
5039                 {   int rights=0;\r
5040     if (appData.debugMode) {\r
5041       fprintf(debugFP, " loop\n");\r
5042     }\r
5043                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5044     if (appData.debugMode) {\r
5045       fprintf(debugFP, "match\n");\r
5046     }\r
5047                         /* compare castling rights */\r
5048                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5049                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5050                                 rights++; /* King lost rights, while rook still had them */\r
5051                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5052                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5053                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5054                                    rights++; /* but at least one rook lost them */\r
5055                         }\r
5056                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5057                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5058                                 rights++; \r
5059                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5060                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5061                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5062                                    rights++;\r
5063                         }\r
5064     if (appData.debugMode) {\r
5065       for(i=0; i<nrCastlingRights; i++)\r
5066       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5067     }\r
5068 \r
5069     if (appData.debugMode) {\r
5070       fprintf(debugFP, " %d %d\n", rights, k);\r
5071     }\r
5072                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5073                             && appData.drawRepeats > 1) {\r
5074                              /* adjudicate after user-specified nr of repeats */\r
5075                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5076                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
5077                              return;\r
5078                         }\r
5079                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
5080                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
5081                     }\r
5082                 }\r
5083 \r
5084                 /* Now we test for 50-move draws. Determine ply count */\r
5085                 count = forwardMostMove;\r
5086                 /* look for last irreversble move */\r
5087                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
5088                     count--;\r
5089                 /* if we hit starting position, add initial plies */\r
5090                 if( count == backwardMostMove )\r
5091                     count -= initialRulePlies;\r
5092                 count = forwardMostMove - count; \r
5093                 if( count >= 100)\r
5094                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
5095                          /* this is used to judge if draw claims are legal */\r
5096                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
5097                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5098                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
5099                          return;\r
5100                  }\r
5101             }\r
5102 \r
5103 \r
5104         }\r
5105 #endif\r
5106         if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
5107             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5108 \r
5109             GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
5110 \r
5111             return;\r
5112         }\r
5113 \r
5114         if (gameMode == TwoMachinesPlay) {\r
5115             if (cps->other->sendTime) {\r
5116                 SendTimeRemaining(cps->other,\r
5117                                   cps->other->twoMachinesColor[0] == 'w');\r
5118             }\r
5119             SendMoveToProgram(forwardMostMove-1, cps->other);\r
5120             if (firstMove) {\r
5121                 firstMove = FALSE;\r
5122                 if (cps->other->useColors) {\r
5123                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
5124                 }\r
5125                 SendToProgram("go\n", cps->other);\r
5126             }\r
5127             cps->other->maybeThinking = TRUE;\r
5128         }\r
5129 \r
5130         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5131         \r
5132         if (!pausing && appData.ringBellAfterMoves) {\r
5133             RingBell();\r
5134         }\r
5135 \r
5136         /* \r
5137          * Reenable menu items that were disabled while\r
5138          * machine was thinking\r
5139          */\r
5140         if (gameMode != TwoMachinesPlay)\r
5141             SetUserThinkingEnables();\r
5142 \r
5143         return;\r
5144     }\r
5145 \r
5146     /* Set special modes for chess engines.  Later something general\r
5147      *  could be added here; for now there is just one kludge feature,\r
5148      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
5149      *  when "xboard" is given as an interactive command.\r
5150      */\r
5151     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
5152         cps->useSigint = FALSE;\r
5153         cps->useSigterm = FALSE;\r
5154     }\r
5155 \r
5156     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
5157      * want this, I was asked to put it in, and obliged.\r
5158      */\r
5159     if (!strncmp(message, "setboard ", 9)) {\r
5160         Board initial_position; int i;\r
5161 \r
5162         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
5163 \r
5164         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
5165             DisplayError("Bad FEN received from engine", 0);\r
5166             return ;\r
5167         } else {\r
5168            Reset(FALSE, FALSE);\r
5169            CopyBoard(boards[0], initial_position);\r
5170            initialRulePlies = FENrulePlies;\r
5171            epStatus[0] = FENepStatus;\r
5172            for( i=0; i<nrCastlingRights; i++ )\r
5173                 castlingRights[0][i] = FENcastlingRights[i];\r
5174            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
5175            else gameMode = MachinePlaysBlack;                 \r
5176            DrawPosition(FALSE, boards[currentMove]);\r
5177         }\r
5178         return;\r
5179     }\r
5180 \r
5181     /*\r
5182      * Look for communication commands\r
5183      */\r
5184     if (!strncmp(message, "telluser ", 9)) {\r
5185         DisplayNote(message + 9);\r
5186         return;\r
5187     }\r
5188     if (!strncmp(message, "tellusererror ", 14)) {\r
5189         DisplayError(message + 14, 0);\r
5190         return;\r
5191     }\r
5192     if (!strncmp(message, "tellopponent ", 13)) {\r
5193       if (appData.icsActive) {\r
5194         if (loggedOn) {\r
5195           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
5196           SendToICS(buf1);\r
5197         }\r
5198       } else {\r
5199         DisplayNote(message + 13);\r
5200       }\r
5201       return;\r
5202     }\r
5203     if (!strncmp(message, "tellothers ", 11)) {\r
5204       if (appData.icsActive) {\r
5205         if (loggedOn) {\r
5206           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
5207           SendToICS(buf1);\r
5208         }\r
5209       }\r
5210       return;\r
5211     }\r
5212     if (!strncmp(message, "tellall ", 8)) {\r
5213       if (appData.icsActive) {\r
5214         if (loggedOn) {\r
5215           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
5216           SendToICS(buf1);\r
5217         }\r
5218       } else {\r
5219         DisplayNote(message + 8);\r
5220       }\r
5221       return;\r
5222     }\r
5223     if (strncmp(message, "warning", 7) == 0) {\r
5224         /* Undocumented feature, use tellusererror in new code */\r
5225         DisplayError(message, 0);\r
5226         return;\r
5227     }\r
5228     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
5229         strcpy(realname, cps->tidy);\r
5230         strcat(realname, " query");\r
5231         AskQuestion(realname, buf2, buf1, cps->pr);\r
5232         return;\r
5233     }\r
5234     /* Commands from the engine directly to ICS.  We don't allow these to be \r
5235      *  sent until we are logged on. Crafty kibitzes have been known to \r
5236      *  interfere with the login process.\r
5237      */\r
5238     if (loggedOn) {\r
5239         if (!strncmp(message, "tellics ", 8)) {\r
5240             SendToICS(message + 8);\r
5241             SendToICS("\n");\r
5242             return;\r
5243         }\r
5244         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
5245             SendToICS(ics_prefix);\r
5246             SendToICS(message + 15);\r
5247             SendToICS("\n");\r
5248             return;\r
5249         }\r
5250         /* The following are for backward compatibility only */\r
5251         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
5252             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
5253             SendToICS(ics_prefix);\r
5254             SendToICS(message);\r
5255             SendToICS("\n");\r
5256             return;\r
5257         }\r
5258     }\r
5259     if (strncmp(message, "feature ", 8) == 0) {\r
5260       ParseFeatures(message+8, cps);\r
5261     }\r
5262     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
5263       return;\r
5264     }\r
5265     /*\r
5266      * If the move is illegal, cancel it and redraw the board.\r
5267      * Also deal with other error cases.  Matching is rather loose\r
5268      * here to accommodate engines written before the spec.\r
5269      */\r
5270     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
5271         strncmp(message, "Error", 5) == 0) {\r
5272         if (StrStr(message, "name") || \r
5273             StrStr(message, "rating") || StrStr(message, "?") ||\r
5274             StrStr(message, "result") || StrStr(message, "board") ||\r
5275             StrStr(message, "bk") || StrStr(message, "computer") ||\r
5276             StrStr(message, "variant") || StrStr(message, "hint") ||\r
5277             StrStr(message, "random") || StrStr(message, "depth") ||\r
5278             StrStr(message, "accepted")) {\r
5279             return;\r
5280         }\r
5281         if (StrStr(message, "protover")) {\r
5282           /* Program is responding to input, so it's apparently done\r
5283              initializing, and this error message indicates it is\r
5284              protocol version 1.  So we don't need to wait any longer\r
5285              for it to initialize and send feature commands. */\r
5286           FeatureDone(cps, 1);\r
5287           cps->protocolVersion = 1;\r
5288           return;\r
5289         }\r
5290         cps->maybeThinking = FALSE;\r
5291 \r
5292         if (StrStr(message, "draw")) {\r
5293             /* Program doesn't have "draw" command */\r
5294             cps->sendDrawOffers = 0;\r
5295             return;\r
5296         }\r
5297         if (cps->sendTime != 1 &&\r
5298             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
5299           /* Program apparently doesn't have "time" or "otim" command */\r
5300           cps->sendTime = 0;\r
5301           return;\r
5302         }\r
5303         if (StrStr(message, "analyze")) {\r
5304             cps->analysisSupport = FALSE;\r
5305             cps->analyzing = FALSE;\r
5306             Reset(FALSE, TRUE);\r
5307             sprintf(buf2, "%s does not support analysis", cps->tidy);\r
5308             DisplayError(buf2, 0);\r
5309             return;\r
5310         }\r
5311         if (StrStr(message, "(no matching move)st")) {\r
5312           /* Special kludge for GNU Chess 4 only */\r
5313           cps->stKludge = TRUE;\r
5314           SendTimeControl(cps, movesPerSession, timeControl,\r
5315                           timeIncrement, appData.searchDepth,\r
5316                           searchTime);\r
5317           return;\r
5318         }\r
5319         if (StrStr(message, "(no matching move)sd")) {\r
5320           /* Special kludge for GNU Chess 4 only */\r
5321           cps->sdKludge = TRUE;\r
5322           SendTimeControl(cps, movesPerSession, timeControl,\r
5323                           timeIncrement, appData.searchDepth,\r
5324                           searchTime);\r
5325           return;\r
5326         }\r
5327         if (!StrStr(message, "llegal")) {\r
5328             return;\r
5329         }\r
5330         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
5331             gameMode == IcsIdle) return;\r
5332         if (forwardMostMove <= backwardMostMove) return;\r
5333 #if 0\r
5334         /* Following removed: it caused a bug where a real illegal move\r
5335            message in analyze mored would be ignored. */\r
5336         if (cps == &first && programStats.ok_to_send == 0) {\r
5337             /* Bogus message from Crafty responding to "."  This filtering\r
5338                can miss some of the bad messages, but fortunately the bug \r
5339                is fixed in current Crafty versions, so it doesn't matter. */\r
5340             return;\r
5341         }\r
5342 #endif\r
5343         if (pausing) PauseEvent();\r
5344         if (gameMode == PlayFromGameFile) {\r
5345             /* Stop reading this game file */\r
5346             gameMode = EditGame;\r
5347             ModeHighlight();\r
5348         }\r
5349         currentMove = --forwardMostMove;\r
5350         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
5351         SwitchClocks();\r
5352         DisplayBothClocks();\r
5353         sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",\r
5354                 parseList[currentMove], cps->which);\r
5355         DisplayMoveError(buf1);\r
5356         DrawPosition(FALSE, boards[currentMove]);\r
5357 \r
5358         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
5359         /* only passes fully legal moves                            */\r
5360         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
5361             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
5362                                 "False illegal-move claim", GE_XBOARD );\r
5363         }\r
5364         return;\r
5365     }\r
5366     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
5367         /* Program has a broken "time" command that\r
5368            outputs a string not ending in newline.\r
5369            Don't use it. */\r
5370         cps->sendTime = 0;\r
5371     }\r
5372     \r
5373     /*\r
5374      * If chess program startup fails, exit with an error message.\r
5375      * Attempts to recover here are futile.\r
5376      */\r
5377     if ((StrStr(message, "unknown host") != NULL)\r
5378         || (StrStr(message, "No remote directory") != NULL)\r
5379         || (StrStr(message, "not found") != NULL)\r
5380         || (StrStr(message, "No such file") != NULL)\r
5381         || (StrStr(message, "can't alloc") != NULL)\r
5382         || (StrStr(message, "Permission denied") != NULL)) {\r
5383 \r
5384         cps->maybeThinking = FALSE;\r
5385         sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",\r
5386                 cps->which, cps->program, cps->host, message);\r
5387         RemoveInputSource(cps->isr);\r
5388         DisplayFatalError(buf1, 0, 1);\r
5389         return;\r
5390     }\r
5391     \r
5392     /* \r
5393      * Look for hint output\r
5394      */\r
5395     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
5396         if (cps == &first && hintRequested) {\r
5397             hintRequested = FALSE;\r
5398             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
5399                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5400                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
5401                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
5402                                     fromY, fromX, toY, toX, promoChar, buf1);\r
5403                 sprintf(buf2, "Hint: %s", buf1);\r
5404                 DisplayInformation(buf2);\r
5405             } else {\r
5406                 /* Hint move could not be parsed!? */\r
5407                 sprintf(buf2,\r
5408                         "Illegal hint move \"%s\"\nfrom %s chess program",\r
5409                         buf1, cps->which);\r
5410                 DisplayError(buf2, 0);\r
5411             }\r
5412         } else {\r
5413             strcpy(lastHint, buf1);\r
5414         }\r
5415         return;\r
5416     }\r
5417 \r
5418     /*\r
5419      * Ignore other messages if game is not in progress\r
5420      */\r
5421     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
5422         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
5423 \r
5424     /*\r
5425      * look for win, lose, draw, or draw offer\r
5426      */\r
5427     if (strncmp(message, "1-0", 3) == 0) {\r
5428         char *p, *q, *r = "";\r
5429         p = strchr(message, '{');\r
5430         if (p) {\r
5431             q = strchr(p, '}');\r
5432             if (q) {\r
5433                 *q = NULLCHAR;\r
5434                 r = p + 1;\r
5435             }\r
5436         }\r
5437         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
5438         return;\r
5439     } else if (strncmp(message, "0-1", 3) == 0) {\r
5440         char *p, *q, *r = "";\r
5441         p = strchr(message, '{');\r
5442         if (p) {\r
5443             q = strchr(p, '}');\r
5444             if (q) {\r
5445                 *q = NULLCHAR;\r
5446                 r = p + 1;\r
5447             }\r
5448         }\r
5449         /* Kludge for Arasan 4.1 bug */\r
5450         if (strcmp(r, "Black resigns") == 0) {\r
5451             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
5452             return;\r
5453         }\r
5454         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
5455         return;\r
5456     } else if (strncmp(message, "1/2", 3) == 0) {\r
5457         char *p, *q, *r = "";\r
5458         p = strchr(message, '{');\r
5459         if (p) {\r
5460             q = strchr(p, '}');\r
5461             if (q) {\r
5462                 *q = NULLCHAR;\r
5463                 r = p + 1;\r
5464             }\r
5465         }\r
5466             \r
5467         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
5468         return;\r
5469 \r
5470     } else if (strncmp(message, "White resign", 12) == 0) {\r
5471         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
5472         return;\r
5473     } else if (strncmp(message, "Black resign", 12) == 0) {\r
5474         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
5475         return;\r
5476     } else if (strncmp(message, "White matches", 13) == 0 ||\r
5477                strncmp(message, "Black matches", 13) == 0   ) {\r
5478         /* [HGM] ignore GNUShogi noises */\r
5479         return;\r
5480     } else if (strncmp(message, "White", 5) == 0 &&\r
5481                message[5] != '(' &&\r
5482                StrStr(message, "Black") == NULL) {\r
5483         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5484         return;\r
5485     } else if (strncmp(message, "Black", 5) == 0 &&\r
5486                message[5] != '(') {\r
5487         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5488         return;\r
5489     } else if (strcmp(message, "resign") == 0 ||\r
5490                strcmp(message, "computer resigns") == 0) {\r
5491         switch (gameMode) {\r
5492           case MachinePlaysBlack:\r
5493           case IcsPlayingBlack:\r
5494             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
5495             break;\r
5496           case MachinePlaysWhite:\r
5497           case IcsPlayingWhite:\r
5498             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
5499             break;\r
5500           case TwoMachinesPlay:\r
5501             if (cps->twoMachinesColor[0] == 'w')\r
5502               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
5503             else\r
5504               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
5505             break;\r
5506           default:\r
5507             /* can't happen */\r
5508             break;\r
5509         }\r
5510         return;\r
5511     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
5512         switch (gameMode) {\r
5513           case MachinePlaysBlack:\r
5514           case IcsPlayingBlack:\r
5515             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
5516             break;\r
5517           case MachinePlaysWhite:\r
5518           case IcsPlayingWhite:\r
5519             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
5520             break;\r
5521           case TwoMachinesPlay:\r
5522             if (cps->twoMachinesColor[0] == 'w')\r
5523               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5524             else\r
5525               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5526             break;\r
5527           default:\r
5528             /* can't happen */\r
5529             break;\r
5530         }\r
5531         return;\r
5532     } else if (strncmp(message, "computer mates", 14) == 0) {\r
5533         switch (gameMode) {\r
5534           case MachinePlaysBlack:\r
5535           case IcsPlayingBlack:\r
5536             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
5537             break;\r
5538           case MachinePlaysWhite:\r
5539           case IcsPlayingWhite:\r
5540             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
5541             break;\r
5542           case TwoMachinesPlay:\r
5543             if (cps->twoMachinesColor[0] == 'w')\r
5544               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5545             else\r
5546               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5547             break;\r
5548           default:\r
5549             /* can't happen */\r
5550             break;\r
5551         }\r
5552         return;\r
5553     } else if (strncmp(message, "checkmate", 9) == 0) {\r
5554         if (WhiteOnMove(forwardMostMove)) {\r
5555             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5556         } else {\r
5557             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5558         }\r
5559         return;\r
5560     } else if (strstr(message, "Draw") != NULL ||\r
5561                strstr(message, "game is a draw") != NULL) {\r
5562         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
5563         return;\r
5564     } else if (strstr(message, "offer") != NULL &&\r
5565                strstr(message, "draw") != NULL) {\r
5566 #if ZIPPY\r
5567         if (appData.zippyPlay && first.initDone) {\r
5568             /* Relay offer to ICS */\r
5569             SendToICS(ics_prefix);\r
5570             SendToICS("draw\n");\r
5571         }\r
5572 #endif\r
5573         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
5574         if (gameMode == TwoMachinesPlay) {\r
5575             if (cps->other->offeredDraw) {\r
5576                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
5577             } else {\r
5578                 if (cps->other->sendDrawOffers) {\r
5579                     SendToProgram("draw\n", cps->other);\r
5580                 }\r
5581             }\r
5582         } else if (gameMode == MachinePlaysWhite ||\r
5583                    gameMode == MachinePlaysBlack) {\r
5584           if (userOfferedDraw) {\r
5585             DisplayInformation("Machine accepts your draw offer");\r
5586             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
5587           } else {\r
5588             DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");\r
5589           }\r
5590         }\r
5591     }\r
5592 \r
5593     \r
5594     /*\r
5595      * Look for thinking output\r
5596      */\r
5597     if ( appData.showThinking) {\r
5598         int plylev, mvleft, mvtot, curscore, time;\r
5599         char mvname[MOVE_LEN];\r
5600         unsigned long nodes;\r
5601         char plyext;\r
5602         int ignore = FALSE;\r
5603         int prefixHint = FALSE;\r
5604         mvname[0] = NULLCHAR;\r
5605 \r
5606         switch (gameMode) {\r
5607           case MachinePlaysBlack:\r
5608           case IcsPlayingBlack:\r
5609             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
5610             break;\r
5611           case MachinePlaysWhite:\r
5612           case IcsPlayingWhite:\r
5613             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
5614             break;\r
5615           case AnalyzeMode:\r
5616           case AnalyzeFile:\r
5617             break;\r
5618           case TwoMachinesPlay:\r
5619             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
5620                 ignore = TRUE;\r
5621             }\r
5622             break;\r
5623           default:\r
5624             ignore = TRUE;\r
5625             break;\r
5626         }\r
5627 \r
5628         if (!ignore) {\r
5629             buf1[0] = NULLCHAR;\r
5630             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
5631                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
5632 \r
5633                 if (plyext != ' ' && plyext != '\t') {\r
5634                     time *= 100;\r
5635                 }\r
5636 \r
5637                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
5638                 if( cps->scoreIsAbsolute && \r
5639                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
5640                 {\r
5641                     curscore = -curscore;\r
5642                 }\r
5643 \r
5644 \r
5645                 programStats.depth = plylev;\r
5646                 programStats.nodes = nodes;\r
5647                 programStats.time = time;\r
5648                 programStats.score = curscore;\r
5649                 programStats.got_only_move = 0;\r
5650 \r
5651                 /* Buffer overflow protection */\r
5652                 if (buf1[0] != NULLCHAR) {\r
5653                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
5654                         && appData.debugMode) {\r
5655                         fprintf(debugFP,\r
5656                                 "PV is too long; using the first %d bytes.\n",\r
5657                                 sizeof(programStats.movelist) - 1);\r
5658                     }\r
5659 \r
5660                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
5661                 } else {\r
5662                     sprintf(programStats.movelist, " no PV\n");\r
5663                 }\r
5664 \r
5665                 if (programStats.seen_stat) {\r
5666                     programStats.ok_to_send = 1;\r
5667                 }\r
5668 \r
5669                 if (strchr(programStats.movelist, '(') != NULL) {\r
5670                     programStats.line_is_book = 1;\r
5671                     programStats.nr_moves = 0;\r
5672                     programStats.moves_left = 0;\r
5673                 } else {\r
5674                     programStats.line_is_book = 0;\r
5675                 }\r
5676 \r
5677                 SendProgramStatsToFrontend( cps, &programStats );\r
5678 \r
5679                 /* \r
5680                     [AS] Protect the thinkOutput buffer from overflow... this\r
5681                     is only useful if buf1 hasn't overflowed first!\r
5682                 */\r
5683                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
5684                         plylev, \r
5685                         (gameMode == TwoMachinesPlay ?\r
5686                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
5687                         ((double) curscore) / 100.0,\r
5688                         prefixHint ? lastHint : "",\r
5689                         prefixHint ? " " : "" );\r
5690 \r
5691                 if( buf1[0] != NULLCHAR ) {\r
5692                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
5693 \r
5694                     if( strlen(buf1) > max_len ) {\r
5695                         if( appData.debugMode) {\r
5696                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
5697                         }\r
5698                         buf1[max_len+1] = '\0';\r
5699                     }\r
5700 \r
5701                     strcat( thinkOutput, buf1 );\r
5702                 }\r
5703 \r
5704                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
5705                     DisplayMove(currentMove - 1);\r
5706                     DisplayAnalysis();\r
5707                 }\r
5708                 return;\r
5709 \r
5710             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
5711                 /* crafty (9.25+) says "(only move) <move>"\r
5712                  * if there is only 1 legal move\r
5713                  */\r
5714                 sscanf(p, "(only move) %s", buf1);\r
5715                 sprintf(thinkOutput, "%s (only move)", buf1);\r
5716                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
5717                 programStats.depth = 1;\r
5718                 programStats.nr_moves = 1;\r
5719                 programStats.moves_left = 1;\r
5720                 programStats.nodes = 1;\r
5721                 programStats.time = 1;\r
5722                 programStats.got_only_move = 1;\r
5723 \r
5724                 /* Not really, but we also use this member to\r
5725                    mean "line isn't going to change" (Crafty\r
5726                    isn't searching, so stats won't change) */\r
5727                 programStats.line_is_book = 1;\r
5728 \r
5729                 SendProgramStatsToFrontend( cps, &programStats );\r
5730                 \r
5731                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
5732                     DisplayMove(currentMove - 1);\r
5733                     DisplayAnalysis();\r
5734                 }\r
5735                 return;\r
5736             } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",\r
5737                               &time, &nodes, &plylev, &mvleft,\r
5738                               &mvtot, mvname) >= 5) {\r
5739                 /* The stat01: line is from Crafty (9.29+) in response\r
5740                    to the "." command */\r
5741                 programStats.seen_stat = 1;\r
5742                 cps->maybeThinking = TRUE;\r
5743 \r
5744                 if (programStats.got_only_move || !appData.periodicUpdates)\r
5745                   return;\r
5746 \r
5747                 programStats.depth = plylev;\r
5748                 programStats.time = time;\r
5749                 programStats.nodes = nodes;\r
5750                 programStats.moves_left = mvleft;\r
5751                 programStats.nr_moves = mvtot;\r
5752                 strcpy(programStats.move_name, mvname);\r
5753                 programStats.ok_to_send = 1;\r
5754                 programStats.movelist[0] = '\0';\r
5755 \r
5756                 SendProgramStatsToFrontend( cps, &programStats );\r
5757 \r
5758                 DisplayAnalysis();\r
5759                 return;\r
5760 \r
5761             } else if (strncmp(message,"++",2) == 0) {\r
5762                 /* Crafty 9.29+ outputs this */\r
5763                 programStats.got_fail = 2;\r
5764                 return;\r
5765 \r
5766             } else if (strncmp(message,"--",2) == 0) {\r
5767                 /* Crafty 9.29+ outputs this */\r
5768                 programStats.got_fail = 1;\r
5769                 return;\r
5770 \r
5771             } else if (thinkOutput[0] != NULLCHAR &&\r
5772                        strncmp(message, "    ", 4) == 0) {\r
5773                 unsigned message_len;\r
5774 \r
5775                 p = message;\r
5776                 while (*p && *p == ' ') p++;\r
5777 \r
5778                 message_len = strlen( p );\r
5779 \r
5780                 /* [AS] Avoid buffer overflow */\r
5781                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
5782                     strcat(thinkOutput, " ");\r
5783                     strcat(thinkOutput, p);\r
5784                 }\r
5785 \r
5786                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
5787                     strcat(programStats.movelist, " ");\r
5788                     strcat(programStats.movelist, p);\r
5789                 }\r
5790 \r
5791                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
5792                     DisplayMove(currentMove - 1);\r
5793                     DisplayAnalysis();\r
5794                 }\r
5795                 return;\r
5796             }\r
5797         }\r
5798         else {\r
5799             buf1[0] = NULLCHAR;\r
5800 \r
5801             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
5802                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
5803             {\r
5804                 ChessProgramStats cpstats;\r
5805 \r
5806                 if (plyext != ' ' && plyext != '\t') {\r
5807                     time *= 100;\r
5808                 }\r
5809 \r
5810                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
5811                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
5812                     curscore = -curscore;\r
5813                 }\r
5814 \r
5815                 cpstats.depth = plylev;\r
5816                 cpstats.nodes = nodes;\r
5817                 cpstats.time = time;\r
5818                 cpstats.score = curscore;\r
5819                 cpstats.got_only_move = 0;\r
5820                 cpstats.movelist[0] = '\0';\r
5821 \r
5822                 if (buf1[0] != NULLCHAR) {\r
5823                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
5824                 }\r
5825 \r
5826                 cpstats.ok_to_send = 0;\r
5827                 cpstats.line_is_book = 0;\r
5828                 cpstats.nr_moves = 0;\r
5829                 cpstats.moves_left = 0;\r
5830 \r
5831                 SendProgramStatsToFrontend( cps, &cpstats );\r
5832             }\r
5833         }\r
5834     }\r
5835 }\r
5836 \r
5837 \r
5838 /* Parse a game score from the character string "game", and\r
5839    record it as the history of the current game.  The game\r
5840    score is NOT assumed to start from the standard position. \r
5841    The display is not updated in any way.\r
5842    */\r
5843 void\r
5844 ParseGameHistory(game)\r
5845      char *game;\r
5846 {\r
5847     ChessMove moveType;\r
5848     int fromX, fromY, toX, toY, boardIndex;\r
5849     char promoChar;\r
5850     char *p, *q;\r
5851     char buf[MSG_SIZ];\r
5852 \r
5853     if (appData.debugMode)\r
5854       fprintf(debugFP, "Parsing game history: %s\n", game);\r
5855 \r
5856     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
5857     gameInfo.site = StrSave(appData.icsHost);\r
5858     gameInfo.date = PGNDate();\r
5859     gameInfo.round = StrSave("-");\r
5860 \r
5861     /* Parse out names of players */\r
5862     while (*game == ' ') game++;\r
5863     p = buf;\r
5864     while (*game != ' ') *p++ = *game++;\r
5865     *p = NULLCHAR;\r
5866     gameInfo.white = StrSave(buf);\r
5867     while (*game == ' ') game++;\r
5868     p = buf;\r
5869     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
5870     *p = NULLCHAR;\r
5871     gameInfo.black = StrSave(buf);\r
5872 \r
5873     /* Parse moves */\r
5874     boardIndex = blackPlaysFirst ? 1 : 0;\r
5875     yynewstr(game);\r
5876     for (;;) {\r
5877         yyboardindex = boardIndex;\r
5878         moveType = (ChessMove) yylex();\r
5879         switch (moveType) {\r
5880           case IllegalMove:             /* maybe suicide chess, etc. */\r
5881   if (appData.debugMode) {\r
5882     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
5883     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
5884     setbuf(debugFP, NULL);\r
5885   }\r
5886           case WhitePromotionChancellor:\r
5887           case BlackPromotionChancellor:\r
5888           case WhitePromotionArchbishop:\r
5889           case BlackPromotionArchbishop:\r
5890           case WhitePromotionQueen:\r
5891           case BlackPromotionQueen:\r
5892           case WhitePromotionRook:\r
5893           case BlackPromotionRook:\r
5894           case WhitePromotionBishop:\r
5895           case BlackPromotionBishop:\r
5896           case WhitePromotionKnight:\r
5897           case BlackPromotionKnight:\r
5898           case WhitePromotionKing:\r
5899           case BlackPromotionKing:\r
5900           case NormalMove:\r
5901           case WhiteCapturesEnPassant:\r
5902           case BlackCapturesEnPassant:\r
5903           case WhiteKingSideCastle:\r
5904           case WhiteQueenSideCastle:\r
5905           case BlackKingSideCastle:\r
5906           case BlackQueenSideCastle:\r
5907           case WhiteKingSideCastleWild:\r
5908           case WhiteQueenSideCastleWild:\r
5909           case BlackKingSideCastleWild:\r
5910           case BlackQueenSideCastleWild:\r
5911           /* PUSH Fabien */\r
5912           case WhiteHSideCastleFR:\r
5913           case WhiteASideCastleFR:\r
5914           case BlackHSideCastleFR:\r
5915           case BlackASideCastleFR:\r
5916           /* POP Fabien */\r
5917             fromX = currentMoveString[0] - AAA;\r
5918             fromY = currentMoveString[1] - ONE;\r
5919             toX = currentMoveString[2] - AAA;\r
5920             toY = currentMoveString[3] - ONE;\r
5921             promoChar = currentMoveString[4];\r
5922             break;\r
5923           case WhiteDrop:\r
5924           case BlackDrop:\r
5925             fromX = moveType == WhiteDrop ?\r
5926               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
5927             (int) CharToPiece(ToLower(currentMoveString[0]));\r
5928             fromY = DROP_RANK;\r
5929             toX = currentMoveString[2] - AAA;\r
5930             toY = currentMoveString[3] - ONE;\r
5931             promoChar = NULLCHAR;\r
5932             break;\r
5933           case AmbiguousMove:\r
5934             /* bug? */\r
5935             sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);\r
5936   if (appData.debugMode) {\r
5937     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
5938     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
5939     setbuf(debugFP, NULL);\r
5940   }\r
5941             DisplayError(buf, 0);\r
5942             return;\r
5943           case ImpossibleMove:\r
5944             /* bug? */\r
5945             sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);\r
5946   if (appData.debugMode) {\r
5947     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
5948     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
5949     setbuf(debugFP, NULL);\r
5950   }\r
5951             DisplayError(buf, 0);\r
5952             return;\r
5953           case (ChessMove) 0:   /* end of file */\r
5954             if (boardIndex < backwardMostMove) {\r
5955                 /* Oops, gap.  How did that happen? */\r
5956                 DisplayError("Gap in move list", 0);\r
5957                 return;\r
5958             }\r
5959             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
5960             if (boardIndex > forwardMostMove) {\r
5961                 forwardMostMove = boardIndex;\r
5962             }\r
5963             return;\r
5964           case ElapsedTime:\r
5965             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
5966                 strcat(parseList[boardIndex-1], " ");\r
5967                 strcat(parseList[boardIndex-1], yy_text);\r
5968             }\r
5969             continue;\r
5970           case Comment:\r
5971           case PGNTag:\r
5972           case NAG:\r
5973           default:\r
5974             /* ignore */\r
5975             continue;\r
5976           case WhiteWins:\r
5977           case BlackWins:\r
5978           case GameIsDrawn:\r
5979           case GameUnfinished:\r
5980             if (gameMode == IcsExamining) {\r
5981                 if (boardIndex < backwardMostMove) {\r
5982                     /* Oops, gap.  How did that happen? */\r
5983                     return;\r
5984                 }\r
5985                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
5986                 return;\r
5987             }\r
5988             gameInfo.result = moveType;\r
5989             p = strchr(yy_text, '{');\r
5990             if (p == NULL) p = strchr(yy_text, '(');\r
5991             if (p == NULL) {\r
5992                 p = yy_text;\r
5993                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
5994             } else {\r
5995                 q = strchr(p, *p == '{' ? '}' : ')');\r
5996                 if (q != NULL) *q = NULLCHAR;\r
5997                 p++;\r
5998             }\r
5999             gameInfo.resultDetails = StrSave(p);\r
6000             continue;\r
6001         }\r
6002         if (boardIndex >= forwardMostMove &&\r
6003             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
6004             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6005             return;\r
6006         }\r
6007         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
6008                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
6009                                  parseList[boardIndex]);\r
6010         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
6011         /* currentMoveString is set as a side-effect of yylex */\r
6012         strcpy(moveList[boardIndex], currentMoveString);\r
6013         strcat(moveList[boardIndex], "\n");\r
6014         boardIndex++;\r
6015         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
6016         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
6017                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
6018           case MT_NONE:\r
6019           case MT_STALEMATE:\r
6020           default:\r
6021             break;\r
6022           case MT_CHECK:\r
6023             if(gameInfo.variant != VariantShogi)\r
6024                 strcat(parseList[boardIndex - 1], "+");\r
6025             break;\r
6026           case MT_CHECKMATE:\r
6027             strcat(parseList[boardIndex - 1], "#");\r
6028             break;\r
6029         }\r
6030     }\r
6031 }\r
6032 \r
6033 \r
6034 /* Apply a move to the given board  */\r
6035 void\r
6036 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
6037      int fromX, fromY, toX, toY;\r
6038      int promoChar;\r
6039      Board board;\r
6040 {\r
6041   ChessSquare captured = board[toY][toX], piece; int p;\r
6042 \r
6043   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
6044   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
6045        && promoChar != 0) promoChar = 'F';\r
6046          \r
6047   if (fromX == toX && fromY == toY) return;\r
6048 \r
6049   if (fromY == DROP_RANK) {\r
6050         /* must be first */\r
6051         board[toY][toX] = (ChessSquare) fromX;\r
6052   } else {\r
6053      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
6054 \r
6055     /* Code added by Tord: */\r
6056     /* FRC castling assumed when king captures friendly rook. */\r
6057     if (board[fromY][fromX] == WhiteKing &&\r
6058              board[toY][toX] == WhiteRook) {\r
6059       board[fromY][fromX] = EmptySquare;\r
6060       board[toY][toX] = EmptySquare;\r
6061       if(toX > fromX) {\r
6062         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
6063       } else {\r
6064         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
6065       }\r
6066     } else if (board[fromY][fromX] == BlackKing &&\r
6067                board[toY][toX] == BlackRook) {\r
6068       board[fromY][fromX] = EmptySquare;\r
6069       board[toY][toX] = EmptySquare;\r
6070       if(toX > fromX) {\r
6071         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
6072       } else {\r
6073         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
6074       }\r
6075     /* End of code added by Tord */\r
6076 \r
6077     } else if (initialPosition[fromY][fromX] == WhiteKing\r
6078         && board[fromY][fromX] == WhiteKing\r
6079         && toY == fromY && toX > fromX+1) {\r
6080         board[fromY][fromX] = EmptySquare;\r
6081         board[toY][toX] = WhiteKing;\r
6082         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
6083         board[toY][toX-1] = WhiteRook;\r
6084     } else if (initialPosition[fromY][fromX] == WhiteKing\r
6085                && board[fromY][fromX] == WhiteKing\r
6086                && toY == fromY && toX < fromX-1) {\r
6087         board[fromY][fromX] = EmptySquare;\r
6088         board[toY][toX] = WhiteKing;\r
6089         board[fromY][BOARD_LEFT] = EmptySquare;\r
6090         board[toY][toX+1] = WhiteRook;\r
6091     } else if (fromY == 0 && fromX == 3\r
6092                && board[fromY][fromX] == WhiteKing\r
6093                && toY == 0 && toX == 5) {\r
6094         board[fromY][fromX] = EmptySquare;\r
6095         board[toY][toX] = WhiteKing;\r
6096         board[fromY][7] = EmptySquare;\r
6097         board[toY][4] = WhiteRook;\r
6098     } else if (fromY == 0 && fromX == 3\r
6099                && board[fromY][fromX] == WhiteKing\r
6100                && toY == 0 && toX == 1) {\r
6101         board[fromY][fromX] = EmptySquare;\r
6102         board[toY][toX] = WhiteKing;\r
6103         board[fromY][0] = EmptySquare;\r
6104         board[toY][2] = WhiteRook;\r
6105     } else if (board[fromY][fromX] == WhitePawn\r
6106                && toY == BOARD_HEIGHT-1\r
6107                && gameInfo.variant != VariantXiangqi\r
6108                ) {\r
6109         /* white pawn promotion */\r
6110         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
6111         if (board[toY][toX] == EmptySquare) {\r
6112             board[toY][toX] = WhiteQueen;\r
6113         }\r
6114         if(gameInfo.variant==VariantBughouse ||\r
6115            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
6116             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
6117         board[fromY][fromX] = EmptySquare;\r
6118     } else if ((fromY == BOARD_HEIGHT-4)\r
6119                && (toX != fromX)\r
6120                && (board[fromY][fromX] == WhitePawn)\r
6121                && (board[toY][toX] == EmptySquare)) {\r
6122         board[fromY][fromX] = EmptySquare;\r
6123         board[toY][toX] = WhitePawn;\r
6124         captured = board[toY - 1][toX];\r
6125         board[toY - 1][toX] = EmptySquare;\r
6126     } else if (initialPosition[fromY][fromX] == BlackKing\r
6127                && board[fromY][fromX] == BlackKing\r
6128                && toY == fromY && toX > fromX+1) {\r
6129         board[fromY][fromX] = EmptySquare;\r
6130         board[toY][toX] = BlackKing;\r
6131         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
6132         board[toY][toX-1] = BlackRook;\r
6133     } else if (initialPosition[fromY][fromX] == BlackKing\r
6134                && board[fromY][fromX] == BlackKing\r
6135                && toY == fromY && toX < fromX-1) {\r
6136         board[fromY][fromX] = EmptySquare;\r
6137         board[toY][toX] = BlackKing;\r
6138         board[fromY][BOARD_LEFT] = EmptySquare;\r
6139         board[toY][toX+1] = BlackRook;\r
6140     } else if (fromY == 7 && fromX == 3\r
6141                && board[fromY][fromX] == BlackKing\r
6142                && toY == 7 && toX == 5) {\r
6143         board[fromY][fromX] = EmptySquare;\r
6144         board[toY][toX] = BlackKing;\r
6145         board[fromY][7] = EmptySquare;\r
6146         board[toY][4] = BlackRook;\r
6147     } else if (fromY == 7 && fromX == 3\r
6148                && board[fromY][fromX] == BlackKing\r
6149                && toY == 7 && toX == 1) {\r
6150         board[fromY][fromX] = EmptySquare;\r
6151         board[toY][toX] = BlackKing;\r
6152         board[fromY][0] = EmptySquare;\r
6153         board[toY][2] = BlackRook;\r
6154     } else if (board[fromY][fromX] == BlackPawn\r
6155                && toY == 0\r
6156                && gameInfo.variant != VariantXiangqi\r
6157                ) {\r
6158         /* black pawn promotion */\r
6159         board[0][toX] = CharToPiece(ToLower(promoChar));\r
6160         if (board[0][toX] == EmptySquare) {\r
6161             board[0][toX] = BlackQueen;\r
6162         }\r
6163         if(gameInfo.variant==VariantBughouse ||\r
6164            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
6165             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
6166         board[fromY][fromX] = EmptySquare;\r
6167     } else if ((fromY == 3)\r
6168                && (toX != fromX)\r
6169                && (board[fromY][fromX] == BlackPawn)\r
6170                && (board[toY][toX] == EmptySquare)) {\r
6171         board[fromY][fromX] = EmptySquare;\r
6172         board[toY][toX] = BlackPawn;\r
6173         captured = board[toY + 1][toX];\r
6174         board[toY + 1][toX] = EmptySquare;\r
6175     } else {\r
6176         board[toY][toX] = board[fromY][fromX];\r
6177         board[fromY][fromX] = EmptySquare;\r
6178     }\r
6179 \r
6180     /* [HGM] now we promote for Shogi, if needed */\r
6181     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
6182         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
6183   }\r
6184 \r
6185     if (gameInfo.holdingsWidth != 0) {\r
6186 \r
6187       /* !!A lot more code needs to be written to support holdings  */\r
6188       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
6189       /* penultimate board files, so they are automaticlly stored   */\r
6190       /* in the game history.                                       */\r
6191       if (fromY == DROP_RANK) {\r
6192         /* Delete from holdings, by decreasing count */\r
6193         /* and erasing image if necessary            */\r
6194         p = (int) fromX;\r
6195         if(p < (int) BlackPawn) { /* white drop */\r
6196              p -= (int)WhitePawn;\r
6197              if(p >= gameInfo.holdingsSize) p = 0;\r
6198              if(--board[p][BOARD_WIDTH-2] == 0)\r
6199                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
6200         } else {                  /* black drop */\r
6201              p -= (int)BlackPawn;\r
6202              if(p >= gameInfo.holdingsSize) p = 0;\r
6203              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
6204                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
6205         }\r
6206       }\r
6207       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
6208           && gameInfo.variant != VariantBughouse        ) {\r
6209         /* Add to holdings, if holdings exist */\r
6210         p = (int) captured;\r
6211         if (p >= (int) BlackPawn) {\r
6212           p -= (int)BlackPawn;\r
6213           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
6214                   /* in Shogi restore piece to its original  first */\r
6215                   captured = (ChessSquare) (DEMOTED captured);\r
6216                   p = DEMOTED p;\r
6217           }\r
6218           p = PieceToNumber((ChessSquare)p);\r
6219           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
6220           board[p][BOARD_WIDTH-2]++;\r
6221           board[p][BOARD_WIDTH-1] =\r
6222                                    BLACK_TO_WHITE captured;\r
6223         } else {\r
6224           p -= (int)WhitePawn;\r
6225           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
6226                   captured = (ChessSquare) (DEMOTED captured);\r
6227                   p = DEMOTED p;\r
6228           }\r
6229           p = PieceToNumber((ChessSquare)p);\r
6230           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
6231           board[BOARD_HEIGHT-1-p][1]++;\r
6232           board[BOARD_HEIGHT-1-p][0] =\r
6233                                   WHITE_TO_BLACK captured;\r
6234         }\r
6235       }\r
6236 \r
6237     } else if (gameInfo.variant == VariantAtomic) {\r
6238       if (captured != EmptySquare) {\r
6239         int y, x;\r
6240         for (y = toY-1; y <= toY+1; y++) {\r
6241           for (x = toX-1; x <= toX+1; x++) {\r
6242             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
6243                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
6244               board[y][x] = EmptySquare;\r
6245             }\r
6246           }\r
6247         }\r
6248         board[toY][toX] = EmptySquare;\r
6249       }\r
6250     }\r
6251     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
6252         /* [HGM] Shogi promotions */\r
6253         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
6254     }\r
6255 \r
6256 }\r
6257 \r
6258 /* Updates forwardMostMove */\r
6259 void\r
6260 MakeMove(fromX, fromY, toX, toY, promoChar)\r
6261      int fromX, fromY, toX, toY;\r
6262      int promoChar;\r
6263 {\r
6264     forwardMostMove++;\r
6265 \r
6266     if (forwardMostMove >= MAX_MOVES) {\r
6267       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
6268                         0, 1);\r
6269       return;\r
6270     }\r
6271     SwitchClocks();\r
6272     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
6273     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
6274     if (commentList[forwardMostMove] != NULL) {\r
6275         free(commentList[forwardMostMove]);\r
6276         commentList[forwardMostMove] = NULL;\r
6277     }\r
6278     CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);\r
6279     /* [HGM] compute & store e.p. status and castling rights for new position */\r
6280     { int i, j;\r
6281 \r
6282       epStatus[forwardMostMove] = EP_NONE;\r
6283 \r
6284       if( boards[forwardMostMove][toY][toX] != EmptySquare ) \r
6285            epStatus[forwardMostMove] = EP_CAPTURE;  \r
6286 \r
6287       if( boards[forwardMostMove][fromY][fromX] == WhitePawn ) {\r
6288            epStatus[forwardMostMove] = EP_PAWN_MOVE; \r
6289            if( toY-fromY==2 &&\r
6290                (toX>BOARD_LEFT   && boards[forwardMostMove][toY][toX-1] == BlackPawn ||\r
6291                 toX<BOARD_RGHT-1 && boards[forwardMostMove][toY][toX+1] == BlackPawn ) )\r
6292               epStatus[forwardMostMove] = toX;\r
6293       } else \r
6294       if( boards[forwardMostMove][fromY][fromX] == BlackPawn ) {\r
6295            epStatus[forwardMostMove] = EP_PAWN_MOVE; \r
6296            if( toY-fromY== -2 &&\r
6297                (toX>BOARD_LEFT   && boards[forwardMostMove][toY][toX-1] == WhitePawn ||\r
6298                 toX<BOARD_RGHT-1 && boards[forwardMostMove][toY][toX+1] == WhitePawn ) )\r
6299               epStatus[forwardMostMove] = toX;\r
6300        }\r
6301 \r
6302        for(i=0; i<nrCastlingRights; i++) {\r
6303            castlingRights[forwardMostMove][i] = castlingRights[forwardMostMove-1][i];\r
6304            if(castlingRights[forwardMostMove][i] == fromX && castlingRank[i] == fromY ||\r
6305               castlingRights[forwardMostMove][i] == toX   && castlingRank[i] == toY   \r
6306              ) castlingRights[forwardMostMove][i] = -1; // revoke for moved or captured piece\r
6307 \r
6308 \r
6309 \r
6310        }\r
6311 \r
6312     }\r
6313     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);\r
6314     gameInfo.result = GameUnfinished;\r
6315     if (gameInfo.resultDetails != NULL) {\r
6316         free(gameInfo.resultDetails);\r
6317         gameInfo.resultDetails = NULL;\r
6318     }\r
6319     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
6320                               moveList[forwardMostMove - 1]);\r
6321     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
6322                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
6323                              fromY, fromX, toY, toX, promoChar,\r
6324                              parseList[forwardMostMove - 1]);\r
6325     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
6326                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
6327                             castlingRights[forwardMostMove]) ) {\r
6328       case MT_NONE:\r
6329       case MT_STALEMATE:\r
6330       default:\r
6331         break;\r
6332       case MT_CHECK:\r
6333         if(gameInfo.variant != VariantShogi)\r
6334             strcat(parseList[forwardMostMove - 1], "+");\r
6335         break;\r
6336       case MT_CHECKMATE:\r
6337         strcat(parseList[forwardMostMove - 1], "#");\r
6338         break;\r
6339     }\r
6340     if (appData.debugMode) {\r
6341         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
6342     }\r
6343 \r
6344 }\r
6345 \r
6346 /* Updates currentMove if not pausing */\r
6347 void\r
6348 ShowMove(fromX, fromY, toX, toY)\r
6349 {\r
6350     int instant = (gameMode == PlayFromGameFile) ?\r
6351         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
6352     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
6353         if (!instant) {\r
6354             if (forwardMostMove == currentMove + 1) {\r
6355                 AnimateMove(boards[forwardMostMove - 1],\r
6356                             fromX, fromY, toX, toY);\r
6357             }\r
6358             if (appData.highlightLastMove) {\r
6359                 SetHighlights(fromX, fromY, toX, toY);\r
6360             }\r
6361         }\r
6362         currentMove = forwardMostMove;\r
6363     }\r
6364 \r
6365     if (instant) return;\r
6366 \r
6367     DisplayMove(currentMove - 1);\r
6368     DrawPosition(FALSE, boards[currentMove]);\r
6369     DisplayBothClocks();\r
6370     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
6371 }\r
6372 \r
6373 \r
6374 void\r
6375 InitChessProgram(cps)\r
6376      ChessProgramState *cps;\r
6377 {\r
6378     char buf[MSG_SIZ], *b; int overruled;\r
6379     if (appData.noChessProgram) return;\r
6380     hintRequested = FALSE;\r
6381     bookRequested = FALSE;\r
6382     SendToProgram(cps->initString, cps);\r
6383     if (gameInfo.variant != VariantNormal &&\r
6384         gameInfo.variant != VariantLoadable\r
6385         /* [HGM] also send variant if board size non-standard */\r
6386         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8\r
6387                                             ) {\r
6388       char *v = VariantName(gameInfo.variant);\r
6389       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
6390         /* [HGM] in protocol 1 we have to assume all variants valid */\r
6391         sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);\r
6392         DisplayFatalError(buf, 0, 1);\r
6393         return;\r
6394       }\r
6395       b = buf;\r
6396       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
6397       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
6398       if( gameInfo.variant == VariantXiangqi )\r
6399            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
6400       if( gameInfo.variant == VariantShogi )\r
6401            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
6402       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
6403            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
6404       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantGothic )\r
6405            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
6406       if( gameInfo.variant == VariantCourier )\r
6407            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
6408 \r
6409       if(overruled) {\r
6410            if (cps->protocolVersion != 1 && StrStr(cps->variants, "boardsize") == NULL) {\r
6411              sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
6412                   gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
6413              DisplayFatalError(buf, 0, 1);\r
6414              return;\r
6415            }\r
6416            /* [HGM] here we really should compare with the maximum supported board size */\r
6417            sprintf(buf, "%dx%d+%d_", gameInfo.boardWidth,\r
6418                               gameInfo.boardHeight, gameInfo.holdingsSize );\r
6419            while(*b++ != '_');\r
6420       }\r
6421       sprintf(b, "variant %s\n", VariantName(gameInfo.variant));\r
6422       SendToProgram(buf, cps);\r
6423     }\r
6424     if (cps->sendICS) {\r
6425       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
6426       SendToProgram(buf, cps);\r
6427     }\r
6428     cps->maybeThinking = FALSE;\r
6429     cps->offeredDraw = 0;\r
6430     if (!appData.icsActive) {\r
6431         SendTimeControl(cps, movesPerSession, timeControl,\r
6432                         timeIncrement, appData.searchDepth,\r
6433                         searchTime);\r
6434     }\r
6435     if (appData.showThinking) {\r
6436         SendToProgram("post\n", cps);\r
6437     }\r
6438     SendToProgram("hard\n", cps);\r
6439     if (!appData.ponderNextMove) {\r
6440         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
6441            it without being sure what state we are in first.  "hard"\r
6442            is not a toggle, so that one is OK.\r
6443          */\r
6444         SendToProgram("easy\n", cps);\r
6445     }\r
6446     if (cps->usePing) {\r
6447       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
6448       SendToProgram(buf, cps);\r
6449     }\r
6450     cps->initDone = TRUE;\r
6451 }   \r
6452 \r
6453 \r
6454 void\r
6455 StartChessProgram(cps)\r
6456      ChessProgramState *cps;\r
6457 {\r
6458     char buf[MSG_SIZ];\r
6459     int err;\r
6460 \r
6461     if (appData.noChessProgram) return;\r
6462     cps->initDone = FALSE;\r
6463 \r
6464     if (strcmp(cps->host, "localhost") == 0) {\r
6465         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
6466     } else if (*appData.remoteShell == NULLCHAR) {\r
6467         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
6468     } else {\r
6469         if (*appData.remoteUser == NULLCHAR) {\r
6470             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
6471                     cps->program);\r
6472         } else {\r
6473             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
6474                     cps->host, appData.remoteUser, cps->program);\r
6475         }\r
6476         err = StartChildProcess(buf, "", &cps->pr);\r
6477     }\r
6478     \r
6479     if (err != 0) {\r
6480         sprintf(buf, "Startup failure on '%s'", cps->program);\r
6481         DisplayFatalError(buf, err, 1);\r
6482         cps->pr = NoProc;\r
6483         cps->isr = NULL;\r
6484         return;\r
6485     }\r
6486     \r
6487     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
6488     if (cps->protocolVersion > 1) {\r
6489       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
6490       SendToProgram(buf, cps);\r
6491     } else {\r
6492       SendToProgram("xboard\n", cps);\r
6493     }\r
6494 }\r
6495 \r
6496 \r
6497 void\r
6498 TwoMachinesEventIfReady P((void))\r
6499 {\r
6500   if (first.lastPing != first.lastPong) {\r
6501     DisplayMessage("", "Waiting for first chess program");\r
6502     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
6503     return;\r
6504   }\r
6505   if (second.lastPing != second.lastPong) {\r
6506     DisplayMessage("", "Waiting for second chess program");\r
6507     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
6508     return;\r
6509   }\r
6510   ThawUI();\r
6511   TwoMachinesEvent();\r
6512 }\r
6513 \r
6514 void\r
6515 NextMatchGame P((void))\r
6516 {\r
6517     Reset(FALSE, TRUE);\r
6518     if (*appData.loadGameFile != NULLCHAR) {\r
6519         LoadGameFromFile(appData.loadGameFile,\r
6520                          appData.loadGameIndex,\r
6521                          appData.loadGameFile, FALSE);\r
6522     } else if (*appData.loadPositionFile != NULLCHAR) {\r
6523         LoadPositionFromFile(appData.loadPositionFile,\r
6524                              appData.loadPositionIndex,\r
6525                              appData.loadPositionFile);\r
6526     }\r
6527     TwoMachinesEventIfReady();\r
6528 }\r
6529 \r
6530 void UserAdjudicationEvent( int result )\r
6531 {\r
6532     ChessMove gameResult = GameIsDrawn;\r
6533 \r
6534     if( result > 0 ) {\r
6535         gameResult = WhiteWins;\r
6536     }\r
6537     else if( result < 0 ) {\r
6538         gameResult = BlackWins;\r
6539     }\r
6540 \r
6541     if( gameMode == TwoMachinesPlay ) {\r
6542         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
6543     }\r
6544 }\r
6545 \r
6546 \r
6547 void\r
6548 GameEnds(result, resultDetails, whosays)\r
6549      ChessMove result;\r
6550      char *resultDetails;\r
6551      int whosays;\r
6552 {\r
6553     GameMode nextGameMode;\r
6554     int isIcsGame;\r
6555     char buf[MSG_SIZ];\r
6556 \r
6557     if (appData.debugMode) {\r
6558       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
6559               result, resultDetails ? resultDetails : "(null)", whosays);\r
6560     }\r
6561 \r
6562     if (appData.icsActive && whosays == (GE_ENGINE || whosays >= GE_ENGINE1)) {\r
6563         /* If we are playing on ICS, the server decides when the\r
6564            game is over, but the engine can offer to draw, claim \r
6565            a draw, or resign. \r
6566          */\r
6567 #if ZIPPY\r
6568         if (appData.zippyPlay && first.initDone) {\r
6569             if (result == GameIsDrawn) {\r
6570                 /* In case draw still needs to be claimed */\r
6571                 SendToICS(ics_prefix);\r
6572                 SendToICS("draw\n");\r
6573             } else if (StrCaseStr(resultDetails, "resign")) {\r
6574                 SendToICS(ics_prefix);\r
6575                 SendToICS("resign\n");\r
6576             }\r
6577         }\r
6578 #endif\r
6579         return;\r
6580     }\r
6581 \r
6582     /* If we're loading the game from a file, stop */\r
6583     if (whosays == GE_FILE) {\r
6584       (void) StopLoadGameTimer();\r
6585       gameFileFP = NULL;\r
6586     }\r
6587 \r
6588     /* Cancel draw offers */\r
6589    first.offeredDraw = second.offeredDraw = 0;\r
6590 \r
6591     /* If this is an ICS game, only ICS can really say it's done;\r
6592        if not, anyone can. */\r
6593     isIcsGame = (gameMode == IcsPlayingWhite || \r
6594                  gameMode == IcsPlayingBlack || \r
6595                  gameMode == IcsObserving    || \r
6596                  gameMode == IcsExamining);\r
6597 \r
6598     if (!isIcsGame || whosays == GE_ICS) {\r
6599         /* OK -- not an ICS game, or ICS said it was done */\r
6600         StopClocks();\r
6601     if (appData.debugMode) {\r
6602       fprintf(debugFP, "GameEnds(%d, %s, %d) clock stopped\n",\r
6603               result, resultDetails ? resultDetails : "(null)", whosays);\r
6604     }\r
6605         if (!isIcsGame && !appData.noChessProgram) \r
6606           SetUserThinkingEnables();\r
6607     \r
6608         /* [HGM] if a machine claims the game end we verify this claim */\r
6609         if( appData.testLegality && gameMode == TwoMachinesPlay &&\r
6610             appData.testClaims && whosays >= GE_ENGINE1 ) {\r
6611                 char claimer;\r
6612 \r
6613     if (appData.debugMode) {\r
6614       fprintf(debugFP, "GameEnds(%d, %s, %d) test claims\n",\r
6615               result, resultDetails ? resultDetails : "(null)", whosays);\r
6616     }\r
6617                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
6618                                             first.twoMachinesColor[0] :\r
6619                                             second.twoMachinesColor[0] ;\r
6620                 if( gameInfo.holdingsWidth == 0 &&\r
6621                     (result == WhiteWins && claimer == 'w' ||\r
6622                      result == BlackWins && claimer == 'b'   ) ) {\r
6623                       /* Xboard immediately adjudicates all mates, so win claims must be false */\r
6624                       sprintf(buf, "False win claim: '%s'", resultDetails);\r
6625                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
6626                       resultDetails = buf;\r
6627                 } else\r
6628                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS ) {\r
6629                       /* Draw that was not flagged by Xboard is false */\r
6630                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
6631                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
6632                       resultDetails = buf;\r
6633                 }\r
6634                 /* (Claiming a loss is accepted no questions asked!) */\r
6635         }\r
6636 \r
6637     if (appData.debugMode) {\r
6638       fprintf(debugFP, "GameEnds(%d, %s, %d) after test\n",\r
6639               result, resultDetails ? resultDetails : "(null)", whosays);\r
6640     }\r
6641         if (resultDetails != NULL) {\r
6642             gameInfo.result = result;\r
6643             gameInfo.resultDetails = StrSave(resultDetails);\r
6644 \r
6645             /* Tell program how game ended in case it is learning */\r
6646             if (gameMode == MachinePlaysWhite ||\r
6647                 gameMode == MachinePlaysBlack ||\r
6648                 gameMode == TwoMachinesPlay ||\r
6649                 gameMode == IcsPlayingWhite ||\r
6650                 gameMode == IcsPlayingBlack ||\r
6651                 gameMode == BeginningOfGame) {\r
6652                 char buf[MSG_SIZ];\r
6653                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
6654                         resultDetails);\r
6655                 if (first.pr != NoProc) {\r
6656                     SendToProgram(buf, &first);\r
6657                 }\r
6658                 if (second.pr != NoProc &&\r
6659                     gameMode == TwoMachinesPlay) {\r
6660                     SendToProgram(buf, &second);\r
6661                 }\r
6662             }\r
6663 \r
6664             /* display last move only if game was not loaded from file */\r
6665             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
6666                 DisplayMove(currentMove - 1);\r
6667     \r
6668             if (forwardMostMove != 0) {\r
6669                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
6670                     if (*appData.saveGameFile != NULLCHAR) {\r
6671                         SaveGameToFile(appData.saveGameFile, TRUE);\r
6672                     } else if (appData.autoSaveGames) {\r
6673                         AutoSaveGame();\r
6674                     }\r
6675                     if (*appData.savePositionFile != NULLCHAR) {\r
6676                         SavePositionToFile(appData.savePositionFile);\r
6677                     }\r
6678                 }\r
6679             }\r
6680         }\r
6681 \r
6682         if (appData.icsActive) {\r
6683             if (appData.quietPlay &&\r
6684                 (gameMode == IcsPlayingWhite ||\r
6685                  gameMode == IcsPlayingBlack)) {\r
6686                 SendToICS(ics_prefix);\r
6687                 SendToICS("set shout 1\n");\r
6688             }\r
6689             nextGameMode = IcsIdle;\r
6690             ics_user_moved = FALSE;\r
6691             /* clean up premove.  It's ugly when the game has ended and the\r
6692              * premove highlights are still on the board.\r
6693              */\r
6694             if (gotPremove) {\r
6695               gotPremove = FALSE;\r
6696               ClearPremoveHighlights();\r
6697               DrawPosition(FALSE, boards[currentMove]);\r
6698             }\r
6699             if (whosays == GE_ICS) {\r
6700                 switch (result) {\r
6701                 case WhiteWins:\r
6702                     if (gameMode == IcsPlayingWhite)\r
6703                         PlayIcsWinSound();\r
6704                     else if(gameMode == IcsPlayingBlack)\r
6705                         PlayIcsLossSound();\r
6706                     break;\r
6707                 case BlackWins:\r
6708                     if (gameMode == IcsPlayingBlack)\r
6709                         PlayIcsWinSound();\r
6710                     else if(gameMode == IcsPlayingWhite)\r
6711                         PlayIcsLossSound();\r
6712                     break;\r
6713                 case GameIsDrawn:\r
6714                     PlayIcsDrawSound();\r
6715                     break;\r
6716                 default:\r
6717                     PlayIcsUnfinishedSound();\r
6718                 }\r
6719             }\r
6720         } else if (gameMode == EditGame ||\r
6721                    gameMode == PlayFromGameFile || \r
6722                    gameMode == AnalyzeMode || \r
6723                    gameMode == AnalyzeFile) {\r
6724             nextGameMode = gameMode;\r
6725         } else {\r
6726             nextGameMode = EndOfGame;\r
6727         }\r
6728         pausing = FALSE;\r
6729         ModeHighlight();\r
6730     } else {\r
6731         nextGameMode = gameMode;\r
6732     }\r
6733 \r
6734     if (appData.noChessProgram) {\r
6735         gameMode = nextGameMode;\r
6736         ModeHighlight();\r
6737         return;\r
6738     }\r
6739 \r
6740     if (first.reuse) {\r
6741         /* Put first chess program into idle state */\r
6742         if (first.pr != NoProc &&\r
6743             (gameMode == MachinePlaysWhite ||\r
6744              gameMode == MachinePlaysBlack ||\r
6745              gameMode == TwoMachinesPlay ||\r
6746              gameMode == IcsPlayingWhite ||\r
6747              gameMode == IcsPlayingBlack ||\r
6748              gameMode == BeginningOfGame)) {\r
6749             SendToProgram("force\n", &first);\r
6750             if (first.usePing) {\r
6751               char buf[MSG_SIZ];\r
6752               sprintf(buf, "ping %d\n", ++first.lastPing);\r
6753               SendToProgram(buf, &first);\r
6754             }\r
6755         }\r
6756     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
6757         /* Kill off first chess program */\r
6758         if (first.isr != NULL)\r
6759           RemoveInputSource(first.isr);\r
6760         first.isr = NULL;\r
6761     \r
6762         if (first.pr != NoProc) {\r
6763             ExitAnalyzeMode();\r
6764             DoSleep( appData.delayBeforeQuit );\r
6765             SendToProgram("quit\n", &first);\r
6766             DoSleep( appData.delayAfterQuit );\r
6767             DestroyChildProcess(first.pr, first.useSigterm);\r
6768         }\r
6769         first.pr = NoProc;\r
6770     }\r
6771     if (second.reuse) {\r
6772         /* Put second chess program into idle state */\r
6773         if (second.pr != NoProc &&\r
6774             gameMode == TwoMachinesPlay) {\r
6775             SendToProgram("force\n", &second);\r
6776             if (second.usePing) {\r
6777               char buf[MSG_SIZ];\r
6778               sprintf(buf, "ping %d\n", ++second.lastPing);\r
6779               SendToProgram(buf, &second);\r
6780             }\r
6781         }\r
6782     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
6783         /* Kill off second chess program */\r
6784         if (second.isr != NULL)\r
6785           RemoveInputSource(second.isr);\r
6786         second.isr = NULL;\r
6787     \r
6788         if (second.pr != NoProc) {\r
6789             DoSleep( appData.delayBeforeQuit );\r
6790             SendToProgram("quit\n", &second);\r
6791             DoSleep( appData.delayAfterQuit );\r
6792             DestroyChildProcess(second.pr, second.useSigterm);\r
6793         }\r
6794         second.pr = NoProc;\r
6795     }\r
6796 \r
6797     if (matchMode && gameMode == TwoMachinesPlay) {\r
6798         switch (result) {\r
6799         case WhiteWins:\r
6800           if (first.twoMachinesColor[0] == 'w') {\r
6801             first.matchWins++;\r
6802           } else {\r
6803             second.matchWins++;\r
6804           }\r
6805           break;\r
6806         case BlackWins:\r
6807           if (first.twoMachinesColor[0] == 'b') {\r
6808             first.matchWins++;\r
6809           } else {\r
6810             second.matchWins++;\r
6811           }\r
6812           break;\r
6813         default:\r
6814           break;\r
6815         }\r
6816         if (matchGame < appData.matchGames) {\r
6817             char *tmp;\r
6818             tmp = first.twoMachinesColor;\r
6819             first.twoMachinesColor = second.twoMachinesColor;\r
6820             second.twoMachinesColor = tmp;\r
6821             gameMode = nextGameMode;\r
6822             matchGame++;\r
6823             if(appData.matchPause>10000 || appData.matchPause<10)\r
6824                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
6825             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
6826             return;\r
6827         } else {\r
6828             char buf[MSG_SIZ];\r
6829             gameMode = nextGameMode;\r
6830             sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",\r
6831                     first.tidy, second.tidy,\r
6832                     first.matchWins, second.matchWins,\r
6833                     appData.matchGames - (first.matchWins + second.matchWins));\r
6834             DisplayFatalError(buf, 0, 0);\r
6835         }\r
6836     }\r
6837     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
6838         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
6839       ExitAnalyzeMode();\r
6840     gameMode = nextGameMode;\r
6841     ModeHighlight();\r
6842 }\r
6843 \r
6844 /* Assumes program was just initialized (initString sent).\r
6845    Leaves program in force mode. */\r
6846 void\r
6847 FeedMovesToProgram(cps, upto) \r
6848      ChessProgramState *cps;\r
6849      int upto;\r
6850 {\r
6851     int i;\r
6852     \r
6853     if (appData.debugMode)\r
6854       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
6855               startedFromSetupPosition ? "position and " : "",\r
6856               backwardMostMove, upto, cps->which);\r
6857     SendToProgram("force\n", cps);\r
6858     if (startedFromSetupPosition) {\r
6859         SendBoard(cps, backwardMostMove);\r
6860     }\r
6861     for (i = backwardMostMove; i < upto; i++) {\r
6862         SendMoveToProgram(i, cps);\r
6863     }\r
6864 }\r
6865 \r
6866 \r
6867 void\r
6868 ResurrectChessProgram()\r
6869 {\r
6870      /* The chess program may have exited.\r
6871         If so, restart it and feed it all the moves made so far. */\r
6872 \r
6873     if (appData.noChessProgram || first.pr != NoProc) return;\r
6874     \r
6875     StartChessProgram(&first);\r
6876     InitChessProgram(&first);\r
6877     FeedMovesToProgram(&first, currentMove);\r
6878 \r
6879     if (!first.sendTime) {\r
6880         /* can't tell gnuchess what its clock should read,\r
6881            so we bow to its notion. */\r
6882         ResetClocks();\r
6883         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
6884         timeRemaining[1][currentMove] = blackTimeRemaining;\r
6885     }\r
6886 \r
6887     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
6888         first.analysisSupport) {\r
6889       SendToProgram("analyze\n", &first);\r
6890       first.analyzing = TRUE;\r
6891     }\r
6892 }\r
6893 \r
6894 /*\r
6895  * Button procedures\r
6896  */\r
6897 void\r
6898 Reset(redraw, init)\r
6899      int redraw, init;\r
6900 {\r
6901     int i;\r
6902 \r
6903     if (appData.debugMode) {\r
6904         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
6905                 redraw, init, gameMode);\r
6906     }\r
6907 \r
6908     pausing = pauseExamInvalid = FALSE;\r
6909     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
6910     firstMove = TRUE;\r
6911     whiteFlag = blackFlag = FALSE;\r
6912     userOfferedDraw = FALSE;\r
6913     hintRequested = bookRequested = FALSE;\r
6914     first.maybeThinking = FALSE;\r
6915     second.maybeThinking = FALSE;\r
6916     thinkOutput[0] = NULLCHAR;\r
6917     lastHint[0] = NULLCHAR;\r
6918     ClearGameInfo(&gameInfo);\r
6919     gameInfo.variant = StringToVariant(appData.variant);\r
6920     ics_user_moved = ics_clock_paused = FALSE;\r
6921     ics_getting_history = H_FALSE;\r
6922     ics_gamenum = -1;\r
6923     white_holding[0] = black_holding[0] = NULLCHAR;\r
6924     ClearProgramStats();\r
6925     \r
6926     ResetFrontEnd();\r
6927     ClearHighlights();\r
6928     flipView = appData.flipView;\r
6929     ClearPremoveHighlights();\r
6930     gotPremove = FALSE;\r
6931     alarmSounded = FALSE;\r
6932 \r
6933     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
6934     ExitAnalyzeMode();\r
6935     gameMode = BeginningOfGame;\r
6936     ModeHighlight();\r
6937     InitPosition(redraw);\r
6938     for (i = 0; i < MAX_MOVES; i++) {\r
6939         if (commentList[i] != NULL) {\r
6940             free(commentList[i]);\r
6941             commentList[i] = NULL;\r
6942         }\r
6943     }\r
6944     ResetClocks();\r
6945     timeRemaining[0][0] = whiteTimeRemaining;\r
6946     timeRemaining[1][0] = blackTimeRemaining;\r
6947     if (first.pr == NULL) {\r
6948         StartChessProgram(&first);\r
6949     }\r
6950     if (init) InitChessProgram(&first);\r
6951     DisplayTitle("");\r
6952     DisplayMessage("", "");\r
6953     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
6954 }\r
6955 \r
6956 void\r
6957 AutoPlayGameLoop()\r
6958 {\r
6959     for (;;) {\r
6960         if (!AutoPlayOneMove())\r
6961           return;\r
6962         if (matchMode || appData.timeDelay == 0)\r
6963           continue;\r
6964         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
6965           return;\r
6966         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
6967         break;\r
6968     }\r
6969 }\r
6970 \r
6971 \r
6972 int\r
6973 AutoPlayOneMove()\r
6974 {\r
6975     int fromX, fromY, toX, toY;\r
6976 \r
6977     if (appData.debugMode) {\r
6978       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
6979     }\r
6980 \r
6981     if (gameMode != PlayFromGameFile)\r
6982       return FALSE;\r
6983 \r
6984     if (currentMove >= forwardMostMove) {\r
6985       gameMode = EditGame;\r
6986       ModeHighlight();\r
6987 \r
6988       /* [AS] Clear current move marker at the end of a game */\r
6989       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
6990 \r
6991       return FALSE;\r
6992     }\r
6993     \r
6994     toX = moveList[currentMove][2] - AAA;\r
6995     toY = moveList[currentMove][3] - ONE;\r
6996 \r
6997     if (moveList[currentMove][1] == '@') {\r
6998         if (appData.highlightLastMove) {\r
6999             SetHighlights(-1, -1, toX, toY);\r
7000         }\r
7001     } else {\r
7002         fromX = moveList[currentMove][0] - AAA;\r
7003         fromY = moveList[currentMove][1] - ONE;\r
7004 \r
7005         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
7006 \r
7007         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
7008 \r
7009         if (appData.highlightLastMove) {\r
7010             SetHighlights(fromX, fromY, toX, toY);\r
7011         }\r
7012     }\r
7013     DisplayMove(currentMove);\r
7014     SendMoveToProgram(currentMove++, &first);\r
7015     DisplayBothClocks();\r
7016     DrawPosition(FALSE, boards[currentMove]);\r
7017     if (commentList[currentMove] != NULL) {\r
7018         DisplayComment(currentMove - 1, commentList[currentMove]);\r
7019     }\r
7020     return TRUE;\r
7021 }\r
7022 \r
7023 \r
7024 int\r
7025 LoadGameOneMove(readAhead)\r
7026      ChessMove readAhead;\r
7027 {\r
7028     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
7029     char promoChar = NULLCHAR;\r
7030     ChessMove moveType;\r
7031     char move[MSG_SIZ];\r
7032     char *p, *q;\r
7033     \r
7034     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
7035         gameMode != AnalyzeMode && gameMode != Training) {\r
7036         gameFileFP = NULL;\r
7037         return FALSE;\r
7038     }\r
7039     \r
7040     yyboardindex = forwardMostMove;\r
7041     if (readAhead != (ChessMove)0) {\r
7042       moveType = readAhead;\r
7043     } else {\r
7044       if (gameFileFP == NULL)\r
7045           return FALSE;\r
7046       moveType = (ChessMove) yylex();\r
7047     }\r
7048     \r
7049     done = FALSE;\r
7050     switch (moveType) {\r
7051       case Comment:\r
7052         if (appData.debugMode) \r
7053           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
7054         p = yy_text;\r
7055         if (*p == '{' || *p == '[' || *p == '(') {\r
7056             p[strlen(p) - 1] = NULLCHAR;\r
7057             p++;\r
7058         }\r
7059 \r
7060         /* append the comment but don't display it */\r
7061         while (*p == '\n') p++;\r
7062         AppendComment(currentMove, p);\r
7063         return TRUE;\r
7064 \r
7065       case WhiteCapturesEnPassant:\r
7066       case BlackCapturesEnPassant:\r
7067       case WhitePromotionChancellor:\r
7068       case BlackPromotionChancellor:\r
7069       case WhitePromotionArchbishop:\r
7070       case BlackPromotionArchbishop:\r
7071       case WhitePromotionQueen:\r
7072       case BlackPromotionQueen:\r
7073       case WhitePromotionRook:\r
7074       case BlackPromotionRook:\r
7075       case WhitePromotionBishop:\r
7076       case BlackPromotionBishop:\r
7077       case WhitePromotionKnight:\r
7078       case BlackPromotionKnight:\r
7079       case WhitePromotionKing:\r
7080       case BlackPromotionKing:\r
7081       case NormalMove:\r
7082       case WhiteKingSideCastle:\r
7083       case WhiteQueenSideCastle:\r
7084       case BlackKingSideCastle:\r
7085       case BlackQueenSideCastle:\r
7086       case WhiteKingSideCastleWild:\r
7087       case WhiteQueenSideCastleWild:\r
7088       case BlackKingSideCastleWild:\r
7089       case BlackQueenSideCastleWild:\r
7090       /* PUSH Fabien */\r
7091       case WhiteHSideCastleFR:\r
7092       case WhiteASideCastleFR:\r
7093       case BlackHSideCastleFR:\r
7094       case BlackASideCastleFR:\r
7095       /* POP Fabien */\r
7096         if (appData.debugMode)\r
7097           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
7098         fromX = currentMoveString[0] - AAA;\r
7099         fromY = currentMoveString[1] - ONE;\r
7100         toX = currentMoveString[2] - AAA;\r
7101         toY = currentMoveString[3] - ONE;\r
7102         promoChar = currentMoveString[4];\r
7103         break;\r
7104 \r
7105       case WhiteDrop:\r
7106       case BlackDrop:\r
7107         if (appData.debugMode)\r
7108           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
7109         fromX = moveType == WhiteDrop ?\r
7110           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
7111         (int) CharToPiece(ToLower(currentMoveString[0]));\r
7112         fromY = DROP_RANK;\r
7113         toX = currentMoveString[2] - AAA;\r
7114         toY = currentMoveString[3] - ONE;\r
7115         break;\r
7116 \r
7117       case WhiteWins:\r
7118       case BlackWins:\r
7119       case GameIsDrawn:\r
7120       case GameUnfinished:\r
7121         if (appData.debugMode)\r
7122           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
7123         p = strchr(yy_text, '{');\r
7124         if (p == NULL) p = strchr(yy_text, '(');\r
7125         if (p == NULL) {\r
7126             p = yy_text;\r
7127             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
7128         } else {\r
7129             q = strchr(p, *p == '{' ? '}' : ')');\r
7130             if (q != NULL) *q = NULLCHAR;\r
7131             p++;\r
7132         }\r
7133         GameEnds(moveType, p, GE_FILE);\r
7134         done = TRUE;\r
7135         if (cmailMsgLoaded) {\r
7136             ClearHighlights();\r
7137             flipView = WhiteOnMove(currentMove);\r
7138             if (moveType == GameUnfinished) flipView = !flipView;\r
7139             if (appData.debugMode)\r
7140               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
7141         }\r
7142         break;\r
7143 \r
7144       case (ChessMove) 0:       /* end of file */\r
7145         if (appData.debugMode)\r
7146           fprintf(debugFP, "Parser hit end of file\n");\r
7147         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
7148                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
7149           case MT_NONE:\r
7150           case MT_CHECK:\r
7151             break;\r
7152           case MT_CHECKMATE:\r
7153             if (WhiteOnMove(currentMove)) {\r
7154                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
7155             } else {\r
7156                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
7157             }\r
7158             break;\r
7159           case MT_STALEMATE:\r
7160             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
7161             break;\r
7162         }\r
7163         done = TRUE;\r
7164         break;\r
7165 \r
7166       case MoveNumberOne:\r
7167         if (lastLoadGameStart == GNUChessGame) {\r
7168             /* GNUChessGames have numbers, but they aren't move numbers */\r
7169             if (appData.debugMode)\r
7170               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
7171                       yy_text, (int) moveType);\r
7172             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
7173         }\r
7174         /* else fall thru */\r
7175 \r
7176       case XBoardGame:\r
7177       case GNUChessGame:\r
7178       case PGNTag:\r
7179         /* Reached start of next game in file */\r
7180         if (appData.debugMode)\r
7181           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
7182         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
7183                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
7184           case MT_NONE:\r
7185           case MT_CHECK:\r
7186             break;\r
7187           case MT_CHECKMATE:\r
7188             if (WhiteOnMove(currentMove)) {\r
7189                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
7190             } else {\r
7191                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
7192             }\r
7193             break;\r
7194           case MT_STALEMATE:\r
7195             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
7196             break;\r
7197         }\r
7198         done = TRUE;\r
7199         break;\r
7200 \r
7201       case PositionDiagram:     /* should not happen; ignore */\r
7202       case ElapsedTime:         /* ignore */\r
7203       case NAG:                 /* ignore */\r
7204         if (appData.debugMode)\r
7205           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
7206                   yy_text, (int) moveType);\r
7207         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
7208 \r
7209       case IllegalMove:\r
7210         if (appData.testLegality) {\r
7211             if (appData.debugMode)\r
7212               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
7213             sprintf(move, "Illegal move: %d.%s%s",\r
7214                     (forwardMostMove / 2) + 1,\r
7215                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
7216             DisplayError(move, 0);\r
7217             done = TRUE;\r
7218         } else {\r
7219             if (appData.debugMode)\r
7220               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
7221                       yy_text, currentMoveString);\r
7222             fromX = currentMoveString[0] - AAA;\r
7223             fromY = currentMoveString[1] - ONE;\r
7224             toX = currentMoveString[2] - AAA;\r
7225             toY = currentMoveString[3] - ONE;\r
7226             promoChar = currentMoveString[4];\r
7227         }\r
7228         break;\r
7229 \r
7230       case AmbiguousMove:\r
7231         if (appData.debugMode)\r
7232           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
7233         sprintf(move, "Ambiguous move: %d.%s%s",\r
7234                 (forwardMostMove / 2) + 1,\r
7235                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
7236         DisplayError(move, 0);\r
7237         done = TRUE;\r
7238         break;\r
7239 \r
7240       default:\r
7241       case ImpossibleMove:\r
7242         if (appData.debugMode)\r
7243           fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);\r
7244         sprintf(move, "Illegal move: %d.%s%s",\r
7245                 (forwardMostMove / 2) + 1,\r
7246                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
7247         DisplayError(move, 0);\r
7248         done = TRUE;\r
7249         break;\r
7250     }\r
7251 \r
7252     if (done) {\r
7253         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
7254             DrawPosition(FALSE, boards[currentMove]);\r
7255             DisplayBothClocks();\r
7256             if (!appData.matchMode && commentList[currentMove] != NULL)\r
7257               DisplayComment(currentMove - 1, commentList[currentMove]);\r
7258         }\r
7259         (void) StopLoadGameTimer();\r
7260         gameFileFP = NULL;\r
7261         cmailOldMove = forwardMostMove;\r
7262         return FALSE;\r
7263     } else {\r
7264         /* currentMoveString is set as a side-effect of yylex */\r
7265         strcat(currentMoveString, "\n");\r
7266         strcpy(moveList[forwardMostMove], currentMoveString);\r
7267         \r
7268         thinkOutput[0] = NULLCHAR;\r
7269         MakeMove(fromX, fromY, toX, toY, promoChar);\r
7270         currentMove = forwardMostMove;\r
7271         return TRUE;\r
7272     }\r
7273 }\r
7274 \r
7275 /* Load the nth game from the given file */\r
7276 int\r
7277 LoadGameFromFile(filename, n, title, useList)\r
7278      char *filename;\r
7279      int n;\r
7280      char *title;\r
7281      /*Boolean*/ int useList;\r
7282 {\r
7283     FILE *f;\r
7284     char buf[MSG_SIZ];\r
7285 \r
7286     if (strcmp(filename, "-") == 0) {\r
7287         f = stdin;\r
7288         title = "stdin";\r
7289     } else {\r
7290         f = fopen(filename, "rb");\r
7291         if (f == NULL) {\r
7292             sprintf(buf, "Can't open \"%s\"", filename);\r
7293             DisplayError(buf, errno);\r
7294             return FALSE;\r
7295         }\r
7296     }\r
7297     if (fseek(f, 0, 0) == -1) {\r
7298         /* f is not seekable; probably a pipe */\r
7299         useList = FALSE;\r
7300     }\r
7301     if (useList && n == 0) {\r
7302         int error = GameListBuild(f);\r
7303         if (error) {\r
7304             DisplayError("Cannot build game list", error);\r
7305         } else if (!ListEmpty(&gameList) &&\r
7306                    ((ListGame *) gameList.tailPred)->number > 1) {\r
7307             GameListPopUp(f, title);\r
7308             return TRUE;\r
7309         }\r
7310         GameListDestroy();\r
7311         n = 1;\r
7312     }\r
7313     if (n == 0) n = 1;\r
7314     return LoadGame(f, n, title, FALSE);\r
7315 }\r
7316 \r
7317 \r
7318 void\r
7319 MakeRegisteredMove()\r
7320 {\r
7321     int fromX, fromY, toX, toY;\r
7322     char promoChar;\r
7323     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
7324         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
7325           case CMAIL_MOVE:\r
7326           case CMAIL_DRAW:\r
7327             if (appData.debugMode)\r
7328               fprintf(debugFP, "Restoring %s for game %d\n",\r
7329                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
7330     \r
7331             thinkOutput[0] = NULLCHAR;\r
7332             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
7333             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
7334             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
7335             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
7336             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
7337             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
7338             MakeMove(fromX, fromY, toX, toY, promoChar);\r
7339             ShowMove(fromX, fromY, toX, toY);\r
7340               \r
7341             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
7342                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
7343               case MT_NONE:\r
7344               case MT_CHECK:\r
7345                 break;\r
7346                 \r
7347               case MT_CHECKMATE:\r
7348                 if (WhiteOnMove(currentMove)) {\r
7349                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
7350                 } else {\r
7351                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
7352                 }\r
7353                 break;\r
7354                 \r
7355               case MT_STALEMATE:\r
7356                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
7357                 break;\r
7358             }\r
7359 \r
7360             break;\r
7361             \r
7362           case CMAIL_RESIGN:\r
7363             if (WhiteOnMove(currentMove)) {\r
7364                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
7365             } else {\r
7366                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
7367             }\r
7368             break;\r
7369             \r
7370           case CMAIL_ACCEPT:\r
7371             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
7372             break;\r
7373               \r
7374           default:\r
7375             break;\r
7376         }\r
7377     }\r
7378 \r
7379     return;\r
7380 }\r
7381 \r
7382 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
7383 int\r
7384 CmailLoadGame(f, gameNumber, title, useList)\r
7385      FILE *f;\r
7386      int gameNumber;\r
7387      char *title;\r
7388      int useList;\r
7389 {\r
7390     int retVal;\r
7391 \r
7392     if (gameNumber > nCmailGames) {\r
7393         DisplayError("No more games in this message", 0);\r
7394         return FALSE;\r
7395     }\r
7396     if (f == lastLoadGameFP) {\r
7397         int offset = gameNumber - lastLoadGameNumber;\r
7398         if (offset == 0) {\r
7399             cmailMsg[0] = NULLCHAR;\r
7400             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
7401                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
7402                 nCmailMovesRegistered--;\r
7403             }\r
7404             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
7405             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
7406                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
7407             }\r
7408         } else {\r
7409             if (! RegisterMove()) return FALSE;\r
7410         }\r
7411     }\r
7412 \r
7413     retVal = LoadGame(f, gameNumber, title, useList);\r
7414 \r
7415     /* Make move registered during previous look at this game, if any */\r
7416     MakeRegisteredMove();\r
7417 \r
7418     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
7419         commentList[currentMove]\r
7420           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
7421         DisplayComment(currentMove - 1, commentList[currentMove]);\r
7422     }\r
7423 \r
7424     return retVal;\r
7425 }\r
7426 \r
7427 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
7428 int\r
7429 ReloadGame(offset)\r
7430      int offset;\r
7431 {\r
7432     int gameNumber = lastLoadGameNumber + offset;\r
7433     if (lastLoadGameFP == NULL) {\r
7434         DisplayError("No game has been loaded yet", 0);\r
7435         return FALSE;\r
7436     }\r
7437     if (gameNumber <= 0) {\r
7438         DisplayError("Can't back up any further", 0);\r
7439         return FALSE;\r
7440     }\r
7441     if (cmailMsgLoaded) {\r
7442         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
7443                              lastLoadGameTitle, lastLoadGameUseList);\r
7444     } else {\r
7445         return LoadGame(lastLoadGameFP, gameNumber,\r
7446                         lastLoadGameTitle, lastLoadGameUseList);\r
7447     }\r
7448 }\r
7449 \r
7450 \r
7451 \r
7452 /* Load the nth game from open file f */\r
7453 int\r
7454 LoadGame(f, gameNumber, title, useList)\r
7455      FILE *f;\r
7456      int gameNumber;\r
7457      char *title;\r
7458      int useList;\r
7459 {\r
7460     ChessMove cm;\r
7461     char buf[MSG_SIZ];\r
7462     int gn = gameNumber;\r
7463     ListGame *lg = NULL;\r
7464     int numPGNTags = 0;\r
7465     int err;\r
7466     GameMode oldGameMode;\r
7467 \r
7468     if (appData.debugMode) \r
7469         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
7470 \r
7471     if (gameMode == Training )\r
7472         SetTrainingModeOff();\r
7473 \r
7474     oldGameMode = gameMode;\r
7475     if (gameMode != BeginningOfGame) {\r
7476       Reset(FALSE, TRUE);\r
7477     }\r
7478 \r
7479     gameFileFP = f;\r
7480     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
7481         fclose(lastLoadGameFP);\r
7482     }\r
7483 \r
7484     if (useList) {\r
7485         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
7486         \r
7487         if (lg) {\r
7488             fseek(f, lg->offset, 0);\r
7489             GameListHighlight(gameNumber);\r
7490             gn = 1;\r
7491         }\r
7492         else {\r
7493             DisplayError("Game number out of range", 0);\r
7494             return FALSE;\r
7495         }\r
7496     } else {\r
7497         GameListDestroy();\r
7498         if (fseek(f, 0, 0) == -1) {\r
7499             if (f == lastLoadGameFP ?\r
7500                 gameNumber == lastLoadGameNumber + 1 :\r
7501                 gameNumber == 1) {\r
7502                 gn = 1;\r
7503             } else {\r
7504                 DisplayError("Can't seek on game file", 0);\r
7505                 return FALSE;\r
7506             }\r
7507         }\r
7508     }\r
7509     lastLoadGameFP = f;\r
7510     lastLoadGameNumber = gameNumber;\r
7511     strcpy(lastLoadGameTitle, title);\r
7512     lastLoadGameUseList = useList;\r
7513 \r
7514     yynewfile(f);\r
7515 \r
7516 \r
7517     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
7518         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
7519                 lg->gameInfo.black);\r
7520             DisplayTitle(buf);\r
7521     } else if (*title != NULLCHAR) {\r
7522         if (gameNumber > 1) {\r
7523             sprintf(buf, "%s %d", title, gameNumber);\r
7524             DisplayTitle(buf);\r
7525         } else {\r
7526             DisplayTitle(title);\r
7527         }\r
7528     }\r
7529 \r
7530     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
7531         gameMode = PlayFromGameFile;\r
7532         ModeHighlight();\r
7533     }\r
7534 \r
7535     currentMove = forwardMostMove = backwardMostMove = 0;\r
7536     CopyBoard(boards[0], initialPosition);\r
7537     StopClocks();\r
7538 \r
7539     /*\r
7540      * Skip the first gn-1 games in the file.\r
7541      * Also skip over anything that precedes an identifiable \r
7542      * start of game marker, to avoid being confused by \r
7543      * garbage at the start of the file.  Currently \r
7544      * recognized start of game markers are the move number "1",\r
7545      * the pattern "gnuchess .* game", the pattern\r
7546      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
7547      * A game that starts with one of the latter two patterns\r
7548      * will also have a move number 1, possibly\r
7549      * following a position diagram.\r
7550      * 5-4-02: Let's try being more lenient and allowing a game to\r
7551      * start with an unnumbered move.  Does that break anything?\r
7552      */\r
7553     cm = lastLoadGameStart = (ChessMove) 0;\r
7554     while (gn > 0) {\r
7555         yyboardindex = forwardMostMove;\r
7556         cm = (ChessMove) yylex();\r
7557         switch (cm) {\r
7558           case (ChessMove) 0:\r
7559             if (cmailMsgLoaded) {\r
7560                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
7561             } else {\r
7562                 Reset(TRUE, TRUE);\r
7563                 DisplayError("Game not found in file", 0);\r
7564             }\r
7565             return FALSE;\r
7566 \r
7567           case GNUChessGame:\r
7568           case XBoardGame:\r
7569             gn--;\r
7570             lastLoadGameStart = cm;\r
7571             break;\r
7572             \r
7573           case MoveNumberOne:\r
7574             switch (lastLoadGameStart) {\r
7575               case GNUChessGame:\r
7576               case XBoardGame:\r
7577               case PGNTag:\r
7578                 break;\r
7579               case MoveNumberOne:\r
7580               case (ChessMove) 0:\r
7581                 gn--;           /* count this game */\r
7582                 lastLoadGameStart = cm;\r
7583                 break;\r
7584               default:\r
7585                 /* impossible */\r
7586                 break;\r
7587             }\r
7588             break;\r
7589 \r
7590           case PGNTag:\r
7591             switch (lastLoadGameStart) {\r
7592               case GNUChessGame:\r
7593               case PGNTag:\r
7594               case MoveNumberOne:\r
7595               case (ChessMove) 0:\r
7596                 gn--;           /* count this game */\r
7597                 lastLoadGameStart = cm;\r
7598                 break;\r
7599               case XBoardGame:\r
7600                 lastLoadGameStart = cm; /* game counted already */\r
7601                 break;\r
7602               default:\r
7603                 /* impossible */\r
7604                 break;\r
7605             }\r
7606             if (gn > 0) {\r
7607                 do {\r
7608                     yyboardindex = forwardMostMove;\r
7609                     cm = (ChessMove) yylex();\r
7610                 } while (cm == PGNTag || cm == Comment);\r
7611             }\r
7612             break;\r
7613 \r
7614           case WhiteWins:\r
7615           case BlackWins:\r
7616           case GameIsDrawn:\r
7617             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
7618                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
7619                     != CMAIL_OLD_RESULT) {\r
7620                     nCmailResults ++ ;\r
7621                     cmailResult[  CMAIL_MAX_GAMES\r
7622                                 - gn - 1] = CMAIL_OLD_RESULT;\r
7623                 }\r
7624             }\r
7625             break;\r
7626 \r
7627           case NormalMove:\r
7628             /* Only a NormalMove can be at the start of a game\r
7629              * without a position diagram. */\r
7630             if (lastLoadGameStart == (ChessMove) 0) {\r
7631               gn--;\r
7632               lastLoadGameStart = MoveNumberOne;\r
7633             }\r
7634             break;\r
7635 \r
7636           default:\r
7637             break;\r
7638         }\r
7639     }\r
7640     \r
7641     if (appData.debugMode)\r
7642       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
7643 \r
7644     if (cm == XBoardGame) {\r
7645         /* Skip any header junk before position diagram and/or move 1 */\r
7646         for (;;) {\r
7647             yyboardindex = forwardMostMove;\r
7648             cm = (ChessMove) yylex();\r
7649 \r
7650             if (cm == (ChessMove) 0 ||\r
7651                 cm == GNUChessGame || cm == XBoardGame) {\r
7652                 /* Empty game; pretend end-of-file and handle later */\r
7653                 cm = (ChessMove) 0;\r
7654                 break;\r
7655             }\r
7656 \r
7657             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
7658                 cm == PGNTag || cm == Comment)\r
7659               break;\r
7660         }\r
7661     } else if (cm == GNUChessGame) {\r
7662         if (gameInfo.event != NULL) {\r
7663             free(gameInfo.event);\r
7664         }\r
7665         gameInfo.event = StrSave(yy_text);\r
7666     }   \r
7667 \r
7668     startedFromSetupPosition = FALSE;\r
7669     while (cm == PGNTag) {\r
7670         if (appData.debugMode) \r
7671           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
7672         err = ParsePGNTag(yy_text, &gameInfo);\r
7673         if (!err) numPGNTags++;\r
7674 \r
7675         if (gameInfo.fen != NULL) {\r
7676           Board initial_position;\r
7677           startedFromSetupPosition = TRUE;\r
7678           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
7679             Reset(TRUE, TRUE);\r
7680             DisplayError("Bad FEN position in file", 0);\r
7681             return FALSE;\r
7682           }\r
7683           CopyBoard(boards[0], initial_position);\r
7684           /* [HGM] copy FEN attributes as well */\r
7685           {   int i;\r
7686               initialRulePlies = FENrulePlies;\r
7687               epStatus[0] = FENepStatus;\r
7688               for( i=0; i< nrCastlingRights; i++ )\r
7689                   castlingRights[0][i] = FENcastlingRights[i];\r
7690           }\r
7691           if (blackPlaysFirst) {\r
7692             currentMove = forwardMostMove = backwardMostMove = 1;\r
7693             CopyBoard(boards[1], initial_position);\r
7694             strcpy(moveList[0], "");\r
7695             strcpy(parseList[0], "");\r
7696             timeRemaining[0][1] = whiteTimeRemaining;\r
7697             timeRemaining[1][1] = blackTimeRemaining;\r
7698             if (commentList[0] != NULL) {\r
7699               commentList[1] = commentList[0];\r
7700               commentList[0] = NULL;\r
7701             }\r
7702           } else {\r
7703             currentMove = forwardMostMove = backwardMostMove = 0;\r
7704           }\r
7705           yyboardindex = forwardMostMove;\r
7706           free(gameInfo.fen);\r
7707           gameInfo.fen = NULL;\r
7708         }\r
7709 \r
7710         yyboardindex = forwardMostMove;\r
7711         cm = (ChessMove) yylex();\r
7712 \r
7713         /* Handle comments interspersed among the tags */\r
7714         while (cm == Comment) {\r
7715             char *p;\r
7716             if (appData.debugMode) \r
7717               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
7718             p = yy_text;\r
7719             if (*p == '{' || *p == '[' || *p == '(') {\r
7720                 p[strlen(p) - 1] = NULLCHAR;\r
7721                 p++;\r
7722             }\r
7723             while (*p == '\n') p++;\r
7724             AppendComment(currentMove, p);\r
7725             yyboardindex = forwardMostMove;\r
7726             cm = (ChessMove) yylex();\r
7727         }\r
7728     }\r
7729 \r
7730     /* don't rely on existence of Event tag since if game was\r
7731      * pasted from clipboard the Event tag may not exist\r
7732      */\r
7733     if (numPGNTags > 0){\r
7734         char *tags;\r
7735         if (gameInfo.variant == VariantNormal) {\r
7736           gameInfo.variant = StringToVariant(gameInfo.event);\r
7737         }\r
7738         if (!matchMode) {\r
7739           if( appData.autoDisplayTags ) {\r
7740             tags = PGNTags(&gameInfo);\r
7741             TagsPopUp(tags, CmailMsg());\r
7742             free(tags);\r
7743           }\r
7744         }\r
7745     } else {\r
7746         /* Make something up, but don't display it now */\r
7747         SetGameInfo();\r
7748         TagsPopDown();\r
7749     }\r
7750 \r
7751     if (cm == PositionDiagram) {\r
7752         int i, j;\r
7753         char *p;\r
7754         Board initial_position;\r
7755 \r
7756         if (appData.debugMode)\r
7757           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
7758 \r
7759         if (!startedFromSetupPosition) {\r
7760             p = yy_text;\r
7761             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
7762               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
7763                 switch (*p) {\r
7764                   case '[':\r
7765                   case '-':\r
7766                   case ' ':\r
7767                   case '\t':\r
7768                   case '\n':\r
7769                   case '\r':\r
7770                     break;\r
7771                   default:\r
7772                     initial_position[i][j++] = CharToPiece(*p);\r
7773                     break;\r
7774                 }\r
7775             while (*p == ' ' || *p == '\t' ||\r
7776                    *p == '\n' || *p == '\r') p++;\r
7777         \r
7778             if (strncmp(p, "black", strlen("black"))==0)\r
7779               blackPlaysFirst = TRUE;\r
7780             else\r
7781               blackPlaysFirst = FALSE;\r
7782             startedFromSetupPosition = TRUE;\r
7783         \r
7784             CopyBoard(boards[0], initial_position);\r
7785             if (blackPlaysFirst) {\r
7786                 currentMove = forwardMostMove = backwardMostMove = 1;\r
7787                 CopyBoard(boards[1], initial_position);\r
7788                 strcpy(moveList[0], "");\r
7789                 strcpy(parseList[0], "");\r
7790                 timeRemaining[0][1] = whiteTimeRemaining;\r
7791                 timeRemaining[1][1] = blackTimeRemaining;\r
7792                 if (commentList[0] != NULL) {\r
7793                     commentList[1] = commentList[0];\r
7794                     commentList[0] = NULL;\r
7795                 }\r
7796             } else {\r
7797                 currentMove = forwardMostMove = backwardMostMove = 0;\r
7798             }\r
7799         }\r
7800         yyboardindex = forwardMostMove;\r
7801         cm = (ChessMove) yylex();\r
7802     }\r
7803 \r
7804     if (first.pr == NoProc) {\r
7805         StartChessProgram(&first);\r
7806     }\r
7807     InitChessProgram(&first);\r
7808     SendToProgram("force\n", &first);\r
7809     if (startedFromSetupPosition) {\r
7810         SendBoard(&first, forwardMostMove);\r
7811         DisplayBothClocks();\r
7812     }      \r
7813 \r
7814     while (cm == Comment) {\r
7815         char *p;\r
7816         if (appData.debugMode) \r
7817           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
7818         p = yy_text;\r
7819         if (*p == '{' || *p == '[' || *p == '(') {\r
7820             p[strlen(p) - 1] = NULLCHAR;\r
7821             p++;\r
7822         }\r
7823         while (*p == '\n') p++;\r
7824         AppendComment(currentMove, p);\r
7825         yyboardindex = forwardMostMove;\r
7826         cm = (ChessMove) yylex();\r
7827     }\r
7828 \r
7829     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
7830         cm == WhiteWins || cm == BlackWins ||\r
7831         cm == GameIsDrawn || cm == GameUnfinished) {\r
7832         DisplayMessage("", "No moves in game");\r
7833         if (cmailMsgLoaded) {\r
7834             if (appData.debugMode)\r
7835               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
7836             ClearHighlights();\r
7837             flipView = FALSE;\r
7838         }\r
7839         DrawPosition(FALSE, boards[currentMove]);\r
7840         DisplayBothClocks();\r
7841         gameMode = EditGame;\r
7842         ModeHighlight();\r
7843         gameFileFP = NULL;\r
7844         cmailOldMove = 0;\r
7845         return TRUE;\r
7846     }\r
7847 \r
7848     if (commentList[currentMove] != NULL) {\r
7849       if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
7850         DisplayComment(currentMove - 1, commentList[currentMove]);\r
7851       }\r
7852     }\r
7853     if (!matchMode && appData.timeDelay != 0) \r
7854       DrawPosition(FALSE, boards[currentMove]);\r
7855 \r
7856     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
7857       programStats.ok_to_send = 1;\r
7858     }\r
7859 \r
7860     /* if the first token after the PGN tags is a move\r
7861      * and not move number 1, retrieve it from the parser \r
7862      */\r
7863     if (cm != MoveNumberOne)\r
7864         LoadGameOneMove(cm);\r
7865 \r
7866     /* load the remaining moves from the file */\r
7867     while (LoadGameOneMove((ChessMove)0)) {\r
7868       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
7869       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
7870     }\r
7871 \r
7872     /* rewind to the start of the game */\r
7873     currentMove = backwardMostMove;\r
7874 \r
7875     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
7876 \r
7877     if (oldGameMode == AnalyzeFile ||\r
7878         oldGameMode == AnalyzeMode) {\r
7879       AnalyzeFileEvent();\r
7880     }\r
7881 \r
7882     if (matchMode || appData.timeDelay == 0) {\r
7883       ToEndEvent();\r
7884       gameMode = EditGame;\r
7885       ModeHighlight();\r
7886     } else if (appData.timeDelay > 0) {\r
7887       AutoPlayGameLoop();\r
7888     }\r
7889 \r
7890     if (appData.debugMode) \r
7891         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
7892     return TRUE;\r
7893 }\r
7894 \r
7895 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
7896 int\r
7897 ReloadPosition(offset)\r
7898      int offset;\r
7899 {\r
7900     int positionNumber = lastLoadPositionNumber + offset;\r
7901     if (lastLoadPositionFP == NULL) {\r
7902         DisplayError("No position has been loaded yet", 0);\r
7903         return FALSE;\r
7904     }\r
7905     if (positionNumber <= 0) {\r
7906         DisplayError("Can't back up any further", 0);\r
7907         return FALSE;\r
7908     }\r
7909     return LoadPosition(lastLoadPositionFP, positionNumber,\r
7910                         lastLoadPositionTitle);\r
7911 }\r
7912 \r
7913 /* Load the nth position from the given file */\r
7914 int\r
7915 LoadPositionFromFile(filename, n, title)\r
7916      char *filename;\r
7917      int n;\r
7918      char *title;\r
7919 {\r
7920     FILE *f;\r
7921     char buf[MSG_SIZ];\r
7922 \r
7923     if (strcmp(filename, "-") == 0) {\r
7924         return LoadPosition(stdin, n, "stdin");\r
7925     } else {\r
7926         f = fopen(filename, "rb");\r
7927         if (f == NULL) {\r
7928             sprintf(buf, "Can't open \"%s\"", filename);\r
7929             DisplayError(buf, errno);\r
7930             return FALSE;\r
7931         } else {\r
7932             return LoadPosition(f, n, title);\r
7933         }\r
7934     }\r
7935 }\r
7936 \r
7937 /* Load the nth position from the given open file, and close it */\r
7938 int\r
7939 LoadPosition(f, positionNumber, title)\r
7940      FILE *f;\r
7941      int positionNumber;\r
7942      char *title;\r
7943 {\r
7944     char *p, line[MSG_SIZ];\r
7945     Board initial_position;\r
7946     int i, j, fenMode, pn;\r
7947     \r
7948     if (gameMode == Training )\r
7949         SetTrainingModeOff();\r
7950 \r
7951     if (gameMode != BeginningOfGame) {\r
7952         Reset(FALSE, TRUE);\r
7953     }\r
7954     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
7955         fclose(lastLoadPositionFP);\r
7956     }\r
7957     if (positionNumber == 0) positionNumber = 1;\r
7958     lastLoadPositionFP = f;\r
7959     lastLoadPositionNumber = positionNumber;\r
7960     strcpy(lastLoadPositionTitle, title);\r
7961     if (first.pr == NoProc) {\r
7962       StartChessProgram(&first);\r
7963       InitChessProgram(&first);\r
7964     }    \r
7965     pn = positionNumber;\r
7966     if (positionNumber < 0) {\r
7967         /* Negative position number means to seek to that byte offset */\r
7968         if (fseek(f, -positionNumber, 0) == -1) {\r
7969             DisplayError("Can't seek on position file", 0);\r
7970             return FALSE;\r
7971         };\r
7972         pn = 1;\r
7973     } else {\r
7974         if (fseek(f, 0, 0) == -1) {\r
7975             if (f == lastLoadPositionFP ?\r
7976                 positionNumber == lastLoadPositionNumber + 1 :\r
7977                 positionNumber == 1) {\r
7978                 pn = 1;\r
7979             } else {\r
7980                 DisplayError("Can't seek on position file", 0);\r
7981                 return FALSE;\r
7982             }\r
7983         }\r
7984     }\r
7985     /* See if this file is FEN or old-style xboard */\r
7986     if (fgets(line, MSG_SIZ, f) == NULL) {\r
7987         DisplayError("Position not found in file", 0);\r
7988         return FALSE;\r
7989     }\r
7990     switch (line[0]) {\r
7991       case '#':  case 'x':\r
7992       default:\r
7993         fenMode = FALSE;\r
7994         break;\r
7995       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
7996       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
7997       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
7998       case '7':  case '8':  case '9':\r
7999 #ifdef FAIRY\r
8000       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
8001       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
8002       case 'C':  case 'W':             case 'c':  case 'w': \r
8003 #endif\r
8004         fenMode = TRUE;\r
8005         break;\r
8006     }\r
8007 \r
8008     if (pn >= 2) {\r
8009         if (fenMode || line[0] == '#') pn--;\r
8010         while (pn > 0) {\r
8011             /* skip postions before number pn */\r
8012             if (fgets(line, MSG_SIZ, f) == NULL) {\r
8013                 Reset(TRUE, TRUE);\r
8014                 DisplayError("Position not found in file", 0);\r
8015                 return FALSE;\r
8016             }\r
8017             if (fenMode || line[0] == '#') pn--;\r
8018         }\r
8019     }\r
8020 \r
8021     if (fenMode) {\r
8022         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
8023             DisplayError("Bad FEN position in file", 0);\r
8024             return FALSE;\r
8025         }\r
8026     } else {\r
8027         (void) fgets(line, MSG_SIZ, f);\r
8028         (void) fgets(line, MSG_SIZ, f);\r
8029     \r
8030         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
8031             (void) fgets(line, MSG_SIZ, f);\r
8032             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
8033                 if (*p == ' ')\r
8034                   continue;\r
8035                 initial_position[i][j++] = CharToPiece(*p);\r
8036             }\r
8037         }\r
8038     \r
8039         blackPlaysFirst = FALSE;\r
8040         if (!feof(f)) {\r
8041             (void) fgets(line, MSG_SIZ, f);\r
8042             if (strncmp(line, "black", strlen("black"))==0)\r
8043               blackPlaysFirst = TRUE;\r
8044         }\r
8045     }\r
8046     startedFromSetupPosition = TRUE;\r
8047     \r
8048     SendToProgram("force\n", &first);\r
8049     CopyBoard(boards[0], initial_position);\r
8050           /* [HGM] copy FEN attributes as well */\r
8051           {   int i;\r
8052               initialRulePlies = FENrulePlies;\r
8053               epStatus[0] = FENepStatus;\r
8054               for( i=0; i< nrCastlingRights; i++ )\r
8055                   castlingRights[0][i] = FENcastlingRights[i];\r
8056           }\r
8057     if (blackPlaysFirst) {\r
8058         currentMove = forwardMostMove = backwardMostMove = 1;\r
8059         strcpy(moveList[0], "");\r
8060         strcpy(parseList[0], "");\r
8061         CopyBoard(boards[1], initial_position);\r
8062         DisplayMessage("", "Black to play");\r
8063     } else {\r
8064         currentMove = forwardMostMove = backwardMostMove = 0;\r
8065         DisplayMessage("", "White to play");\r
8066     }\r
8067     SendBoard(&first, forwardMostMove);\r
8068 \r
8069     if (positionNumber > 1) {\r
8070         sprintf(line, "%s %d", title, positionNumber);\r
8071         DisplayTitle(line);\r
8072     } else {\r
8073         DisplayTitle(title);\r
8074     }\r
8075     gameMode = EditGame;\r
8076     ModeHighlight();\r
8077     ResetClocks();\r
8078     timeRemaining[0][1] = whiteTimeRemaining;\r
8079     timeRemaining[1][1] = blackTimeRemaining;\r
8080     DrawPosition(FALSE, boards[currentMove]);\r
8081    \r
8082     return TRUE;\r
8083 }\r
8084 \r
8085 \r
8086 void\r
8087 CopyPlayerNameIntoFileName(dest, src)\r
8088      char **dest, *src;\r
8089 {\r
8090     while (*src != NULLCHAR && *src != ',') {\r
8091         if (*src == ' ') {\r
8092             *(*dest)++ = '_';\r
8093             src++;\r
8094         } else {\r
8095             *(*dest)++ = *src++;\r
8096         }\r
8097     }\r
8098 }\r
8099 \r
8100 char *DefaultFileName(ext)\r
8101      char *ext;\r
8102 {\r
8103     static char def[MSG_SIZ];\r
8104     char *p;\r
8105 \r
8106     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
8107         p = def;\r
8108         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
8109         *p++ = '-';\r
8110         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
8111         *p++ = '.';\r
8112         strcpy(p, ext);\r
8113     } else {\r
8114         def[0] = NULLCHAR;\r
8115     }\r
8116     return def;\r
8117 }\r
8118 \r
8119 /* Save the current game to the given file */\r
8120 int\r
8121 SaveGameToFile(filename, append)\r
8122      char *filename;\r
8123      int append;\r
8124 {\r
8125     FILE *f;\r
8126     char buf[MSG_SIZ];\r
8127 \r
8128     if (strcmp(filename, "-") == 0) {\r
8129         return SaveGame(stdout, 0, NULL);\r
8130     } else {\r
8131         f = fopen(filename, append ? "a" : "w");\r
8132         if (f == NULL) {\r
8133             sprintf(buf, "Can't open \"%s\"", filename);\r
8134             DisplayError(buf, errno);\r
8135             return FALSE;\r
8136         } else {\r
8137             return SaveGame(f, 0, NULL);\r
8138         }\r
8139     }\r
8140 }\r
8141 \r
8142 char *\r
8143 SavePart(str)\r
8144      char *str;\r
8145 {\r
8146     static char buf[MSG_SIZ];\r
8147     char *p;\r
8148     \r
8149     p = strchr(str, ' ');\r
8150     if (p == NULL) return str;\r
8151     strncpy(buf, str, p - str);\r
8152     buf[p - str] = NULLCHAR;\r
8153     return buf;\r
8154 }\r
8155 \r
8156 #define PGN_MAX_LINE 75\r
8157 \r
8158 #define PGN_SIDE_WHITE  0\r
8159 #define PGN_SIDE_BLACK  1\r
8160 \r
8161 /* [AS] */\r
8162 static int FindFirstMoveOutOfBook( int side )\r
8163 {\r
8164     int result = -1;\r
8165 \r
8166     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
8167         int index = backwardMostMove;\r
8168         int has_book_hit = 0;\r
8169 \r
8170         if( (index % 2) != side ) {\r
8171             index++;\r
8172         }\r
8173 \r
8174         while( index < forwardMostMove ) {\r
8175             /* Check to see if engine is in book */\r
8176             int depth = pvInfoList[index].depth;\r
8177             int score = pvInfoList[index].score;\r
8178             int in_book = 0;\r
8179 \r
8180             if( depth <= 2 ) {\r
8181                 in_book = 1;\r
8182             }\r
8183             else if( score == 0 && depth == 63 ) {\r
8184                 in_book = 1; /* Zappa */\r
8185             }\r
8186             else if( score == 2 && depth == 99 ) {\r
8187                 in_book = 1; /* Abrok */\r
8188             }\r
8189 \r
8190             has_book_hit += in_book;\r
8191 \r
8192             if( ! in_book ) {\r
8193                 result = index;\r
8194 \r
8195                 break;\r
8196             }\r
8197 \r
8198             index += 2;\r
8199         }\r
8200     }\r
8201 \r
8202     return result;\r
8203 }\r
8204 \r
8205 /* [AS] */\r
8206 void GetOutOfBookInfo( char * buf )\r
8207 {\r
8208     int oob[2];\r
8209     int i;\r
8210     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
8211 \r
8212     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
8213     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
8214 \r
8215     *buf = '\0';\r
8216 \r
8217     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
8218         for( i=0; i<2; i++ ) {\r
8219             int idx = oob[i];\r
8220 \r
8221             if( idx >= 0 ) {\r
8222                 if( i > 0 && oob[0] >= 0 ) {\r
8223                     strcat( buf, "   " );\r
8224                 }\r
8225 \r
8226                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
8227                 sprintf( buf+strlen(buf), "%s%.2f", \r
8228                     pvInfoList[idx].score >= 0 ? "+" : "",\r
8229                     pvInfoList[idx].score / 100.0 );\r
8230             }\r
8231         }\r
8232     }\r
8233 }\r
8234 \r
8235 /* Save game in PGN style and close the file */\r
8236 int\r
8237 SaveGamePGN(f)\r
8238      FILE *f;\r
8239 {\r
8240     int i, offset, linelen, newblock;\r
8241     time_t tm;\r
8242     char *movetext;\r
8243     char numtext[32];\r
8244     int movelen, numlen, blank;\r
8245     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
8246 \r
8247     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
8248     \r
8249     tm = time((time_t *) NULL);\r
8250     \r
8251     PrintPGNTags(f, &gameInfo);\r
8252     \r
8253     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
8254         char *fen = PositionToFEN(backwardMostMove, 1);\r
8255         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
8256         fprintf(f, "\n{--------------\n");\r
8257         PrintPosition(f, backwardMostMove);\r
8258         fprintf(f, "--------------}\n");\r
8259         free(fen);\r
8260     }\r
8261     else {\r
8262         /* [AS] Out of book annotation */\r
8263         if( appData.saveOutOfBookInfo ) {\r
8264             char buf[64];\r
8265 \r
8266             GetOutOfBookInfo( buf );\r
8267 \r
8268             if( buf[0] != '\0' ) {\r
8269                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
8270             }\r
8271         }\r
8272 \r
8273         fprintf(f, "\n");\r
8274     }\r
8275 \r
8276     i = backwardMostMove;\r
8277     linelen = 0;\r
8278     newblock = TRUE;\r
8279 \r
8280     while (i < forwardMostMove) {\r
8281         /* Print comments preceding this move */\r
8282         if (commentList[i] != NULL) {\r
8283             if (linelen > 0) fprintf(f, "\n");\r
8284             fprintf(f, "{\n%s}\n", commentList[i]);\r
8285             linelen = 0;\r
8286             newblock = TRUE;\r
8287         }\r
8288 \r
8289         /* Format move number */\r
8290         if ((i % 2) == 0) {\r
8291             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
8292         } else {\r
8293             if (newblock) {\r
8294                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
8295             } else {\r
8296                 numtext[0] = NULLCHAR;\r
8297             }\r
8298         }\r
8299         numlen = strlen(numtext);\r
8300         newblock = FALSE;\r
8301 \r
8302         /* Print move number */\r
8303         blank = linelen > 0 && numlen > 0;\r
8304         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
8305             fprintf(f, "\n");\r
8306             linelen = 0;\r
8307             blank = 0;\r
8308         }\r
8309         if (blank) {\r
8310             fprintf(f, " ");\r
8311             linelen++;\r
8312         }\r
8313         fprintf(f, numtext);\r
8314         linelen += numlen;\r
8315 \r
8316         /* Get move */\r
8317         movetext = SavePart(parseList[i]);\r
8318 \r
8319         /* [AS] Add PV info if present */\r
8320         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
8321             /* [HGM] add time */\r
8322             char buf[MSG_SIZ]; int seconds = 0;\r
8323 \r
8324             if(i >= backwardMostMove) {\r
8325                 /* take the time that changed */\r
8326                 seconds = timeRemaining[0][i] - timeRemaining[0][i+1];\r
8327                 if(seconds <= 0)\r
8328                     seconds = timeRemaining[1][i] - timeRemaining[1][i+1];\r
8329             }\r
8330             seconds /= 1000;\r
8331     if (appData.debugMode) {\r
8332         fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",\r
8333                 timeRemaining[0][i+1], timeRemaining[0][i],\r
8334                      timeRemaining[1][i+1], timeRemaining[1][i], seconds\r
8335         );\r
8336     }\r
8337 \r
8338             if( seconds < 0 ) buf[0] = 0; else\r
8339             if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0);\r
8340             else    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
8341 \r
8342             sprintf( move_buffer, "%s {%s%.2f/%d%s}", \r
8343                 movetext, \r
8344                 pvInfoList[i].score >= 0 ? "+" : "",\r
8345                 pvInfoList[i].score / 100.0,\r
8346                 pvInfoList[i].depth,\r
8347                 buf );\r
8348             movetext = move_buffer;\r
8349         }\r
8350 \r
8351         movelen = strlen(movetext);\r
8352 \r
8353         /* Print move */\r
8354         blank = linelen > 0 && movelen > 0;\r
8355         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
8356             fprintf(f, "\n");\r
8357             linelen = 0;\r
8358             blank = 0;\r
8359         }\r
8360         if (blank) {\r
8361             fprintf(f, " ");\r
8362             linelen++;\r
8363         }\r
8364         fprintf(f, movetext);\r
8365         linelen += movelen;\r
8366 \r
8367         i++;\r
8368     }\r
8369     \r
8370     /* Start a new line */\r
8371     if (linelen > 0) fprintf(f, "\n");\r
8372 \r
8373     /* Print comments after last move */\r
8374     if (commentList[i] != NULL) {\r
8375         fprintf(f, "{\n%s}\n", commentList[i]);\r
8376     }\r
8377 \r
8378     /* Print result */\r
8379     if (gameInfo.resultDetails != NULL &&\r
8380         gameInfo.resultDetails[0] != NULLCHAR) {\r
8381         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
8382                 PGNResult(gameInfo.result));\r
8383     } else {\r
8384         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
8385     }\r
8386 \r
8387     fclose(f);\r
8388     return TRUE;\r
8389 }\r
8390 \r
8391 /* Save game in old style and close the file */\r
8392 int\r
8393 SaveGameOldStyle(f)\r
8394      FILE *f;\r
8395 {\r
8396     int i, offset;\r
8397     time_t tm;\r
8398     \r
8399     tm = time((time_t *) NULL);\r
8400     \r
8401     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
8402     PrintOpponents(f);\r
8403     \r
8404     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
8405         fprintf(f, "\n[--------------\n");\r
8406         PrintPosition(f, backwardMostMove);\r
8407         fprintf(f, "--------------]\n");\r
8408     } else {\r
8409         fprintf(f, "\n");\r
8410     }\r
8411 \r
8412     i = backwardMostMove;\r
8413     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
8414 \r
8415     while (i < forwardMostMove) {\r
8416         if (commentList[i] != NULL) {\r
8417             fprintf(f, "[%s]\n", commentList[i]);\r
8418         }\r
8419 \r
8420         if ((i % 2) == 1) {\r
8421             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
8422             i++;\r
8423         } else {\r
8424             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
8425             i++;\r
8426             if (commentList[i] != NULL) {\r
8427                 fprintf(f, "\n");\r
8428                 continue;\r
8429             }\r
8430             if (i >= forwardMostMove) {\r
8431                 fprintf(f, "\n");\r
8432                 break;\r
8433             }\r
8434             fprintf(f, "%s\n", parseList[i]);\r
8435             i++;\r
8436         }\r
8437     }\r
8438     \r
8439     if (commentList[i] != NULL) {\r
8440         fprintf(f, "[%s]\n", commentList[i]);\r
8441     }\r
8442 \r
8443     /* This isn't really the old style, but it's close enough */\r
8444     if (gameInfo.resultDetails != NULL &&\r
8445         gameInfo.resultDetails[0] != NULLCHAR) {\r
8446         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
8447                 gameInfo.resultDetails);\r
8448     } else {\r
8449         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
8450     }\r
8451 \r
8452     fclose(f);\r
8453     return TRUE;\r
8454 }\r
8455 \r
8456 /* Save the current game to open file f and close the file */\r
8457 int\r
8458 SaveGame(f, dummy, dummy2)\r
8459      FILE *f;\r
8460      int dummy;\r
8461      char *dummy2;\r
8462 {\r
8463     if (gameMode == EditPosition) EditPositionDone();\r
8464     if (appData.oldSaveStyle)\r
8465       return SaveGameOldStyle(f);\r
8466     else\r
8467       return SaveGamePGN(f);\r
8468 }\r
8469 \r
8470 /* Save the current position to the given file */\r
8471 int\r
8472 SavePositionToFile(filename)\r
8473      char *filename;\r
8474 {\r
8475     FILE *f;\r
8476     char buf[MSG_SIZ];\r
8477 \r
8478     if (strcmp(filename, "-") == 0) {\r
8479         return SavePosition(stdout, 0, NULL);\r
8480     } else {\r
8481         f = fopen(filename, "a");\r
8482         if (f == NULL) {\r
8483             sprintf(buf, "Can't open \"%s\"", filename);\r
8484             DisplayError(buf, errno);\r
8485             return FALSE;\r
8486         } else {\r
8487             SavePosition(f, 0, NULL);\r
8488             return TRUE;\r
8489         }\r
8490     }\r
8491 }\r
8492 \r
8493 /* Save the current position to the given open file and close the file */\r
8494 int\r
8495 SavePosition(f, dummy, dummy2)\r
8496      FILE *f;\r
8497      int dummy;\r
8498      char *dummy2;\r
8499 {\r
8500     time_t tm;\r
8501     char *fen;\r
8502     \r
8503     if (appData.oldSaveStyle) {\r
8504         tm = time((time_t *) NULL);\r
8505     \r
8506         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
8507         PrintOpponents(f);\r
8508         fprintf(f, "[--------------\n");\r
8509         PrintPosition(f, currentMove);\r
8510         fprintf(f, "--------------]\n");\r
8511     } else {\r
8512         fen = PositionToFEN(currentMove, 1);\r
8513         fprintf(f, "%s\n", fen);\r
8514         free(fen);\r
8515     }\r
8516     fclose(f);\r
8517     return TRUE;\r
8518 }\r
8519 \r
8520 void\r
8521 ReloadCmailMsgEvent(unregister)\r
8522      int unregister;\r
8523 {\r
8524 #if !WIN32\r
8525     static char *inFilename = NULL;\r
8526     static char *outFilename;\r
8527     int i;\r
8528     struct stat inbuf, outbuf;\r
8529     int status;\r
8530     \r
8531     /* Any registered moves are unregistered if unregister is set, */\r
8532     /* i.e. invoked by the signal handler */\r
8533     if (unregister) {\r
8534         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
8535             cmailMoveRegistered[i] = FALSE;\r
8536             if (cmailCommentList[i] != NULL) {\r
8537                 free(cmailCommentList[i]);\r
8538                 cmailCommentList[i] = NULL;\r
8539             }\r
8540         }\r
8541         nCmailMovesRegistered = 0;\r
8542     }\r
8543 \r
8544     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
8545         cmailResult[i] = CMAIL_NOT_RESULT;\r
8546     }\r
8547     nCmailResults = 0;\r
8548 \r
8549     if (inFilename == NULL) {\r
8550         /* Because the filenames are static they only get malloced once  */\r
8551         /* and they never get freed                                      */\r
8552         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
8553         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
8554 \r
8555         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
8556         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
8557     }\r
8558     \r
8559     status = stat(outFilename, &outbuf);\r
8560     if (status < 0) {\r
8561         cmailMailedMove = FALSE;\r
8562     } else {\r
8563         status = stat(inFilename, &inbuf);\r
8564         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
8565     }\r
8566     \r
8567     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
8568        counts the games, notes how each one terminated, etc.\r
8569        \r
8570        It would be nice to remove this kludge and instead gather all\r
8571        the information while building the game list.  (And to keep it\r
8572        in the game list nodes instead of having a bunch of fixed-size\r
8573        parallel arrays.)  Note this will require getting each game's\r
8574        termination from the PGN tags, as the game list builder does\r
8575        not process the game moves.  --mann\r
8576        */\r
8577     cmailMsgLoaded = TRUE;\r
8578     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
8579     \r
8580     /* Load first game in the file or popup game menu */\r
8581     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
8582 \r
8583 #endif /* !WIN32 */\r
8584     return;\r
8585 }\r
8586 \r
8587 int\r
8588 RegisterMove()\r
8589 {\r
8590     FILE *f;\r
8591     char string[MSG_SIZ];\r
8592 \r
8593     if (   cmailMailedMove\r
8594         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
8595         return TRUE;            /* Allow free viewing  */\r
8596     }\r
8597 \r
8598     /* Unregister move to ensure that we don't leave RegisterMove        */\r
8599     /* with the move registered when the conditions for registering no   */\r
8600     /* longer hold                                                       */\r
8601     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8602         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8603         nCmailMovesRegistered --;\r
8604 \r
8605         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
8606           {\r
8607               free(cmailCommentList[lastLoadGameNumber - 1]);\r
8608               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
8609           }\r
8610     }\r
8611 \r
8612     if (cmailOldMove == -1) {\r
8613         DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);\r
8614         return FALSE;\r
8615     }\r
8616 \r
8617     if (currentMove > cmailOldMove + 1) {\r
8618         DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);\r
8619         return FALSE;\r
8620     }\r
8621 \r
8622     if (currentMove < cmailOldMove) {\r
8623         DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);\r
8624         return FALSE;\r
8625     }\r
8626 \r
8627     if (forwardMostMove > currentMove) {\r
8628         /* Silently truncate extra moves */\r
8629         TruncateGame();\r
8630     }\r
8631 \r
8632     if (   (currentMove == cmailOldMove + 1)\r
8633         || (   (currentMove == cmailOldMove)\r
8634             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
8635                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
8636         if (gameInfo.result != GameUnfinished) {\r
8637             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
8638         }\r
8639 \r
8640         if (commentList[currentMove] != NULL) {\r
8641             cmailCommentList[lastLoadGameNumber - 1]\r
8642               = StrSave(commentList[currentMove]);\r
8643         }\r
8644         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
8645 \r
8646         if (appData.debugMode)\r
8647           fprintf(debugFP, "Saving %s for game %d\n",\r
8648                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8649 \r
8650         sprintf(string,\r
8651                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
8652         \r
8653         f = fopen(string, "w");\r
8654         if (appData.oldSaveStyle) {\r
8655             SaveGameOldStyle(f); /* also closes the file */\r
8656             \r
8657             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
8658             f = fopen(string, "w");\r
8659             SavePosition(f, 0, NULL); /* also closes the file */\r
8660         } else {\r
8661             fprintf(f, "{--------------\n");\r
8662             PrintPosition(f, currentMove);\r
8663             fprintf(f, "--------------}\n\n");\r
8664             \r
8665             SaveGame(f, 0, NULL); /* also closes the file*/\r
8666         }\r
8667         \r
8668         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
8669         nCmailMovesRegistered ++;\r
8670     } else if (nCmailGames == 1) {\r
8671         DisplayError("You have not made a move yet", 0);\r
8672         return FALSE;\r
8673     }\r
8674 \r
8675     return TRUE;\r
8676 }\r
8677 \r
8678 void\r
8679 MailMoveEvent()\r
8680 {\r
8681 #if !WIN32\r
8682     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
8683     FILE *commandOutput;\r
8684     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
8685     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
8686     int nBuffers;\r
8687     int i;\r
8688     int archived;\r
8689     char *arcDir;\r
8690 \r
8691     if (! cmailMsgLoaded) {\r
8692         DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);\r
8693         return;\r
8694     }\r
8695 \r
8696     if (nCmailGames == nCmailResults) {\r
8697         DisplayError("No unfinished games", 0);\r
8698         return;\r
8699     }\r
8700 \r
8701 #if CMAIL_PROHIBIT_REMAIL\r
8702     if (cmailMailedMove) {\r
8703         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
8704         DisplayError(msg, 0);\r
8705         return;\r
8706     }\r
8707 #endif\r
8708 \r
8709     if (! (cmailMailedMove || RegisterMove())) return;\r
8710     \r
8711     if (   cmailMailedMove\r
8712         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
8713         sprintf(string, partCommandString,\r
8714                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
8715         commandOutput = popen(string, "rb");\r
8716 \r
8717         if (commandOutput == NULL) {\r
8718             DisplayError("Failed to invoke cmail", 0);\r
8719         } else {\r
8720             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
8721                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
8722             }\r
8723             if (nBuffers > 1) {\r
8724                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
8725                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
8726                 nBytes = MSG_SIZ - 1;\r
8727             } else {\r
8728                 (void) memcpy(msg, buffer, nBytes);\r
8729             }\r
8730             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
8731 \r
8732             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
8733                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
8734 \r
8735                 archived = TRUE;\r
8736                 for (i = 0; i < nCmailGames; i ++) {\r
8737                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
8738                         archived = FALSE;\r
8739                     }\r
8740                 }\r
8741                 if (   archived\r
8742                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
8743                         != NULL)) {\r
8744                     sprintf(buffer, "%s/%s.%s.archive",\r
8745                             arcDir,\r
8746                             appData.cmailGameName,\r
8747                             gameInfo.date);\r
8748                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
8749                     cmailMsgLoaded = FALSE;\r
8750                 }\r
8751             }\r
8752 \r
8753             DisplayInformation(msg);\r
8754             pclose(commandOutput);\r
8755         }\r
8756     } else {\r
8757         if ((*cmailMsg) != '\0') {\r
8758             DisplayInformation(cmailMsg);\r
8759         }\r
8760     }\r
8761 \r
8762     return;\r
8763 #endif /* !WIN32 */\r
8764 }\r
8765 \r
8766 char *\r
8767 CmailMsg()\r
8768 {\r
8769 #if WIN32\r
8770     return NULL;\r
8771 #else\r
8772     int  prependComma = 0;\r
8773     char number[5];\r
8774     char string[MSG_SIZ];       /* Space for game-list */\r
8775     int  i;\r
8776     \r
8777     if (!cmailMsgLoaded) return "";\r
8778 \r
8779     if (cmailMailedMove) {\r
8780         sprintf(cmailMsg, "Waiting for reply from opponent\n");\r
8781     } else {\r
8782         /* Create a list of games left */\r
8783         sprintf(string, "[");\r
8784         for (i = 0; i < nCmailGames; i ++) {\r
8785             if (! (   cmailMoveRegistered[i]\r
8786                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
8787                 if (prependComma) {\r
8788                     sprintf(number, ",%d", i + 1);\r
8789                 } else {\r
8790                     sprintf(number, "%d", i + 1);\r
8791                     prependComma = 1;\r
8792                 }\r
8793                 \r
8794                 strcat(string, number);\r
8795             }\r
8796         }\r
8797         strcat(string, "]");\r
8798 \r
8799         if (nCmailMovesRegistered + nCmailResults == 0) {\r
8800             switch (nCmailGames) {\r
8801               case 1:\r
8802                 sprintf(cmailMsg,\r
8803                         "Still need to make move for game\n");\r
8804                 break;\r
8805                 \r
8806               case 2:\r
8807                 sprintf(cmailMsg,\r
8808                         "Still need to make moves for both games\n");\r
8809                 break;\r
8810                 \r
8811               default:\r
8812                 sprintf(cmailMsg,\r
8813                         "Still need to make moves for all %d games\n",\r
8814                         nCmailGames);\r
8815                 break;\r
8816             }\r
8817         } else {\r
8818             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
8819               case 1:\r
8820                 sprintf(cmailMsg,\r
8821                         "Still need to make a move for game %s\n",\r
8822                         string);\r
8823                 break;\r
8824                 \r
8825               case 0:\r
8826                 if (nCmailResults == nCmailGames) {\r
8827                     sprintf(cmailMsg, "No unfinished games\n");\r
8828                 } else {\r
8829                     sprintf(cmailMsg, "Ready to send mail\n");\r
8830                 }\r
8831                 break;\r
8832                 \r
8833               default:\r
8834                 sprintf(cmailMsg,\r
8835                         "Still need to make moves for games %s\n",\r
8836                         string);\r
8837             }\r
8838         }\r
8839     }\r
8840     return cmailMsg;\r
8841 #endif /* WIN32 */\r
8842 }\r
8843 \r
8844 void\r
8845 ResetGameEvent()\r
8846 {\r
8847     if (gameMode == Training)\r
8848       SetTrainingModeOff();\r
8849 \r
8850     Reset(TRUE, TRUE);\r
8851     cmailMsgLoaded = FALSE;\r
8852     if (appData.icsActive) {\r
8853       SendToICS(ics_prefix);\r
8854       SendToICS("refresh\n");\r
8855     }\r
8856 }\r
8857 \r
8858 static int exiting = 0;\r
8859 \r
8860 void\r
8861 ExitEvent(status)\r
8862      int status;\r
8863 {\r
8864     exiting++;\r
8865     if (exiting > 2) {\r
8866       /* Give up on clean exit */\r
8867       exit(status);\r
8868     }\r
8869     if (exiting > 1) {\r
8870       /* Keep trying for clean exit */\r
8871       return;\r
8872     }\r
8873 \r
8874     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
8875 \r
8876     if (telnetISR != NULL) {\r
8877       RemoveInputSource(telnetISR);\r
8878     }\r
8879     if (icsPR != NoProc) {\r
8880       DestroyChildProcess(icsPR, TRUE);\r
8881     }\r
8882     /* Save game if resource set and not already saved by GameEnds() */\r
8883     if (gameInfo.resultDetails == NULL && forwardMostMove > 0) {\r
8884       if (*appData.saveGameFile != NULLCHAR) {\r
8885         SaveGameToFile(appData.saveGameFile, TRUE);\r
8886       } else if (appData.autoSaveGames) {\r
8887         AutoSaveGame();\r
8888       }\r
8889       if (*appData.savePositionFile != NULLCHAR) {\r
8890         SavePositionToFile(appData.savePositionFile);\r
8891       }\r
8892     }\r
8893     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
8894 \r
8895     /* Kill off chess programs */\r
8896     if (first.pr != NoProc) {\r
8897         ExitAnalyzeMode();\r
8898         \r
8899         DoSleep( appData.delayBeforeQuit );\r
8900         SendToProgram("quit\n", &first);\r
8901         DoSleep( appData.delayAfterQuit );\r
8902         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
8903     }\r
8904     if (second.pr != NoProc) {\r
8905         DoSleep( appData.delayBeforeQuit );\r
8906         SendToProgram("quit\n", &second);\r
8907         DoSleep( appData.delayAfterQuit );\r
8908         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
8909     }\r
8910     if (first.isr != NULL) {\r
8911         RemoveInputSource(first.isr);\r
8912     }\r
8913     if (second.isr != NULL) {\r
8914         RemoveInputSource(second.isr);\r
8915     }\r
8916 \r
8917     ShutDownFrontEnd();\r
8918     exit(status);\r
8919 }\r
8920 \r
8921 void\r
8922 PauseEvent()\r
8923 {\r
8924     if (appData.debugMode)\r
8925         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
8926     if (pausing) {\r
8927         pausing = FALSE;\r
8928         ModeHighlight();\r
8929         if (gameMode == MachinePlaysWhite ||\r
8930             gameMode == MachinePlaysBlack) {\r
8931             StartClocks();\r
8932         } else {\r
8933             DisplayBothClocks();\r
8934         }\r
8935         if (gameMode == PlayFromGameFile) {\r
8936             if (appData.timeDelay >= 0) \r
8937                 AutoPlayGameLoop();\r
8938         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
8939             Reset(FALSE, TRUE);\r
8940             SendToICS(ics_prefix);\r
8941             SendToICS("refresh\n");\r
8942         } else if (currentMove < forwardMostMove) {\r
8943             ForwardInner(forwardMostMove);\r
8944         }\r
8945         pauseExamInvalid = FALSE;\r
8946     } else {\r
8947         switch (gameMode) {\r
8948           default:\r
8949             return;\r
8950           case IcsExamining:\r
8951             pauseExamForwardMostMove = forwardMostMove;\r
8952             pauseExamInvalid = FALSE;\r
8953             /* fall through */\r
8954           case IcsObserving:\r
8955           case IcsPlayingWhite:\r
8956           case IcsPlayingBlack:\r
8957             pausing = TRUE;\r
8958             ModeHighlight();\r
8959             return;\r
8960           case PlayFromGameFile:\r
8961             (void) StopLoadGameTimer();\r
8962             pausing = TRUE;\r
8963             ModeHighlight();\r
8964             break;\r
8965           case BeginningOfGame:\r
8966             if (appData.icsActive) return;\r
8967             /* else fall through */\r
8968           case MachinePlaysWhite:\r
8969           case MachinePlaysBlack:\r
8970           case TwoMachinesPlay:\r
8971             if (forwardMostMove == 0)\r
8972               return;           /* don't pause if no one has moved */\r
8973             if ((gameMode == MachinePlaysWhite &&\r
8974                  !WhiteOnMove(forwardMostMove)) ||\r
8975                 (gameMode == MachinePlaysBlack &&\r
8976                  WhiteOnMove(forwardMostMove))) {\r
8977                 StopClocks();\r
8978             }\r
8979             pausing = TRUE;\r
8980             ModeHighlight();\r
8981             break;\r
8982         }\r
8983     }\r
8984 }\r
8985 \r
8986 void\r
8987 EditCommentEvent()\r
8988 {\r
8989     char title[MSG_SIZ];\r
8990 \r
8991     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
8992         strcpy(title, "Edit comment");\r
8993     } else {\r
8994         sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,\r
8995                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
8996                 parseList[currentMove - 1]);\r
8997     }\r
8998 \r
8999     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
9000 }\r
9001 \r
9002 \r
9003 void\r
9004 EditTagsEvent()\r
9005 {\r
9006     char *tags = PGNTags(&gameInfo);\r
9007     EditTagsPopUp(tags);\r
9008     free(tags);\r
9009 }\r
9010 \r
9011 void\r
9012 AnalyzeModeEvent()\r
9013 {\r
9014     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
9015       return;\r
9016 \r
9017     if (gameMode != AnalyzeFile) {\r
9018         EditGameEvent();\r
9019         if (gameMode != EditGame) return;\r
9020         ResurrectChessProgram();\r
9021         SendToProgram("analyze\n", &first);\r
9022         first.analyzing = TRUE;\r
9023         /*first.maybeThinking = TRUE;*/\r
9024         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
9025         AnalysisPopUp("Analysis",\r
9026                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
9027     }\r
9028     gameMode = AnalyzeMode;\r
9029     pausing = FALSE;\r
9030     ModeHighlight();\r
9031     SetGameInfo();\r
9032 \r
9033     StartAnalysisClock();\r
9034     GetTimeMark(&lastNodeCountTime);\r
9035     lastNodeCount = 0;\r
9036 }\r
9037 \r
9038 void\r
9039 AnalyzeFileEvent()\r
9040 {\r
9041     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
9042       return;\r
9043 \r
9044     if (gameMode != AnalyzeMode) {\r
9045         EditGameEvent();\r
9046         if (gameMode != EditGame) return;\r
9047         ResurrectChessProgram();\r
9048         SendToProgram("analyze\n", &first);\r
9049         first.analyzing = TRUE;\r
9050         /*first.maybeThinking = TRUE;*/\r
9051         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
9052         AnalysisPopUp("Analysis",\r
9053                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
9054     }\r
9055     gameMode = AnalyzeFile;\r
9056     pausing = FALSE;\r
9057     ModeHighlight();\r
9058     SetGameInfo();\r
9059 \r
9060     StartAnalysisClock();\r
9061     GetTimeMark(&lastNodeCountTime);\r
9062     lastNodeCount = 0;\r
9063 }\r
9064 \r
9065 void\r
9066 MachineWhiteEvent()\r
9067 {\r
9068     char buf[MSG_SIZ];\r
9069 \r
9070     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
9071       return;\r
9072 \r
9073 \r
9074     if (gameMode == PlayFromGameFile || \r
9075         gameMode == TwoMachinesPlay  || \r
9076         gameMode == Training         || \r
9077         gameMode == AnalyzeMode      || \r
9078         gameMode == EndOfGame)\r
9079         EditGameEvent();\r
9080 \r
9081     if (gameMode == EditPosition) \r
9082         EditPositionDone();\r
9083 \r
9084     if (!WhiteOnMove(currentMove)) {\r
9085         DisplayError("It is not White's turn", 0);\r
9086         return;\r
9087     }\r
9088   \r
9089     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
9090       ExitAnalyzeMode();\r
9091 \r
9092     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
9093         gameMode == AnalyzeFile)\r
9094         TruncateGame();\r
9095 \r
9096     ResurrectChessProgram();    /* in case it isn't running */\r
9097     gameMode = MachinePlaysWhite;\r
9098     pausing = FALSE;\r
9099     ModeHighlight();\r
9100     SetGameInfo();\r
9101     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
9102     DisplayTitle(buf);\r
9103     if (first.sendName) {\r
9104       sprintf(buf, "name %s\n", gameInfo.black);\r
9105       SendToProgram(buf, &first);\r
9106     }\r
9107     if (first.sendTime) {\r
9108       if (first.useColors) {\r
9109         SendToProgram("black\n", &first); /*gnu kludge*/\r
9110       }\r
9111       SendTimeRemaining(&first, TRUE);\r
9112     }\r
9113     if (first.useColors) {\r
9114       SendToProgram("white\ngo\n", &first);\r
9115     } else {\r
9116       SendToProgram("go\n", &first);\r
9117     }\r
9118     SetMachineThinkingEnables();\r
9119     first.maybeThinking = TRUE;\r
9120     StartClocks();\r
9121 \r
9122     if (appData.autoFlipView && !flipView) {\r
9123       flipView = !flipView;\r
9124       DrawPosition(FALSE, NULL);\r
9125     }\r
9126 }\r
9127 \r
9128 void\r
9129 MachineBlackEvent()\r
9130 {\r
9131     char buf[MSG_SIZ];\r
9132 \r
9133     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
9134         return;\r
9135 \r
9136 \r
9137     if (gameMode == PlayFromGameFile || \r
9138         gameMode == TwoMachinesPlay  || \r
9139         gameMode == Training         || \r
9140         gameMode == AnalyzeMode      || \r
9141         gameMode == EndOfGame)\r
9142         EditGameEvent();\r
9143 \r
9144     if (gameMode == EditPosition) \r
9145         EditPositionDone();\r
9146 \r
9147     if (WhiteOnMove(currentMove)) {\r
9148         DisplayError("It is not Black's turn", 0);\r
9149         return;\r
9150     }\r
9151     \r
9152     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
9153       ExitAnalyzeMode();\r
9154 \r
9155     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
9156         gameMode == AnalyzeFile)\r
9157         TruncateGame();\r
9158 \r
9159     ResurrectChessProgram();    /* in case it isn't running */\r
9160     gameMode = MachinePlaysBlack;\r
9161     pausing = FALSE;\r
9162     ModeHighlight();\r
9163     SetGameInfo();\r
9164     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
9165     DisplayTitle(buf);\r
9166     if (first.sendName) {\r
9167       sprintf(buf, "name %s\n", gameInfo.white);\r
9168       SendToProgram(buf, &first);\r
9169     }\r
9170     if (first.sendTime) {\r
9171       if (first.useColors) {\r
9172         SendToProgram("white\n", &first); /*gnu kludge*/\r
9173       }\r
9174       SendTimeRemaining(&first, FALSE);\r
9175     }\r
9176     if (first.useColors) {\r
9177       SendToProgram("black\ngo\n", &first);\r
9178     } else {\r
9179       SendToProgram("go\n", &first);\r
9180     }\r
9181     SetMachineThinkingEnables();\r
9182     first.maybeThinking = TRUE;\r
9183     StartClocks();\r
9184 \r
9185     if (appData.autoFlipView && flipView) {\r
9186       flipView = !flipView;\r
9187       DrawPosition(FALSE, NULL);\r
9188     }\r
9189 }\r
9190 \r
9191 \r
9192 void\r
9193 DisplayTwoMachinesTitle()\r
9194 {\r
9195     char buf[MSG_SIZ];\r
9196     if (appData.matchGames > 0) {\r
9197         if (first.twoMachinesColor[0] == 'w') {\r
9198             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
9199                     gameInfo.white, gameInfo.black,\r
9200                     first.matchWins, second.matchWins,\r
9201                     matchGame - 1 - (first.matchWins + second.matchWins));\r
9202         } else {\r
9203             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
9204                     gameInfo.white, gameInfo.black,\r
9205                     second.matchWins, first.matchWins,\r
9206                     matchGame - 1 - (first.matchWins + second.matchWins));\r
9207         }\r
9208     } else {\r
9209         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
9210     }\r
9211     DisplayTitle(buf);\r
9212 }\r
9213 \r
9214 void\r
9215 TwoMachinesEvent P((void))\r
9216 {\r
9217     int i;\r
9218     char buf[MSG_SIZ];\r
9219     ChessProgramState *onmove;\r
9220     \r
9221     if (appData.noChessProgram) return;\r
9222 \r
9223     switch (gameMode) {\r
9224       case TwoMachinesPlay:\r
9225         return;\r
9226       case MachinePlaysWhite:\r
9227       case MachinePlaysBlack:\r
9228         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
9229             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
9230             return;\r
9231         }\r
9232         /* fall through */\r
9233       case BeginningOfGame:\r
9234       case PlayFromGameFile:\r
9235       case EndOfGame:\r
9236         EditGameEvent();\r
9237         if (gameMode != EditGame) return;\r
9238         break;\r
9239       case EditPosition:\r
9240         EditPositionDone();\r
9241         break;\r
9242       case AnalyzeMode:\r
9243       case AnalyzeFile:\r
9244         ExitAnalyzeMode();\r
9245         break;\r
9246       case EditGame:\r
9247       default:\r
9248         break;\r
9249     }\r
9250 \r
9251     forwardMostMove = currentMove;\r
9252     ResurrectChessProgram();    /* in case first program isn't running */\r
9253 \r
9254     if (second.pr == NULL) {\r
9255         StartChessProgram(&second);\r
9256         if (second.protocolVersion == 1) {\r
9257           TwoMachinesEventIfReady();\r
9258         } else {\r
9259           /* kludge: allow timeout for initial "feature" command */\r
9260           FreezeUI();\r
9261           DisplayMessage("", "Starting second chess program");\r
9262           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
9263         }\r
9264         return;\r
9265     }\r
9266     DisplayMessage("", "");\r
9267     InitChessProgram(&second);\r
9268     SendToProgram("force\n", &second);\r
9269     if (startedFromSetupPosition) {\r
9270         SendBoard(&second, backwardMostMove);\r
9271     }\r
9272     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
9273         SendMoveToProgram(i, &second);\r
9274     }\r
9275 \r
9276     gameMode = TwoMachinesPlay;\r
9277     pausing = FALSE;\r
9278     ModeHighlight();\r
9279     SetGameInfo();\r
9280     DisplayTwoMachinesTitle();\r
9281     firstMove = TRUE;\r
9282     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
9283         onmove = &first;\r
9284     } else {\r
9285         onmove = &second;\r
9286     }\r
9287 \r
9288     SendToProgram(first.computerString, &first);\r
9289     if (first.sendName) {\r
9290       sprintf(buf, "name %s\n", second.tidy);\r
9291       SendToProgram(buf, &first);\r
9292     }\r
9293     SendToProgram(second.computerString, &second);\r
9294     if (second.sendName) {\r
9295       sprintf(buf, "name %s\n", first.tidy);\r
9296       SendToProgram(buf, &second);\r
9297     }\r
9298 \r
9299     if (!first.sendTime || !second.sendTime) {\r
9300         ResetClocks();\r
9301         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9302         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9303     }\r
9304     if (onmove->sendTime) {\r
9305       if (onmove->useColors) {\r
9306         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
9307       }\r
9308       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
9309     }\r
9310     if (onmove->useColors) {\r
9311       SendToProgram(onmove->twoMachinesColor, onmove);\r
9312     }\r
9313     SendToProgram("go\n", onmove);\r
9314     onmove->maybeThinking = TRUE;\r
9315     SetMachineThinkingEnables();\r
9316 \r
9317     StartClocks();\r
9318 }\r
9319 \r
9320 void\r
9321 TrainingEvent()\r
9322 {\r
9323     if (gameMode == Training) {\r
9324       SetTrainingModeOff();\r
9325       gameMode = PlayFromGameFile;\r
9326       DisplayMessage("", "Training mode off");\r
9327     } else {\r
9328       gameMode = Training;\r
9329       animateTraining = appData.animate;\r
9330 \r
9331       /* make sure we are not already at the end of the game */\r
9332       if (currentMove < forwardMostMove) {\r
9333         SetTrainingModeOn();\r
9334         DisplayMessage("", "Training mode on");\r
9335       } else {\r
9336         gameMode = PlayFromGameFile;\r
9337         DisplayError("Already at end of game", 0);\r
9338       }\r
9339     }\r
9340     ModeHighlight();\r
9341 }\r
9342 \r
9343 void\r
9344 IcsClientEvent()\r
9345 {\r
9346     if (!appData.icsActive) return;\r
9347     switch (gameMode) {\r
9348       case IcsPlayingWhite:\r
9349       case IcsPlayingBlack:\r
9350       case IcsObserving:\r
9351       case IcsIdle:\r
9352       case BeginningOfGame:\r
9353       case IcsExamining:\r
9354         return;\r
9355 \r
9356       case EditGame:\r
9357         break;\r
9358 \r
9359       case EditPosition:\r
9360         EditPositionDone();\r
9361         break;\r
9362 \r
9363       case AnalyzeMode:\r
9364       case AnalyzeFile:\r
9365         ExitAnalyzeMode();\r
9366         break;\r
9367         \r
9368       default:\r
9369         EditGameEvent();\r
9370         break;\r
9371     }\r
9372 \r
9373     gameMode = IcsIdle;\r
9374     ModeHighlight();\r
9375     return;\r
9376 }\r
9377 \r
9378 \r
9379 void\r
9380 EditGameEvent()\r
9381 {\r
9382     int i;\r
9383 \r
9384     switch (gameMode) {\r
9385       case Training:\r
9386         SetTrainingModeOff();\r
9387         break;\r
9388       case MachinePlaysWhite:\r
9389       case MachinePlaysBlack:\r
9390       case BeginningOfGame:\r
9391         SendToProgram("force\n", &first);\r
9392         SetUserThinkingEnables();\r
9393         break;\r
9394       case PlayFromGameFile:\r
9395         (void) StopLoadGameTimer();\r
9396         if (gameFileFP != NULL) {\r
9397             gameFileFP = NULL;\r
9398         }\r
9399         break;\r
9400       case EditPosition:\r
9401         EditPositionDone();\r
9402         break;\r
9403       case AnalyzeMode:\r
9404       case AnalyzeFile:\r
9405         ExitAnalyzeMode();\r
9406         SendToProgram("force\n", &first);\r
9407         break;\r
9408       case TwoMachinesPlay:\r
9409         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
9410         ResurrectChessProgram();\r
9411         SetUserThinkingEnables();\r
9412         break;\r
9413       case EndOfGame:\r
9414         ResurrectChessProgram();\r
9415         break;\r
9416       case IcsPlayingBlack:\r
9417       case IcsPlayingWhite:\r
9418         DisplayError("Warning: You are still playing a game", 0);\r
9419         break;\r
9420       case IcsObserving:\r
9421         DisplayError("Warning: You are still observing a game", 0);\r
9422         break;\r
9423       case IcsExamining:\r
9424         DisplayError("Warning: You are still examining a game", 0);\r
9425         break;\r
9426       case IcsIdle:\r
9427         break;\r
9428       case EditGame:\r
9429       default:\r
9430         return;\r
9431     }\r
9432     \r
9433     pausing = FALSE;\r
9434     StopClocks();\r
9435     first.offeredDraw = second.offeredDraw = 0;\r
9436 \r
9437     if (gameMode == PlayFromGameFile) {\r
9438         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9439         blackTimeRemaining = timeRemaining[1][currentMove];\r
9440         DisplayTitle("");\r
9441     }\r
9442 \r
9443     if (gameMode == MachinePlaysWhite ||\r
9444         gameMode == MachinePlaysBlack ||\r
9445         gameMode == TwoMachinesPlay ||\r
9446         gameMode == EndOfGame) {\r
9447         i = forwardMostMove;\r
9448         while (i > currentMove) {\r
9449             SendToProgram("undo\n", &first);\r
9450             i--;\r
9451         }\r
9452         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9453         blackTimeRemaining = timeRemaining[1][currentMove];\r
9454         DisplayBothClocks();\r
9455         if (whiteFlag || blackFlag) {\r
9456             whiteFlag = blackFlag = 0;\r
9457         }\r
9458         DisplayTitle("");\r
9459     }           \r
9460     \r
9461     gameMode = EditGame;\r
9462     ModeHighlight();\r
9463     SetGameInfo();\r
9464 }\r
9465 \r
9466 \r
9467 void\r
9468 EditPositionEvent()\r
9469 {\r
9470     if (gameMode == EditPosition) {\r
9471         EditGameEvent();\r
9472         return;\r
9473     }\r
9474     \r
9475     EditGameEvent();\r
9476     if (gameMode != EditGame) return;\r
9477     \r
9478     gameMode = EditPosition;\r
9479     ModeHighlight();\r
9480     SetGameInfo();\r
9481     if (currentMove > 0)\r
9482       CopyBoard(boards[0], boards[currentMove]);\r
9483     \r
9484     blackPlaysFirst = !WhiteOnMove(currentMove);\r
9485     ResetClocks();\r
9486     currentMove = forwardMostMove = backwardMostMove = 0;\r
9487     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9488     DisplayMove(-1);\r
9489 }\r
9490 \r
9491 void\r
9492 ExitAnalyzeMode()\r
9493 {\r
9494     if (first.analysisSupport && first.analyzing) {\r
9495       SendToProgram("exit\n", &first);\r
9496       first.analyzing = FALSE;\r
9497     }\r
9498     AnalysisPopDown();\r
9499     thinkOutput[0] = NULLCHAR;\r
9500 }\r
9501 \r
9502 void\r
9503 EditPositionDone()\r
9504 {\r
9505     startedFromSetupPosition = TRUE;\r
9506     InitChessProgram(&first);\r
9507     SendToProgram("force\n", &first);\r
9508     if (blackPlaysFirst) {\r
9509         strcpy(moveList[0], "");\r
9510         strcpy(parseList[0], "");\r
9511         currentMove = forwardMostMove = backwardMostMove = 1;\r
9512         CopyBoard(boards[1], boards[0]);\r
9513     } else {\r
9514         currentMove = forwardMostMove = backwardMostMove = 0;\r
9515     }\r
9516     SendBoard(&first, forwardMostMove);\r
9517     DisplayTitle("");\r
9518     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9519     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9520     gameMode = EditGame;\r
9521     ModeHighlight();\r
9522     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9523     ClearHighlights(); /* [AS] */\r
9524 }\r
9525 \r
9526 /* Pause for `ms' milliseconds */\r
9527 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
9528 void\r
9529 TimeDelay(ms)\r
9530      long ms;\r
9531 {\r
9532     TimeMark m1, m2;\r
9533 \r
9534     GetTimeMark(&m1);\r
9535     do {\r
9536         GetTimeMark(&m2);\r
9537     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
9538 }\r
9539 \r
9540 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
9541 void\r
9542 SendMultiLineToICS(buf)\r
9543      char *buf;\r
9544 {\r
9545     char temp[MSG_SIZ+1], *p;\r
9546     int len;\r
9547 \r
9548     len = strlen(buf);\r
9549     if (len > MSG_SIZ)\r
9550       len = MSG_SIZ;\r
9551   \r
9552     strncpy(temp, buf, len);\r
9553     temp[len] = 0;\r
9554 \r
9555     p = temp;\r
9556     while (*p) {\r
9557         if (*p == '\n' || *p == '\r')\r
9558           *p = ' ';\r
9559         ++p;\r
9560     }\r
9561 \r
9562     strcat(temp, "\n");\r
9563     SendToICS(temp);\r
9564     SendToPlayer(temp, strlen(temp));\r
9565 }\r
9566 \r
9567 void\r
9568 SetWhiteToPlayEvent()\r
9569 {\r
9570     if (gameMode == EditPosition) {\r
9571         blackPlaysFirst = FALSE;\r
9572         DisplayBothClocks();    /* works because currentMove is 0 */\r
9573     } else if (gameMode == IcsExamining) {\r
9574         SendToICS(ics_prefix);\r
9575         SendToICS("tomove white\n");\r
9576     }\r
9577 }\r
9578 \r
9579 void\r
9580 SetBlackToPlayEvent()\r
9581 {\r
9582     if (gameMode == EditPosition) {\r
9583         blackPlaysFirst = TRUE;\r
9584         currentMove = 1;        /* kludge */\r
9585         DisplayBothClocks();\r
9586         currentMove = 0;\r
9587     } else if (gameMode == IcsExamining) {\r
9588         SendToICS(ics_prefix);\r
9589         SendToICS("tomove black\n");\r
9590     }\r
9591 }\r
9592 \r
9593 void\r
9594 EditPositionMenuEvent(selection, x, y)\r
9595      ChessSquare selection;\r
9596      int x, y;\r
9597 {\r
9598     char buf[MSG_SIZ];\r
9599     ChessSquare piece = boards[0][y][x];\r
9600 \r
9601     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
9602 \r
9603     switch (selection) {\r
9604       case ClearBoard:\r
9605         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
9606             SendToICS(ics_prefix);\r
9607             SendToICS("bsetup clear\n");\r
9608         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
9609             SendToICS(ics_prefix);\r
9610             SendToICS("clearboard\n");\r
9611         } else {\r
9612             for (x = 0; x < BOARD_WIDTH; x++) {\r
9613                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
9614                     if (gameMode == IcsExamining) {\r
9615                         if (boards[currentMove][y][x] != EmptySquare) {\r
9616                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
9617                                     AAA + x, ONE + y);\r
9618                             SendToICS(buf);\r
9619                         }\r
9620                     } else {\r
9621                         boards[0][y][x] = EmptySquare;\r
9622                     }\r
9623                 }\r
9624             }\r
9625         }\r
9626         if (gameMode == EditPosition) {\r
9627             DrawPosition(FALSE, boards[0]);\r
9628         }\r
9629         break;\r
9630 \r
9631       case WhitePlay:\r
9632         SetWhiteToPlayEvent();\r
9633         break;\r
9634 \r
9635       case BlackPlay:\r
9636         SetBlackToPlayEvent();\r
9637         break;\r
9638 \r
9639       case EmptySquare:\r
9640         if (gameMode == IcsExamining) {\r
9641             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
9642             SendToICS(buf);\r
9643         } else {\r
9644             boards[0][y][x] = EmptySquare;\r
9645             DrawPosition(FALSE, boards[0]);\r
9646         }\r
9647         break;\r
9648 \r
9649       case PromotePiece:\r
9650         if(piece >= (int)WhitePawn && piece < (int)WhiteWazir ||\r
9651            piece >= (int)BlackPawn && piece < (int)BlackWazir   ) {\r
9652             selection = (ChessSquare) (PROMOTED piece);\r
9653         } else if(piece == EmptySquare) selection = WhiteWazir;\r
9654         else selection = (ChessSquare)((int)piece - 1);\r
9655         goto defaultlabel;\r
9656 \r
9657       case DemotePiece:\r
9658         if(piece >= (int)WhiteUnicorn && piece < (int)WhiteKing ||\r
9659            piece >= (int)BlackUnicorn && piece < (int)BlackKing   ) {\r
9660             selection = (ChessSquare) (DEMOTED piece);\r
9661         } else if( piece == WhiteKing || piece == BlackKing )\r
9662             selection = (ChessSquare)((int)piece - (int)WhiteKing + (int)WhiteMan);\r
9663         else if(piece == EmptySquare) selection = BlackWazir;\r
9664         else selection = (ChessSquare)((int)piece + 1);       \r
9665         goto defaultlabel;\r
9666 \r
9667       case WhiteQueen:\r
9668       case BlackQueen:\r
9669         if(gameInfo.variant == VariantShatranj ||\r
9670            gameInfo.variant == VariantXiangqi  ||\r
9671            gameInfo.variant == VariantCourier    )\r
9672             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
9673         goto defaultlabel;\r
9674 \r
9675       case WhiteKing:\r
9676       case BlackKing:\r
9677         if(gameInfo.variant == VariantXiangqi)\r
9678             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
9679         if(gameInfo.variant == VariantKnightmate)\r
9680             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
9681       default:\r
9682         defaultlabel:\r
9683         if (gameMode == IcsExamining) {\r
9684             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
9685                     PieceToChar(selection), AAA + x, ONE + y);\r
9686             SendToICS(buf);\r
9687         } else {\r
9688             boards[0][y][x] = selection;\r
9689             DrawPosition(FALSE, boards[0]);\r
9690         }\r
9691         break;\r
9692     }\r
9693 }\r
9694 \r
9695 \r
9696 void\r
9697 DropMenuEvent(selection, x, y)\r
9698      ChessSquare selection;\r
9699      int x, y;\r
9700 {\r
9701     ChessMove moveType;\r
9702 \r
9703     switch (gameMode) {\r
9704       case IcsPlayingWhite:\r
9705       case MachinePlaysBlack:\r
9706         if (!WhiteOnMove(currentMove)) {\r
9707             DisplayMoveError("It is Black's turn");\r
9708             return;\r
9709         }\r
9710         moveType = WhiteDrop;\r
9711         break;\r
9712       case IcsPlayingBlack:\r
9713       case MachinePlaysWhite:\r
9714         if (WhiteOnMove(currentMove)) {\r
9715             DisplayMoveError("It is White's turn");\r
9716             return;\r
9717         }\r
9718         moveType = BlackDrop;\r
9719         break;\r
9720       case EditGame:\r
9721         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
9722         break;\r
9723       default:\r
9724         return;\r
9725     }\r
9726 \r
9727     if (moveType == BlackDrop && selection < BlackPawn) {\r
9728       selection = (ChessSquare) ((int) selection\r
9729                                  + (int) BlackPawn - (int) WhitePawn);\r
9730     }\r
9731     if (boards[currentMove][y][x] != EmptySquare) {\r
9732         DisplayMoveError("That square is occupied");\r
9733         return;\r
9734     }\r
9735 \r
9736     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
9737 }\r
9738 \r
9739 void\r
9740 AcceptEvent()\r
9741 {\r
9742     /* Accept a pending offer of any kind from opponent */\r
9743     \r
9744     if (appData.icsActive) {\r
9745         SendToICS(ics_prefix);\r
9746         SendToICS("accept\n");\r
9747     } else if (cmailMsgLoaded) {\r
9748         if (currentMove == cmailOldMove &&\r
9749             commentList[cmailOldMove] != NULL &&\r
9750             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
9751                    "Black offers a draw" : "White offers a draw")) {\r
9752             TruncateGame();\r
9753             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
9754             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
9755         } else {\r
9756             DisplayError("There is no pending offer on this move", 0);\r
9757             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
9758         }\r
9759     } else {\r
9760         /* Not used for offers from chess program */\r
9761     }\r
9762 }\r
9763 \r
9764 void\r
9765 DeclineEvent()\r
9766 {\r
9767     /* Decline a pending offer of any kind from opponent */\r
9768     \r
9769     if (appData.icsActive) {\r
9770         SendToICS(ics_prefix);\r
9771         SendToICS("decline\n");\r
9772     } else if (cmailMsgLoaded) {\r
9773         if (currentMove == cmailOldMove &&\r
9774             commentList[cmailOldMove] != NULL &&\r
9775             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
9776                    "Black offers a draw" : "White offers a draw")) {\r
9777 #ifdef NOTDEF\r
9778             AppendComment(cmailOldMove, "Draw declined");\r
9779             DisplayComment(cmailOldMove - 1, "Draw declined");\r
9780 #endif /*NOTDEF*/\r
9781         } else {\r
9782             DisplayError("There is no pending offer on this move", 0);\r
9783         }\r
9784     } else {\r
9785         /* Not used for offers from chess program */\r
9786     }\r
9787 }\r
9788 \r
9789 void\r
9790 RematchEvent()\r
9791 {\r
9792     /* Issue ICS rematch command */\r
9793     if (appData.icsActive) {\r
9794         SendToICS(ics_prefix);\r
9795         SendToICS("rematch\n");\r
9796     }\r
9797 }\r
9798 \r
9799 void\r
9800 CallFlagEvent()\r
9801 {\r
9802     /* Call your opponent's flag (claim a win on time) */\r
9803     if (appData.icsActive) {\r
9804         SendToICS(ics_prefix);\r
9805         SendToICS("flag\n");\r
9806     } else {\r
9807         switch (gameMode) {\r
9808           default:\r
9809             return;\r
9810           case MachinePlaysWhite:\r
9811             if (whiteFlag) {\r
9812                 if (blackFlag)\r
9813                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
9814                            GE_PLAYER);\r
9815                 else\r
9816                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
9817             } else {\r
9818                 DisplayError("Your opponent is not out of time", 0);\r
9819             }\r
9820             break;\r
9821           case MachinePlaysBlack:\r
9822             if (blackFlag) {\r
9823                 if (whiteFlag)\r
9824                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
9825                            GE_PLAYER);\r
9826                 else\r
9827                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
9828             } else {\r
9829                 DisplayError("Your opponent is not out of time", 0);\r
9830             }\r
9831             break;\r
9832         }\r
9833     }\r
9834 }\r
9835 \r
9836 void\r
9837 DrawEvent()\r
9838 {\r
9839     /* Offer draw or accept pending draw offer from opponent */\r
9840     \r
9841     if (appData.icsActive) {\r
9842         /* Note: tournament rules require draw offers to be\r
9843            made after you make your move but before you punch\r
9844            your clock.  Currently ICS doesn't let you do that;\r
9845            instead, you immediately punch your clock after making\r
9846            a move, but you can offer a draw at any time. */\r
9847         \r
9848         SendToICS(ics_prefix);\r
9849         SendToICS("draw\n");\r
9850     } else if (cmailMsgLoaded) {\r
9851         if (currentMove == cmailOldMove &&\r
9852             commentList[cmailOldMove] != NULL &&\r
9853             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
9854                    "Black offers a draw" : "White offers a draw")) {\r
9855             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
9856             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
9857         } else if (currentMove == cmailOldMove + 1) {\r
9858             char *offer = WhiteOnMove(cmailOldMove) ?\r
9859               "White offers a draw" : "Black offers a draw";\r
9860             AppendComment(currentMove, offer);\r
9861             DisplayComment(currentMove - 1, offer);\r
9862             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
9863         } else {\r
9864             DisplayError("You must make your move before offering a draw", 0);\r
9865             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
9866         }\r
9867     } else if (first.offeredDraw) {\r
9868         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
9869     } else {\r
9870         if (first.sendDrawOffers) {\r
9871             SendToProgram("draw\n", &first);\r
9872             userOfferedDraw = TRUE;\r
9873         }\r
9874     }\r
9875 }\r
9876 \r
9877 void\r
9878 AdjournEvent()\r
9879 {\r
9880     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
9881     \r
9882     if (appData.icsActive) {\r
9883         SendToICS(ics_prefix);\r
9884         SendToICS("adjourn\n");\r
9885     } else {\r
9886         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
9887     }\r
9888 }\r
9889 \r
9890 \r
9891 void\r
9892 AbortEvent()\r
9893 {\r
9894     /* Offer Abort or accept pending Abort offer from opponent */\r
9895     \r
9896     if (appData.icsActive) {\r
9897         SendToICS(ics_prefix);\r
9898         SendToICS("abort\n");\r
9899     } else {\r
9900         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
9901     }\r
9902 }\r
9903 \r
9904 void\r
9905 ResignEvent()\r
9906 {\r
9907     /* Resign.  You can do this even if it's not your turn. */\r
9908     \r
9909     if (appData.icsActive) {\r
9910         SendToICS(ics_prefix);\r
9911         SendToICS("resign\n");\r
9912     } else {\r
9913         switch (gameMode) {\r
9914           case MachinePlaysWhite:\r
9915             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
9916             break;\r
9917           case MachinePlaysBlack:\r
9918             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
9919             break;\r
9920           case EditGame:\r
9921             if (cmailMsgLoaded) {\r
9922                 TruncateGame();\r
9923                 if (WhiteOnMove(cmailOldMove)) {\r
9924                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
9925                 } else {\r
9926                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
9927                 }\r
9928                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
9929             }\r
9930             break;\r
9931           default:\r
9932             break;\r
9933         }\r
9934     }\r
9935 }\r
9936 \r
9937 \r
9938 void\r
9939 StopObservingEvent()\r
9940 {\r
9941     /* Stop observing current games */\r
9942     SendToICS(ics_prefix);\r
9943     SendToICS("unobserve\n");\r
9944 }\r
9945 \r
9946 void\r
9947 StopExaminingEvent()\r
9948 {\r
9949     /* Stop observing current game */\r
9950     SendToICS(ics_prefix);\r
9951     SendToICS("unexamine\n");\r
9952 }\r
9953 \r
9954 void\r
9955 ForwardInner(target)\r
9956      int target;\r
9957 {\r
9958     int limit;\r
9959 \r
9960     if (appData.debugMode)\r
9961         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
9962                 target, currentMove, forwardMostMove);\r
9963 \r
9964     if (gameMode == EditPosition)\r
9965       return;\r
9966 \r
9967     if (gameMode == PlayFromGameFile && !pausing)\r
9968       PauseEvent();\r
9969     \r
9970     if (gameMode == IcsExamining && pausing)\r
9971       limit = pauseExamForwardMostMove;\r
9972     else\r
9973       limit = forwardMostMove;\r
9974     \r
9975     if (target > limit) target = limit;\r
9976 \r
9977     if (target > 0 && moveList[target - 1][0]) {\r
9978         int fromX, fromY, toX, toY;\r
9979         toX = moveList[target - 1][2] - AAA;\r
9980         toY = moveList[target - 1][3] - ONE;\r
9981         if (moveList[target - 1][1] == '@') {\r
9982             if (appData.highlightLastMove) {\r
9983                 SetHighlights(-1, -1, toX, toY);\r
9984             }\r
9985         } else {\r
9986             fromX = moveList[target - 1][0] - AAA;\r
9987             fromY = moveList[target - 1][1] - ONE;\r
9988             if (target == currentMove + 1) {\r
9989                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
9990             }\r
9991             if (appData.highlightLastMove) {\r
9992                 SetHighlights(fromX, fromY, toX, toY);\r
9993             }\r
9994         }\r
9995     }\r
9996     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
9997         gameMode == Training || gameMode == PlayFromGameFile || \r
9998         gameMode == AnalyzeFile) {\r
9999         while (currentMove < target) {\r
10000             SendMoveToProgram(currentMove++, &first);\r
10001         }\r
10002     } else {\r
10003         currentMove = target;\r
10004     }\r
10005     \r
10006     if (gameMode == EditGame || gameMode == EndOfGame) {\r
10007         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10008         blackTimeRemaining = timeRemaining[1][currentMove];\r
10009     }\r
10010     DisplayBothClocks();\r
10011     DisplayMove(currentMove - 1);\r
10012     DrawPosition(FALSE, boards[currentMove]);\r
10013     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
10014     if (commentList[currentMove] && !matchMode && gameMode != Training) {\r
10015         DisplayComment(currentMove - 1, commentList[currentMove]);\r
10016     }\r
10017 }\r
10018 \r
10019 \r
10020 void\r
10021 ForwardEvent()\r
10022 {\r
10023     if (gameMode == IcsExamining && !pausing) {\r
10024         SendToICS(ics_prefix);\r
10025         SendToICS("forward\n");\r
10026     } else {\r
10027         ForwardInner(currentMove + 1);\r
10028     }\r
10029 }\r
10030 \r
10031 void\r
10032 ToEndEvent()\r
10033 {\r
10034     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10035         /* to optimze, we temporarily turn off analysis mode while we feed\r
10036          * the remaining moves to the engine. Otherwise we get analysis output\r
10037          * after each move.\r
10038          */ \r
10039         if (first.analysisSupport) {\r
10040           SendToProgram("exit\nforce\n", &first);\r
10041           first.analyzing = FALSE;\r
10042         }\r
10043     }\r
10044         \r
10045     if (gameMode == IcsExamining && !pausing) {\r
10046         SendToICS(ics_prefix);\r
10047         SendToICS("forward 999999\n");\r
10048     } else {\r
10049         ForwardInner(forwardMostMove);\r
10050     }\r
10051 \r
10052     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10053         /* we have fed all the moves, so reactivate analysis mode */\r
10054         SendToProgram("analyze\n", &first);\r
10055         first.analyzing = TRUE;\r
10056         /*first.maybeThinking = TRUE;*/\r
10057         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10058     }\r
10059 }\r
10060 \r
10061 void\r
10062 BackwardInner(target)\r
10063      int target;\r
10064 {\r
10065     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
10066 \r
10067     if (appData.debugMode)\r
10068         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
10069                 target, currentMove, forwardMostMove);\r
10070 \r
10071     if (gameMode == EditPosition) return;\r
10072     if (currentMove <= backwardMostMove) {\r
10073         ClearHighlights();\r
10074         DrawPosition(full_redraw, boards[currentMove]);\r
10075         return;\r
10076     }\r
10077     if (gameMode == PlayFromGameFile && !pausing)\r
10078       PauseEvent();\r
10079     \r
10080     if (moveList[target][0]) {\r
10081         int fromX, fromY, toX, toY;\r
10082         toX = moveList[target][2] - AAA;\r
10083         toY = moveList[target][3] - ONE;\r
10084         if (moveList[target][1] == '@') {\r
10085             if (appData.highlightLastMove) {\r
10086                 SetHighlights(-1, -1, toX, toY);\r
10087             }\r
10088         } else {\r
10089             fromX = moveList[target][0] - AAA;\r
10090             fromY = moveList[target][1] - ONE;\r
10091             if (target == currentMove - 1) {\r
10092                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
10093             }\r
10094             if (appData.highlightLastMove) {\r
10095                 SetHighlights(fromX, fromY, toX, toY);\r
10096             }\r
10097         }\r
10098     }\r
10099     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
10100         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
10101         while (currentMove > target) {\r
10102             SendToProgram("undo\n", &first);\r
10103             currentMove--;\r
10104         }\r
10105     } else {\r
10106         currentMove = target;\r
10107     }\r
10108     \r
10109     if (gameMode == EditGame || gameMode == EndOfGame) {\r
10110         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10111         blackTimeRemaining = timeRemaining[1][currentMove];\r
10112     }\r
10113     DisplayBothClocks();\r
10114     DisplayMove(currentMove - 1);\r
10115     DrawPosition(full_redraw, boards[currentMove]);\r
10116     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
10117     if (commentList[currentMove] != NULL) {\r
10118         DisplayComment(currentMove - 1, commentList[currentMove]);\r
10119     }\r
10120 }\r
10121 \r
10122 void\r
10123 BackwardEvent()\r
10124 {\r
10125     if (gameMode == IcsExamining && !pausing) {\r
10126         SendToICS(ics_prefix);\r
10127         SendToICS("backward\n");\r
10128     } else {\r
10129         BackwardInner(currentMove - 1);\r
10130     }\r
10131 }\r
10132 \r
10133 void\r
10134 ToStartEvent()\r
10135 {\r
10136     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10137         /* to optimze, we temporarily turn off analysis mode while we undo\r
10138          * all the moves. Otherwise we get analysis output after each undo.\r
10139          */ \r
10140         if (first.analysisSupport) {\r
10141           SendToProgram("exit\nforce\n", &first);\r
10142           first.analyzing = FALSE;\r
10143         }\r
10144     }\r
10145 \r
10146     if (gameMode == IcsExamining && !pausing) {\r
10147         SendToICS(ics_prefix);\r
10148         SendToICS("backward 999999\n");\r
10149     } else {\r
10150         BackwardInner(backwardMostMove);\r
10151     }\r
10152 \r
10153     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10154         /* we have fed all the moves, so reactivate analysis mode */\r
10155         SendToProgram("analyze\n", &first);\r
10156         first.analyzing = TRUE;\r
10157         /*first.maybeThinking = TRUE;*/\r
10158         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10159     }\r
10160 }\r
10161 \r
10162 void\r
10163 ToNrEvent(int to)\r
10164 {\r
10165   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
10166   if (to >= forwardMostMove) to = forwardMostMove;\r
10167   if (to <= backwardMostMove) to = backwardMostMove;\r
10168   if (to < currentMove) {\r
10169     BackwardInner(to);\r
10170   } else {\r
10171     ForwardInner(to);\r
10172   }\r
10173 }\r
10174 \r
10175 void\r
10176 RevertEvent()\r
10177 {\r
10178     if (gameMode != IcsExamining) {\r
10179         DisplayError("You are not examining a game", 0);\r
10180         return;\r
10181     }\r
10182     if (pausing) {\r
10183         DisplayError("You can't revert while pausing", 0);\r
10184         return;\r
10185     }\r
10186     SendToICS(ics_prefix);\r
10187     SendToICS("revert\n");\r
10188 }\r
10189 \r
10190 void\r
10191 RetractMoveEvent()\r
10192 {\r
10193     switch (gameMode) {\r
10194       case MachinePlaysWhite:\r
10195       case MachinePlaysBlack:\r
10196         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10197             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
10198             return;\r
10199         }\r
10200         if (forwardMostMove < 2) return;\r
10201         currentMove = forwardMostMove = forwardMostMove - 2;\r
10202         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10203         blackTimeRemaining = timeRemaining[1][currentMove];\r
10204         DisplayBothClocks();\r
10205         DisplayMove(currentMove - 1);\r
10206         ClearHighlights();/*!! could figure this out*/\r
10207         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
10208         SendToProgram("remove\n", &first);\r
10209         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
10210         break;\r
10211 \r
10212       case BeginningOfGame:\r
10213       default:\r
10214         break;\r
10215 \r
10216       case IcsPlayingWhite:\r
10217       case IcsPlayingBlack:\r
10218         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
10219             SendToICS(ics_prefix);\r
10220             SendToICS("takeback 2\n");\r
10221         } else {\r
10222             SendToICS(ics_prefix);\r
10223             SendToICS("takeback 1\n");\r
10224         }\r
10225         break;\r
10226     }\r
10227 }\r
10228 \r
10229 void\r
10230 MoveNowEvent()\r
10231 {\r
10232     ChessProgramState *cps;\r
10233 \r
10234     switch (gameMode) {\r
10235       case MachinePlaysWhite:\r
10236         if (!WhiteOnMove(forwardMostMove)) {\r
10237             DisplayError("It is your turn", 0);\r
10238             return;\r
10239         }\r
10240         cps = &first;\r
10241         break;\r
10242       case MachinePlaysBlack:\r
10243         if (WhiteOnMove(forwardMostMove)) {\r
10244             DisplayError("It is your turn", 0);\r
10245             return;\r
10246         }\r
10247         cps = &first;\r
10248         break;\r
10249       case TwoMachinesPlay:\r
10250         if (WhiteOnMove(forwardMostMove) ==\r
10251             (first.twoMachinesColor[0] == 'w')) {\r
10252             cps = &first;\r
10253         } else {\r
10254             cps = &second;\r
10255         }\r
10256         break;\r
10257       case BeginningOfGame:\r
10258       default:\r
10259         return;\r
10260     }\r
10261     SendToProgram("?\n", cps);\r
10262 }\r
10263 \r
10264 void\r
10265 TruncateGameEvent()\r
10266 {\r
10267     EditGameEvent();\r
10268     if (gameMode != EditGame) return;\r
10269     TruncateGame();\r
10270 }\r
10271 \r
10272 void\r
10273 TruncateGame()\r
10274 {\r
10275     if (forwardMostMove > currentMove) {\r
10276         if (gameInfo.resultDetails != NULL) {\r
10277             free(gameInfo.resultDetails);\r
10278             gameInfo.resultDetails = NULL;\r
10279             gameInfo.result = GameUnfinished;\r
10280         }\r
10281         forwardMostMove = currentMove;\r
10282         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
10283                    currentMove-1);\r
10284     }\r
10285 }\r
10286 \r
10287 void\r
10288 HintEvent()\r
10289 {\r
10290     if (appData.noChessProgram) return;\r
10291     switch (gameMode) {\r
10292       case MachinePlaysWhite:\r
10293         if (WhiteOnMove(forwardMostMove)) {\r
10294             DisplayError("Wait until your turn", 0);\r
10295             return;\r
10296         }\r
10297         break;\r
10298       case BeginningOfGame:\r
10299       case MachinePlaysBlack:\r
10300         if (!WhiteOnMove(forwardMostMove)) {\r
10301             DisplayError("Wait until your turn", 0);\r
10302             return;\r
10303         }\r
10304         break;\r
10305       default:\r
10306         DisplayError("No hint available", 0);\r
10307         return;\r
10308     }\r
10309     SendToProgram("hint\n", &first);\r
10310     hintRequested = TRUE;\r
10311 }\r
10312 \r
10313 void\r
10314 BookEvent()\r
10315 {\r
10316     if (appData.noChessProgram) return;\r
10317     switch (gameMode) {\r
10318       case MachinePlaysWhite:\r
10319         if (WhiteOnMove(forwardMostMove)) {\r
10320             DisplayError("Wait until your turn", 0);\r
10321             return;\r
10322         }\r
10323         break;\r
10324       case BeginningOfGame:\r
10325       case MachinePlaysBlack:\r
10326         if (!WhiteOnMove(forwardMostMove)) {\r
10327             DisplayError("Wait until your turn", 0);\r
10328             return;\r
10329         }\r
10330         break;\r
10331       case EditPosition:\r
10332         EditPositionDone();\r
10333         break;\r
10334       case TwoMachinesPlay:\r
10335         return;\r
10336       default:\r
10337         break;\r
10338     }\r
10339     SendToProgram("bk\n", &first);\r
10340     bookOutput[0] = NULLCHAR;\r
10341     bookRequested = TRUE;\r
10342 }\r
10343 \r
10344 void\r
10345 AboutGameEvent()\r
10346 {\r
10347     char *tags = PGNTags(&gameInfo);\r
10348     TagsPopUp(tags, CmailMsg());\r
10349     free(tags);\r
10350 }\r
10351 \r
10352 /* end button procedures */\r
10353 \r
10354 void\r
10355 PrintPosition(fp, move)\r
10356      FILE *fp;\r
10357      int move;\r
10358 {\r
10359     int i, j;\r
10360     \r
10361     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
10362         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
10363             char c = PieceToChar(boards[move][i][j]);\r
10364             fputc(c == 'x' ? '.' : c, fp);\r
10365             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
10366         }\r
10367     }\r
10368     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
10369       fprintf(fp, "white to play\n");\r
10370     else\r
10371       fprintf(fp, "black to play\n");\r
10372 }\r
10373 \r
10374 void\r
10375 PrintOpponents(fp)\r
10376      FILE *fp;\r
10377 {\r
10378     if (gameInfo.white != NULL) {\r
10379         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
10380     } else {\r
10381         fprintf(fp, "\n");\r
10382     }\r
10383 }\r
10384 \r
10385 /* Find last component of program's own name, using some heuristics */\r
10386 void\r
10387 TidyProgramName(prog, host, buf)\r
10388      char *prog, *host, buf[MSG_SIZ];\r
10389 {\r
10390     char *p, *q;\r
10391     int local = (strcmp(host, "localhost") == 0);\r
10392     while (!local && (p = strchr(prog, ';')) != NULL) {\r
10393         p++;\r
10394         while (*p == ' ') p++;\r
10395         prog = p;\r
10396     }\r
10397     if (*prog == '"' || *prog == '\'') {\r
10398         q = strchr(prog + 1, *prog);\r
10399     } else {\r
10400         q = strchr(prog, ' ');\r
10401     }\r
10402     if (q == NULL) q = prog + strlen(prog);\r
10403     p = q;\r
10404     while (p >= prog && *p != '/' && *p != '\\') p--;\r
10405     p++;\r
10406     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
10407     memcpy(buf, p, q - p);\r
10408     buf[q - p] = NULLCHAR;\r
10409     if (!local) {\r
10410         strcat(buf, "@");\r
10411         strcat(buf, host);\r
10412     }\r
10413 }\r
10414 \r
10415 char *\r
10416 TimeControlTagValue()\r
10417 {\r
10418     char buf[MSG_SIZ];\r
10419     if (!appData.clockMode) {\r
10420         strcpy(buf, "-");\r
10421     } else if (movesPerSession > 0) {\r
10422         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
10423     } else if (timeIncrement == 0) {\r
10424         sprintf(buf, "%ld", timeControl/1000);\r
10425     } else {\r
10426         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
10427     }\r
10428     return StrSave(buf);\r
10429 }\r
10430 \r
10431 void\r
10432 SetGameInfo()\r
10433 {\r
10434     /* This routine is used only for certain modes */\r
10435     VariantClass v = gameInfo.variant;\r
10436     ClearGameInfo(&gameInfo);\r
10437     gameInfo.variant = v;\r
10438 \r
10439     switch (gameMode) {\r
10440       case MachinePlaysWhite:\r
10441         gameInfo.event = StrSave( appData.pgnEventHeader );\r
10442         gameInfo.site = StrSave(HostName());\r
10443         gameInfo.date = PGNDate();\r
10444         gameInfo.round = StrSave("-");\r
10445         gameInfo.white = StrSave(first.tidy);\r
10446         gameInfo.black = StrSave(UserName());\r
10447         gameInfo.timeControl = TimeControlTagValue();\r
10448         break;\r
10449 \r
10450       case MachinePlaysBlack:\r
10451         gameInfo.event = StrSave( appData.pgnEventHeader );\r
10452         gameInfo.site = StrSave(HostName());\r
10453         gameInfo.date = PGNDate();\r
10454         gameInfo.round = StrSave("-");\r
10455         gameInfo.white = StrSave(UserName());\r
10456         gameInfo.black = StrSave(first.tidy);\r
10457         gameInfo.timeControl = TimeControlTagValue();\r
10458         break;\r
10459 \r
10460       case TwoMachinesPlay:\r
10461         gameInfo.event = StrSave( appData.pgnEventHeader );\r
10462         gameInfo.site = StrSave(HostName());\r
10463         gameInfo.date = PGNDate();\r
10464         if (matchGame > 0) {\r
10465             char buf[MSG_SIZ];\r
10466             sprintf(buf, "%d", matchGame);\r
10467             gameInfo.round = StrSave(buf);\r
10468         } else {\r
10469             gameInfo.round = StrSave("-");\r
10470         }\r
10471         if (first.twoMachinesColor[0] == 'w') {\r
10472             gameInfo.white = StrSave(first.tidy);\r
10473             gameInfo.black = StrSave(second.tidy);\r
10474         } else {\r
10475             gameInfo.white = StrSave(second.tidy);\r
10476             gameInfo.black = StrSave(first.tidy);\r
10477         }\r
10478         gameInfo.timeControl = TimeControlTagValue();\r
10479         break;\r
10480 \r
10481       case EditGame:\r
10482         gameInfo.event = StrSave("Edited game");\r
10483         gameInfo.site = StrSave(HostName());\r
10484         gameInfo.date = PGNDate();\r
10485         gameInfo.round = StrSave("-");\r
10486         gameInfo.white = StrSave("-");\r
10487         gameInfo.black = StrSave("-");\r
10488         break;\r
10489 \r
10490       case EditPosition:\r
10491         gameInfo.event = StrSave("Edited position");\r
10492         gameInfo.site = StrSave(HostName());\r
10493         gameInfo.date = PGNDate();\r
10494         gameInfo.round = StrSave("-");\r
10495         gameInfo.white = StrSave("-");\r
10496         gameInfo.black = StrSave("-");\r
10497         break;\r
10498 \r
10499       case IcsPlayingWhite:\r
10500       case IcsPlayingBlack:\r
10501       case IcsObserving:\r
10502       case IcsExamining:\r
10503         break;\r
10504 \r
10505       case PlayFromGameFile:\r
10506         gameInfo.event = StrSave("Game from non-PGN file");\r
10507         gameInfo.site = StrSave(HostName());\r
10508         gameInfo.date = PGNDate();\r
10509         gameInfo.round = StrSave("-");\r
10510         gameInfo.white = StrSave("?");\r
10511         gameInfo.black = StrSave("?");\r
10512         break;\r
10513 \r
10514       default:\r
10515         break;\r
10516     }\r
10517 }\r
10518 \r
10519 void\r
10520 ReplaceComment(index, text)\r
10521      int index;\r
10522      char *text;\r
10523 {\r
10524     int len;\r
10525 \r
10526     while (*text == '\n') text++;\r
10527     len = strlen(text);\r
10528     while (len > 0 && text[len - 1] == '\n') len--;\r
10529 \r
10530     if (commentList[index] != NULL)\r
10531       free(commentList[index]);\r
10532 \r
10533     if (len == 0) {\r
10534         commentList[index] = NULL;\r
10535         return;\r
10536     }\r
10537     commentList[index] = (char *) malloc(len + 2);\r
10538     strncpy(commentList[index], text, len);\r
10539     commentList[index][len] = '\n';\r
10540     commentList[index][len + 1] = NULLCHAR;\r
10541 }\r
10542 \r
10543 void\r
10544 CrushCRs(text)\r
10545      char *text;\r
10546 {\r
10547   char *p = text;\r
10548   char *q = text;\r
10549   char ch;\r
10550 \r
10551   do {\r
10552     ch = *p++;\r
10553     if (ch == '\r') continue;\r
10554     *q++ = ch;\r
10555   } while (ch != '\0');\r
10556 }\r
10557 \r
10558 void\r
10559 AppendComment(index, text)\r
10560      int index;\r
10561      char *text;\r
10562 {\r
10563     int oldlen, len;\r
10564     char *old;\r
10565 \r
10566     GetInfoFromComment( index, text );\r
10567 \r
10568     CrushCRs(text);\r
10569     while (*text == '\n') text++;\r
10570     len = strlen(text);\r
10571     while (len > 0 && text[len - 1] == '\n') len--;\r
10572 \r
10573     if (len == 0) return;\r
10574 \r
10575     if (commentList[index] != NULL) {\r
10576         old = commentList[index];\r
10577         oldlen = strlen(old);\r
10578         commentList[index] = (char *) malloc(oldlen + len + 2);\r
10579         strcpy(commentList[index], old);\r
10580         free(old);\r
10581         strncpy(&commentList[index][oldlen], text, len);\r
10582         commentList[index][oldlen + len] = '\n';\r
10583         commentList[index][oldlen + len + 1] = NULLCHAR;\r
10584     } else {\r
10585         commentList[index] = (char *) malloc(len + 2);\r
10586         strncpy(commentList[index], text, len);\r
10587         commentList[index][len] = '\n';\r
10588         commentList[index][len + 1] = NULLCHAR;\r
10589     }\r
10590 }\r
10591 \r
10592 static char * FindStr( char * text, char * sub_text )\r
10593 {\r
10594     char * result = strstr( text, sub_text );\r
10595 \r
10596     if( result != NULL ) {\r
10597         result += strlen( sub_text );\r
10598     }\r
10599 \r
10600     return result;\r
10601 }\r
10602 \r
10603 /* [AS] Try to extract PV info from PGN comment */\r
10604 void GetInfoFromComment( int index, char * text )\r
10605 {\r
10606     if( text != NULL && index > 0 ) {\r
10607         int score = 0;\r
10608         int depth = 0;\r
10609         int time = -1;\r
10610         char * s_eval = FindStr( text, "[%eval " );\r
10611         char * s_emt = FindStr( text, "[%emt " );\r
10612 \r
10613         if( s_eval != NULL || s_emt != NULL ) {\r
10614             /* New style */\r
10615             char delim;\r
10616 \r
10617             if( s_eval != NULL ) {\r
10618                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
10619                     return;\r
10620                 }\r
10621 \r
10622                 if( delim != ']' ) {\r
10623                     return;\r
10624                 }\r
10625             }\r
10626 \r
10627             if( s_emt != NULL ) {\r
10628             }\r
10629         }\r
10630         else {\r
10631             /* We expect something like: [+|-]nnn.nn/dd */\r
10632             char * sep = strchr( text, '/' );\r
10633             int score_lo = 0;\r
10634 \r
10635             if( sep == NULL || sep < (text+4) ) {\r
10636                 return;\r
10637             }\r
10638 \r
10639             if( sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {\r
10640                 return;\r
10641             }\r
10642 \r
10643             if( score_lo < 0 || score_lo >= 100 ) {\r
10644                 return;\r
10645             }\r
10646 \r
10647             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
10648         }\r
10649 \r
10650         if( depth <= 0 ) {\r
10651             return;\r
10652         }\r
10653 \r
10654         if( time < 0 ) {\r
10655             time = -1;\r
10656         }\r
10657 \r
10658         pvInfoList[index-1].depth = depth;\r
10659         pvInfoList[index-1].score = score;\r
10660         pvInfoList[index-1].time = time;\r
10661     }\r
10662 }\r
10663 \r
10664 void\r
10665 SendToProgram(message, cps)\r
10666      char *message;\r
10667      ChessProgramState *cps;\r
10668 {\r
10669     int count, outCount, error;\r
10670     char buf[MSG_SIZ];\r
10671 \r
10672     if (cps->pr == NULL) return;\r
10673     Attention(cps);\r
10674     \r
10675     if (appData.debugMode) {\r
10676         TimeMark now;\r
10677         GetTimeMark(&now);\r
10678         fprintf(debugFP, "%ld >%-6s: %s", \r
10679                 SubtractTimeMarks(&now, &programStartTime),\r
10680                 cps->which, message);\r
10681     }\r
10682     \r
10683     count = strlen(message);\r
10684     outCount = OutputToProcess(cps->pr, message, count, &error);\r
10685     if (outCount < count && !exiting) {\r
10686         sprintf(buf, "Error writing to %s chess program", cps->which);\r
10687         DisplayFatalError(buf, error, 1);\r
10688     }\r
10689 }\r
10690 \r
10691 void\r
10692 ReceiveFromProgram(isr, closure, message, count, error)\r
10693      InputSourceRef isr;\r
10694      VOIDSTAR closure;\r
10695      char *message;\r
10696      int count;\r
10697      int error;\r
10698 {\r
10699     char *end_str;\r
10700     char buf[MSG_SIZ];\r
10701     ChessProgramState *cps = (ChessProgramState *)closure;\r
10702 \r
10703     if (isr != cps->isr) return; /* Killed intentionally */\r
10704     if (count <= 0) {\r
10705         if (count == 0) {\r
10706             sprintf(buf,\r
10707                     "Error: %s chess program (%s) exited unexpectedly",\r
10708                     cps->which, cps->program);\r
10709             RemoveInputSource(cps->isr);\r
10710             DisplayFatalError(buf, 0, 1);\r
10711         } else {\r
10712             sprintf(buf,\r
10713                     "Error reading from %s chess program (%s)",\r
10714                     cps->which, cps->program);\r
10715             RemoveInputSource(cps->isr);\r
10716 \r
10717             /* [AS] Program is misbehaving badly... kill it */\r
10718             if( count == -2 ) {\r
10719                 DestroyChildProcess( cps->pr, 9 );\r
10720                 cps->pr = NoProc;\r
10721             }\r
10722 \r
10723             DisplayFatalError(buf, error, 1);\r
10724         }\r
10725         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10726         return;\r
10727     }\r
10728     \r
10729     if ((end_str = strchr(message, '\r')) != NULL)\r
10730       *end_str = NULLCHAR;\r
10731     if ((end_str = strchr(message, '\n')) != NULL)\r
10732       *end_str = NULLCHAR;\r
10733     \r
10734     if (appData.debugMode) {\r
10735         TimeMark now;\r
10736         GetTimeMark(&now);\r
10737         fprintf(debugFP, "%ld <%-6s: %s\n", \r
10738                 SubtractTimeMarks(&now, &programStartTime),\r
10739                 cps->which, message);\r
10740     }\r
10741     HandleMachineMove(message, cps);\r
10742 }\r
10743 \r
10744 \r
10745 void\r
10746 SendTimeControl(cps, mps, tc, inc, sd, st)\r
10747      ChessProgramState *cps;\r
10748      int mps, inc, sd, st;\r
10749      long tc;\r
10750 {\r
10751     char buf[MSG_SIZ];\r
10752     int seconds = (tc / 1000) % 60;\r
10753 \r
10754     if( timeControl_2 > 0 ) {\r
10755         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
10756             tc = timeControl_2;\r
10757         }\r
10758     }\r
10759 \r
10760     if (st > 0) {\r
10761       /* Set exact time per move, normally using st command */\r
10762       if (cps->stKludge) {\r
10763         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
10764         seconds = st % 60;\r
10765         if (seconds == 0) {\r
10766           sprintf(buf, "level 1 %d\n", st/60);\r
10767         } else {\r
10768           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
10769         }\r
10770       } else {\r
10771         sprintf(buf, "st %d\n", st);\r
10772       }\r
10773     } else {\r
10774       /* Set conventional or incremental time control, using level command */\r
10775       if (seconds == 0) {\r
10776         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
10777            Fixed in later versions, but still avoid :seconds\r
10778            when seconds is 0. */\r
10779         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
10780       } else {\r
10781         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
10782                 seconds, inc/1000);\r
10783       }\r
10784     }\r
10785     SendToProgram(buf, cps);\r
10786 \r
10787     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
10788     /* Orthogonally, limit search to given depth */\r
10789     if (sd > 0) {\r
10790       if (cps->sdKludge) {\r
10791         sprintf(buf, "depth\n%d\n", sd);\r
10792       } else {\r
10793         sprintf(buf, "sd %d\n", sd);\r
10794       }\r
10795       SendToProgram(buf, cps);\r
10796     }\r
10797 }\r
10798 \r
10799 void\r
10800 SendTimeRemaining(cps, machineWhite)\r
10801      ChessProgramState *cps;\r
10802      int /*boolean*/ machineWhite;\r
10803 {\r
10804     char message[MSG_SIZ];\r
10805     long time, otime;\r
10806 \r
10807     /* Note: this routine must be called when the clocks are stopped\r
10808        or when they have *just* been set or switched; otherwise\r
10809        it will be off by the time since the current tick started.\r
10810     */\r
10811     if (machineWhite) {\r
10812         time = whiteTimeRemaining / 10;\r
10813         otime = blackTimeRemaining / 10;\r
10814     } else {\r
10815         time = blackTimeRemaining / 10;\r
10816         otime = whiteTimeRemaining / 10;\r
10817     }\r
10818     if (time <= 0) time = 1;\r
10819     if (otime <= 0) otime = 1;\r
10820     \r
10821     sprintf(message, "time %ld\n", time);\r
10822     SendToProgram(message, cps);\r
10823 \r
10824     sprintf(message, "otim %ld\n", otime);\r
10825     SendToProgram(message, cps);\r
10826 }\r
10827 \r
10828 int\r
10829 BoolFeature(p, name, loc, cps)\r
10830      char **p;\r
10831      char *name;\r
10832      int *loc;\r
10833      ChessProgramState *cps;\r
10834 {\r
10835   char buf[MSG_SIZ];\r
10836   int len = strlen(name);\r
10837   int val;\r
10838   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
10839     (*p) += len + 1;\r
10840     sscanf(*p, "%d", &val);\r
10841     *loc = (val != 0);\r
10842     while (**p && **p != ' ') (*p)++;\r
10843     sprintf(buf, "accepted %s\n", name);\r
10844     SendToProgram(buf, cps);\r
10845     return TRUE;\r
10846   }\r
10847   return FALSE;\r
10848 }\r
10849 \r
10850 int\r
10851 IntFeature(p, name, loc, cps)\r
10852      char **p;\r
10853      char *name;\r
10854      int *loc;\r
10855      ChessProgramState *cps;\r
10856 {\r
10857   char buf[MSG_SIZ];\r
10858   int len = strlen(name);\r
10859   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
10860     (*p) += len + 1;\r
10861     sscanf(*p, "%d", loc);\r
10862     while (**p && **p != ' ') (*p)++;\r
10863     sprintf(buf, "accepted %s\n", name);\r
10864     SendToProgram(buf, cps);\r
10865     return TRUE;\r
10866   }\r
10867   return FALSE;\r
10868 }\r
10869 \r
10870 int\r
10871 StringFeature(p, name, loc, cps)\r
10872      char **p;\r
10873      char *name;\r
10874      char loc[];\r
10875      ChessProgramState *cps;\r
10876 {\r
10877   char buf[MSG_SIZ];\r
10878   int len = strlen(name);\r
10879   if (strncmp((*p), name, len) == 0\r
10880       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
10881     (*p) += len + 2;\r
10882     sscanf(*p, "%[^\"]", loc);\r
10883     while (**p && **p != '\"') (*p)++;\r
10884     if (**p == '\"') (*p)++;\r
10885     sprintf(buf, "accepted %s\n", name);\r
10886     SendToProgram(buf, cps);\r
10887     return TRUE;\r
10888   }\r
10889   return FALSE;\r
10890 }\r
10891 \r
10892 void\r
10893 FeatureDone(cps, val)\r
10894      ChessProgramState* cps;\r
10895      int val;\r
10896 {\r
10897   DelayedEventCallback cb = GetDelayedEvent();\r
10898   if ((cb == InitBackEnd3 && cps == &first) ||\r
10899       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
10900     CancelDelayedEvent();\r
10901     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
10902   }\r
10903   cps->initDone = val;\r
10904 }\r
10905 \r
10906 /* Parse feature command from engine */\r
10907 void\r
10908 ParseFeatures(args, cps)\r
10909      char* args;\r
10910      ChessProgramState *cps;  \r
10911 {\r
10912   char *p = args;\r
10913   char *q;\r
10914   int val;\r
10915   char buf[MSG_SIZ];\r
10916 \r
10917   for (;;) {\r
10918     while (*p == ' ') p++;\r
10919     if (*p == NULLCHAR) return;\r
10920 \r
10921     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
10922     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
10923     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
10924     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
10925     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
10926     if (BoolFeature(&p, "reuse", &val, cps)) {\r
10927       /* Engine can disable reuse, but can't enable it if user said no */\r
10928       if (!val) cps->reuse = FALSE;\r
10929       continue;\r
10930     }\r
10931     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
10932     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
10933       if (gameMode == TwoMachinesPlay) {\r
10934         DisplayTwoMachinesTitle();\r
10935       } else {\r
10936         DisplayTitle("");\r
10937       }\r
10938       continue;\r
10939     }\r
10940     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
10941     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
10942     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
10943     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
10944     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
10945     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
10946     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
10947     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
10948     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
10949     if (IntFeature(&p, "done", &val, cps)) {\r
10950       FeatureDone(cps, val);\r
10951       continue;\r
10952     }\r
10953     /* Added by Tord: */\r
10954     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
10955     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
10956     /* End of additions by Tord */\r
10957 \r
10958     /* unknown feature: complain and skip */\r
10959     q = p;\r
10960     while (*q && *q != '=') q++;\r
10961     sprintf(buf, "rejected %.*s\n", q-p, p);\r
10962     SendToProgram(buf, cps);\r
10963     p = q;\r
10964     if (*p == '=') {\r
10965       p++;\r
10966       if (*p == '\"') {\r
10967         p++;\r
10968         while (*p && *p != '\"') p++;\r
10969         if (*p == '\"') p++;\r
10970       } else {\r
10971         while (*p && *p != ' ') p++;\r
10972       }\r
10973     }\r
10974   }\r
10975 \r
10976 }\r
10977 \r
10978 void\r
10979 PeriodicUpdatesEvent(newState)\r
10980      int newState;\r
10981 {\r
10982     if (newState == appData.periodicUpdates)\r
10983       return;\r
10984 \r
10985     appData.periodicUpdates=newState;\r
10986 \r
10987     /* Display type changes, so update it now */\r
10988     DisplayAnalysis();\r
10989 \r
10990     /* Get the ball rolling again... */\r
10991     if (newState) {\r
10992         AnalysisPeriodicEvent(1);\r
10993         StartAnalysisClock();\r
10994     }\r
10995 }\r
10996 \r
10997 void\r
10998 PonderNextMoveEvent(newState)\r
10999      int newState;\r
11000 {\r
11001     if (newState == appData.ponderNextMove) return;\r
11002     if (gameMode == EditPosition) EditPositionDone();\r
11003     if (newState) {\r
11004         SendToProgram("hard\n", &first);\r
11005         if (gameMode == TwoMachinesPlay) {\r
11006             SendToProgram("hard\n", &second);\r
11007         }\r
11008     } else {\r
11009         SendToProgram("easy\n", &first);\r
11010         thinkOutput[0] = NULLCHAR;\r
11011         if (gameMode == TwoMachinesPlay) {\r
11012             SendToProgram("easy\n", &second);\r
11013         }\r
11014     }\r
11015     appData.ponderNextMove = newState;\r
11016 }\r
11017 \r
11018 void\r
11019 ShowThinkingEvent(newState)\r
11020      int newState;\r
11021 {\r
11022     if (newState == appData.showThinking) return;\r
11023     if (gameMode == EditPosition) EditPositionDone();\r
11024     if (newState) {\r
11025         SendToProgram("post\n", &first);\r
11026         if (gameMode == TwoMachinesPlay) {\r
11027             SendToProgram("post\n", &second);\r
11028         }\r
11029     } else {\r
11030         SendToProgram("nopost\n", &first);\r
11031         thinkOutput[0] = NULLCHAR;\r
11032         if (gameMode == TwoMachinesPlay) {\r
11033             SendToProgram("nopost\n", &second);\r
11034         }\r
11035     }\r
11036     appData.showThinking = newState;\r
11037 }\r
11038 \r
11039 void\r
11040 AskQuestionEvent(title, question, replyPrefix, which)\r
11041      char *title; char *question; char *replyPrefix; char *which;\r
11042 {\r
11043   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
11044   if (pr == NoProc) return;\r
11045   AskQuestion(title, question, replyPrefix, pr);\r
11046 }\r
11047 \r
11048 void\r
11049 DisplayMove(moveNumber)\r
11050      int moveNumber;\r
11051 {\r
11052     char message[MSG_SIZ];\r
11053     char res[MSG_SIZ];\r
11054     char cpThinkOutput[MSG_SIZ];\r
11055 \r
11056     if (moveNumber == forwardMostMove - 1 || \r
11057         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11058 \r
11059         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
11060 \r
11061         if (strchr(cpThinkOutput, '\n')) {\r
11062             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
11063         }\r
11064     } else {\r
11065         *cpThinkOutput = NULLCHAR;\r
11066     }\r
11067 \r
11068     /* [AS] Hide thinking from human user */\r
11069     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
11070         *cpThinkOutput = NULLCHAR;\r
11071         if( thinkOutput[0] != NULLCHAR ) {\r
11072             int i;\r
11073 \r
11074             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
11075                 cpThinkOutput[i] = '.';\r
11076             }\r
11077             cpThinkOutput[i] = NULLCHAR;\r
11078             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
11079         }\r
11080     }\r
11081 \r
11082     if (moveNumber == forwardMostMove - 1 &&\r
11083         gameInfo.resultDetails != NULL) {\r
11084         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
11085             sprintf(res, " %s", PGNResult(gameInfo.result));\r
11086         } else {\r
11087             sprintf(res, " {%s} %s",\r
11088                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
11089         }\r
11090     } else {\r
11091         res[0] = NULLCHAR;\r
11092     }\r
11093     \r
11094     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
11095         DisplayMessage(res, cpThinkOutput);\r
11096     } else {\r
11097         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
11098                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
11099                 parseList[moveNumber], res);\r
11100         DisplayMessage(message, cpThinkOutput);\r
11101     }\r
11102 }\r
11103 \r
11104 void\r
11105 DisplayAnalysisText(text)\r
11106      char *text;\r
11107 {\r
11108     char buf[MSG_SIZ];\r
11109 \r
11110     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11111         sprintf(buf, "Analysis (%s)", first.tidy);\r
11112         AnalysisPopUp(buf, text);\r
11113     }\r
11114 }\r
11115 \r
11116 static int\r
11117 only_one_move(str)\r
11118      char *str;\r
11119 {\r
11120     while (*str && isspace(*str)) ++str;\r
11121     while (*str && !isspace(*str)) ++str;\r
11122     if (!*str) return 1;\r
11123     while (*str && isspace(*str)) ++str;\r
11124     if (!*str) return 1;\r
11125     return 0;\r
11126 }\r
11127 \r
11128 void\r
11129 DisplayAnalysis()\r
11130 {\r
11131     char buf[MSG_SIZ];\r
11132     char lst[MSG_SIZ / 2];\r
11133     double nps;\r
11134     static char *xtra[] = { "", " (--)", " (++)" };\r
11135     int h, m, s, cs;\r
11136   \r
11137     if (programStats.time == 0) {\r
11138         programStats.time = 1;\r
11139     }\r
11140   \r
11141     if (programStats.got_only_move) {\r
11142         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
11143     } else {\r
11144         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
11145 \r
11146         nps = (((double)programStats.nodes) /\r
11147                (((double)programStats.time)/100.0));\r
11148 \r
11149         cs = programStats.time % 100;\r
11150         s = programStats.time / 100;\r
11151         h = (s / (60*60));\r
11152         s = s - h*60*60;\r
11153         m = (s/60);\r
11154         s = s - m*60;\r
11155 \r
11156         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
11157           if (programStats.move_name[0] != NULLCHAR) {\r
11158             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
11159                     programStats.depth,\r
11160                     programStats.nr_moves-programStats.moves_left,\r
11161                     programStats.nr_moves, programStats.move_name,\r
11162                     ((float)programStats.score)/100.0, lst,\r
11163                     only_one_move(lst)?\r
11164                     xtra[programStats.got_fail] : "",\r
11165                     programStats.nodes, (int)nps, h, m, s, cs);\r
11166           } else {\r
11167             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
11168                     programStats.depth,\r
11169                     programStats.nr_moves-programStats.moves_left,\r
11170                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
11171                     lst,\r
11172                     only_one_move(lst)?\r
11173                     xtra[programStats.got_fail] : "",\r
11174                     programStats.nodes, (int)nps, h, m, s, cs);\r
11175           }\r
11176         } else {\r
11177             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
11178                     programStats.depth,\r
11179                     ((float)programStats.score)/100.0,\r
11180                     lst,\r
11181                     only_one_move(lst)?\r
11182                     xtra[programStats.got_fail] : "",\r
11183                     programStats.nodes, (int)nps, h, m, s, cs);\r
11184         }\r
11185     }\r
11186     DisplayAnalysisText(buf);\r
11187 }\r
11188 \r
11189 void\r
11190 DisplayComment(moveNumber, text)\r
11191      int moveNumber;\r
11192      char *text;\r
11193 {\r
11194     char title[MSG_SIZ];\r
11195 \r
11196     if( appData.autoDisplayComment ) {\r
11197         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
11198             strcpy(title, "Comment");\r
11199         } else {\r
11200             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
11201                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
11202                     parseList[moveNumber]);\r
11203         }\r
11204 \r
11205         CommentPopUp(title, text);\r
11206     }\r
11207 }\r
11208 \r
11209 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
11210  * might be busy thinking or pondering.  It can be omitted if your\r
11211  * gnuchess is configured to stop thinking immediately on any user\r
11212  * input.  However, that gnuchess feature depends on the FIONREAD\r
11213  * ioctl, which does not work properly on some flavors of Unix.\r
11214  */\r
11215 void\r
11216 Attention(cps)\r
11217      ChessProgramState *cps;\r
11218 {\r
11219 #if ATTENTION\r
11220     if (!cps->useSigint) return;\r
11221     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
11222     switch (gameMode) {\r
11223       case MachinePlaysWhite:\r
11224       case MachinePlaysBlack:\r
11225       case TwoMachinesPlay:\r
11226       case IcsPlayingWhite:\r
11227       case IcsPlayingBlack:\r
11228       case AnalyzeMode:\r
11229       case AnalyzeFile:\r
11230         /* Skip if we know it isn't thinking */\r
11231         if (!cps->maybeThinking) return;\r
11232         if (appData.debugMode)\r
11233           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
11234         InterruptChildProcess(cps->pr);\r
11235         cps->maybeThinking = FALSE;\r
11236         break;\r
11237       default:\r
11238         break;\r
11239     }\r
11240 #endif /*ATTENTION*/\r
11241 }\r
11242 \r
11243 int\r
11244 CheckFlags()\r
11245 {\r
11246     if (whiteTimeRemaining <= 0) {\r
11247         if (!whiteFlag) {\r
11248             whiteFlag = TRUE;\r
11249             if (appData.icsActive) {\r
11250                 if (appData.autoCallFlag &&\r
11251                     gameMode == IcsPlayingBlack && !blackFlag) {\r
11252                   SendToICS(ics_prefix);\r
11253                   SendToICS("flag\n");\r
11254                 }\r
11255             } else {\r
11256                 if (blackFlag) {\r
11257                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
11258                 } else {\r
11259                     if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell");\r
11260                     if (appData.autoCallFlag) {\r
11261                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
11262                         return TRUE;\r
11263                     }\r
11264                 }\r
11265             }\r
11266         }\r
11267     }\r
11268     if (blackTimeRemaining <= 0) {\r
11269         if (!blackFlag) {\r
11270             blackFlag = TRUE;\r
11271             if (appData.icsActive) {\r
11272                 if (appData.autoCallFlag &&\r
11273                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
11274                   SendToICS(ics_prefix);\r
11275                   SendToICS("flag\n");\r
11276                 }\r
11277             } else {\r
11278                 if (whiteFlag) {\r
11279                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
11280                 } else {\r
11281                     if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell");\r
11282                     if (appData.autoCallFlag) {\r
11283                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
11284                         return TRUE;\r
11285                     }\r
11286                 }\r
11287             }\r
11288         }\r
11289     }\r
11290     return FALSE;\r
11291 }\r
11292 \r
11293 void\r
11294 CheckTimeControl()\r
11295 {\r
11296     if (!appData.clockMode || appData.icsActive ||\r
11297         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
11298 \r
11299     if (timeIncrement >= 0) {\r
11300         if (WhiteOnMove(forwardMostMove)) {\r
11301             blackTimeRemaining += timeIncrement;\r
11302         } else {\r
11303             whiteTimeRemaining += timeIncrement;\r
11304         }\r
11305     }\r
11306     /*\r
11307      * add time to clocks when time control is achieved\r
11308      */\r
11309     if (movesPerSession) {\r
11310       switch ((forwardMostMove + 1) % (movesPerSession * 2)) {\r
11311       case 0:\r
11312         /* White made time control */\r
11313         whiteTimeRemaining += GetTimeControlForWhite();\r
11314         break;\r
11315       case 1:\r
11316         /* Black made time control */\r
11317         blackTimeRemaining += GetTimeControlForBlack();\r
11318         break;\r
11319       default:\r
11320         break;\r
11321       }\r
11322     }\r
11323 }\r
11324 \r
11325 void\r
11326 DisplayBothClocks()\r
11327 {\r
11328     int wom = gameMode == EditPosition ?\r
11329       !blackPlaysFirst : WhiteOnMove(currentMove);\r
11330     DisplayWhiteClock(whiteTimeRemaining, wom);\r
11331     DisplayBlackClock(blackTimeRemaining, !wom);\r
11332 }\r
11333 \r
11334 \r
11335 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
11336    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
11337    to use other calls if you don't.  Clocks will be less accurate if\r
11338    you have neither ftime nor gettimeofday.\r
11339 */\r
11340 \r
11341 /* Get the current time as a TimeMark */\r
11342 void\r
11343 GetTimeMark(tm)\r
11344      TimeMark *tm;\r
11345 {\r
11346 #if HAVE_GETTIMEOFDAY\r
11347 \r
11348     struct timeval timeVal;\r
11349     struct timezone timeZone;\r
11350 \r
11351     gettimeofday(&timeVal, &timeZone);\r
11352     tm->sec = (long) timeVal.tv_sec; \r
11353     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
11354 \r
11355 #else /*!HAVE_GETTIMEOFDAY*/\r
11356 #if HAVE_FTIME\r
11357 \r
11358 #include <sys/timeb.h>\r
11359     struct timeb timeB;\r
11360 \r
11361     ftime(&timeB);\r
11362     tm->sec = (long) timeB.time;\r
11363     tm->ms = (int) timeB.millitm;\r
11364 \r
11365 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
11366     tm->sec = (long) time(NULL);\r
11367     tm->ms = 0;\r
11368 #endif\r
11369 #endif\r
11370 }\r
11371 \r
11372 /* Return the difference in milliseconds between two\r
11373    time marks.  We assume the difference will fit in a long!\r
11374 */\r
11375 long\r
11376 SubtractTimeMarks(tm2, tm1)\r
11377      TimeMark *tm2, *tm1;\r
11378 {\r
11379     return 1000L*(tm2->sec - tm1->sec) +\r
11380            (long) (tm2->ms - tm1->ms);\r
11381 }\r
11382 \r
11383 \r
11384 /*\r
11385  * Code to manage the game clocks.\r
11386  *\r
11387  * In tournament play, black starts the clock and then white makes a move.\r
11388  * We give the human user a slight advantage if he is playing white---the\r
11389  * clocks don't run until he makes his first move, so it takes zero time.\r
11390  * Also, we don't account for network lag, so we could get out of sync\r
11391  * with GNU Chess's clock -- but then, referees are always right.  \r
11392  */\r
11393 \r
11394 static TimeMark tickStartTM;\r
11395 static long intendedTickLength;\r
11396 \r
11397 long\r
11398 NextTickLength(timeRemaining)\r
11399      long timeRemaining;\r
11400 {\r
11401     long nominalTickLength, nextTickLength;\r
11402 \r
11403     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
11404       nominalTickLength = 100L;\r
11405     else\r
11406       nominalTickLength = 1000L;\r
11407     nextTickLength = timeRemaining % nominalTickLength;\r
11408     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
11409 \r
11410     return nextTickLength;\r
11411 }\r
11412 \r
11413 /* Adjust clock one minute up or down */\r
11414 void\r
11415 AdjustClock(Boolean which, int dir)\r
11416 {\r
11417     if(which) blackTimeRemaining += 60000*dir;\r
11418     else      whiteTimeRemaining += 60000*dir;\r
11419     DisplayBothClocks();\r
11420 }\r
11421 \r
11422 /* Stop clocks and reset to a fresh time control */\r
11423 void\r
11424 ResetClocks() \r
11425 {\r
11426     (void) StopClockTimer();\r
11427     if (appData.icsActive) {\r
11428         whiteTimeRemaining = blackTimeRemaining = 0;\r
11429     } else {\r
11430         whiteTimeRemaining = GetTimeControlForWhite();\r
11431         blackTimeRemaining = GetTimeControlForBlack();\r
11432     }\r
11433     if (whiteFlag || blackFlag) {\r
11434         DisplayTitle("");\r
11435         whiteFlag = blackFlag = FALSE;\r
11436     }\r
11437     DisplayBothClocks();\r
11438 }\r
11439 \r
11440 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
11441 \r
11442 /* Decrement running clock by amount of time that has passed */\r
11443 void\r
11444 DecrementClocks()\r
11445 {\r
11446     long timeRemaining;\r
11447     long lastTickLength, fudge;\r
11448     TimeMark now;\r
11449 \r
11450     if (!appData.clockMode) return;\r
11451     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
11452         \r
11453     GetTimeMark(&now);\r
11454 \r
11455     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
11456 \r
11457     /* Fudge if we woke up a little too soon */\r
11458     fudge = intendedTickLength - lastTickLength;\r
11459     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
11460 \r
11461     if (WhiteOnMove(forwardMostMove)) {\r
11462         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
11463         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
11464                           WhiteOnMove(currentMove));\r
11465     } else {\r
11466         timeRemaining = blackTimeRemaining -= lastTickLength;\r
11467         DisplayBlackClock(blackTimeRemaining - fudge,\r
11468                           !WhiteOnMove(currentMove));\r
11469     }\r
11470 \r
11471     if (CheckFlags()) return;\r
11472         \r
11473     tickStartTM = now;\r
11474     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
11475     StartClockTimer(intendedTickLength);\r
11476 \r
11477     /* if the time remaining has fallen below the alarm threshold, sound the\r
11478      * alarm. if the alarm has sounded and (due to a takeback or time control\r
11479      * with increment) the time remaining has increased to a level above the\r
11480      * threshold, reset the alarm so it can sound again. \r
11481      */\r
11482     \r
11483     if (appData.icsActive && appData.icsAlarm) {\r
11484 \r
11485         /* make sure we are dealing with the user's clock */\r
11486         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
11487                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
11488            )) return;\r
11489 \r
11490         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
11491             alarmSounded = FALSE;\r
11492         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
11493             PlayAlarmSound();\r
11494             alarmSounded = TRUE;\r
11495         }\r
11496     }\r
11497 }\r
11498 \r
11499 \r
11500 /* A player has just moved, so stop the previously running\r
11501    clock and (if in clock mode) start the other one.\r
11502    We redisplay both clocks in case we're in ICS mode, because\r
11503    ICS gives us an update to both clocks after every move.\r
11504    Note that this routine is called *after* forwardMostMove\r
11505    is updated, so the last fractional tick must be subtracted\r
11506    from the color that is *not* on move now.\r
11507 */\r
11508 void\r
11509 SwitchClocks()\r
11510 {\r
11511     long lastTickLength;\r
11512     TimeMark now;\r
11513     int flagged = FALSE;\r
11514 \r
11515     GetTimeMark(&now);\r
11516 \r
11517     if (StopClockTimer() && appData.clockMode) {\r
11518         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
11519         if (WhiteOnMove(forwardMostMove)) {\r
11520             blackTimeRemaining -= lastTickLength;\r
11521         } else {\r
11522             whiteTimeRemaining -= lastTickLength;\r
11523         }\r
11524         /* [HGM] save time for PGN file if engine did not give it */\r
11525         if(pvInfoList[forwardMostMove-1].time == -1)\r
11526              pvInfoList[forwardMostMove-1].time = lastTickLength/100;\r
11527         flagged = CheckFlags();\r
11528     }\r
11529     CheckTimeControl();\r
11530 \r
11531     if (flagged || !appData.clockMode) return;\r
11532 \r
11533     switch (gameMode) {\r
11534       case MachinePlaysBlack:\r
11535       case MachinePlaysWhite:\r
11536       case BeginningOfGame:\r
11537         if (pausing) return;\r
11538         break;\r
11539 \r
11540       case EditGame:\r
11541       case PlayFromGameFile:\r
11542       case IcsExamining:\r
11543         return;\r
11544 \r
11545       default:\r
11546         break;\r
11547     }\r
11548 \r
11549     tickStartTM = now;\r
11550     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
11551       whiteTimeRemaining : blackTimeRemaining);\r
11552     StartClockTimer(intendedTickLength);\r
11553 }\r
11554         \r
11555 \r
11556 /* Stop both clocks */\r
11557 void\r
11558 StopClocks()\r
11559 {       \r
11560     long lastTickLength;\r
11561     TimeMark now;\r
11562 \r
11563     if (!StopClockTimer()) return;\r
11564     if (!appData.clockMode) return;\r
11565 \r
11566     GetTimeMark(&now);\r
11567 \r
11568     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
11569     if (WhiteOnMove(forwardMostMove)) {\r
11570         whiteTimeRemaining -= lastTickLength;\r
11571         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
11572     } else {\r
11573         blackTimeRemaining -= lastTickLength;\r
11574         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
11575     }\r
11576     CheckFlags();\r
11577 }\r
11578         \r
11579 /* Start clock of player on move.  Time may have been reset, so\r
11580    if clock is already running, stop and restart it. */\r
11581 void\r
11582 StartClocks()\r
11583 {\r
11584     (void) StopClockTimer(); /* in case it was running already */\r
11585     DisplayBothClocks();\r
11586     if (CheckFlags()) return;\r
11587 \r
11588     if (!appData.clockMode) return;\r
11589     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
11590 \r
11591     GetTimeMark(&tickStartTM);\r
11592     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
11593       whiteTimeRemaining : blackTimeRemaining);\r
11594     StartClockTimer(intendedTickLength);\r
11595 }\r
11596 \r
11597 char *\r
11598 TimeString(ms)\r
11599      long ms;\r
11600 {\r
11601     long second, minute, hour, day;\r
11602     char *sign = "";\r
11603     static char buf[32];\r
11604     \r
11605     if (ms > 0 && ms <= 9900) {\r
11606       /* convert milliseconds to tenths, rounding up */\r
11607       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
11608 \r
11609       sprintf(buf, " %03.1f ", tenths/10.0);\r
11610       return buf;\r
11611     }\r
11612 \r
11613     /* convert milliseconds to seconds, rounding up */\r
11614     /* use floating point to avoid strangeness of integer division\r
11615        with negative dividends on many machines */\r
11616     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
11617 \r
11618     if (second < 0) {\r
11619         sign = "-";\r
11620         second = -second;\r
11621     }\r
11622     \r
11623     day = second / (60 * 60 * 24);\r
11624     second = second % (60 * 60 * 24);\r
11625     hour = second / (60 * 60);\r
11626     second = second % (60 * 60);\r
11627     minute = second / 60;\r
11628     second = second % 60;\r
11629     \r
11630     if (day > 0)\r
11631       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
11632               sign, day, hour, minute, second);\r
11633     else if (hour > 0)\r
11634       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
11635     else\r
11636       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
11637     \r
11638     return buf;\r
11639 }\r
11640 \r
11641 \r
11642 /*\r
11643  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
11644  */\r
11645 char *\r
11646 StrStr(string, match)\r
11647      char *string, *match;\r
11648 {\r
11649     int i, length;\r
11650     \r
11651     length = strlen(match);\r
11652     \r
11653     for (i = strlen(string) - length; i >= 0; i--, string++)\r
11654       if (!strncmp(match, string, length))\r
11655         return string;\r
11656     \r
11657     return NULL;\r
11658 }\r
11659 \r
11660 char *\r
11661 StrCaseStr(string, match)\r
11662      char *string, *match;\r
11663 {\r
11664     int i, j, length;\r
11665     \r
11666     length = strlen(match);\r
11667     \r
11668     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
11669         for (j = 0; j < length; j++) {\r
11670             if (ToLower(match[j]) != ToLower(string[j]))\r
11671               break;\r
11672         }\r
11673         if (j == length) return string;\r
11674     }\r
11675 \r
11676     return NULL;\r
11677 }\r
11678 \r
11679 #ifndef _amigados\r
11680 int\r
11681 StrCaseCmp(s1, s2)\r
11682      char *s1, *s2;\r
11683 {\r
11684     char c1, c2;\r
11685     \r
11686     for (;;) {\r
11687         c1 = ToLower(*s1++);\r
11688         c2 = ToLower(*s2++);\r
11689         if (c1 > c2) return 1;\r
11690         if (c1 < c2) return -1;\r
11691         if (c1 == NULLCHAR) return 0;\r
11692     }\r
11693 }\r
11694 \r
11695 \r
11696 int\r
11697 ToLower(c)\r
11698      int c;\r
11699 {\r
11700     return isupper(c) ? tolower(c) : c;\r
11701 }\r
11702 \r
11703 \r
11704 int\r
11705 ToUpper(c)\r
11706      int c;\r
11707 {\r
11708     return islower(c) ? toupper(c) : c;\r
11709 }\r
11710 #endif /* !_amigados    */\r
11711 \r
11712 char *\r
11713 StrSave(s)\r
11714      char *s;\r
11715 {\r
11716     char *ret;\r
11717 \r
11718     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
11719         strcpy(ret, s);\r
11720     }\r
11721     return ret;\r
11722 }\r
11723 \r
11724 char *\r
11725 StrSavePtr(s, savePtr)\r
11726      char *s, **savePtr;\r
11727 {\r
11728     if (*savePtr) {\r
11729         free(*savePtr);\r
11730     }\r
11731     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
11732         strcpy(*savePtr, s);\r
11733     }\r
11734     return(*savePtr);\r
11735 }\r
11736 \r
11737 char *\r
11738 PGNDate()\r
11739 {\r
11740     time_t clock;\r
11741     struct tm *tm;\r
11742     char buf[MSG_SIZ];\r
11743 \r
11744     clock = time((time_t *)NULL);\r
11745     tm = localtime(&clock);\r
11746     sprintf(buf, "%04d.%02d.%02d",\r
11747             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
11748     return StrSave(buf);\r
11749 }\r
11750 \r
11751 \r
11752 char *\r
11753 PositionToFEN(move, useFEN960)\r
11754      int move;\r
11755      int useFEN960;\r
11756 {\r
11757     int i, j, fromX, fromY, toX, toY;\r
11758     int whiteToPlay;\r
11759     char buf[128];\r
11760     char *p, *q;\r
11761     int emptycount;\r
11762     ChessSquare piece;\r
11763 \r
11764     whiteToPlay = (gameMode == EditPosition) ?\r
11765       !blackPlaysFirst : (move % 2 == 0);\r
11766     p = buf;\r
11767 \r
11768     /* Piece placement data */\r
11769     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11770         emptycount = 0;\r
11771         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11772             if (boards[move][i][j] == EmptySquare) {\r
11773                 emptycount++;\r
11774             } else { ChessSquare piece = boards[move][i][j];\r
11775                 if (emptycount > 0) {\r
11776                     if(emptycount<10) /* [HGM] can be >= 10 */\r
11777                         *p++ = '0' + emptycount;\r
11778                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
11779                     emptycount = 0;\r
11780                 }\r
11781                 if(PieceToChar(piece) == '+') {\r
11782                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
11783                     *p++ = '+';\r
11784                     piece = (ChessSquare)(DEMOTED piece);\r
11785                 } \r
11786                 *p++ = PieceToChar(piece);\r
11787                 if(p[-1] == '~') {\r
11788                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
11789                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
11790                     *p++ = '~';\r
11791                 }\r
11792             }\r
11793         }\r
11794         if (emptycount > 0) {\r
11795             if(emptycount<10) /* [HGM] can be >= 10 */\r
11796                 *p++ = '0' + emptycount;\r
11797             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
11798             emptycount = 0;\r
11799         }\r
11800         *p++ = '/';\r
11801     }\r
11802     *(p - 1) = ' ';\r
11803 \r
11804     /* Active color */\r
11805     *p++ = whiteToPlay ? 'w' : 'b';\r
11806     *p++ = ' ';\r
11807 \r
11808   if(nrCastlingRights) {\r
11809     /* HACK: we don't keep track of castling availability, so fake it! */\r
11810     /* Tord! please fix with the aid of castlingRights[move][...] */\r
11811 \r
11812     /* PUSH Fabien & Tord */\r
11813 \r
11814     /* Declare all potential FRC castling rights (conservative) */\r
11815     /* outermost rook on each side of the king */\r
11816 \r
11817     if( gameInfo.variant == VariantFischeRandom ) {\r
11818        int fk, fr;\r
11819 \r
11820        q = p;\r
11821 \r
11822        /* White castling rights */\r
11823 \r
11824        for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) {\r
11825 \r
11826           if (boards[move][0][fk] == WhiteKing) {\r
11827 \r
11828              for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */\r
11829                 if (boards[move][0][fr] == WhiteRook) {\r
11830                    *p++ = useFEN960 ? 'A' + fr : 'K';\r
11831                    break;\r
11832                 }\r
11833              }\r
11834 \r
11835              for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */\r
11836                 if (boards[move][0][fr] == WhiteRook) {\r
11837                    *p++ = useFEN960 ? 'A' + fr : 'Q';\r
11838                    break;\r
11839                 }\r
11840              }\r
11841           }\r
11842        }\r
11843 \r
11844        /* Black castling rights */\r
11845 \r
11846        for (fk = BOARD_LEFT+1; fk < BOARD_RGHT-1; fk++) {\r
11847 \r
11848           if (boards[move][BOARD_HEIGHT-1][fk] == BlackKing) {\r
11849 \r
11850              for (fr = BOARD_RGHT-1; fr > fk; fr--) { /* H side */\r
11851                 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {\r
11852                    *p++ = useFEN960 ? 'a' + fr : 'k';\r
11853                    break;\r
11854                 }\r
11855              }\r
11856 \r
11857              for (fr = BOARD_LEFT; fr < fk; fr++) { /* A side */\r
11858                 if (boards[move][BOARD_HEIGHT-1][fr] == BlackRook) {\r
11859                    *p++ = useFEN960 ? 'a' + fr : 'q';\r
11860                    break;\r
11861                 }\r
11862              }\r
11863           }\r
11864        }\r
11865 \r
11866        if (q == p) *p++ = '-'; /* No castling rights */\r
11867        *p++ = ' ';\r
11868     }\r
11869     else {\r
11870         q = p;\r
11871 \r
11872 #ifdef OLDCASTLINGCODE\r
11873         if (boards[move][0][BOARD_WIDTH>>1] == WhiteKing) {\r
11874             if (boards[move][0][BOARD_RGHT-1] == WhiteRook) *p++ = 'K';\r
11875             if (boards[move][0][BOARD_LEFT] == WhiteRook) *p++ = 'Q';\r
11876         }\r
11877         if (boards[move][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == BlackKing) {\r
11878             if (boards[move][BOARD_HEIGHT-1][BOARD_HEIGHT-1] == BlackRook) *p++ = 'k';\r
11879             if (boards[move][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook) *p++ = 'q';\r
11880         }           \r
11881 #else\r
11882         /* [HGM] write true castling rights */\r
11883         if( nrCastlingRights == 6 ) {\r
11884             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
11885                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
11886             if(castlingRights[move][1] == BOARD_LEFT &&\r
11887                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
11888             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
11889                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
11890             if(castlingRights[move][4] == BOARD_LEFT &&\r
11891                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
11892         }\r
11893 #endif\r
11894         if (q == p) *p++ = '-';\r
11895         *p++ = ' ';\r
11896     }\r
11897 \r
11898     /* POP Fabien & Tord */\r
11899   }\r
11900 \r
11901   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
11902      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
11903     /* En passant target square */\r
11904     if (move > backwardMostMove) {\r
11905         fromX = moveList[move - 1][0] - AAA;\r
11906         fromY = moveList[move - 1][1] - ONE;\r
11907         toX = moveList[move - 1][2] - AAA;\r
11908         toY = moveList[move - 1][3] - ONE;\r
11909         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
11910             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
11911             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
11912             fromX == toX) {\r
11913             /* 2-square pawn move just happened */\r
11914             *p++ = toX + AAA;\r
11915             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
11916         } else {\r
11917             *p++ = '-';\r
11918         }\r
11919     } else {\r
11920         *p++ = '-';\r
11921     }\r
11922     *p++ = ' ';\r
11923   }\r
11924 \r
11925     /* [HGM] print Crazyhouse or Shogi holdings */\r
11926     if( gameInfo.holdingsWidth ) {\r
11927         q = p;\r
11928         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
11929             piece = boards[move][i][BOARD_WIDTH-1];\r
11930             if( piece != EmptySquare )\r
11931               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
11932                   *p++ = PieceToChar(piece);\r
11933         }\r
11934         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
11935             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
11936             if( piece != EmptySquare )\r
11937               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
11938                   *p++ = PieceToChar(piece);\r
11939         }\r
11940 \r
11941         if( q == p ) *p++ = '-';\r
11942         *p++ = ' ';\r
11943     }\r
11944 \r
11945     /* [HGM] find reversible plies */\r
11946     {   int i = 0, j=move;\r
11947 \r
11948         if (appData.debugMode) { int k;\r
11949             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
11950             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
11951                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
11952 \r
11953         }\r
11954 \r
11955         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
11956         if( j == backwardMostMove ) i += initialRulePlies;\r
11957         sprintf(p, "%d ", i);\r
11958         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
11959     }\r
11960     /* Fullmove number */\r
11961     sprintf(p, "%d", (move / 2) + 1);\r
11962     \r
11963     return StrSave(buf);\r
11964 }\r
11965 \r
11966 Boolean\r
11967 ParseFEN(board, blackPlaysFirst, fen)\r
11968     Board board;\r
11969      int *blackPlaysFirst;\r
11970      char *fen;\r
11971 {\r
11972     int i, j;\r
11973     char *p;\r
11974     int emptycount;\r
11975     ChessSquare piece;\r
11976 \r
11977     p = fen;\r
11978 \r
11979     /* [HGM] by default clear Crazyhouse holdings, if present */\r
11980     if(gameInfo.holdingsWidth) {\r
11981        for(i=0; i<BOARD_HEIGHT; i++) {\r
11982            board[i][0]             = EmptySquare; /* black holdings */\r
11983            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
11984            board[i][1]             = (ChessSquare) 0; /* black counts */\r
11985            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
11986        }\r
11987     }\r
11988 \r
11989     /* Piece placement data */\r
11990     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11991         j = 0;\r
11992         for (;;) {\r
11993             if (*p == '/' || *p == ' ') {\r
11994                 if (*p == '/') p++;\r
11995                 emptycount = gameInfo.boardWidth - j;\r
11996                 while (emptycount--)\r
11997                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
11998                 break;\r
11999 #if(BOARD_SIZE >= 10)\r
12000             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
12001                 p++; emptycount=10;\r
12002                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
12003                 while (emptycount--)\r
12004                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
12005 #endif\r
12006             } else if (isdigit(*p)) {\r
12007                 emptycount = *p++ - '0';\r
12008                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
12009                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
12010                 while (emptycount--)\r
12011                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
12012             } else if (*p == '+' || isalpha(*p)) {\r
12013                 if (j >= gameInfo.boardWidth) return FALSE;\r
12014                 if(*p=='+') {\r
12015                     piece = CharToPiece(*++p);\r
12016                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
12017                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
12018                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
12019                 } else piece = CharToPiece(*p++);\r
12020 \r
12021                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
12022                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
12023                     piece = (ChessSquare) (PROMOTED piece);\r
12024                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
12025                     p++;\r
12026                 }\r
12027                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
12028             } else {\r
12029                 return FALSE;\r
12030             }\r
12031         }\r
12032     }\r
12033     while (*p == '/' || *p == ' ') p++;\r
12034 \r
12035     /* Active color */\r
12036     switch (*p++) {\r
12037       case 'w':\r
12038         *blackPlaysFirst = FALSE;\r
12039         break;\r
12040       case 'b': \r
12041         *blackPlaysFirst = TRUE;\r
12042         break;\r
12043       default:\r
12044         return FALSE;\r
12045     }\r
12046 \r
12047     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
12048     /* return the extra info in global variiables             */\r
12049 \r
12050     /* set defaults in case FEN is incomplete */\r
12051     FENepStatus = EP_UNKNOWN;\r
12052     for(i=0; i<nrCastlingRights; i++ ) {\r
12053         FENcastlingRights[i] = initialRights[i];\r
12054     }   /* assume possible unless obviously impossible */\r
12055     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
12056     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
12057     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
12058     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
12059     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
12060     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
12061     FENrulePlies = 0;\r
12062 \r
12063     while(*p==' ') p++;\r
12064     if(nrCastlingRights) {\r
12065       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
12066           /* castling indicator present, so default becomes no castlings */\r
12067           for(i=0; i<nrCastlingRights; i++ ) {\r
12068                  FENcastlingRights[i] = -1;\r
12069           }\r
12070       }\r
12071       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
12072         switch(*p++) {\r
12073           case'K':\r
12074               FENcastlingRights[0] = BOARD_RGHT-1;\r
12075               FENcastlingRights[2] = BOARD_WIDTH>>1;\r
12076               break;\r
12077           case'Q':\r
12078               FENcastlingRights[1] = BOARD_LEFT;\r
12079               FENcastlingRights[2] = BOARD_WIDTH>>1;\r
12080               break;\r
12081           case'k':\r
12082               FENcastlingRights[3] = BOARD_RGHT-1;\r
12083               FENcastlingRights[5] = BOARD_WIDTH>>1;\r
12084               break;\r
12085           case'q':\r
12086               FENcastlingRights[4] = BOARD_LEFT;\r
12087               FENcastlingRights[5] = BOARD_WIDTH>>1;\r
12088               break;\r
12089           /* Tord! FRC! */\r
12090         }\r
12091       }\r
12092 \r
12093       while(*p==' ') p++;\r
12094     }\r
12095 \r
12096     /* read e.p. field in games that know e.p. capture */\r
12097     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
12098        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
12099       if(*p=='-') {\r
12100         p++; FENepStatus = EP_NONE;\r
12101       } else {\r
12102          char c = *p++ - AAA;\r
12103 \r
12104          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
12105          if(*p >= '0' && *p <='9') *p++;\r
12106          FENepStatus = c;\r
12107       }\r
12108     }\r
12109 \r
12110     /* [HGM] look for Crazyhouse holdings here */\r
12111     while(*p==' ') p++;\r
12112     if( gameInfo.holdingsWidth ) {\r
12113         if(*p == '-' ) *p++; /* empty holdings */ else {\r
12114             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
12115             /* if we would allow FEN reading to set board size, we would   */\r
12116             /* have to add holdings and shift the board read so far here   */\r
12117             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
12118                 *p++;\r
12119                 if((int) piece >= (int) BlackPawn ) {\r
12120                     i = (int)piece - (int)BlackPawn;\r
12121                     if( i >= BOARD_HEIGHT ) return FALSE;\r
12122                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
12123                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
12124                 } else {\r
12125                     i = (int)piece - (int)WhitePawn;\r
12126                     if( i >= BOARD_HEIGHT ) return FALSE;\r
12127                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
12128                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
12129                 }\r
12130             }\r
12131         }\r
12132     }\r
12133 \r
12134 \r
12135     if(sscanf(p, "%d", &i) == 1) {\r
12136         FENrulePlies = i; /* 50-move ply counter */\r
12137         /* (The move number is still ignored)    */\r
12138     }\r
12139 \r
12140     return TRUE;\r
12141 }\r
12142       \r
12143 void\r
12144 EditPositionPasteFEN(char *fen)\r
12145 {\r
12146   if (fen != NULL) {\r
12147     Board initial_position;\r
12148 \r
12149     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
12150       DisplayError("Bad FEN position in clipboard", 0);\r
12151       return ;\r
12152     } else {\r
12153       int savedBlackPlaysFirst = blackPlaysFirst;\r
12154       EditPositionEvent();\r
12155       blackPlaysFirst = savedBlackPlaysFirst;\r
12156       CopyBoard(boards[0], initial_position);\r
12157           /* [HGM] copy FEN attributes as well */\r
12158           {   int i;\r
12159               initialRulePlies = FENrulePlies;\r
12160               epStatus[0] = FENepStatus;\r
12161               for( i=0; i<nrCastlingRights; i++ )\r
12162                   castlingRights[0][i] = FENcastlingRights[i];\r
12163           }\r
12164       EditPositionDone();\r
12165       DisplayBothClocks();\r
12166       DrawPosition(FALSE, boards[currentMove]);\r
12167     }\r
12168   }\r
12169 }\r