changes from H.G. Muller; version 4.3.13
[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, int setup));\r
218 \r
219 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment\r
220 \r
221 extern int tinyLayout, smallLayout;\r
222 static ChessProgramStats programStats;\r
223 static int exiting = 0; /* [HGM] moved to top */\r
224 static int setboardSpoiledMachineBlack = 0, errorExitFlag = 0;\r
225 extern int startedFromPositionFile;\r
226 int startedFromPositionFile = FALSE; Board filePosition;    /* [HGM] loadPos */\r
227 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */\r
228 \r
229 /* States for ics_getting_history */\r
230 #define H_FALSE 0\r
231 #define H_REQUESTED 1\r
232 #define H_GOT_REQ_HEADER 2\r
233 #define H_GOT_UNREQ_HEADER 3\r
234 #define H_GETTING_MOVES 4\r
235 #define H_GOT_UNWANTED_HEADER 5\r
236 \r
237 /* whosays values for GameEnds */\r
238 #define GE_ICS 0\r
239 #define GE_ENGINE 1\r
240 #define GE_PLAYER 2\r
241 #define GE_FILE 3\r
242 #define GE_XBOARD 4\r
243 #define GE_ENGINE1 5\r
244 #define GE_ENGINE2 6\r
245 \r
246 /* Maximum number of games in a cmail message */\r
247 #define CMAIL_MAX_GAMES 20\r
248 \r
249 /* Different types of move when calling RegisterMove */\r
250 #define CMAIL_MOVE   0\r
251 #define CMAIL_RESIGN 1\r
252 #define CMAIL_DRAW   2\r
253 #define CMAIL_ACCEPT 3\r
254 \r
255 /* Different types of result to remember for each game */\r
256 #define CMAIL_NOT_RESULT 0\r
257 #define CMAIL_OLD_RESULT 1\r
258 #define CMAIL_NEW_RESULT 2\r
259 \r
260 /* Telnet protocol constants */\r
261 #define TN_WILL 0373\r
262 #define TN_WONT 0374\r
263 #define TN_DO   0375\r
264 #define TN_DONT 0376\r
265 #define TN_IAC  0377\r
266 #define TN_ECHO 0001\r
267 #define TN_SGA  0003\r
268 #define TN_PORT 23\r
269 \r
270 /* [AS] */\r
271 static char * safeStrCpy( char * dst, const char * src, size_t count )\r
272 {\r
273     assert( dst != NULL );\r
274     assert( src != NULL );\r
275     assert( count > 0 );\r
276 \r
277     strncpy( dst, src, count );\r
278     dst[ count-1 ] = '\0';\r
279     return dst;\r
280 }\r
281 \r
282 static char * safeStrCat( char * dst, const char * src, size_t count )\r
283 {\r
284     size_t  dst_len;\r
285 \r
286     assert( dst != NULL );\r
287     assert( src != NULL );\r
288     assert( count > 0 );\r
289 \r
290     dst_len = strlen(dst);\r
291 \r
292     assert( count > dst_len ); /* Buffer size must be greater than current length */\r
293 \r
294     safeStrCpy( dst + dst_len, src, count - dst_len );\r
295 \r
296     return dst;\r
297 }\r
298 \r
299 /* Fake up flags for now, as we aren't keeping track of castling\r
300    availability yet. [HGM] Change of logic: the flag now only\r
301    indicates the type of castlings allowed by the rule of the game.\r
302    The actual rights themselves are maintained in the array\r
303    castlingRights, as part of the game history, and are not probed\r
304    by this function.\r
305  */\r
306 int\r
307 PosFlags(index)\r
308 {\r
309   int flags = F_ALL_CASTLE_OK;\r
310   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;\r
311   switch (gameInfo.variant) {\r
312   case VariantSuicide:\r
313   case VariantGiveaway:\r
314     flags |= F_IGNORE_CHECK;\r
315     flags &= ~F_ALL_CASTLE_OK;\r
316     break;\r
317   case VariantAtomic:\r
318     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
319     break;\r
320   case VariantKriegspiel:\r
321     flags |= F_KRIEGSPIEL_CAPTURE;\r
322     break;\r
323 /*  case VariantCapaRandom: */\r
324   case VariantFischeRandom:\r
325     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */\r
326   case VariantNoCastle:\r
327     flags &= ~F_ALL_CASTLE_OK;\r
328     break;\r
329   default:\r
330     break;\r
331   }\r
332   return flags;\r
333 }\r
334 \r
335 FILE *gameFileFP, *debugFP;\r
336 \r
337 /* \r
338     [AS] Note: sometimes, the sscanf() function is used to parse the input\r
339     into a fixed-size buffer. Because of this, we must be prepared to\r
340     receive strings as long as the size of the input buffer, which is currently\r
341     set to 4K for Windows and 8K for the rest.\r
342     So, we must either allocate sufficiently large buffers here, or\r
343     reduce the size of the input buffer in the input reading part.\r
344 */\r
345 \r
346 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
347 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
348 char thinkOutput1[MSG_SIZ*10];\r
349 \r
350 ChessProgramState first, second;\r
351 \r
352 /* premove variables */\r
353 int premoveToX = 0;\r
354 int premoveToY = 0;\r
355 int premoveFromX = 0;\r
356 int premoveFromY = 0;\r
357 int premovePromoChar = 0;\r
358 int gotPremove = 0;\r
359 Boolean alarmSounded;\r
360 /* end premove variables */\r
361 \r
362 #define ICS_GENERIC 0\r
363 #define ICS_ICC 1\r
364 #define ICS_FICS 2\r
365 #define ICS_CHESSNET 3 /* not really supported */\r
366 int ics_type = ICS_GENERIC;\r
367 char *ics_prefix = "$";\r
368 \r
369 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
370 int pauseExamForwardMostMove = 0;\r
371 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
372 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
373 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
374 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
375 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
376 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
377 int whiteFlag = FALSE, blackFlag = FALSE;\r
378 int userOfferedDraw = FALSE;\r
379 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
380 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
381 int cmailMoveType[CMAIL_MAX_GAMES];\r
382 long ics_clock_paused = 0;\r
383 ProcRef icsPR = NoProc, cmailPR = NoProc;\r
384 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
385 GameMode gameMode = BeginningOfGame;\r
386 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
387 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
388 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
389 int hiddenThinkOutputState = 0; /* [AS] */\r
390 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
391 int adjudicateLossPlies = 6;\r
392 char white_holding[64], black_holding[64];\r
393 TimeMark lastNodeCountTime;\r
394 long lastNodeCount=0;\r
395 int have_sent_ICS_logon = 0;\r
396 int movesPerSession;\r
397 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
398 long timeControl_2; /* [AS] Allow separate time controls */\r
399 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */\r
400 long timeRemaining[2][MAX_MOVES];\r
401 int matchGame = 0;\r
402 TimeMark programStartTime;\r
403 char ics_handle[MSG_SIZ];\r
404 int have_set_title = 0;\r
405 \r
406 /* animateTraining preserves the state of appData.animate\r
407  * when Training mode is activated. This allows the\r
408  * response to be animated when appData.animate == TRUE and\r
409  * appData.animateDragging == TRUE.\r
410  */\r
411 Boolean animateTraining;\r
412 \r
413 GameInfo gameInfo;\r
414 \r
415 AppData appData;\r
416 \r
417 Board boards[MAX_MOVES];\r
418 /* [HGM] Following 7 needed for accurate legality tests: */\r
419 char  epStatus[MAX_MOVES];\r
420 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
421 char  castlingRank[BOARD_SIZE]; // and corresponding ranks\r
422 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];\r
423 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
424 int   initialRulePlies, FENrulePlies;\r
425 char  FENepStatus;\r
426 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)\r
427 int loadFlag = 0; \r
428 \r
429 ChessSquare  FIDEArray[2][BOARD_SIZE] = {\r
430     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
431         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
432     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
433         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
434 };\r
435 \r
436 ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
437     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
438         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
439     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
440         BlackKing, BlackKing, BlackKnight, BlackRook }\r
441 };\r
442 \r
443 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {\r
444     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
445         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
446     { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
447         BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
448 };\r
449 \r
450 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
451     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
452         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
453     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
454         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
455 };\r
456 \r
457 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
458     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,\r
459         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
460     { BlackRook, BlackKnight, BlackAlfil, BlackKing,\r
461         BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
462 };\r
463 \r
464 \r
465 #if (BOARD_SIZE>=10)\r
466 ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
467     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
468         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
469     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
470         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
471 };\r
472 \r
473 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
474     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
475         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
476     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
477         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
478 };\r
479 \r
480 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
481     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, \r
482         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
483     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, \r
484         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
485 };\r
486 \r
487 #ifdef GOTHIC\r
488 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
489     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
490         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },\r
491     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
492         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }\r
493 };\r
494 #else // !GOTHIC\r
495 #define GothicArray CapablancaArray\r
496 #endif // !GOTHIC\r
497 \r
498 #ifdef FALCON\r
499 ChessSquare FalconArray[2][BOARD_SIZE] = {\r
500     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, \r
501         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },\r
502     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, \r
503         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }\r
504 };\r
505 #else // !FALCON\r
506 #define FalconArray CapablancaArray\r
507 #endif // !FALCON\r
508 \r
509 #else // !(BOARD_SIZE>=10)\r
510 #define XiangqiPosition FIDEArray\r
511 #define CapablancaArray FIDEArray\r
512 #define GothicArray FIDEArray\r
513 #endif // !(BOARD_SIZE>=10)\r
514 \r
515 #if (BOARD_SIZE>=12)\r
516 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
517     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
518         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
519     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
520         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
521 };\r
522 #else // !(BOARD_SIZE>=12)\r
523 #define CourierArray CapablancaArray\r
524 #endif // !(BOARD_SIZE>=12)\r
525 \r
526 \r
527 Board initialPosition;\r
528 \r
529 \r
530 /* Convert str to a rating. Checks for special cases of "----",\r
531 \r
532    "++++", etc. Also strips ()'s */\r
533 int\r
534 string_to_rating(str)\r
535   char *str;\r
536 {\r
537   while(*str && !isdigit(*str)) ++str;\r
538   if (!*str)\r
539     return 0;   /* One of the special "no rating" cases */\r
540   else\r
541     return atoi(str);\r
542 }\r
543 \r
544 void\r
545 ClearProgramStats()\r
546 {\r
547     /* Init programStats */\r
548     programStats.movelist[0] = 0;\r
549     programStats.depth = 0;\r
550     programStats.nr_moves = 0;\r
551     programStats.moves_left = 0;\r
552     programStats.nodes = 0;\r
553     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output\r
554     programStats.score = 0;\r
555     programStats.got_only_move = 0;\r
556     programStats.got_fail = 0;\r
557     programStats.line_is_book = 0;\r
558 }\r
559 \r
560 void\r
561 InitBackEnd1()\r
562 {\r
563     int matched, min, sec;\r
564 \r
565     GetTimeMark(&programStartTime);\r
566 \r
567     ClearProgramStats();\r
568     programStats.ok_to_send = 1;\r
569     programStats.seen_stat = 0;\r
570 \r
571     /*\r
572      * Initialize game list\r
573      */\r
574     ListNew(&gameList);\r
575 \r
576 \r
577     /*\r
578      * Internet chess server status\r
579      */\r
580     if (appData.icsActive) {\r
581         appData.matchMode = FALSE;\r
582         appData.matchGames = 0;\r
583 #if ZIPPY       \r
584         appData.noChessProgram = !appData.zippyPlay;\r
585 #else\r
586         appData.zippyPlay = FALSE;\r
587         appData.zippyTalk = FALSE;\r
588         appData.noChessProgram = TRUE;\r
589 #endif\r
590         if (*appData.icsHelper != NULLCHAR) {\r
591             appData.useTelnet = TRUE;\r
592             appData.telnetProgram = appData.icsHelper;\r
593         }\r
594     } else {\r
595         appData.zippyTalk = appData.zippyPlay = FALSE;\r
596     }\r
597 \r
598     /* [AS] Initialize pv info list [HGM] and game state */\r
599     {\r
600         int i, j;\r
601 \r
602         for( i=0; i<MAX_MOVES; i++ ) {\r
603             pvInfoList[i].depth = -1;\r
604             epStatus[i]=EP_NONE;\r
605             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
606         }\r
607     }\r
608 \r
609     /*\r
610      * Parse timeControl resource\r
611      */\r
612     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
613                           appData.movesPerSession)) {\r
614         char buf[MSG_SIZ];\r
615         sprintf(buf, "bad timeControl option %s", appData.timeControl);\r
616         DisplayFatalError(buf, 0, 2);\r
617     }\r
618 \r
619     /*\r
620      * Parse searchTime resource\r
621      */\r
622     if (*appData.searchTime != NULLCHAR) {\r
623         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
624         if (matched == 1) {\r
625             searchTime = min * 60;\r
626         } else if (matched == 2) {\r
627             searchTime = min * 60 + sec;\r
628         } else {\r
629             char buf[MSG_SIZ];\r
630             sprintf(buf, "bad searchTime option %s", appData.searchTime);\r
631             DisplayFatalError(buf, 0, 2);\r
632         }\r
633     }\r
634 \r
635     /* [AS] Adjudication threshold */\r
636     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
637     \r
638     first.which = "first";\r
639     second.which = "second";\r
640     first.maybeThinking = second.maybeThinking = FALSE;\r
641     first.pr = second.pr = NoProc;\r
642     first.isr = second.isr = NULL;\r
643     first.sendTime = second.sendTime = 2;\r
644     first.sendDrawOffers = 1;\r
645     if (appData.firstPlaysBlack) {\r
646         first.twoMachinesColor = "black\n";\r
647         second.twoMachinesColor = "white\n";\r
648     } else {\r
649         first.twoMachinesColor = "white\n";\r
650         second.twoMachinesColor = "black\n";\r
651     }\r
652     first.program = appData.firstChessProgram;\r
653     second.program = appData.secondChessProgram;\r
654     first.host = appData.firstHost;\r
655     second.host = appData.secondHost;\r
656     first.dir = appData.firstDirectory;\r
657     second.dir = appData.secondDirectory;\r
658     first.other = &second;\r
659     second.other = &first;\r
660     first.initString = appData.initString;\r
661     second.initString = appData.secondInitString;\r
662     first.computerString = appData.firstComputerString;\r
663     second.computerString = appData.secondComputerString;\r
664     first.useSigint = second.useSigint = TRUE;\r
665     first.useSigterm = second.useSigterm = TRUE;\r
666     first.reuse = appData.reuseFirst;\r
667     second.reuse = appData.reuseSecond;\r
668     first.useSetboard = second.useSetboard = FALSE;\r
669     first.useSAN = second.useSAN = FALSE;\r
670     first.usePing = second.usePing = FALSE;\r
671     first.lastPing = second.lastPing = 0;\r
672     first.lastPong = second.lastPong = 0;\r
673     first.usePlayother = second.usePlayother = FALSE;\r
674     first.useColors = second.useColors = TRUE;\r
675     first.useUsermove = second.useUsermove = FALSE;\r
676     first.sendICS = second.sendICS = FALSE;\r
677     first.sendName = second.sendName = appData.icsActive;\r
678     first.sdKludge = second.sdKludge = FALSE;\r
679     first.stKludge = second.stKludge = FALSE;\r
680     TidyProgramName(first.program, first.host, first.tidy);\r
681     TidyProgramName(second.program, second.host, second.tidy);\r
682     first.matchWins = second.matchWins = 0;\r
683     strcpy(first.variants, appData.variant);\r
684     strcpy(second.variants, appData.variant);\r
685     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
686     first.analyzing = second.analyzing = FALSE;\r
687     first.initDone = second.initDone = FALSE;\r
688 \r
689     /* New features added by Tord: */\r
690     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
691     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
692     /* End of new features added by Tord. */\r
693 \r
694     /* [HGM] time odds: set factor for each machine */\r
695     first.timeOdds  = appData.firstTimeOdds;\r
696     second.timeOdds = appData.secondTimeOdds;\r
697     { int norm = 1;\r
698         if(appData.timeOddsMode) {\r
699             norm = first.timeOdds;\r
700             if(norm > second.timeOdds) norm = second.timeOdds;\r
701         }\r
702         first.timeOdds /= norm;\r
703         second.timeOdds /= norm;\r
704     }\r
705 \r
706     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
707     first.accumulateTC = appData.firstAccumulateTC;\r
708     second.accumulateTC = appData.secondAccumulateTC;\r
709     first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
710 \r
711     /* [HGM] debug */\r
712     first.debug = second.debug = FALSE;\r
713 \r
714     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
715     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
716     first.isUCI = appData.firstIsUCI; /* [AS] */\r
717     second.isUCI = appData.secondIsUCI; /* [AS] */\r
718     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
719     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
720 \r
721     if (appData.firstProtocolVersion > PROTOVER ||\r
722         appData.firstProtocolVersion < 1) {\r
723       char buf[MSG_SIZ];\r
724       sprintf(buf, "protocol version %d not supported",\r
725               appData.firstProtocolVersion);\r
726       DisplayFatalError(buf, 0, 2);\r
727     } else {\r
728       first.protocolVersion = appData.firstProtocolVersion;\r
729     }\r
730 \r
731     if (appData.secondProtocolVersion > PROTOVER ||\r
732         appData.secondProtocolVersion < 1) {\r
733       char buf[MSG_SIZ];\r
734       sprintf(buf, "protocol version %d not supported",\r
735               appData.secondProtocolVersion);\r
736       DisplayFatalError(buf, 0, 2);\r
737     } else {\r
738       second.protocolVersion = appData.secondProtocolVersion;\r
739     }\r
740 \r
741     if (appData.icsActive) {\r
742         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
743     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
744         appData.clockMode = FALSE;\r
745         first.sendTime = second.sendTime = 0;\r
746     }\r
747     \r
748 #if ZIPPY\r
749     /* Override some settings from environment variables, for backward\r
750        compatibility.  Unfortunately it's not feasible to have the env\r
751        vars just set defaults, at least in xboard.  Ugh.\r
752     */\r
753     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
754       ZippyInit();\r
755     }\r
756 #endif\r
757     \r
758     if (appData.noChessProgram) {\r
759         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
760                                         + strlen(PATCHLEVEL));\r
761         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
762     } else {\r
763         char *p, *q;\r
764         q = first.program;\r
765         while (*q != ' ' && *q != NULLCHAR) q++;\r
766         p = q;\r
767         while (p > first.program && *(p-1) != '/') p--;\r
768         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
769                                         + strlen(PATCHLEVEL) + (q - p));\r
770         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
771         strncat(programVersion, p, q - p);\r
772     }\r
773 \r
774     if (!appData.icsActive) {\r
775       char buf[MSG_SIZ];\r
776       /* Check for variants that are supported only in ICS mode,\r
777          or not at all.  Some that are accepted here nevertheless\r
778          have bugs; see comments below.\r
779       */\r
780       VariantClass variant = StringToVariant(appData.variant);\r
781       switch (variant) {\r
782       case VariantBughouse:     /* need four players and two boards */\r
783       case VariantKriegspiel:   /* need to hide pieces and move details */\r
784       /* case VariantFischeRandom: (Fabien: moved below) */\r
785         sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);\r
786         DisplayFatalError(buf, 0, 2);\r
787         return;\r
788 \r
789       case VariantUnknown:\r
790       case VariantLoadable:\r
791       case Variant29:\r
792       case Variant30:\r
793       case Variant31:\r
794       case Variant32:\r
795       case Variant33:\r
796       case Variant34:\r
797       case Variant35:\r
798       case Variant36:\r
799       default:\r
800         sprintf(buf, "Unknown variant name %s", appData.variant);\r
801         DisplayFatalError(buf, 0, 2);\r
802         return;\r
803 \r
804       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
805       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
806       case VariantGothic:     /* [HGM] should work */\r
807       case VariantCapablanca: /* [HGM] should work */\r
808       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
809       case VariantShogi:      /* [HGM] drops not tested for legality */\r
810       case VariantKnightmate: /* [HGM] should work */\r
811       case VariantCylinder:   /* [HGM] untested */\r
812       case VariantFalcon:     /* [HGM] untested */\r
813       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
814                                  offboard interposition not understood */\r
815       case VariantNormal:     /* definitely works! */\r
816       case VariantWildCastle: /* pieces not automatically shuffled */\r
817       case VariantNoCastle:   /* pieces not automatically shuffled */\r
818       case VariantFischeRandom: /* Fabien: pieces not automatically shuffled */\r
819       case VariantLosers:     /* should work except for win condition,\r
820                                  and doesn't know captures are mandatory */\r
821       case VariantSuicide:    /* should work except for win condition,\r
822                                  and doesn't know captures are mandatory */\r
823       case VariantGiveaway:   /* should work except for win condition,\r
824                                  and doesn't know captures are mandatory */\r
825       case VariantTwoKings:   /* should work */\r
826       case VariantAtomic:     /* should work except for win condition */\r
827       case Variant3Check:     /* should work except for win condition */\r
828       case VariantShatranj:   /* might work if TestLegality is off */\r
829         break;\r
830       }\r
831     }\r
832 }\r
833 \r
834 int NextIntegerFromString( char ** str, long * value )\r
835 {\r
836     int result = -1;\r
837     char * s = *str;\r
838 \r
839     while( *s == ' ' || *s == '\t' ) {\r
840         s++;\r
841     }\r
842 \r
843     *value = 0;\r
844 \r
845     if( *s >= '0' && *s <= '9' ) {\r
846         while( *s >= '0' && *s <= '9' ) {\r
847             *value = *value * 10 + (*s - '0');\r
848             s++;\r
849         }\r
850 \r
851         result = 0;\r
852     }\r
853 \r
854     *str = s;\r
855 \r
856     return result;\r
857 }\r
858 \r
859 int NextTimeControlFromString( char ** str, long * value )\r
860 {\r
861     long temp;\r
862     int result = NextIntegerFromString( str, &temp );\r
863 \r
864     if( result == 0 ) {\r
865         *value = temp * 60; /* Minutes */\r
866         if( **str == ':' ) {\r
867             (*str)++;\r
868             result = NextIntegerFromString( str, &temp );\r
869             *value += temp; /* Seconds */\r
870         }\r
871     }\r
872 \r
873     return result;\r
874 }\r
875 \r
876 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
877 {   /* [HGM] routine added to read '+moves/time' for secondary time control */\r
878     int result = -1; long temp, temp2;\r
879 \r
880     if(**str != '+') return -1; // old params remain in force!\r
881     (*str)++;\r
882     if( NextTimeControlFromString( str, &temp ) ) return -1;\r
883 \r
884     if(**str != '/') {\r
885         /* time only: incremental or sudden-death time control */\r
886         if(**str == '+') { /* increment follows; read it */\r
887             (*str)++;\r
888             if(result = NextIntegerFromString( str, &temp2)) return -1;\r
889             *inc = temp2 * 1000;\r
890         } else *inc = 0;\r
891         *moves = 0; *tc = temp * 1000; \r
892         return 0;\r
893     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */\r
894 \r
895     (*str)++; /* classical time control */\r
896     result = NextTimeControlFromString( str, &temp2);\r
897     if(result == 0) {\r
898         *moves = temp/60;\r
899         *tc    = temp2 * 1000;\r
900         *inc   = 0;\r
901     }\r
902     return result;\r
903 }\r
904 \r
905 int GetTimeQuota(int movenr)\r
906 {   /* [HGM] get time to add from the multi-session time-control string */\r
907     int moves=1; /* kludge to force reading of first session */\r
908     long time, increment;\r
909     char *s = fullTimeControlString;\r
910 \r
911     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
912     do {\r
913         if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
914         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
915         if(movenr == -1) return time;    /* last move before new session     */\r
916         if(!moves) return increment;     /* current session is incremental   */\r
917         if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
918     } while(movenr >= -1);               /* try again for next session       */\r
919 \r
920     return 0; // no new time quota on this move\r
921 }\r
922 \r
923 int\r
924 ParseTimeControl(tc, ti, mps)\r
925      char *tc;\r
926      int ti;\r
927      int mps;\r
928 {\r
929 #if 0\r
930     int matched, min, sec;\r
931 \r
932     matched = sscanf(tc, "%d:%d", &min, &sec);\r
933     if (matched == 1) {\r
934         timeControl = min * 60 * 1000;\r
935     } else if (matched == 2) {\r
936         timeControl = (min * 60 + sec) * 1000;\r
937     } else {\r
938         return FALSE;\r
939     }\r
940 #else\r
941     long tc1;\r
942     long tc2;\r
943     char buf[MSG_SIZ];\r
944 \r
945     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
946     if(ti > 0) {\r
947         if(mps)\r
948              sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
949         else sprintf(buf, "+%s+%d", tc, ti);\r
950     } else {\r
951         if(mps)\r
952              sprintf(buf, "+%d/%s", mps, tc);\r
953         else sprintf(buf, "+%s", tc);\r
954     }\r
955     fullTimeControlString = StrSave(buf);\r
956 \r
957     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
958         return FALSE;\r
959     }\r
960 \r
961     if( *tc == '/' ) {\r
962         /* Parse second time control */\r
963         tc++;\r
964 \r
965         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
966             return FALSE;\r
967         }\r
968 \r
969         if( tc2 == 0 ) {\r
970             return FALSE;\r
971         }\r
972 \r
973         timeControl_2 = tc2 * 1000;\r
974     }\r
975     else {\r
976         timeControl_2 = 0;\r
977     }\r
978 \r
979     if( tc1 == 0 ) {\r
980         return FALSE;\r
981     }\r
982 \r
983     timeControl = tc1 * 1000;\r
984 #endif\r
985 \r
986     if (ti >= 0) {\r
987         timeIncrement = ti * 1000;  /* convert to ms */\r
988         movesPerSession = 0;\r
989     } else {\r
990         timeIncrement = 0;\r
991         movesPerSession = mps;\r
992     }\r
993     return TRUE;\r
994 }\r
995 \r
996 void\r
997 InitBackEnd2()\r
998 {\r
999     if (appData.debugMode) {\r
1000         fprintf(debugFP, "%s\n", programVersion);\r
1001     }\r
1002 \r
1003     if (appData.matchGames > 0) {\r
1004         appData.matchMode = TRUE;\r
1005     } else if (appData.matchMode) {\r
1006         appData.matchGames = 1;\r
1007     }\r
1008     Reset(TRUE, FALSE);\r
1009     if (appData.noChessProgram || first.protocolVersion == 1) {\r
1010       InitBackEnd3();\r
1011     } else {\r
1012       /* kludge: allow timeout for initial "feature" commands */\r
1013       FreezeUI();\r
1014       DisplayMessage("", "Starting chess program");\r
1015       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
1016     }\r
1017 }\r
1018 \r
1019 void\r
1020 InitBackEnd3 P((void))\r
1021 {\r
1022     GameMode initialMode;\r
1023     char buf[MSG_SIZ];\r
1024     int err;\r
1025 \r
1026     if (appData.debugMode) {\r
1027         fprintf(debugFP, "From InitBackend3\n");\r
1028     }\r
1029     InitChessProgram(&first, startedFromSetupPosition);\r
1030 \r
1031     if (appData.icsActive) {\r
1032         err = establish();\r
1033         if (err != 0) {\r
1034             if (*appData.icsCommPort != NULLCHAR) {\r
1035                 sprintf(buf, "Could not open comm port %s",  \r
1036                         appData.icsCommPort);\r
1037             } else {\r
1038                 sprintf(buf, "Could not connect to host %s, port %s",  \r
1039                         appData.icsHost, appData.icsPort);\r
1040             }\r
1041             DisplayFatalError(buf, err, 1);\r
1042             return;\r
1043         }\r
1044         SetICSMode();\r
1045         telnetISR =\r
1046           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
1047         fromUserISR =\r
1048           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
1049     } else if (appData.noChessProgram) {\r
1050         SetNCPMode();\r
1051     } else {\r
1052         SetGNUMode();\r
1053     }\r
1054 \r
1055     if (*appData.cmailGameName != NULLCHAR) {\r
1056         SetCmailMode();\r
1057         OpenLoopback(&cmailPR);\r
1058         cmailISR =\r
1059           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
1060     }\r
1061     \r
1062     ThawUI();\r
1063     DisplayMessage("", "");\r
1064     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
1065       initialMode = BeginningOfGame;\r
1066     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
1067       initialMode = TwoMachinesPlay;\r
1068     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
1069       initialMode = AnalyzeFile; \r
1070     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
1071       initialMode = AnalyzeMode;\r
1072     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
1073       initialMode = MachinePlaysWhite;\r
1074     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
1075       initialMode = MachinePlaysBlack;\r
1076     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
1077       initialMode = EditGame;\r
1078     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
1079       initialMode = EditPosition;\r
1080     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
1081       initialMode = Training;\r
1082     } else {\r
1083       sprintf(buf, "Unknown initialMode %s", appData.initialMode);\r
1084       DisplayFatalError(buf, 0, 2);\r
1085       return;\r
1086     }\r
1087 \r
1088     if (appData.matchMode) {\r
1089         /* Set up machine vs. machine match */\r
1090         if (appData.noChessProgram) {\r
1091             DisplayFatalError("Can't have a match with no chess programs",\r
1092                               0, 2);\r
1093             return;\r
1094         }\r
1095         matchMode = TRUE;\r
1096         matchGame = 1;\r
1097         if (*appData.loadGameFile != NULLCHAR) {\r
1098             if (!LoadGameFromFile(appData.loadGameFile,\r
1099                                   appData.loadGameIndex,\r
1100                                   appData.loadGameFile, FALSE)) {\r
1101                 DisplayFatalError("Bad game file", 0, 1);\r
1102                 return;\r
1103             }\r
1104         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1105             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1106                                       appData.loadPositionIndex,\r
1107                                       appData.loadPositionFile)) {\r
1108                 DisplayFatalError("Bad position file", 0, 1);\r
1109                 return;\r
1110             }\r
1111         }\r
1112         TwoMachinesEvent();\r
1113     } else if (*appData.cmailGameName != NULLCHAR) {\r
1114         /* Set up cmail mode */\r
1115         ReloadCmailMsgEvent(TRUE);\r
1116     } else {\r
1117         /* Set up other modes */\r
1118         if (initialMode == AnalyzeFile) {\r
1119           if (*appData.loadGameFile == NULLCHAR) {\r
1120             DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);\r
1121             return;\r
1122           }\r
1123         }\r
1124         if (*appData.loadGameFile != NULLCHAR) {\r
1125             (void) LoadGameFromFile(appData.loadGameFile,\r
1126                                     appData.loadGameIndex,\r
1127                                     appData.loadGameFile, TRUE);\r
1128         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1129             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1130                                         appData.loadPositionIndex,\r
1131                                         appData.loadPositionFile);\r
1132             /* [HGM] try to make self-starting even after FEN load */\r
1133             /* to allow automatic setup of fairy variants with wtm */\r
1134             if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
1135                 gameMode = BeginningOfGame;\r
1136                 setboardSpoiledMachineBlack = 1;\r
1137             }\r
1138             /* [HGM] loadPos: make that every new game uses the setup */\r
1139             /* from file as long as we do not switch variant          */\r
1140             if(!blackPlaysFirst) { int i;\r
1141                 startedFromPositionFile = TRUE;\r
1142                 CopyBoard(filePosition, boards[0]);\r
1143                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
1144             }\r
1145         }\r
1146         if (initialMode == AnalyzeMode) {\r
1147           if (appData.noChessProgram) {\r
1148             DisplayFatalError("Analysis mode requires a chess engine", 0, 2);\r
1149             return;\r
1150           }\r
1151           if (appData.icsActive) {\r
1152             DisplayFatalError("Analysis mode does not work with ICS mode",0,2);\r
1153             return;\r
1154           }\r
1155           AnalyzeModeEvent();\r
1156         } else if (initialMode == AnalyzeFile) {\r
1157           ShowThinkingEvent(TRUE);\r
1158           AnalyzeFileEvent();\r
1159           AnalysisPeriodicEvent(1);\r
1160         } else if (initialMode == MachinePlaysWhite) {\r
1161           if (appData.noChessProgram) {\r
1162             DisplayFatalError("MachineWhite mode requires a chess engine",\r
1163                               0, 2);\r
1164             return;\r
1165           }\r
1166           if (appData.icsActive) {\r
1167             DisplayFatalError("MachineWhite mode does not work with ICS mode",\r
1168                               0, 2);\r
1169             return;\r
1170           }\r
1171           MachineWhiteEvent();\r
1172         } else if (initialMode == MachinePlaysBlack) {\r
1173           if (appData.noChessProgram) {\r
1174             DisplayFatalError("MachineBlack mode requires a chess engine",\r
1175                               0, 2);\r
1176             return;\r
1177           }\r
1178           if (appData.icsActive) {\r
1179             DisplayFatalError("MachineBlack mode does not work with ICS mode",\r
1180                               0, 2);\r
1181             return;\r
1182           }\r
1183           MachineBlackEvent();\r
1184         } else if (initialMode == TwoMachinesPlay) {\r
1185           if (appData.noChessProgram) {\r
1186             DisplayFatalError("TwoMachines mode requires a chess engine",\r
1187                               0, 2);\r
1188             return;\r
1189           }\r
1190           if (appData.icsActive) {\r
1191             DisplayFatalError("TwoMachines mode does not work with ICS mode",\r
1192                               0, 2);\r
1193             return;\r
1194           }\r
1195           TwoMachinesEvent();\r
1196         } else if (initialMode == EditGame) {\r
1197           EditGameEvent();\r
1198         } else if (initialMode == EditPosition) {\r
1199           EditPositionEvent();\r
1200         } else if (initialMode == Training) {\r
1201           if (*appData.loadGameFile == NULLCHAR) {\r
1202             DisplayFatalError("Training mode requires a game file", 0, 2);\r
1203             return;\r
1204           }\r
1205           TrainingEvent();\r
1206         }\r
1207     }\r
1208 }\r
1209 \r
1210 /*\r
1211  * Establish will establish a contact to a remote host.port.\r
1212  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1213  *  used to talk to the host.\r
1214  * Returns 0 if okay, error code if not.\r
1215  */\r
1216 int\r
1217 establish()\r
1218 {\r
1219     char buf[MSG_SIZ];\r
1220 \r
1221     if (*appData.icsCommPort != NULLCHAR) {\r
1222         /* Talk to the host through a serial comm port */\r
1223         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1224 \r
1225     } else if (*appData.gateway != NULLCHAR) {\r
1226         if (*appData.remoteShell == NULLCHAR) {\r
1227             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1228             sprintf(buf, "%s %s %s",\r
1229                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1230             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1231 \r
1232         } else {\r
1233             /* Use the rsh program to run telnet program on a gateway host */\r
1234             if (*appData.remoteUser == NULLCHAR) {\r
1235                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1236                         appData.gateway, appData.telnetProgram,\r
1237                         appData.icsHost, appData.icsPort);\r
1238             } else {\r
1239                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1240                         appData.remoteShell, appData.gateway, \r
1241                         appData.remoteUser, appData.telnetProgram,\r
1242                         appData.icsHost, appData.icsPort);\r
1243             }\r
1244             return StartChildProcess(buf, "", &icsPR);\r
1245 \r
1246         }\r
1247     } else if (appData.useTelnet) {\r
1248         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1249 \r
1250     } else {\r
1251         /* TCP socket interface differs somewhat between\r
1252            Unix and NT; handle details in the front end.\r
1253            */\r
1254         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1255     }\r
1256 }\r
1257 \r
1258 void\r
1259 show_bytes(fp, buf, count)\r
1260      FILE *fp;\r
1261      char *buf;\r
1262      int count;\r
1263 {\r
1264     while (count--) {\r
1265         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1266             fprintf(fp, "\\%03o", *buf & 0xff);\r
1267         } else {\r
1268             putc(*buf, fp);\r
1269         }\r
1270         buf++;\r
1271     }\r
1272     fflush(fp);\r
1273 }\r
1274 \r
1275 /* Returns an errno value */\r
1276 int\r
1277 OutputMaybeTelnet(pr, message, count, outError)\r
1278      ProcRef pr;\r
1279      char *message;\r
1280      int count;\r
1281      int *outError;\r
1282 {\r
1283     char buf[8192], *p, *q, *buflim;\r
1284     int left, newcount, outcount;\r
1285 \r
1286     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1287         *appData.gateway != NULLCHAR) {\r
1288         if (appData.debugMode) {\r
1289             fprintf(debugFP, ">ICS: ");\r
1290             show_bytes(debugFP, message, count);\r
1291             fprintf(debugFP, "\n");\r
1292         }\r
1293         return OutputToProcess(pr, message, count, outError);\r
1294     }\r
1295 \r
1296     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1297     p = message;\r
1298     q = buf;\r
1299     left = count;\r
1300     newcount = 0;\r
1301     while (left) {\r
1302         if (q >= buflim) {\r
1303             if (appData.debugMode) {\r
1304                 fprintf(debugFP, ">ICS: ");\r
1305                 show_bytes(debugFP, buf, newcount);\r
1306                 fprintf(debugFP, "\n");\r
1307             }\r
1308             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1309             if (outcount < newcount) return -1; /* to be sure */\r
1310             q = buf;\r
1311             newcount = 0;\r
1312         }\r
1313         if (*p == '\n') {\r
1314             *q++ = '\r';\r
1315             newcount++;\r
1316         } else if (((unsigned char) *p) == TN_IAC) {\r
1317             *q++ = (char) TN_IAC;\r
1318             newcount ++;\r
1319         }\r
1320         *q++ = *p++;\r
1321         newcount++;\r
1322         left--;\r
1323     }\r
1324     if (appData.debugMode) {\r
1325         fprintf(debugFP, ">ICS: ");\r
1326         show_bytes(debugFP, buf, newcount);\r
1327         fprintf(debugFP, "\n");\r
1328     }\r
1329     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1330     if (outcount < newcount) return -1; /* to be sure */\r
1331     return count;\r
1332 }\r
1333 \r
1334 void\r
1335 read_from_player(isr, closure, message, count, error)\r
1336      InputSourceRef isr;\r
1337      VOIDSTAR closure;\r
1338      char *message;\r
1339      int count;\r
1340      int error;\r
1341 {\r
1342     int outError, outCount;\r
1343     static int gotEof = 0;\r
1344 \r
1345     /* Pass data read from player on to ICS */\r
1346     if (count > 0) {\r
1347         gotEof = 0;\r
1348         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1349         if (outCount < count) {\r
1350             DisplayFatalError("Error writing to ICS", outError, 1);\r
1351         }\r
1352     } else if (count < 0) {\r
1353         RemoveInputSource(isr);\r
1354         DisplayFatalError("Error reading from keyboard", error, 1);\r
1355     } else if (gotEof++ > 0) {\r
1356         RemoveInputSource(isr);\r
1357         DisplayFatalError("Got end of file from keyboard", 0, 0);\r
1358     }\r
1359 }\r
1360 \r
1361 void\r
1362 SendToICS(s)\r
1363      char *s;\r
1364 {\r
1365     int count, outCount, outError;\r
1366 \r
1367     if (icsPR == NULL) return;\r
1368 \r
1369     count = strlen(s);\r
1370     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1371     if (outCount < count) {\r
1372         DisplayFatalError("Error writing to ICS", outError, 1);\r
1373     }\r
1374 }\r
1375 \r
1376 /* This is used for sending logon scripts to the ICS. Sending\r
1377    without a delay causes problems when using timestamp on ICC\r
1378    (at least on my machine). */\r
1379 void\r
1380 SendToICSDelayed(s,msdelay)\r
1381      char *s;\r
1382      long msdelay;\r
1383 {\r
1384     int count, outCount, outError;\r
1385 \r
1386     if (icsPR == NULL) return;\r
1387 \r
1388     count = strlen(s);\r
1389     if (appData.debugMode) {\r
1390         fprintf(debugFP, ">ICS: ");\r
1391         show_bytes(debugFP, s, count);\r
1392         fprintf(debugFP, "\n");\r
1393     }\r
1394     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1395                                       msdelay);\r
1396     if (outCount < count) {\r
1397         DisplayFatalError("Error writing to ICS", outError, 1);\r
1398     }\r
1399 }\r
1400 \r
1401 \r
1402 /* Remove all highlighting escape sequences in s\r
1403    Also deletes any suffix starting with '(' \r
1404    */\r
1405 char *\r
1406 StripHighlightAndTitle(s)\r
1407      char *s;\r
1408 {\r
1409     static char retbuf[MSG_SIZ];\r
1410     char *p = retbuf;\r
1411 \r
1412     while (*s != NULLCHAR) {\r
1413         while (*s == '\033') {\r
1414             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1415             if (*s != NULLCHAR) s++;\r
1416         }\r
1417         while (*s != NULLCHAR && *s != '\033') {\r
1418             if (*s == '(' || *s == '[') {\r
1419                 *p = NULLCHAR;\r
1420                 return retbuf;\r
1421             }\r
1422             *p++ = *s++;\r
1423         }\r
1424     }\r
1425     *p = NULLCHAR;\r
1426     return retbuf;\r
1427 }\r
1428 \r
1429 /* Remove all highlighting escape sequences in s */\r
1430 char *\r
1431 StripHighlight(s)\r
1432      char *s;\r
1433 {\r
1434     static char retbuf[MSG_SIZ];\r
1435     char *p = retbuf;\r
1436 \r
1437     while (*s != NULLCHAR) {\r
1438         while (*s == '\033') {\r
1439             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1440             if (*s != NULLCHAR) s++;\r
1441         }\r
1442         while (*s != NULLCHAR && *s != '\033') {\r
1443             *p++ = *s++;\r
1444         }\r
1445     }\r
1446     *p = NULLCHAR;\r
1447     return retbuf;\r
1448 }\r
1449 \r
1450 char *variantNames[] = VARIANT_NAMES;\r
1451 char *\r
1452 VariantName(v)\r
1453      VariantClass v;\r
1454 {\r
1455     return variantNames[v];\r
1456 }\r
1457 \r
1458 \r
1459 /* Identify a variant from the strings the chess servers use or the\r
1460    PGN Variant tag names we use. */\r
1461 VariantClass\r
1462 StringToVariant(e)\r
1463      char *e;\r
1464 {\r
1465     char *p;\r
1466     int wnum = -1;\r
1467     VariantClass v = VariantNormal;\r
1468     int i, found = FALSE;\r
1469     char buf[MSG_SIZ];\r
1470 \r
1471     if (!e) return v;\r
1472 \r
1473     /* [HGM] skip over optional board-size prefixes */\r
1474     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
1475         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1476         while( *e++ != '_');\r
1477     }\r
1478 \r
1479     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1480       if (StrCaseStr(e, variantNames[i])) {\r
1481         v = (VariantClass) i;\r
1482         found = TRUE;\r
1483         break;\r
1484       }\r
1485     }\r
1486 \r
1487     if (!found) {\r
1488       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1489           || StrCaseStr(e, "wild/fr")) {\r
1490         v = VariantFischeRandom;\r
1491       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1492                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1493         p += i;\r
1494         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1495         if (isdigit(*p)) {\r
1496           wnum = atoi(p);\r
1497         } else {\r
1498           wnum = -1;\r
1499         }\r
1500         switch (wnum) {\r
1501         case 0: /* FICS only, actually */\r
1502         case 1:\r
1503           /* Castling legal even if K starts on d-file */\r
1504           v = VariantWildCastle;\r
1505           break;\r
1506         case 2:\r
1507         case 3:\r
1508         case 4:\r
1509           /* Castling illegal even if K & R happen to start in\r
1510              normal positions. */\r
1511           v = VariantNoCastle;\r
1512           break;\r
1513         case 5:\r
1514         case 7:\r
1515         case 8:\r
1516         case 10:\r
1517         case 11:\r
1518         case 12:\r
1519         case 13:\r
1520         case 14:\r
1521         case 15:\r
1522         case 18:\r
1523         case 19:\r
1524           /* Castling legal iff K & R start in normal positions */\r
1525           v = VariantNormal;\r
1526           break;\r
1527         case 6:\r
1528         case 20:\r
1529         case 21:\r
1530           /* Special wilds for position setup; unclear what to do here */\r
1531           v = VariantLoadable;\r
1532           break;\r
1533         case 9:\r
1534           /* Bizarre ICC game */\r
1535           v = VariantTwoKings;\r
1536           break;\r
1537         case 16:\r
1538           v = VariantKriegspiel;\r
1539           break;\r
1540         case 17:\r
1541           v = VariantLosers;\r
1542           break;\r
1543         case 22:\r
1544           v = VariantFischeRandom;\r
1545           break;\r
1546         case 23:\r
1547           v = VariantCrazyhouse;\r
1548           break;\r
1549         case 24:\r
1550           v = VariantBughouse;\r
1551           break;\r
1552         case 25:\r
1553           v = Variant3Check;\r
1554           break;\r
1555         case 26:\r
1556           /* Not quite the same as FICS suicide! */\r
1557           v = VariantGiveaway;\r
1558           break;\r
1559         case 27:\r
1560           v = VariantAtomic;\r
1561           break;\r
1562         case 28:\r
1563           v = VariantShatranj;\r
1564           break;\r
1565 \r
1566         /* Temporary names for future ICC types.  The name *will* change in \r
1567            the next xboard/WinBoard release after ICC defines it. */\r
1568         case 29:\r
1569           v = Variant29;\r
1570           break;\r
1571         case 30:\r
1572           v = Variant30;\r
1573           break;\r
1574         case 31:\r
1575           v = Variant31;\r
1576           break;\r
1577         case 32:\r
1578           v = Variant32;\r
1579           break;\r
1580         case 33:\r
1581           v = Variant33;\r
1582           break;\r
1583         case 34:\r
1584           v = Variant34;\r
1585           break;\r
1586         case 35:\r
1587           v = Variant35;\r
1588           break;\r
1589         case 36:\r
1590           v = Variant36;\r
1591           break;\r
1592         case 37:\r
1593           v = VariantShogi;\r
1594           break;\r
1595         case 38:\r
1596           v = VariantXiangqi;\r
1597           break;\r
1598         case 39:\r
1599           v = VariantCourier;\r
1600           break;\r
1601         case 40:\r
1602           v = VariantGothic;\r
1603           break;\r
1604         case 41:\r
1605           v = VariantCapablanca;\r
1606           break;\r
1607         case 42:\r
1608           v = VariantKnightmate;\r
1609           break;\r
1610         case 43:\r
1611           v = VariantFairy;\r
1612           break;\r
1613         case 44:\r
1614           v = VariantCylinder;\r
1615           break;\r
1616         case 45:\r
1617           v = VariantFalcon;\r
1618           break;\r
1619 \r
1620         case -1:\r
1621           /* Found "wild" or "w" in the string but no number;\r
1622              must assume it's normal chess. */\r
1623           v = VariantNormal;\r
1624           break;\r
1625         default:\r
1626           sprintf(buf, "Unknown wild type %d", wnum);\r
1627           DisplayError(buf, 0);\r
1628           v = VariantUnknown;\r
1629           break;\r
1630         }\r
1631       }\r
1632     }\r
1633     if (appData.debugMode) {\r
1634       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",\r
1635               e, wnum, VariantName(v));\r
1636     }\r
1637     return v;\r
1638 }\r
1639 \r
1640 static int leftover_start = 0, leftover_len = 0;\r
1641 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1642 \r
1643 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1644    advance *index beyond it, and set leftover_start to the new value of\r
1645    *index; else return FALSE.  If pattern contains the character '*', it\r
1646    matches any sequence of characters not containing '\r', '\n', or the\r
1647    character following the '*' (if any), and the matched sequence(s) are\r
1648    copied into star_match.\r
1649    */\r
1650 int\r
1651 looking_at(buf, index, pattern)\r
1652      char *buf;\r
1653      int *index;\r
1654      char *pattern;\r
1655 {\r
1656     char *bufp = &buf[*index], *patternp = pattern;\r
1657     int star_count = 0;\r
1658     char *matchp = star_match[0];\r
1659     \r
1660     for (;;) {\r
1661         if (*patternp == NULLCHAR) {\r
1662             *index = leftover_start = bufp - buf;\r
1663             *matchp = NULLCHAR;\r
1664             return TRUE;\r
1665         }\r
1666         if (*bufp == NULLCHAR) return FALSE;\r
1667         if (*patternp == '*') {\r
1668             if (*bufp == *(patternp + 1)) {\r
1669                 *matchp = NULLCHAR;\r
1670                 matchp = star_match[++star_count];\r
1671                 patternp += 2;\r
1672                 bufp++;\r
1673                 continue;\r
1674             } else if (*bufp == '\n' || *bufp == '\r') {\r
1675                 patternp++;\r
1676                 if (*patternp == NULLCHAR)\r
1677                   continue;\r
1678                 else\r
1679                   return FALSE;\r
1680             } else {\r
1681                 *matchp++ = *bufp++;\r
1682                 continue;\r
1683             }\r
1684         }\r
1685         if (*patternp != *bufp) return FALSE;\r
1686         patternp++;\r
1687         bufp++;\r
1688     }\r
1689 }\r
1690 \r
1691 void\r
1692 SendToPlayer(data, length)\r
1693      char *data;\r
1694      int length;\r
1695 {\r
1696     int error, outCount;\r
1697     outCount = OutputToProcess(NoProc, data, length, &error);\r
1698     if (outCount < length) {\r
1699         DisplayFatalError("Error writing to display", error, 1);\r
1700     }\r
1701 }\r
1702 \r
1703 void\r
1704 PackHolding(packed, holding)\r
1705      char packed[];\r
1706      char *holding;\r
1707 {\r
1708     char *p = holding;\r
1709     char *q = packed;\r
1710     int runlength = 0;\r
1711     int curr = 9999;\r
1712     do {\r
1713         if (*p == curr) {\r
1714             runlength++;\r
1715         } else {\r
1716             switch (runlength) {\r
1717               case 0:\r
1718                 break;\r
1719               case 1:\r
1720                 *q++ = curr;\r
1721                 break;\r
1722               case 2:\r
1723                 *q++ = curr;\r
1724                 *q++ = curr;\r
1725                 break;\r
1726               default:\r
1727                 sprintf(q, "%d", runlength);\r
1728                 while (*q) q++;\r
1729                 *q++ = curr;\r
1730                 break;\r
1731             }\r
1732             runlength = 1;\r
1733             curr = *p;\r
1734         }\r
1735     } while (*p++);\r
1736     *q = NULLCHAR;\r
1737 }\r
1738 \r
1739 /* Telnet protocol requests from the front end */\r
1740 void\r
1741 TelnetRequest(ddww, option)\r
1742      unsigned char ddww, option;\r
1743 {\r
1744     unsigned char msg[3];\r
1745     int outCount, outError;\r
1746 \r
1747     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1748 \r
1749     if (appData.debugMode) {\r
1750         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1751         switch (ddww) {\r
1752           case TN_DO:\r
1753             ddwwStr = "DO";\r
1754             break;\r
1755           case TN_DONT:\r
1756             ddwwStr = "DONT";\r
1757             break;\r
1758           case TN_WILL:\r
1759             ddwwStr = "WILL";\r
1760             break;\r
1761           case TN_WONT:\r
1762             ddwwStr = "WONT";\r
1763             break;\r
1764           default:\r
1765             ddwwStr = buf1;\r
1766             sprintf(buf1, "%d", ddww);\r
1767             break;\r
1768         }\r
1769         switch (option) {\r
1770           case TN_ECHO:\r
1771             optionStr = "ECHO";\r
1772             break;\r
1773           default:\r
1774             optionStr = buf2;\r
1775             sprintf(buf2, "%d", option);\r
1776             break;\r
1777         }\r
1778         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1779     }\r
1780     msg[0] = TN_IAC;\r
1781     msg[1] = ddww;\r
1782     msg[2] = option;\r
1783     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1784     if (outCount < 3) {\r
1785         DisplayFatalError("Error writing to ICS", outError, 1);\r
1786     }\r
1787 }\r
1788 \r
1789 void\r
1790 DoEcho()\r
1791 {\r
1792     if (!appData.icsActive) return;\r
1793     TelnetRequest(TN_DO, TN_ECHO);\r
1794 }\r
1795 \r
1796 void\r
1797 DontEcho()\r
1798 {\r
1799     if (!appData.icsActive) return;\r
1800     TelnetRequest(TN_DONT, TN_ECHO);\r
1801 }\r
1802 \r
1803 void\r
1804 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1805 {\r
1806     /* put the holdings sent to us by the server on the board holdings area */\r
1807     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1808     char p;\r
1809     ChessSquare piece;\r
1810 \r
1811     if(gameInfo.holdingsWidth < 2)  return;\r
1812 \r
1813     if( (int)lowestPiece >= BlackPawn ) {\r
1814         holdingsColumn = 0;\r
1815         countsColumn = 1;\r
1816         holdingsStartRow = BOARD_HEIGHT-1;\r
1817         direction = -1;\r
1818     } else {\r
1819         holdingsColumn = BOARD_WIDTH-1;\r
1820         countsColumn = BOARD_WIDTH-2;\r
1821         holdingsStartRow = 0;\r
1822         direction = 1;\r
1823     }\r
1824 \r
1825     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1826         board[i][holdingsColumn] = EmptySquare;\r
1827         board[i][countsColumn]   = (ChessSquare) 0;\r
1828     }\r
1829     while( (p=*holdings++) != NULLCHAR ) {\r
1830         piece = CharToPiece( ToUpper(p) );\r
1831         if(piece == EmptySquare) continue;\r
1832         /*j = (int) piece - (int) WhitePawn;*/\r
1833         j = PieceToNumber(piece);\r
1834         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1835         if(j < 0) continue;               /* should not happen */\r
1836         piece = (ChessSquare) ( j + (int)lowestPiece );\r
1837         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1838         board[holdingsStartRow+j*direction][countsColumn]++;\r
1839     }\r
1840 \r
1841 }\r
1842 \r
1843 char startBoard[MSG_SIZ]; /* [HGM] variantswitch */\r
1844 \r
1845 void\r
1846 VariantSwitch(Board board, VariantClass newVariant)\r
1847 {\r
1848    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j, oldCurrentMove = currentMove;\r
1849    Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
1850 \r
1851    startedFromPositionFile = FALSE;\r
1852    if(gameInfo.variant == newVariant) return;\r
1853 \r
1854    /* [HGM] This routine is called each time an assignment is made to\r
1855     * gameInfo.variant during a game, to make sure the board sizes\r
1856     * are set to match the new variant. If that means adding or deleting\r
1857     * holdings, we shift the playing board accordingly\r
1858     * This kludge is needed because in ICS observe mode, we get boards\r
1859     * of an ongoing game without knowing the variant, and learn about the\r
1860     * latter only later. This can be because of the move list we requested,\r
1861     * in which case the game history is refilled from the beginning anyway,\r
1862     * but also when receiving holdings of a crazyhouse game. In the latter\r
1863     * case we want to add those holdings to the already received position.\r
1864     */\r
1865 \r
1866 \r
1867   if (appData.debugMode) {\r
1868     fprintf(debugFP, "Switch board from %s to %s\n",\r
1869                VariantName(gameInfo.variant), VariantName(newVariant));\r
1870     setbuf(debugFP, NULL);\r
1871   }\r
1872     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1873          switch(newVariant) {\r
1874             case VariantShogi:\r
1875               newWidth = 9;  newHeight = 9;\r
1876               gameInfo.holdingsSize = 7;\r
1877             case VariantBughouse:\r
1878             case VariantCrazyhouse:\r
1879               newHoldingsWidth = 2; break;\r
1880             default:\r
1881               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1882     }\r
1883 \r
1884     if(newWidth  != gameInfo.boardWidth  ||\r
1885        newHeight != gameInfo.boardHeight ||\r
1886        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1887 \r
1888         /* shift position to new playing area, if needed */\r
1889         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
1890            for(i=0; i<BOARD_HEIGHT; i++) \r
1891                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
1892                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1893                                                      board[i][j];\r
1894            for(i=0; i<newHeight; i++) {\r
1895                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
1896                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
1897            }\r
1898         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
1899            for(i=0; i<BOARD_HEIGHT; i++)\r
1900                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
1901                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1902                                                  board[i][j];\r
1903         }\r
1904 \r
1905         gameInfo.boardWidth  = newWidth;\r
1906         gameInfo.boardHeight = newHeight;\r
1907         gameInfo.holdingsWidth = newHoldingsWidth;\r
1908         gameInfo.variant = newVariant;\r
1909         InitDrawingSizes(-2, 0);\r
1910 \r
1911         /* [HGM] The following should definitely be solved in a better way */\r
1912 #if 0\r
1913         CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
1914         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
1915         saveEP = epStatus[0];\r
1916 #endif\r
1917         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */\r
1918         forwardMostMove = backwardMostMove =\r
1919         currentMove = oldCurrentMove; /* InitPos reset this, but we need still to redraw it */\r
1920 #if 0\r
1921         epStatus[0] = saveEP;\r
1922         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
1923         CopyBoard(tempBoard, board); /* restore position received from ICS   */\r
1924 #endif\r
1925     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
1926 }\r
1927 \r
1928 static int loggedOn = FALSE;\r
1929 \r
1930 /*-- Game start info cache: --*/\r
1931 int gs_gamenum;\r
1932 char gs_kind[MSG_SIZ];\r
1933 static char player1Name[128] = "";\r
1934 static char player2Name[128] = "";\r
1935 static int player1Rating = -1;\r
1936 static int player2Rating = -1;\r
1937 /*----------------------------*/\r
1938 \r
1939 ColorClass curColor = ColorNormal;\r
1940 \r
1941 void\r
1942 read_from_ics(isr, closure, data, count, error)\r
1943      InputSourceRef isr;\r
1944      VOIDSTAR closure;\r
1945      char *data;\r
1946      int count;\r
1947      int error;\r
1948 {\r
1949 #define BUF_SIZE 8192\r
1950 #define STARTED_NONE 0\r
1951 #define STARTED_MOVES 1\r
1952 #define STARTED_BOARD 2\r
1953 #define STARTED_OBSERVE 3\r
1954 #define STARTED_HOLDINGS 4\r
1955 #define STARTED_CHATTER 5\r
1956 #define STARTED_COMMENT 6\r
1957 #define STARTED_MOVES_NOHIDE 7\r
1958     \r
1959     static int started = STARTED_NONE;\r
1960     static char parse[20000];\r
1961     static int parse_pos = 0;\r
1962     static char buf[BUF_SIZE + 1];\r
1963     static int firstTime = TRUE, intfSet = FALSE;\r
1964     static ColorClass prevColor = ColorNormal;\r
1965     static int savingComment = FALSE;\r
1966     char str[500];\r
1967     int i, oldi;\r
1968     int buf_len;\r
1969     int next_out;\r
1970     int tkind;\r
1971     char *p;\r
1972 \r
1973 #ifdef WIN32\r
1974     if (appData.debugMode) {\r
1975       if (!error) {\r
1976         fprintf(debugFP, "<ICS: ");\r
1977         show_bytes(debugFP, data, count);\r
1978         fprintf(debugFP, "\n");\r
1979       }\r
1980     }\r
1981 #endif\r
1982 \r
1983     if (appData.debugMode) { int f = forwardMostMove;\r
1984         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
1985                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
1986     }\r
1987     if (count > 0) {\r
1988         /* If last read ended with a partial line that we couldn't parse,\r
1989            prepend it to the new read and try again. */\r
1990         if (leftover_len > 0) {\r
1991             for (i=0; i<leftover_len; i++)\r
1992               buf[i] = buf[leftover_start + i];\r
1993         }\r
1994 \r
1995         /* Copy in new characters, removing nulls and \r's */\r
1996         buf_len = leftover_len;\r
1997         for (i = 0; i < count; i++) {\r
1998             if (data[i] != NULLCHAR && data[i] != '\r')\r
1999               buf[buf_len++] = data[i];\r
2000         }\r
2001 \r
2002         buf[buf_len] = NULLCHAR;\r
2003         next_out = leftover_len;\r
2004         leftover_start = 0;\r
2005         \r
2006         i = 0;\r
2007         while (i < buf_len) {\r
2008             /* Deal with part of the TELNET option negotiation\r
2009                protocol.  We refuse to do anything beyond the\r
2010                defaults, except that we allow the WILL ECHO option,\r
2011                which ICS uses to turn off password echoing when we are\r
2012                directly connected to it.  We reject this option\r
2013                if localLineEditing mode is on (always on in xboard)\r
2014                and we are talking to port 23, which might be a real\r
2015                telnet server that will try to keep WILL ECHO on permanently.\r
2016              */\r
2017             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
2018                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
2019                 unsigned char option;\r
2020                 oldi = i;\r
2021                 switch ((unsigned char) buf[++i]) {\r
2022                   case TN_WILL:\r
2023                     if (appData.debugMode)\r
2024                       fprintf(debugFP, "\n<WILL ");\r
2025                     switch (option = (unsigned char) buf[++i]) {\r
2026                       case TN_ECHO:\r
2027                         if (appData.debugMode)\r
2028                           fprintf(debugFP, "ECHO ");\r
2029                         /* Reply only if this is a change, according\r
2030                            to the protocol rules. */\r
2031                         if (remoteEchoOption) break;\r
2032                         if (appData.localLineEditing &&\r
2033                             atoi(appData.icsPort) == TN_PORT) {\r
2034                             TelnetRequest(TN_DONT, TN_ECHO);\r
2035                         } else {\r
2036                             EchoOff();\r
2037                             TelnetRequest(TN_DO, TN_ECHO);\r
2038                             remoteEchoOption = TRUE;\r
2039                         }\r
2040                         break;\r
2041                       default:\r
2042                         if (appData.debugMode)\r
2043                           fprintf(debugFP, "%d ", option);\r
2044                         /* Whatever this is, we don't want it. */\r
2045                         TelnetRequest(TN_DONT, option);\r
2046                         break;\r
2047                     }\r
2048                     break;\r
2049                   case TN_WONT:\r
2050                     if (appData.debugMode)\r
2051                       fprintf(debugFP, "\n<WONT ");\r
2052                     switch (option = (unsigned char) buf[++i]) {\r
2053                       case TN_ECHO:\r
2054                         if (appData.debugMode)\r
2055                           fprintf(debugFP, "ECHO ");\r
2056                         /* Reply only if this is a change, according\r
2057                            to the protocol rules. */\r
2058                         if (!remoteEchoOption) break;\r
2059                         EchoOn();\r
2060                         TelnetRequest(TN_DONT, TN_ECHO);\r
2061                         remoteEchoOption = FALSE;\r
2062                         break;\r
2063                       default:\r
2064                         if (appData.debugMode)\r
2065                           fprintf(debugFP, "%d ", (unsigned char) option);\r
2066                         /* Whatever this is, it must already be turned\r
2067                            off, because we never agree to turn on\r
2068                            anything non-default, so according to the\r
2069                            protocol rules, we don't reply. */\r
2070                         break;\r
2071                     }\r
2072                     break;\r
2073                   case TN_DO:\r
2074                     if (appData.debugMode)\r
2075                       fprintf(debugFP, "\n<DO ");\r
2076                     switch (option = (unsigned char) buf[++i]) {\r
2077                       default:\r
2078                         /* Whatever this is, we refuse to do it. */\r
2079                         if (appData.debugMode)\r
2080                           fprintf(debugFP, "%d ", option);\r
2081                         TelnetRequest(TN_WONT, option);\r
2082                         break;\r
2083                     }\r
2084                     break;\r
2085                   case TN_DONT:\r
2086                     if (appData.debugMode)\r
2087                       fprintf(debugFP, "\n<DONT ");\r
2088                     switch (option = (unsigned char) buf[++i]) {\r
2089                       default:\r
2090                         if (appData.debugMode)\r
2091                           fprintf(debugFP, "%d ", option);\r
2092                         /* Whatever this is, we are already not doing\r
2093                            it, because we never agree to do anything\r
2094                            non-default, so according to the protocol\r
2095                            rules, we don't reply. */\r
2096                         break;\r
2097                     }\r
2098                     break;\r
2099                   case TN_IAC:\r
2100                     if (appData.debugMode)\r
2101                       fprintf(debugFP, "\n<IAC ");\r
2102                     /* Doubled IAC; pass it through */\r
2103                     i--;\r
2104                     break;\r
2105                   default:\r
2106                     if (appData.debugMode)\r
2107                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
2108                     /* Drop all other telnet commands on the floor */\r
2109                     break;\r
2110                 }\r
2111                 if (oldi > next_out)\r
2112                   SendToPlayer(&buf[next_out], oldi - next_out);\r
2113                 if (++i > next_out)\r
2114                   next_out = i;\r
2115                 continue;\r
2116             }\r
2117                 \r
2118             /* OK, this at least will *usually* work */\r
2119             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
2120                 loggedOn = TRUE;\r
2121             }\r
2122             \r
2123             if (loggedOn && !intfSet) {\r
2124                 if (ics_type == ICS_ICC) {\r
2125                   sprintf(str,\r
2126                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
2127                           programVersion);\r
2128 \r
2129                 } else if (ics_type == ICS_CHESSNET) {\r
2130                   sprintf(str, "/style 12\n");\r
2131                 } else {\r
2132                   strcpy(str, "alias $ @\n$set interface ");\r
2133                   strcat(str, programVersion);\r
2134                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2135 #ifdef WIN32\r
2136                   strcat(str, "$iset nohighlight 1\n");\r
2137 #endif\r
2138                   strcat(str, "$iset lock 1\n$style 12\n");\r
2139                 }\r
2140                 SendToICS(str);\r
2141                 intfSet = TRUE;\r
2142             }\r
2143 \r
2144             if (started == STARTED_COMMENT) {\r
2145                 /* Accumulate characters in comment */\r
2146                 parse[parse_pos++] = buf[i];\r
2147                 if (buf[i] == '\n') {\r
2148                     parse[parse_pos] = NULLCHAR;\r
2149                     AppendComment(forwardMostMove, StripHighlight(parse));\r
2150                     started = STARTED_NONE;\r
2151                 } else {\r
2152                     /* Don't match patterns against characters in chatter */\r
2153                     i++;\r
2154                     continue;\r
2155                 }\r
2156             }\r
2157             if (started == STARTED_CHATTER) {\r
2158                 if (buf[i] != '\n') {\r
2159                     /* Don't match patterns against characters in chatter */\r
2160                     i++;\r
2161                     continue;\r
2162                 }\r
2163                 started = STARTED_NONE;\r
2164             }\r
2165 \r
2166             /* Kludge to deal with rcmd protocol */\r
2167             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2168                 DisplayFatalError(&buf[1], 0, 1);\r
2169                 continue;\r
2170             } else {\r
2171                 firstTime = FALSE;\r
2172             }\r
2173 \r
2174             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2175                 ics_type = ICS_ICC;\r
2176                 ics_prefix = "/";\r
2177                 if (appData.debugMode)\r
2178                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2179                 continue;\r
2180             }\r
2181             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2182                 ics_type = ICS_FICS;\r
2183                 ics_prefix = "$";\r
2184                 if (appData.debugMode)\r
2185                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2186                 continue;\r
2187             }\r
2188             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2189                 ics_type = ICS_CHESSNET;\r
2190                 ics_prefix = "/";\r
2191                 if (appData.debugMode)\r
2192                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2193                 continue;\r
2194             }\r
2195 \r
2196             if (!loggedOn &&\r
2197                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2198                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2199                  looking_at(buf, &i, "will be \"*\""))) {\r
2200               strcpy(ics_handle, star_match[0]);\r
2201               continue;\r
2202             }\r
2203 \r
2204             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2205               char buf[MSG_SIZ];\r
2206               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2207               DisplayIcsInteractionTitle(buf);\r
2208               have_set_title = TRUE;\r
2209             }\r
2210 \r
2211             /* skip finger notes */\r
2212             if (started == STARTED_NONE &&\r
2213                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2214                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2215                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2216               started = STARTED_CHATTER;\r
2217               i += 3;\r
2218               continue;\r
2219             }\r
2220 \r
2221             /* skip formula vars */\r
2222             if (started == STARTED_NONE &&\r
2223                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2224               started = STARTED_CHATTER;\r
2225               i += 3;\r
2226               continue;\r
2227             }\r
2228 \r
2229             oldi = i;\r
2230             if (appData.zippyTalk || appData.zippyPlay) {\r
2231 #if ZIPPY\r
2232                 if (ZippyControl(buf, &i) ||\r
2233                     ZippyConverse(buf, &i) ||\r
2234                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2235                     loggedOn = TRUE;\r
2236                     continue;\r
2237                 }\r
2238 #endif\r
2239             } else {\r
2240                 if (/* Don't color "message" or "messages" output */\r
2241                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2242                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2243                     looking_at(buf, &i, "--* (*:*): ") ||\r
2244                     /* Regular tells and says */\r
2245                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2246                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2247                     looking_at(buf, &i, "* says: ") ||\r
2248                     /* Message notifications (same color as tells) */\r
2249                     looking_at(buf, &i, "* has left a message ") ||\r
2250                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2251                     /* Whispers and kibitzes */\r
2252                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2253                     looking_at(buf, &i, "* kibitzes: ") ||\r
2254                     /* Channel tells */\r
2255                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2256 \r
2257                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2258                       /* Avoid "tells you:" spoofs in channels */\r
2259                      tkind = 3;\r
2260                   }\r
2261                   if (star_match[0][0] == NULLCHAR ||\r
2262                       strchr(star_match[0], ' ') ||\r
2263                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2264                     /* Reject bogus matches */\r
2265                     i = oldi;\r
2266                   } else {\r
2267                     if (appData.colorize) {\r
2268                       if (oldi > next_out) {\r
2269                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2270                         next_out = oldi;\r
2271                       }\r
2272                       switch (tkind) {\r
2273                       case 1:\r
2274                         Colorize(ColorTell, FALSE);\r
2275                         curColor = ColorTell;\r
2276                         break;\r
2277                       case 2:\r
2278                         Colorize(ColorKibitz, FALSE);\r
2279                         curColor = ColorKibitz;\r
2280                         break;\r
2281                       case 3:\r
2282                         p = strrchr(star_match[1], '(');\r
2283                         if (p == NULL) {\r
2284                           p = star_match[1];\r
2285                         } else {\r
2286                           p++;\r
2287                         }\r
2288                         if (atoi(p) == 1) {\r
2289                           Colorize(ColorChannel1, FALSE);\r
2290                           curColor = ColorChannel1;\r
2291                         } else {\r
2292                           Colorize(ColorChannel, FALSE);\r
2293                           curColor = ColorChannel;\r
2294                         }\r
2295                         break;\r
2296                       case 5:\r
2297                         curColor = ColorNormal;\r
2298                         break;\r
2299                       }\r
2300                     }\r
2301                     if (started == STARTED_NONE && appData.autoComment &&\r
2302                         (gameMode == IcsObserving ||\r
2303                          gameMode == IcsPlayingWhite ||\r
2304                          gameMode == IcsPlayingBlack)) {\r
2305                       parse_pos = i - oldi;\r
2306                       memcpy(parse, &buf[oldi], parse_pos);\r
2307                       parse[parse_pos] = NULLCHAR;\r
2308                       started = STARTED_COMMENT;\r
2309                       savingComment = TRUE;\r
2310                     } else {\r
2311                       started = STARTED_CHATTER;\r
2312                       savingComment = FALSE;\r
2313                     }\r
2314                     loggedOn = TRUE;\r
2315                     continue;\r
2316                   }\r
2317                 }\r
2318 \r
2319                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2320                     looking_at(buf, &i, "* c-shouts: ")) {\r
2321                     if (appData.colorize) {\r
2322                         if (oldi > next_out) {\r
2323                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2324                             next_out = oldi;\r
2325                         }\r
2326                         Colorize(ColorSShout, FALSE);\r
2327                         curColor = ColorSShout;\r
2328                     }\r
2329                     loggedOn = TRUE;\r
2330                     started = STARTED_CHATTER;\r
2331                     continue;\r
2332                 }\r
2333 \r
2334                 if (looking_at(buf, &i, "--->")) {\r
2335                     loggedOn = TRUE;\r
2336                     continue;\r
2337                 }\r
2338 \r
2339                 if (looking_at(buf, &i, "* shouts: ") ||\r
2340                     looking_at(buf, &i, "--> ")) {\r
2341                     if (appData.colorize) {\r
2342                         if (oldi > next_out) {\r
2343                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2344                             next_out = oldi;\r
2345                         }\r
2346                         Colorize(ColorShout, FALSE);\r
2347                         curColor = ColorShout;\r
2348                     }\r
2349                     loggedOn = TRUE;\r
2350                     started = STARTED_CHATTER;\r
2351                     continue;\r
2352                 }\r
2353 \r
2354                 if (looking_at( buf, &i, "Challenge:")) {\r
2355                     if (appData.colorize) {\r
2356                         if (oldi > next_out) {\r
2357                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2358                             next_out = oldi;\r
2359                         }\r
2360                         Colorize(ColorChallenge, FALSE);\r
2361                         curColor = ColorChallenge;\r
2362                     }\r
2363                     loggedOn = TRUE;\r
2364                     continue;\r
2365                 }\r
2366 \r
2367                 if (looking_at(buf, &i, "* offers you") ||\r
2368                     looking_at(buf, &i, "* offers to be") ||\r
2369                     looking_at(buf, &i, "* would like to") ||\r
2370                     looking_at(buf, &i, "* requests to") ||\r
2371                     looking_at(buf, &i, "Your opponent offers") ||\r
2372                     looking_at(buf, &i, "Your opponent requests")) {\r
2373 \r
2374                     if (appData.colorize) {\r
2375                         if (oldi > next_out) {\r
2376                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2377                             next_out = oldi;\r
2378                         }\r
2379                         Colorize(ColorRequest, FALSE);\r
2380                         curColor = ColorRequest;\r
2381                     }\r
2382                     continue;\r
2383                 }\r
2384 \r
2385                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2386                     if (appData.colorize) {\r
2387                         if (oldi > next_out) {\r
2388                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2389                             next_out = oldi;\r
2390                         }\r
2391                         Colorize(ColorSeek, FALSE);\r
2392                         curColor = ColorSeek;\r
2393                     }\r
2394                     continue;\r
2395                 }\r
2396             }\r
2397 \r
2398             if (looking_at(buf, &i, "\\   ")) {\r
2399                 if (prevColor != ColorNormal) {\r
2400                     if (oldi > next_out) {\r
2401                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2402                         next_out = oldi;\r
2403                     }\r
2404                     Colorize(prevColor, TRUE);\r
2405                     curColor = prevColor;\r
2406                 }\r
2407                 if (savingComment) {\r
2408                     parse_pos = i - oldi;\r
2409                     memcpy(parse, &buf[oldi], parse_pos);\r
2410                     parse[parse_pos] = NULLCHAR;\r
2411                     started = STARTED_COMMENT;\r
2412                 } else {\r
2413                     started = STARTED_CHATTER;\r
2414                 }\r
2415                 continue;\r
2416             }\r
2417 \r
2418             if (looking_at(buf, &i, "Black Strength :") ||\r
2419                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2420                 looking_at(buf, &i, "<10>") ||\r
2421                 looking_at(buf, &i, "#@#")) {\r
2422                 /* Wrong board style */\r
2423                 loggedOn = TRUE;\r
2424                 SendToICS(ics_prefix);\r
2425                 SendToICS("set style 12\n");\r
2426                 SendToICS(ics_prefix);\r
2427                 SendToICS("refresh\n");\r
2428                 continue;\r
2429             }\r
2430             \r
2431             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2432                 ICSInitScript();\r
2433                 have_sent_ICS_logon = 1;\r
2434                 continue;\r
2435             }\r
2436               \r
2437             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2438                 (looking_at(buf, &i, "\n<12> ") ||\r
2439                  looking_at(buf, &i, "<12> "))) {\r
2440                 loggedOn = TRUE;\r
2441                 if (oldi > next_out) {\r
2442                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2443                 }\r
2444                 next_out = i;\r
2445                 started = STARTED_BOARD;\r
2446                 parse_pos = 0;\r
2447                 continue;\r
2448             }\r
2449 \r
2450             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2451                 looking_at(buf, &i, "<b1> ")) {\r
2452                 if (oldi > next_out) {\r
2453                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2454                 }\r
2455                 next_out = i;\r
2456                 started = STARTED_HOLDINGS;\r
2457                 parse_pos = 0;\r
2458                 continue;\r
2459             }\r
2460 \r
2461             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2462                 loggedOn = TRUE;\r
2463                 /* Header for a move list -- first line */\r
2464 \r
2465                 switch (ics_getting_history) {\r
2466                   case H_FALSE:\r
2467                     switch (gameMode) {\r
2468                       case IcsIdle:\r
2469                       case BeginningOfGame:\r
2470                         /* User typed "moves" or "oldmoves" while we\r
2471                            were idle.  Pretend we asked for these\r
2472                            moves and soak them up so user can step\r
2473                            through them and/or save them.\r
2474                            */\r
2475                         Reset(FALSE, TRUE);\r
2476                         gameMode = IcsObserving;\r
2477                         ModeHighlight();\r
2478                         ics_gamenum = -1;\r
2479                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2480                         break;\r
2481                       case EditGame: /*?*/\r
2482                       case EditPosition: /*?*/\r
2483                         /* Should above feature work in these modes too? */\r
2484                         /* For now it doesn't */\r
2485                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2486                         break;\r
2487                       default:\r
2488                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2489                         break;\r
2490                     }\r
2491                     break;\r
2492                   case H_REQUESTED:\r
2493                     /* Is this the right one? */\r
2494                     if (gameInfo.white && gameInfo.black &&\r
2495                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2496                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2497                         /* All is well */\r
2498                         ics_getting_history = H_GOT_REQ_HEADER;\r
2499                     }\r
2500                     break;\r
2501                   case H_GOT_REQ_HEADER:\r
2502                   case H_GOT_UNREQ_HEADER:\r
2503                   case H_GOT_UNWANTED_HEADER:\r
2504                   case H_GETTING_MOVES:\r
2505                     /* Should not happen */\r
2506                     DisplayError("Error gathering move list: two headers", 0);\r
2507                     ics_getting_history = H_FALSE;\r
2508                     break;\r
2509                 }\r
2510 \r
2511                 /* Save player ratings into gameInfo if needed */\r
2512                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2513                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2514                     (gameInfo.whiteRating == -1 ||\r
2515                      gameInfo.blackRating == -1)) {\r
2516 \r
2517                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2518                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2519                     if (appData.debugMode)\r
2520                       fprintf(debugFP, "Ratings from header: W %d, B %d\n", \r
2521                               gameInfo.whiteRating, gameInfo.blackRating);\r
2522                 }\r
2523                 continue;\r
2524             }\r
2525 \r
2526             if (looking_at(buf, &i,\r
2527               "* * match, initial time: * minute*, increment: * second")) {\r
2528                 /* Header for a move list -- second line */\r
2529                 /* Initial board will follow if this is a wild game */\r
2530                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2531                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2532                 gameInfo.event = StrSave(str);\r
2533                 /* [HGM] we switched variant. Translate boards if needed. */\r
2534                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2535                 continue;\r
2536             }\r
2537 \r
2538             if (looking_at(buf, &i, "Move  ")) {\r
2539                 /* Beginning of a move list */\r
2540                 switch (ics_getting_history) {\r
2541                   case H_FALSE:\r
2542                     /* Normally should not happen */\r
2543                     /* Maybe user hit reset while we were parsing */\r
2544                     break;\r
2545                   case H_REQUESTED:\r
2546                     /* Happens if we are ignoring a move list that is not\r
2547                      * the one we just requested.  Common if the user\r
2548                      * tries to observe two games without turning off\r
2549                      * getMoveList */\r
2550                     break;\r
2551                   case H_GETTING_MOVES:\r
2552                     /* Should not happen */\r
2553                     DisplayError("Error gathering move list: nested", 0);\r
2554                     ics_getting_history = H_FALSE;\r
2555                     break;\r
2556                   case H_GOT_REQ_HEADER:\r
2557                     ics_getting_history = H_GETTING_MOVES;\r
2558                     started = STARTED_MOVES;\r
2559                     parse_pos = 0;\r
2560                     if (oldi > next_out) {\r
2561                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2562                     }\r
2563                     break;\r
2564                   case H_GOT_UNREQ_HEADER:\r
2565                     ics_getting_history = H_GETTING_MOVES;\r
2566                     started = STARTED_MOVES_NOHIDE;\r
2567                     parse_pos = 0;\r
2568                     break;\r
2569                   case H_GOT_UNWANTED_HEADER:\r
2570                     ics_getting_history = H_FALSE;\r
2571                     break;\r
2572                 }\r
2573                 continue;\r
2574             }                           \r
2575             \r
2576             if (looking_at(buf, &i, "% ") ||\r
2577                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2578                  && looking_at(buf, &i, "}*"))) {\r
2579                 savingComment = FALSE;\r
2580                 switch (started) {\r
2581                   case STARTED_MOVES:\r
2582                   case STARTED_MOVES_NOHIDE:\r
2583                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2584                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2585                     ParseGameHistory(parse);\r
2586 #if ZIPPY\r
2587                     if (appData.zippyPlay && first.initDone) {\r
2588                         FeedMovesToProgram(&first, forwardMostMove);\r
2589                         if (gameMode == IcsPlayingWhite) {\r
2590                             if (WhiteOnMove(forwardMostMove)) {\r
2591                                 if (first.sendTime) {\r
2592                                   if (first.useColors) {\r
2593                                     SendToProgram("black\n", &first); \r
2594                                   }\r
2595                                   SendTimeRemaining(&first, TRUE);\r
2596                                 }\r
2597                                 if (first.useColors) {\r
2598                                   SendToProgram("white\ngo\n", &first);\r
2599                                 } else {\r
2600                                   SendToProgram("go\n", &first);\r
2601                                 }\r
2602                                 first.maybeThinking = TRUE;\r
2603                             } else {\r
2604                                 if (first.usePlayother) {\r
2605                                   if (first.sendTime) {\r
2606                                     SendTimeRemaining(&first, TRUE);\r
2607                                   }\r
2608                                   SendToProgram("playother\n", &first);\r
2609                                   firstMove = FALSE;\r
2610                                 } else {\r
2611                                   firstMove = TRUE;\r
2612                                 }\r
2613                             }\r
2614                         } else if (gameMode == IcsPlayingBlack) {\r
2615                             if (!WhiteOnMove(forwardMostMove)) {\r
2616                                 if (first.sendTime) {\r
2617                                   if (first.useColors) {\r
2618                                     SendToProgram("white\n", &first);\r
2619                                   }\r
2620                                   SendTimeRemaining(&first, FALSE);\r
2621                                 }\r
2622                                 if (first.useColors) {\r
2623                                   SendToProgram("black\ngo\n", &first);\r
2624                                 } else {\r
2625                                   SendToProgram("go\n", &first);\r
2626                                 }\r
2627                                 first.maybeThinking = TRUE;\r
2628                             } else {\r
2629                                 if (first.usePlayother) {\r
2630                                   if (first.sendTime) {\r
2631                                     SendTimeRemaining(&first, FALSE);\r
2632                                   }\r
2633                                   SendToProgram("playother\n", &first);\r
2634                                   firstMove = FALSE;\r
2635                                 } else {\r
2636                                   firstMove = TRUE;\r
2637                                 }\r
2638                             }\r
2639                         }                       \r
2640                     }\r
2641 #endif\r
2642                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2643                         /* Moves came from oldmoves or moves command\r
2644                            while we weren't doing anything else.\r
2645                            */\r
2646                         currentMove = forwardMostMove;\r
2647                         ClearHighlights();/*!!could figure this out*/\r
2648                         flipView = appData.flipView;\r
2649                         DrawPosition(FALSE, boards[currentMove]);\r
2650                         DisplayBothClocks();\r
2651                         sprintf(str, "%s vs. %s",\r
2652                                 gameInfo.white, gameInfo.black);\r
2653                         DisplayTitle(str);\r
2654                         gameMode = IcsIdle;\r
2655                     } else {\r
2656                         /* Moves were history of an active game */\r
2657                         if (gameInfo.resultDetails != NULL) {\r
2658                             free(gameInfo.resultDetails);\r
2659                             gameInfo.resultDetails = NULL;\r
2660                         }\r
2661                     }\r
2662                     HistorySet(parseList, backwardMostMove,\r
2663                                forwardMostMove, currentMove-1);\r
2664                     DisplayMove(currentMove - 1);\r
2665                     if (started == STARTED_MOVES) next_out = i;\r
2666                     started = STARTED_NONE;\r
2667                     ics_getting_history = H_FALSE;\r
2668                     break;\r
2669 \r
2670                   case STARTED_OBSERVE:\r
2671                     started = STARTED_NONE;\r
2672                     SendToICS(ics_prefix);\r
2673                     SendToICS("refresh\n");\r
2674                     break;\r
2675 \r
2676                   default:\r
2677                     break;\r
2678                 }\r
2679                 continue;\r
2680             }\r
2681             \r
2682             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2683                  started == STARTED_HOLDINGS ||\r
2684                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2685                 /* Accumulate characters in move list or board */\r
2686                 parse[parse_pos++] = buf[i];\r
2687             }\r
2688             \r
2689             /* Start of game messages.  Mostly we detect start of game\r
2690                when the first board image arrives.  On some versions\r
2691                of the ICS, though, we need to do a "refresh" after starting\r
2692                to observe in order to get the current board right away. */\r
2693             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2694                 started = STARTED_OBSERVE;\r
2695                 continue;\r
2696             }\r
2697 \r
2698             /* Handle auto-observe */\r
2699             if (appData.autoObserve &&\r
2700                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2701                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2702                 char *player;\r
2703                 /* Choose the player that was highlighted, if any. */\r
2704                 if (star_match[0][0] == '\033' ||\r
2705                     star_match[1][0] != '\033') {\r
2706                     player = star_match[0];\r
2707                 } else {\r
2708                     player = star_match[2];\r
2709                 }\r
2710                 sprintf(str, "%sobserve %s\n",\r
2711                         ics_prefix, StripHighlightAndTitle(player));\r
2712                 SendToICS(str);\r
2713 \r
2714                 /* Save ratings from notify string */\r
2715                 strcpy(player1Name, star_match[0]);\r
2716                 player1Rating = string_to_rating(star_match[1]);\r
2717                 strcpy(player2Name, star_match[2]);\r
2718                 player2Rating = string_to_rating(star_match[3]);\r
2719 \r
2720                 if (appData.debugMode)\r
2721                   fprintf(debugFP, \r
2722                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2723                           player1Name, player1Rating,\r
2724                           player2Name, player2Rating);\r
2725 \r
2726                 continue;\r
2727             }\r
2728 \r
2729             /* Deal with automatic examine mode after a game,\r
2730                and with IcsObserving -> IcsExamining transition */\r
2731             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2732                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2733 \r
2734                 int gamenum = atoi(star_match[0]);\r
2735                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2736                     gamenum == ics_gamenum) {\r
2737                     /* We were already playing or observing this game;\r
2738                        no need to refetch history */\r
2739                     gameMode = IcsExamining;\r
2740                     if (pausing) {\r
2741                         pauseExamForwardMostMove = forwardMostMove;\r
2742                     } else if (currentMove < forwardMostMove) {\r
2743                         ForwardInner(forwardMostMove);\r
2744                     }\r
2745                 } else {\r
2746                     /* I don't think this case really can happen */\r
2747                     SendToICS(ics_prefix);\r
2748                     SendToICS("refresh\n");\r
2749                 }\r
2750                 continue;\r
2751             }    \r
2752             \r
2753             /* Error messages */\r
2754             if (ics_user_moved) {\r
2755                 if (looking_at(buf, &i, "Illegal move") ||\r
2756                     looking_at(buf, &i, "Not a legal move") ||\r
2757                     looking_at(buf, &i, "Your king is in check") ||\r
2758                     looking_at(buf, &i, "It isn't your turn") ||\r
2759                     looking_at(buf, &i, "It is not your move")) {\r
2760                     /* Illegal move */\r
2761                     ics_user_moved = 0;\r
2762                     if (forwardMostMove > backwardMostMove) {\r
2763                         currentMove = --forwardMostMove;\r
2764                         DisplayMove(currentMove - 1); /* before DMError */\r
2765                         DisplayMoveError("Illegal move (rejected by ICS)");\r
2766                         DrawPosition(FALSE, boards[currentMove]);\r
2767                         SwitchClocks();\r
2768                         DisplayBothClocks();\r
2769                     }\r
2770                     continue;\r
2771                 }\r
2772             }\r
2773 \r
2774             if (looking_at(buf, &i, "still have time") ||\r
2775                 looking_at(buf, &i, "not out of time") ||\r
2776                 looking_at(buf, &i, "either player is out of time") ||\r
2777                 looking_at(buf, &i, "has timeseal; checking")) {\r
2778                 /* We must have called his flag a little too soon */\r
2779                 whiteFlag = blackFlag = FALSE;\r
2780                 continue;\r
2781             }\r
2782 \r
2783             if (looking_at(buf, &i, "added * seconds to") ||\r
2784                 looking_at(buf, &i, "seconds were added to")) {\r
2785                 /* Update the clocks */\r
2786                 SendToICS(ics_prefix);\r
2787                 SendToICS("refresh\n");\r
2788                 continue;\r
2789             }\r
2790 \r
2791             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2792                 ics_clock_paused = TRUE;\r
2793                 StopClocks();\r
2794                 continue;\r
2795             }\r
2796 \r
2797             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2798                 ics_clock_paused = FALSE;\r
2799                 StartClocks();\r
2800                 continue;\r
2801             }\r
2802 \r
2803             /* Grab player ratings from the Creating: message.\r
2804                Note we have to check for the special case when\r
2805                the ICS inserts things like [white] or [black]. */\r
2806             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
2807                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
2808                 /* star_matches:\r
2809                    0    player 1 name (not necessarily white)\r
2810                    1    player 1 rating\r
2811                    2    empty, white, or black (IGNORED)\r
2812                    3    player 2 name (not necessarily black)\r
2813                    4    player 2 rating\r
2814                    \r
2815                    The names/ratings are sorted out when the game\r
2816                    actually starts (below).\r
2817                 */\r
2818                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
2819                 player1Rating = string_to_rating(star_match[1]);\r
2820                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
2821                 player2Rating = string_to_rating(star_match[4]);\r
2822 \r
2823                 if (appData.debugMode)\r
2824                   fprintf(debugFP, \r
2825                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
2826                           player1Name, player1Rating,\r
2827                           player2Name, player2Rating);\r
2828 \r
2829                 continue;\r
2830             }\r
2831             \r
2832             /* Improved generic start/end-of-game messages */\r
2833             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
2834                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
2835                 /* If tkind == 0: */\r
2836                 /* star_match[0] is the game number */\r
2837                 /*           [1] is the white player's name */\r
2838                 /*           [2] is the black player's name */\r
2839                 /* For end-of-game: */\r
2840                 /*           [3] is the reason for the game end */\r
2841                 /*           [4] is a PGN end game-token, preceded by " " */\r
2842                 /* For start-of-game: */\r
2843                 /*           [3] begins with "Creating" or "Continuing" */\r
2844                 /*           [4] is " *" or empty (don't care). */\r
2845                 int gamenum = atoi(star_match[0]);\r
2846                 char *whitename, *blackname, *why, *endtoken;\r
2847                 ChessMove endtype = (ChessMove) 0;\r
2848 \r
2849                 if (tkind == 0) {\r
2850                   whitename = star_match[1];\r
2851                   blackname = star_match[2];\r
2852                   why = star_match[3];\r
2853                   endtoken = star_match[4];\r
2854                 } else {\r
2855                   whitename = star_match[1];\r
2856                   blackname = star_match[3];\r
2857                   why = star_match[5];\r
2858                   endtoken = star_match[6];\r
2859                 }\r
2860 \r
2861                 /* Game start messages */\r
2862                 if (strncmp(why, "Creating ", 9) == 0 ||\r
2863                     strncmp(why, "Continuing ", 11) == 0) {\r
2864                     gs_gamenum = gamenum;\r
2865                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
2866 #if ZIPPY\r
2867                     if (appData.zippyPlay) {\r
2868                         ZippyGameStart(whitename, blackname);\r
2869                     }\r
2870 #endif /*ZIPPY*/\r
2871                     continue;\r
2872                 }\r
2873 \r
2874                 /* Game end messages */\r
2875                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
2876                     ics_gamenum != gamenum) {\r
2877                     continue;\r
2878                 }\r
2879                 while (endtoken[0] == ' ') endtoken++;\r
2880                 switch (endtoken[0]) {\r
2881                   case '*':\r
2882                   default:\r
2883                     endtype = GameUnfinished;\r
2884                     break;\r
2885                   case '0':\r
2886                     endtype = BlackWins;\r
2887                     break;\r
2888                   case '1':\r
2889                     if (endtoken[1] == '/')\r
2890                       endtype = GameIsDrawn;\r
2891                     else\r
2892                       endtype = WhiteWins;\r
2893                     break;\r
2894                 }\r
2895                 GameEnds(endtype, why, GE_ICS);\r
2896 #if ZIPPY\r
2897                 if (appData.zippyPlay && first.initDone) {\r
2898                     ZippyGameEnd(endtype, why);\r
2899                     if (first.pr == NULL) {\r
2900                       /* Start the next process early so that we'll\r
2901                          be ready for the next challenge */\r
2902                       StartChessProgram(&first);\r
2903                     }\r
2904                     /* Send "new" early, in case this command takes\r
2905                        a long time to finish, so that we'll be ready\r
2906                        for the next challenge. */\r
2907                     Reset(TRUE, TRUE);\r
2908                 }\r
2909 #endif /*ZIPPY*/\r
2910                 continue;\r
2911             }\r
2912 \r
2913             if (looking_at(buf, &i, "Removing game * from observation") ||\r
2914                 looking_at(buf, &i, "no longer observing game *") ||\r
2915                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
2916                 if (gameMode == IcsObserving &&\r
2917                     atoi(star_match[0]) == ics_gamenum)\r
2918                   {\r
2919                       StopClocks();\r
2920                       gameMode = IcsIdle;\r
2921                       ics_gamenum = -1;\r
2922                       ics_user_moved = FALSE;\r
2923                   }\r
2924                 continue;\r
2925             }\r
2926 \r
2927             if (looking_at(buf, &i, "no longer examining game *")) {\r
2928                 if (gameMode == IcsExamining &&\r
2929                     atoi(star_match[0]) == ics_gamenum)\r
2930                   {\r
2931                       gameMode = IcsIdle;\r
2932                       ics_gamenum = -1;\r
2933                       ics_user_moved = FALSE;\r
2934                   }\r
2935                 continue;\r
2936             }\r
2937 \r
2938             /* Advance leftover_start past any newlines we find,\r
2939                so only partial lines can get reparsed */\r
2940             if (looking_at(buf, &i, "\n")) {\r
2941                 prevColor = curColor;\r
2942                 if (curColor != ColorNormal) {\r
2943                     if (oldi > next_out) {\r
2944                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2945                         next_out = oldi;\r
2946                     }\r
2947                     Colorize(ColorNormal, FALSE);\r
2948                     curColor = ColorNormal;\r
2949                 }\r
2950                 if (started == STARTED_BOARD) {\r
2951                     started = STARTED_NONE;\r
2952                     parse[parse_pos] = NULLCHAR;\r
2953                     ParseBoard12(parse);\r
2954                     ics_user_moved = 0;\r
2955 \r
2956                     /* Send premove here */\r
2957                     if (appData.premove) {\r
2958                       char str[MSG_SIZ];\r
2959                       if (currentMove == 0 &&\r
2960                           gameMode == IcsPlayingWhite &&\r
2961                           appData.premoveWhite) {\r
2962                         sprintf(str, "%s%s\n", ics_prefix,\r
2963                                 appData.premoveWhiteText);\r
2964                         if (appData.debugMode)\r
2965                           fprintf(debugFP, "Sending premove:\n");\r
2966                         SendToICS(str);\r
2967                       } else if (currentMove == 1 &&\r
2968                                  gameMode == IcsPlayingBlack &&\r
2969                                  appData.premoveBlack) {\r
2970                         sprintf(str, "%s%s\n", ics_prefix,\r
2971                                 appData.premoveBlackText);\r
2972                         if (appData.debugMode)\r
2973                           fprintf(debugFP, "Sending premove:\n");\r
2974                         SendToICS(str);\r
2975                       } else if (gotPremove) {\r
2976                         gotPremove = 0;\r
2977                         ClearPremoveHighlights();\r
2978                         if (appData.debugMode)\r
2979                           fprintf(debugFP, "Sending premove:\n");\r
2980                           UserMoveEvent(premoveFromX, premoveFromY, \r
2981                                         premoveToX, premoveToY, \r
2982                                         premovePromoChar);\r
2983                       }\r
2984                     }\r
2985 \r
2986                     /* Usually suppress following prompt */\r
2987                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
2988                         if (looking_at(buf, &i, "*% ")) {\r
2989                             savingComment = FALSE;\r
2990                         }\r
2991                     }\r
2992                     next_out = i;\r
2993                 } else if (started == STARTED_HOLDINGS) {\r
2994                     int gamenum;\r
2995                     char new_piece[MSG_SIZ];\r
2996                     started = STARTED_NONE;\r
2997                     parse[parse_pos] = NULLCHAR;\r
2998                     if (appData.debugMode)\r
2999                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3000                                                         parse, currentMove);\r
3001                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3002                         gamenum == ics_gamenum) {\r
3003                         if (gameInfo.variant == VariantNormal) {\r
3004                           /* [HGM] We seem to switch variant during a game!\r
3005                            * Presumably no holdings were displayed, so we have\r
3006                            * to move the position two files to the right to\r
3007                            * create room for them!\r
3008                            */\r
3009                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3010                           /* Get a move list just to see the header, which\r
3011                              will tell us whether this is really bug or zh */\r
3012                           if (ics_getting_history == H_FALSE) {\r
3013                             ics_getting_history = H_REQUESTED;\r
3014                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3015                             SendToICS(str);\r
3016                           }\r
3017                         }\r
3018                         new_piece[0] = NULLCHAR;\r
3019                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3020                                &gamenum, white_holding, black_holding,\r
3021                                new_piece);\r
3022                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3023                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3024                         /* [HGM] copy holdings to board holdings area */\r
3025                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3026                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3027 #if ZIPPY\r
3028                         if (appData.zippyPlay && first.initDone) {\r
3029                             ZippyHoldings(white_holding, black_holding,\r
3030                                           new_piece);\r
3031                         }\r
3032 #endif /*ZIPPY*/\r
3033                         if (tinyLayout || smallLayout) {\r
3034                             char wh[16], bh[16];\r
3035                             PackHolding(wh, white_holding);\r
3036                             PackHolding(bh, black_holding);\r
3037                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3038                                     gameInfo.white, gameInfo.black);\r
3039                         } else {\r
3040                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3041                                     gameInfo.white, white_holding,\r
3042                                     gameInfo.black, black_holding);\r
3043                         }\r
3044 \r
3045                         DrawPosition(FALSE, boards[currentMove]);\r
3046                         DisplayTitle(str);\r
3047                     }\r
3048                     /* Suppress following prompt */\r
3049                     if (looking_at(buf, &i, "*% ")) {\r
3050                         savingComment = FALSE;\r
3051                     }\r
3052                     next_out = i;\r
3053                 }\r
3054                 continue;\r
3055             }\r
3056 \r
3057             i++;                /* skip unparsed character and loop back */\r
3058         }\r
3059         \r
3060         if (started != STARTED_MOVES && started != STARTED_BOARD &&\r
3061             started != STARTED_HOLDINGS && i > next_out) {\r
3062             SendToPlayer(&buf[next_out], i - next_out);\r
3063             next_out = i;\r
3064         }\r
3065         \r
3066         leftover_len = buf_len - leftover_start;\r
3067         /* if buffer ends with something we couldn't parse,\r
3068            reparse it after appending the next read */\r
3069         \r
3070     } else if (count == 0) {\r
3071         RemoveInputSource(isr);\r
3072         DisplayFatalError("Connection closed by ICS", 0, 0);\r
3073     } else {\r
3074         DisplayFatalError("Error reading from ICS", error, 1);\r
3075     }\r
3076 }\r
3077 \r
3078 \r
3079 /* Board style 12 looks like this:\r
3080    \r
3081    <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
3082    \r
3083  * The "<12> " is stripped before it gets to this routine.  The two\r
3084  * trailing 0's (flip state and clock ticking) are later addition, and\r
3085  * some chess servers may not have them, or may have only the first.\r
3086  * Additional trailing fields may be added in the future.  \r
3087  */\r
3088 \r
3089 #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
3090 \r
3091 #define RELATION_OBSERVING_PLAYED    0\r
3092 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3093 #define RELATION_PLAYING_MYMOVE      1\r
3094 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3095 #define RELATION_EXAMINING           2\r
3096 #define RELATION_ISOLATED_BOARD     -3\r
3097 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3098 \r
3099 void\r
3100 ParseBoard12(string)\r
3101      char *string;\r
3102\r
3103     GameMode newGameMode;\r
3104     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;\r
3105     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;\r
3106     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3107     char to_play, board_chars[72];\r
3108     char move_str[500], str[500], elapsed_time[500];\r
3109     char black[32], white[32];\r
3110     Board board;\r
3111     int prevMove = currentMove;\r
3112     int ticking = 2;\r
3113     ChessMove moveType;\r
3114     int fromX, fromY, toX, toY;\r
3115     char promoChar;\r
3116 \r
3117     fromX = fromY = toX = toY = -1;\r
3118     \r
3119     newGame = FALSE;\r
3120 \r
3121     if (appData.debugMode)\r
3122       fprintf(debugFP, "Parsing board: %s\n", string);\r
3123 \r
3124     move_str[0] = NULLCHAR;\r
3125     elapsed_time[0] = NULLCHAR;\r
3126     n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,\r
3127                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3128                &gamenum, white, black, &relation, &basetime, &increment,\r
3129                &white_stren, &black_stren, &white_time, &black_time,\r
3130                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3131                &ticking);\r
3132 \r
3133     if (n < 22) {\r
3134         sprintf(str, "Failed to parse board string:\n\"%s\"", string);\r
3135         DisplayError(str, 0);\r
3136         return;\r
3137     }\r
3138 \r
3139     /* Convert the move number to internal form */\r
3140     moveNum = (moveNum - 1) * 2;\r
3141     if (to_play == 'B') moveNum++;\r
3142     if (moveNum >= MAX_MOVES) {\r
3143       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
3144                         0, 1);\r
3145       return;\r
3146     }\r
3147     \r
3148     switch (relation) {\r
3149       case RELATION_OBSERVING_PLAYED:\r
3150       case RELATION_OBSERVING_STATIC:\r
3151         if (gamenum == -1) {\r
3152             /* Old ICC buglet */\r
3153             relation = RELATION_OBSERVING_STATIC;\r
3154         }\r
3155         newGameMode = IcsObserving;\r
3156         break;\r
3157       case RELATION_PLAYING_MYMOVE:\r
3158       case RELATION_PLAYING_NOTMYMOVE:\r
3159         newGameMode =\r
3160           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3161             IcsPlayingWhite : IcsPlayingBlack;\r
3162         break;\r
3163       case RELATION_EXAMINING:\r
3164         newGameMode = IcsExamining;\r
3165         break;\r
3166       case RELATION_ISOLATED_BOARD:\r
3167       default:\r
3168         /* Just display this board.  If user was doing something else,\r
3169            we will forget about it until the next board comes. */ \r
3170         newGameMode = IcsIdle;\r
3171         break;\r
3172       case RELATION_STARTING_POSITION:\r
3173         newGameMode = gameMode;\r
3174         break;\r
3175     }\r
3176     \r
3177     /* Modify behavior for initial board display on move listing\r
3178        of wild games.\r
3179        */\r
3180     switch (ics_getting_history) {\r
3181       case H_FALSE:\r
3182       case H_REQUESTED:\r
3183         break;\r
3184       case H_GOT_REQ_HEADER:\r
3185       case H_GOT_UNREQ_HEADER:\r
3186         /* This is the initial position of the current game */\r
3187         gamenum = ics_gamenum;\r
3188         moveNum = 0;            /* old ICS bug workaround */\r
3189         if (to_play == 'B') {\r
3190           startedFromSetupPosition = TRUE;\r
3191           blackPlaysFirst = TRUE;\r
3192           moveNum = 1;\r
3193           if (forwardMostMove == 0) forwardMostMove = 1;\r
3194           if (backwardMostMove == 0) backwardMostMove = 1;\r
3195           if (currentMove == 0) currentMove = 1;\r
3196         }\r
3197         newGameMode = gameMode;\r
3198         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3199         break;\r
3200       case H_GOT_UNWANTED_HEADER:\r
3201         /* This is an initial board that we don't want */\r
3202         return;\r
3203       case H_GETTING_MOVES:\r
3204         /* Should not happen */\r
3205         DisplayError("Error gathering move list: extra board", 0);\r
3206         ics_getting_history = H_FALSE;\r
3207         return;\r
3208     }\r
3209     \r
3210     /* Take action if this is the first board of a new game, or of a\r
3211        different game than is currently being displayed.  */\r
3212     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3213         relation == RELATION_ISOLATED_BOARD) {\r
3214         \r
3215         /* Forget the old game and get the history (if any) of the new one */\r
3216         if (gameMode != BeginningOfGame) {\r
3217           Reset(FALSE, TRUE);\r
3218         }\r
3219         newGame = TRUE;\r
3220         if (appData.autoRaiseBoard) BoardToTop();\r
3221         prevMove = -3;\r
3222         if (gamenum == -1) {\r
3223             newGameMode = IcsIdle;\r
3224         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3225                    appData.getMoveList) {\r
3226             /* Need to get game history */\r
3227             ics_getting_history = H_REQUESTED;\r
3228             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3229             SendToICS(str);\r
3230         }\r
3231         \r
3232         /* Initially flip the board to have black on the bottom if playing\r
3233            black or if the ICS flip flag is set, but let the user change\r
3234            it with the Flip View button. */\r
3235         flipView = appData.autoFlipView ? \r
3236           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3237           appData.flipView;\r
3238         \r
3239         /* Done with values from previous mode; copy in new ones */\r
3240         gameMode = newGameMode;\r
3241         ModeHighlight();\r
3242         ics_gamenum = gamenum;\r
3243         if (gamenum == gs_gamenum) {\r
3244             int klen = strlen(gs_kind);\r
3245             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3246             sprintf(str, "ICS %s", gs_kind);\r
3247             gameInfo.event = StrSave(str);\r
3248         } else {\r
3249             gameInfo.event = StrSave("ICS game");\r
3250         }\r
3251         gameInfo.site = StrSave(appData.icsHost);\r
3252         gameInfo.date = PGNDate();\r
3253         gameInfo.round = StrSave("-");\r
3254         gameInfo.white = StrSave(white);\r
3255         gameInfo.black = StrSave(black);\r
3256         timeControl = basetime * 60 * 1000;\r
3257         timeControl_2 = 0;\r
3258         timeIncrement = increment * 1000;\r
3259         movesPerSession = 0;\r
3260         gameInfo.timeControl = TimeControlTagValue();\r
3261         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3262   if (appData.debugMode) {\r
3263     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3264     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3265     setbuf(debugFP, NULL);\r
3266   }\r
3267 \r
3268         gameInfo.outOfBook = NULL;\r
3269         \r
3270         /* Do we have the ratings? */\r
3271         if (strcmp(player1Name, white) == 0 &&\r
3272             strcmp(player2Name, black) == 0) {\r
3273             if (appData.debugMode)\r
3274               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3275                       player1Rating, player2Rating);\r
3276             gameInfo.whiteRating = player1Rating;\r
3277             gameInfo.blackRating = player2Rating;\r
3278         } else if (strcmp(player2Name, white) == 0 &&\r
3279                    strcmp(player1Name, black) == 0) {\r
3280             if (appData.debugMode)\r
3281               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3282                       player2Rating, player1Rating);\r
3283             gameInfo.whiteRating = player2Rating;\r
3284             gameInfo.blackRating = player1Rating;\r
3285         }\r
3286         player1Name[0] = player2Name[0] = NULLCHAR;\r
3287 \r
3288         /* Silence shouts if requested */\r
3289         if (appData.quietPlay &&\r
3290             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3291             SendToICS(ics_prefix);\r
3292             SendToICS("set shout 0\n");\r
3293         }\r
3294     }\r
3295     \r
3296     /* Deal with midgame name changes */\r
3297     if (!newGame) {\r
3298         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3299             if (gameInfo.white) free(gameInfo.white);\r
3300             gameInfo.white = StrSave(white);\r
3301         }\r
3302         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3303             if (gameInfo.black) free(gameInfo.black);\r
3304             gameInfo.black = StrSave(black);\r
3305         }\r
3306     }\r
3307     \r
3308     /* Throw away game result if anything actually changes in examine mode */\r
3309     if (gameMode == IcsExamining && !newGame) {\r
3310         gameInfo.result = GameUnfinished;\r
3311         if (gameInfo.resultDetails != NULL) {\r
3312             free(gameInfo.resultDetails);\r
3313             gameInfo.resultDetails = NULL;\r
3314         }\r
3315     }\r
3316     \r
3317     /* In pausing && IcsExamining mode, we ignore boards coming\r
3318        in if they are in a different variation than we are. */\r
3319     if (pauseExamInvalid) return;\r
3320     if (pausing && gameMode == IcsExamining) {\r
3321         if (moveNum <= pauseExamForwardMostMove) {\r
3322             pauseExamInvalid = TRUE;\r
3323             forwardMostMove = pauseExamForwardMostMove;\r
3324             return;\r
3325         }\r
3326     }\r
3327     \r
3328     /* Parse the board */\r
3329     for (k = 0; k < 8; k++) {\r
3330       for (j = 0; j < 8; j++)\r
3331         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(7-k)*9 + j]);\r
3332       if(gameInfo.holdingsWidth > 1) {\r
3333            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3334            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3335       }\r
3336     }\r
3337     CopyBoard(boards[moveNum], board);\r
3338     if (moveNum == 0) {\r
3339         startedFromSetupPosition =\r
3340           !CompareBoards(board, initialPosition);\r
3341         if(startedFromSetupPosition)\r
3342             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3343     }\r
3344     /* [HGM] variantswitch: remember the last initial position parsed, */\r
3345     /* because it might have been parsed in the wrong variant, so that */\r
3346     /* we can re-parse it once we know the proper variant (which might */\r
3347     /* have different piece assignments for the same letters).         */\r
3348     if(moveNum == 0) strcpy(startBoard, string);\r
3349 \r
3350     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3351        to make it also work for FRC opening positions. Note that board12\r
3352        is really defective for later FRC positions, as it has no way to\r
3353        indicate which Rook can castle if they are on the same side of King.\r
3354        For the initial position we grant rights to the outermost Rooks,\r
3355        and remember thos rights, and we then copy them on positions\r
3356        later in an FRC game. This means WB might not recognize castlings with\r
3357        Rooks that have moved back to their original position as illegal,\r
3358        but in ICS mode that is not its job anyway.\r
3359     */\r
3360     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3361     { int i, j;\r
3362 \r
3363         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3364             if(board[0][i] == WhiteRook) j = i;\r
3365         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3366         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3367             if(board[0][i] == WhiteRook) j = i;\r
3368         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3369         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3370             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3371         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3372         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3373             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3374         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3375 \r
3376         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3377             if(board[0][k] == WhiteKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3378         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3379             if(board[BOARD_HEIGHT-1][k] == BlackKing)\r
3380                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3381     } else { int r;\r
3382         r = castlingRights[moveNum][0] = initialRights[0];\r
3383         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3384         r = castlingRights[moveNum][1] = initialRights[1];\r
3385         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3386         r = castlingRights[moveNum][3] = initialRights[3];\r
3387         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3388         r = castlingRights[moveNum][4] = initialRights[4];\r
3389         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3390         /* wildcastle kludge: always assume King has rights */\r
3391         r = castlingRights[moveNum][2] = initialRights[2];\r
3392         r = castlingRights[moveNum][5] = initialRights[5];\r
3393     }\r
3394     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3395     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3396 \r
3397     \r
3398     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3399         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3400         /* This was an initial position from a move list, not\r
3401            the current position */\r
3402         return;\r
3403     }\r
3404     \r
3405     /* Update currentMove and known move number limits */\r
3406     newMove = newGame || moveNum > forwardMostMove;\r
3407     if (newGame) {\r
3408         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3409         if (gameMode == IcsExamining && moveNum == 0) {\r
3410           /* Workaround for ICS limitation: we are not told the wild\r
3411              type when starting to examine a game.  But if we ask for\r
3412              the move list, the move list header will tell us */\r
3413             ics_getting_history = H_REQUESTED;\r
3414             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3415             SendToICS(str);\r
3416         }\r
3417     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3418                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3419         forwardMostMove = moveNum;\r
3420         if (!pausing || currentMove > forwardMostMove)\r
3421           currentMove = forwardMostMove;\r
3422     } else {\r
3423         /* New part of history that is not contiguous with old part */ \r
3424         if (pausing && gameMode == IcsExamining) {\r
3425             pauseExamInvalid = TRUE;\r
3426             forwardMostMove = pauseExamForwardMostMove;\r
3427             return;\r
3428         }\r
3429         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3430         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3431             ics_getting_history = H_REQUESTED;\r
3432             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3433             SendToICS(str);\r
3434         }\r
3435     }\r
3436     \r
3437     /* Update the clocks */\r
3438     if (strchr(elapsed_time, '.')) {\r
3439       /* Time is in ms */\r
3440       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3441       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3442     } else {\r
3443       /* Time is in seconds */\r
3444       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3445       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3446     }\r
3447       \r
3448 \r
3449 #if ZIPPY\r
3450     if (appData.zippyPlay && newGame &&\r
3451         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3452         gameMode != IcsExamining)\r
3453       ZippyFirstBoard(moveNum, basetime, increment);\r
3454 #endif\r
3455     \r
3456     /* Put the move on the move list, first converting\r
3457        to canonical algebraic form. */\r
3458     if (moveNum > 0) {\r
3459   if (appData.debugMode) {\r
3460     if (appData.debugMode) { int f = forwardMostMove;\r
3461         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3462                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3463     }\r
3464     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3465     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3466     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3467     setbuf(debugFP, NULL);\r
3468   }\r
3469         if (moveNum <= backwardMostMove) {\r
3470             /* We don't know what the board looked like before\r
3471                this move.  Punt. */\r
3472             strcpy(parseList[moveNum - 1], move_str);\r
3473             strcat(parseList[moveNum - 1], " ");\r
3474             strcat(parseList[moveNum - 1], elapsed_time);\r
3475             moveList[moveNum - 1][0] = NULLCHAR;\r
3476         } else if (ParseOneMove(move_str, moveNum - 1, &moveType,\r
3477                                 &fromX, &fromY, &toX, &toY, &promoChar)) {\r
3478             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3479                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3480                                      fromY, fromX, toY, toX, promoChar,\r
3481                                      parseList[moveNum-1]);\r
3482             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3483                              castlingRights[moveNum]) ) {\r
3484               case MT_NONE:\r
3485               case MT_STALEMATE:\r
3486               default:\r
3487                 break;\r
3488               case MT_CHECK:\r
3489                 if(gameInfo.variant != VariantShogi)\r
3490                     strcat(parseList[moveNum - 1], "+");\r
3491                 break;\r
3492               case MT_CHECKMATE:\r
3493                 strcat(parseList[moveNum - 1], "#");\r
3494                 break;\r
3495             }\r
3496             strcat(parseList[moveNum - 1], " ");\r
3497             strcat(parseList[moveNum - 1], elapsed_time);\r
3498             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3499             strcpy(moveList[moveNum - 1], currentMoveString);\r
3500             strcat(moveList[moveNum - 1], "\n");\r
3501         } else if (strcmp(move_str, "none") == 0) {\r
3502             /* Again, we don't know what the board looked like;\r
3503                this is really the start of the game. */\r
3504             parseList[moveNum - 1][0] = NULLCHAR;\r
3505             moveList[moveNum - 1][0] = NULLCHAR;\r
3506             backwardMostMove = moveNum;\r
3507             startedFromSetupPosition = TRUE;\r
3508             fromX = fromY = toX = toY = -1;\r
3509         } else {\r
3510             /* Move from ICS was illegal!?  Punt. */\r
3511   if (appData.debugMode) {\r
3512     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3513     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3514   }\r
3515 #if 0\r
3516             if (appData.testLegality && appData.debugMode) {\r
3517                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3518                 DisplayError(str, 0);\r
3519             }\r
3520 #endif\r
3521             strcpy(parseList[moveNum - 1], move_str);\r
3522             strcat(parseList[moveNum - 1], " ");\r
3523             strcat(parseList[moveNum - 1], elapsed_time);\r
3524             moveList[moveNum - 1][0] = NULLCHAR;\r
3525             fromX = fromY = toX = toY = -1;\r
3526         }\r
3527   if (appData.debugMode) {\r
3528     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3529     setbuf(debugFP, NULL);\r
3530   }\r
3531 \r
3532 #if ZIPPY\r
3533         /* Send move to chess program (BEFORE animating it). */\r
3534         if (appData.zippyPlay && !newGame && newMove && \r
3535            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3536 \r
3537             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3538                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3539                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3540                     sprintf(str, "Couldn't parse move \"%s\" from ICS",\r
3541                             move_str);\r
3542                     DisplayError(str, 0);\r
3543                 } else {\r
3544                     if (first.sendTime) {\r
3545                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3546                     }\r
3547                     SendMoveToProgram(moveNum - 1, &first);\r
3548                     if (firstMove) {\r
3549                         firstMove = FALSE;\r
3550                         if (first.useColors) {\r
3551                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3552                                         "white\ngo\n" :\r
3553                                         "black\ngo\n", &first);\r
3554                         } else {\r
3555                           SendToProgram("go\n", &first);\r
3556                         }\r
3557                         first.maybeThinking = TRUE;\r
3558                     }\r
3559                 }\r
3560             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3561               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3562                 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);\r
3563                 DisplayError(str, 0);\r
3564               } else {\r
3565                 SendMoveToProgram(moveNum - 1, &first);\r
3566               }\r
3567             }\r
3568         }\r
3569 #endif\r
3570     }\r
3571 \r
3572     if (moveNum > 0 && !gotPremove) {\r
3573         /* If move comes from a remote source, animate it.  If it\r
3574            isn't remote, it will have already been animated. */\r
3575         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3576             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3577         }\r
3578         if (!pausing && appData.highlightLastMove) {\r
3579             SetHighlights(fromX, fromY, toX, toY);\r
3580         }\r
3581     }\r
3582     \r
3583     /* Start the clocks */\r
3584     whiteFlag = blackFlag = FALSE;\r
3585     appData.clockMode = !(basetime == 0 && increment == 0);\r
3586     if (ticking == 0) {\r
3587       ics_clock_paused = TRUE;\r
3588       StopClocks();\r
3589     } else if (ticking == 1) {\r
3590       ics_clock_paused = FALSE;\r
3591     }\r
3592     if (gameMode == IcsIdle ||\r
3593         relation == RELATION_OBSERVING_STATIC ||\r
3594         relation == RELATION_EXAMINING ||\r
3595         ics_clock_paused)\r
3596       DisplayBothClocks();\r
3597     else\r
3598       StartClocks();\r
3599     \r
3600     /* Display opponents and material strengths */\r
3601     if (gameInfo.variant != VariantBughouse &&\r
3602         gameInfo.variant != VariantCrazyhouse) {\r
3603         if (tinyLayout || smallLayout) {\r
3604             sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3605                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3606                     basetime, increment);\r
3607         } else {\r
3608             sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3609                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3610                     basetime, increment);\r
3611         }\r
3612         DisplayTitle(str);\r
3613     }\r
3614 \r
3615    \r
3616     /* Display the board */\r
3617     if (!pausing) {\r
3618       \r
3619       if (appData.premove)\r
3620           if (!gotPremove || \r
3621              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3622              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3623               ClearPremoveHighlights();\r
3624 \r
3625       DrawPosition(FALSE, boards[currentMove]);\r
3626       DisplayMove(moveNum - 1);\r
3627       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3628         RingBell();\r
3629     }\r
3630 \r
3631     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3632 }\r
3633 \r
3634 void\r
3635 GetMoveListEvent()\r
3636 {\r
3637     char buf[MSG_SIZ];\r
3638     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3639         ics_getting_history = H_REQUESTED;\r
3640         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3641         SendToICS(buf);\r
3642     }\r
3643 }\r
3644 \r
3645 void\r
3646 AnalysisPeriodicEvent(force)\r
3647      int force;\r
3648 {\r
3649     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3650          && !force) || !appData.periodicUpdates)\r
3651       return;\r
3652 \r
3653     /* Send . command to Crafty to collect stats */\r
3654     SendToProgram(".\n", &first);\r
3655 \r
3656     /* Don't send another until we get a response (this makes\r
3657        us stop sending to old Crafty's which don't understand\r
3658        the "." command (sending illegal cmds resets node count & time,\r
3659        which looks bad)) */\r
3660     programStats.ok_to_send = 0;\r
3661 }\r
3662 \r
3663 void\r
3664 SendMoveToProgram(moveNum, cps)\r
3665      int moveNum;\r
3666      ChessProgramState *cps;\r
3667 {\r
3668     char buf[MSG_SIZ];\r
3669 \r
3670     if (cps->useUsermove) {\r
3671       SendToProgram("usermove ", cps);\r
3672     }\r
3673     if (cps->useSAN) {\r
3674       char *space;\r
3675       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3676         int len = space - parseList[moveNum];\r
3677         memcpy(buf, parseList[moveNum], len);\r
3678         buf[len++] = '\n';\r
3679         buf[len] = NULLCHAR;\r
3680       } else {\r
3681         sprintf(buf, "%s\n", parseList[moveNum]);\r
3682       }\r
3683       SendToProgram(buf, cps);\r
3684     } else {\r
3685       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3686        * the engine. It would be nice to have a better way to identify castle \r
3687        * moves here. */\r
3688       if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {\r
3689   if (appData.debugMode) {\r
3690     fprintf(debugFP, "Tord's FRC castling code\n");\r
3691   }\r
3692         int fromX = moveList[moveNum][0] - AAA; \r
3693         int fromY = moveList[moveNum][1] - ONE;\r
3694         int toX = moveList[moveNum][2] - AAA; \r
3695         int toY = moveList[moveNum][3] - ONE;\r
3696         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3697             && boards[moveNum][toY][toX] == WhiteRook)\r
3698            || (boards[moveNum][fromY][fromX] == BlackKing \r
3699                && boards[moveNum][toY][toX] == BlackRook)) {\r
3700           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3701           else SendToProgram("O-O-O\n", cps);\r
3702         }\r
3703         else SendToProgram(moveList[moveNum], cps);\r
3704       }\r
3705       else SendToProgram(moveList[moveNum], cps);\r
3706       /* End of additions by Tord */\r
3707     }\r
3708 \r
3709     /* [HGM] setting up the opening has brought engine in force mode! */\r
3710     /*       Send 'go' if we are in a mode where machine should play. */\r
3711     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3712         (gameMode == TwoMachinesPlay   ||\r
3713 #ifdef ZIPPY\r
3714          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3715 #endif\r
3716          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
3717         SendToProgram("go\n", cps);\r
3718   if (appData.debugMode) {\r
3719     fprintf(debugFP, "(extra)\n");\r
3720   }\r
3721     }\r
3722     setboardSpoiledMachineBlack = 0;\r
3723 }\r
3724 \r
3725 void\r
3726 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
3727      ChessMove moveType;\r
3728      int fromX, fromY, toX, toY;\r
3729 {\r
3730     char user_move[MSG_SIZ];\r
3731 \r
3732     switch (moveType) {\r
3733       default:\r
3734         sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",\r
3735                 (int)moveType, fromX, fromY, toX, toY);\r
3736         DisplayError(user_move + strlen("say "), 0);\r
3737         break;\r
3738       case WhiteKingSideCastle:\r
3739       case BlackKingSideCastle:\r
3740       case WhiteQueenSideCastleWild:\r
3741       case BlackQueenSideCastleWild:\r
3742       /* PUSH Fabien */\r
3743       case WhiteHSideCastleFR:\r
3744       case BlackHSideCastleFR:\r
3745       /* POP Fabien */\r
3746         sprintf(user_move, "o-o\n");\r
3747         break;\r
3748       case WhiteQueenSideCastle:\r
3749       case BlackQueenSideCastle:\r
3750       case WhiteKingSideCastleWild:\r
3751       case BlackKingSideCastleWild:\r
3752       /* PUSH Fabien */\r
3753       case WhiteASideCastleFR:\r
3754       case BlackASideCastleFR:\r
3755       /* POP Fabien */\r
3756         sprintf(user_move, "o-o-o\n");\r
3757         break;\r
3758       case WhitePromotionQueen:\r
3759       case BlackPromotionQueen:\r
3760       case WhitePromotionRook:\r
3761       case BlackPromotionRook:\r
3762       case WhitePromotionBishop:\r
3763       case BlackPromotionBishop:\r
3764       case WhitePromotionKnight:\r
3765       case BlackPromotionKnight:\r
3766       case WhitePromotionKing:\r
3767       case BlackPromotionKing:\r
3768       case WhitePromotionChancellor:\r
3769       case BlackPromotionChancellor:\r
3770       case WhitePromotionArchbishop:\r
3771       case BlackPromotionArchbishop:\r
3772         if(gameInfo.variant == VariantShatranj)\r
3773             sprintf(user_move, "%c%c%c%c=%c\n",\r
3774                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
3775                 PieceToChar(WhiteFerz));\r
3776         else\r
3777             sprintf(user_move, "%c%c%c%c=%c\n",\r
3778                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
3779                 PieceToChar(PromoPiece(moveType)));\r
3780         break;\r
3781       case WhiteDrop:\r
3782       case BlackDrop:\r
3783         sprintf(user_move, "%c@%c%c\n",\r
3784                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
3785                 AAA + toX, ONE + toY);\r
3786         break;\r
3787       case NormalMove:\r
3788       case WhiteCapturesEnPassant:\r
3789       case BlackCapturesEnPassant:\r
3790       case IllegalMove:  /* could be a variant we don't quite understand */\r
3791         sprintf(user_move, "%c%c%c%c\n",\r
3792                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
3793         break;\r
3794     }\r
3795     SendToICS(user_move);\r
3796 }\r
3797 \r
3798 void\r
3799 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
3800      int rf, ff, rt, ft;\r
3801      char promoChar;\r
3802      char move[7];\r
3803 {\r
3804     if (rf == DROP_RANK) {\r
3805         sprintf(move, "%c@%c%c\n",\r
3806                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
3807     } else {\r
3808         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
3809             sprintf(move, "%c%c%c%c\n",\r
3810                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
3811         } else {\r
3812             sprintf(move, "%c%c%c%c%c\n",\r
3813                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
3814         }\r
3815     }\r
3816     AlphaRank(move, 4);\r
3817 }\r
3818 \r
3819 void\r
3820 ProcessICSInitScript(f)\r
3821      FILE *f;\r
3822 {\r
3823     char buf[MSG_SIZ];\r
3824 \r
3825     while (fgets(buf, MSG_SIZ, f)) {\r
3826         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
3827     }\r
3828 \r
3829     fclose(f);\r
3830 }\r
3831 \r
3832 \r
3833 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
3834 void\r
3835 AlphaRank(char *move, int n)\r
3836 {\r
3837     char *p = move, c; int x, y;\r
3838 \r
3839     if( !appData.alphaRank ) return;\r
3840 \r
3841     if (appData.debugMode) {\r
3842         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
3843     }\r
3844 \r
3845     if(move[1]=='*' && \r
3846        move[2]>='0' && move[2]<='9' &&\r
3847        move[3]>='a' && move[3]<='x'    ) {\r
3848         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
3849         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
3850     } else\r
3851     if(move[0]>='0' && move[0]<='9' &&\r
3852        move[1]>='a' && move[1]<='x' &&\r
3853        move[2]>='0' && move[2]<='9' &&\r
3854        move[3]>='a' && move[3]<='x'    ) {\r
3855         /* input move, Shogi -> normal */\r
3856         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
3857         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
3858         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
3859         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
3860     } else\r
3861     if(move[1]=='@' &&\r
3862        move[3]>='0' && move[3]<='9' &&\r
3863        move[2]>='a' && move[2]<='x'    ) {\r
3864         move[1] = '*';\r
3865         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
3866         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
3867     } else\r
3868     if(\r
3869        move[0]>='a' && move[0]<='x' &&\r
3870        move[3]>='0' && move[3]<='9' &&\r
3871        move[2]>='a' && move[2]<='x'    ) {\r
3872          /* output move, normal -> Shogi */\r
3873         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
3874         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
3875         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
3876         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
3877         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
3878     }\r
3879     if (appData.debugMode) {\r
3880         fprintf(debugFP, "   out = '%s'\n", move);\r
3881     }\r
3882 }\r
3883 \r
3884 /* Parser for moves from gnuchess, ICS, or user typein box */\r
3885 Boolean\r
3886 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
3887      char *move;\r
3888      int moveNum;\r
3889      ChessMove *moveType;\r
3890      int *fromX, *fromY, *toX, *toY;\r
3891      char *promoChar;\r
3892 {       \r
3893     if (appData.debugMode) {\r
3894         fprintf(debugFP, "move to parse: %s\n", move);\r
3895     }\r
3896     *moveType = yylexstr(moveNum, move);\r
3897 \r
3898     switch (*moveType) {\r
3899       case WhitePromotionChancellor:\r
3900       case BlackPromotionChancellor:\r
3901       case WhitePromotionArchbishop:\r
3902       case BlackPromotionArchbishop:\r
3903       case WhitePromotionQueen:\r
3904       case BlackPromotionQueen:\r
3905       case WhitePromotionRook:\r
3906       case BlackPromotionRook:\r
3907       case WhitePromotionBishop:\r
3908       case BlackPromotionBishop:\r
3909       case WhitePromotionKnight:\r
3910       case BlackPromotionKnight:\r
3911       case WhitePromotionKing:\r
3912       case BlackPromotionKing:\r
3913       case NormalMove:\r
3914       case WhiteCapturesEnPassant:\r
3915       case BlackCapturesEnPassant:\r
3916       case WhiteKingSideCastle:\r
3917       case WhiteQueenSideCastle:\r
3918       case BlackKingSideCastle:\r
3919       case BlackQueenSideCastle:\r
3920       case WhiteKingSideCastleWild:\r
3921       case WhiteQueenSideCastleWild:\r
3922       case BlackKingSideCastleWild:\r
3923       case BlackQueenSideCastleWild:\r
3924       /* Code added by Tord: */\r
3925       case WhiteHSideCastleFR:\r
3926       case WhiteASideCastleFR:\r
3927       case BlackHSideCastleFR:\r
3928       case BlackASideCastleFR:\r
3929       /* End of code added by Tord */\r
3930       case IllegalMove:         /* bug or odd chess variant */\r
3931         *fromX = currentMoveString[0] - AAA;\r
3932         *fromY = currentMoveString[1] - ONE;\r
3933         *toX = currentMoveString[2] - AAA;\r
3934         *toY = currentMoveString[3] - ONE;\r
3935         *promoChar = currentMoveString[4];\r
3936         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
3937             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
3938     if (appData.debugMode) {\r
3939         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
3940     }\r
3941             *fromX = *fromY = *toX = *toY = 0;\r
3942             return FALSE;\r
3943         }\r
3944         if (appData.testLegality) {\r
3945           return (*moveType != IllegalMove);\r
3946         } else {\r
3947           return !(fromX == fromY && toX == toY);\r
3948         }\r
3949 \r
3950       case WhiteDrop:\r
3951       case BlackDrop:\r
3952         *fromX = *moveType == WhiteDrop ?\r
3953           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
3954         (int) CharToPiece(ToLower(currentMoveString[0]));\r
3955         *fromY = DROP_RANK;\r
3956         *toX = currentMoveString[2] - AAA;\r
3957         *toY = currentMoveString[3] - ONE;\r
3958         *promoChar = NULLCHAR;\r
3959         return TRUE;\r
3960 \r
3961       case AmbiguousMove:\r
3962       case ImpossibleMove:\r
3963       case (ChessMove) 0:       /* end of file */\r
3964       case ElapsedTime:\r
3965       case Comment:\r
3966       case PGNTag:\r
3967       case NAG:\r
3968       case WhiteWins:\r
3969       case BlackWins:\r
3970       case GameIsDrawn:\r
3971       default:\r
3972     if (appData.debugMode) {\r
3973         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
3974     }\r
3975         /* bug? */\r
3976         *fromX = *fromY = *toX = *toY = 0;\r
3977         *promoChar = NULLCHAR;\r
3978         return FALSE;\r
3979     }\r
3980 }\r
3981 \r
3982 /* [AS] FRC game initialization */\r
3983 static int FindEmptySquare( Board board, int n )\r
3984 {\r
3985     int i = 0;\r
3986 \r
3987     while( 1 ) {\r
3988         while( board[0][i] != EmptySquare ) i++;\r
3989         if( n == 0 )\r
3990             break;\r
3991         n--;\r
3992         i++;\r
3993     }\r
3994 \r
3995     return i;\r
3996 }\r
3997 \r
3998 static void ShuffleFRC( Board board )\r
3999 {\r
4000     int i;\r
4001 \r
4002     srand( time(0) );\r
4003     \r
4004     for( i=0; i<8; i++ ) {\r
4005         board[0][i] = EmptySquare;\r
4006     }\r
4007 \r
4008     board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */\r
4009     board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */\r
4010     board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;\r
4011     board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;\r
4012     board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;\r
4013     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4014     initialRights[1]  = initialRights[4]  =\r
4015     castlingRights[0][1] = castlingRights[0][4] = i;\r
4016     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4017     initialRights[2]  = initialRights[5]  =\r
4018     castlingRights[0][2] = castlingRights[0][5] = i;\r
4019     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4020     initialRights[0]  = initialRights[3]  =\r
4021     castlingRights[0][0] = castlingRights[0][3] = i;\r
4022 \r
4023     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4024         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4025     }\r
4026 }\r
4027 \r
4028 static unsigned char FRC_KnightTable[10] = {\r
4029     0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33\r
4030 };\r
4031 \r
4032 static void SetupFRC( Board board, int pos_index )\r
4033 {\r
4034     int i;\r
4035     unsigned char knights;\r
4036 \r
4037     /* Bring the position index into a safe range (just in case...) */\r
4038     if( pos_index < 0 ) pos_index = 0;\r
4039 \r
4040     pos_index %= 960;\r
4041 \r
4042     /* Clear the board */\r
4043     for( i=0; i<8; i++ ) {\r
4044         board[0][i] = EmptySquare;\r
4045     }\r
4046 \r
4047     /* Place bishops and queen */\r
4048     board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */\r
4049     pos_index /= 4;\r
4050     \r
4051     board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */\r
4052     pos_index /= 4;\r
4053 \r
4054     board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;\r
4055     pos_index /= 6;\r
4056 \r
4057     /* Place knigths */\r
4058     knights = FRC_KnightTable[ pos_index ];\r
4059 \r
4060     board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;\r
4061     board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;\r
4062 \r
4063     /* Place rooks and king */\r
4064     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4065     initialRights[1]  = initialRights[4]  =\r
4066     castlingRights[0][1] = castlingRights[0][4] = i;\r
4067     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4068     initialRights[2]  = initialRights[5]  =\r
4069     castlingRights[0][2] = castlingRights[0][5] = i;\r
4070     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4071     initialRights[0]  = initialRights[3]  =\r
4072     castlingRights[0][0] = castlingRights[0][3] = i;\r
4073 \r
4074     /* Mirror piece placement for black */\r
4075     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4076         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4077     }\r
4078 }\r
4079 \r
4080 BOOL SetCharTable( char *table, const char * map )\r
4081 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4082 /*       Basically a safe strcpy that uses the last character as King */\r
4083 {\r
4084     BOOL result = FALSE; int NrPieces;\r
4085 \r
4086     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4087                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4088         int i; /* [HGM] Accept even length from 12 to 34 */\r
4089 \r
4090         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4091         for( i=0; i<NrPieces/2-1; i++ ) {\r
4092             table[i] = map[i];\r
4093             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4094         }\r
4095         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4096         table[(int) BlackKing]  = map[NrPieces-1];\r
4097 \r
4098         result = TRUE;\r
4099     }\r
4100 \r
4101     return result;\r
4102 }\r
4103 \r
4104 void\r
4105 InitPosition(redraw)\r
4106      int redraw;\r
4107 {\r
4108     ChessSquare (* pieces)[BOARD_SIZE];\r
4109     int i, j, pawnRow, overrule,\r
4110     oldx = gameInfo.boardWidth,\r
4111     oldy = gameInfo.boardHeight,\r
4112     oldh = gameInfo.holdingsWidth,\r
4113     oldv = gameInfo.variant;\r
4114 \r
4115     currentMove = forwardMostMove = backwardMostMove = 0;\r
4116 \r
4117     /* [AS] Initialize pv info list [HGM] and game status */\r
4118     {\r
4119         for( i=0; i<MAX_MOVES; i++ ) {\r
4120             pvInfoList[i].depth = 0;\r
4121             epStatus[i]=EP_NONE;\r
4122             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4123         }\r
4124 \r
4125         initialRulePlies = 0; /* 50-move counter start */\r
4126 \r
4127         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4128         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4129     }\r
4130 \r
4131     \r
4132     /* [HGM] logic here is completely changed. In stead of full positions */\r
4133     /* the initialized data only consist of the two backranks. The switch */\r
4134     /* selects which one we will use, which is than copied to the Board   */\r
4135     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4136     /* empty squares. This initial position is then copied to boards[0],  */\r
4137     /* possibly after shuffling, so that it remains available.            */\r
4138 \r
4139     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4140     gameInfo.boardWidth    = 8;\r
4141     gameInfo.boardHeight   = 8;\r
4142     gameInfo.holdingsSize  = 0;\r
4143     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4144     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4145     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4146 \r
4147     switch (gameInfo.variant) {\r
4148     default:\r
4149       pieces = FIDEArray;\r
4150       break;\r
4151     case VariantShatranj:\r
4152       pieces = ShatranjArray;\r
4153       nrCastlingRights = 0;\r
4154       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4155       break;\r
4156     case VariantTwoKings:\r
4157       pieces = twoKingsArray;\r
4158       nrCastlingRights = 8;                 /* add rights for second King */\r
4159       castlingRights[0][6] = initialRights[2] = 5;\r
4160       castlingRights[0][7] = initialRights[5] = 5;\r
4161       castlingRank[6] = 0;\r
4162       castlingRank[7] = BOARD_HEIGHT-1;\r
4163       break;\r
4164     case VariantCapablanca:\r
4165       pieces = CapablancaArray;\r
4166       gameInfo.boardWidth = 10;\r
4167       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4168       break;\r
4169     case VariantGothic:\r
4170       pieces = GothicArray;\r
4171       gameInfo.boardWidth = 10;\r
4172       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4173       break;\r
4174     case VariantFalcon:\r
4175       pieces = FalconArray;\r
4176       gameInfo.boardWidth = 10;\r
4177       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4178       break;\r
4179     case VariantXiangqi:\r
4180       pieces = XiangqiArray;\r
4181       gameInfo.boardWidth  = 9;\r
4182       gameInfo.boardHeight = 10;\r
4183       nrCastlingRights = 0;\r
4184       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4185       break;\r
4186     case VariantShogi:\r
4187       pieces = ShogiArray;\r
4188       gameInfo.boardWidth  = 9;\r
4189       gameInfo.boardHeight = 9;\r
4190       gameInfo.holdingsSize = 7;\r
4191       nrCastlingRights = 0;\r
4192       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4193       break;\r
4194     case VariantCourier:\r
4195       pieces = CourierArray;\r
4196       gameInfo.boardWidth  = 12;\r
4197       nrCastlingRights = 0;\r
4198       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4199       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4200       break;\r
4201     case VariantKnightmate:\r
4202       pieces = KnightmateArray;\r
4203       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4204       break;\r
4205     case VariantFairy:\r
4206       pieces = fairyArray;\r
4207       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4208       break;\r
4209     case VariantCrazyhouse:\r
4210     case VariantBughouse:\r
4211       pieces = FIDEArray;\r
4212       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4213       gameInfo.holdingsSize = 5;\r
4214       break;\r
4215     case VariantWildCastle:\r
4216       pieces = FIDEArray;\r
4217       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4218       break;\r
4219     case VariantNoCastle:\r
4220       pieces = FIDEArray;\r
4221       nrCastlingRights = 0;\r
4222       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4223       /* !!?unconstrained back-rank shuffle */\r
4224       break;\r
4225     }\r
4226 \r
4227     overrule = 0;\r
4228     if(appData.NrFiles >= 0) {\r
4229         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4230         gameInfo.boardWidth = appData.NrFiles;\r
4231     }\r
4232     if(appData.NrRanks >= 0) {\r
4233         gameInfo.boardHeight = appData.NrRanks;\r
4234     }\r
4235     if(appData.holdingsSize >= 0) {\r
4236         i = appData.holdingsSize;\r
4237         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4238         gameInfo.holdingsSize = i;\r
4239     }\r
4240     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4241     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4242         DisplayFatalError("Recompile to support this BOARD_SIZE!", 0, 2);\r
4243 \r
4244     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4245     if(pawnRow < 1) pawnRow = 1;\r
4246 \r
4247     /* User pieceToChar list overrules defaults */\r
4248     if(appData.pieceToCharTable != NULL)\r
4249         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4250 \r
4251     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4252 \r
4253         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4254             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4255         for( i=0; i<BOARD_HEIGHT; i++ )\r
4256             initialPosition[i][j] = s;\r
4257 \r
4258         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4259         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4260         initialPosition[pawnRow][j] = WhitePawn;\r
4261         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4262         if(gameInfo.variant == VariantXiangqi) {\r
4263             if(j&1) {\r
4264                 initialPosition[pawnRow][j] = \r
4265                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4266                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4267                    initialPosition[2][j] = WhiteCannon;\r
4268                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4269                 }\r
4270             }\r
4271         }\r
4272         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4273     }\r
4274     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4275 \r
4276             j=BOARD_LEFT+1;\r
4277             initialPosition[1][j] = WhiteBishop;\r
4278             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4279             j=BOARD_RGHT-2;\r
4280             initialPosition[1][j] = WhiteRook;\r
4281             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4282     }\r
4283 \r
4284     if( nrCastlingRights == -1) {\r
4285         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4286         /*       This sets default castling rights from none to normal corners   */\r
4287         /* Variants with other castling rights must set them themselves above    */\r
4288         nrCastlingRights = 6;\r
4289        \r
4290         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4291         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4292         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4293         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4294         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4295         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4296      }\r
4297 \r
4298     if(gameInfo.variant == VariantFischeRandom) {\r
4299       if( appData.defaultFrcPosition < 0 ) {\r
4300         ShuffleFRC( initialPosition );\r
4301       }\r
4302       else {\r
4303         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4304       }\r
4305       startedFromSetupPosition = TRUE;\r
4306     } else if(startedFromPositionFile) {\r
4307       /* [HGM] loadPos: use PositionFile for every new game */\r
4308       CopyBoard(initialPosition, filePosition);\r
4309       for(i=0; i<nrCastlingRights; i++)\r
4310           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4311       startedFromSetupPosition = TRUE;\r
4312     }\r
4313 \r
4314     CopyBoard(boards[0], initialPosition);\r
4315 \r
4316     if(oldx != gameInfo.boardWidth ||\r
4317        oldy != gameInfo.boardHeight ||\r
4318        oldh != gameInfo.holdingsWidth\r
4319 #ifdef GOTHIC\r
4320        || oldv == VariantGothic ||        // For licensing popups\r
4321        gameInfo.variant == VariantGothic\r
4322 #endif\r
4323 #ifdef FALCON\r
4324        || oldv == VariantFalcon ||\r
4325        gameInfo.variant == VariantFalcon\r
4326 #endif\r
4327                                          )\r
4328             InitDrawingSizes(-2 ,0);\r
4329 \r
4330     if (redraw)\r
4331       DrawPosition(TRUE, boards[currentMove]);\r
4332 }\r
4333 \r
4334 void\r
4335 SendBoard(cps, moveNum)\r
4336      ChessProgramState *cps;\r
4337      int moveNum;\r
4338 {\r
4339     char message[MSG_SIZ];\r
4340     \r
4341     if (cps->useSetboard) {\r
4342       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
4343       sprintf(message, "setboard %s\n", fen);\r
4344       SendToProgram(message, cps);\r
4345       free(fen);\r
4346 \r
4347     } else {\r
4348       ChessSquare *bp;\r
4349       int i, j;\r
4350       /* Kludge to set black to move, avoiding the troublesome and now\r
4351        * deprecated "black" command.\r
4352        */\r
4353       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4354 \r
4355       SendToProgram("edit\n", cps);\r
4356       SendToProgram("#\n", cps);\r
4357       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4358         bp = &boards[moveNum][i][0];\r
4359         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4360           if ((int) *bp < (int) BlackPawn) {\r
4361             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4362                     AAA + j, ONE + i);\r
4363             if(message[0] == '+' || message[0] == '~') {\r
4364                 sprintf(message, "%c%c%c+\n",\r
4365                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4366                         AAA + j, ONE + i);\r
4367             }\r
4368             if(appData.alphaRank) {\r
4369                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4370                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4371             }\r
4372             SendToProgram(message, cps);\r
4373           }\r
4374         }\r
4375       }\r
4376     \r
4377       SendToProgram("c\n", cps);\r
4378       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4379         bp = &boards[moveNum][i][0];\r
4380         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4381           if (((int) *bp != (int) EmptySquare)\r
4382               && ((int) *bp >= (int) BlackPawn)) {\r
4383             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4384                     AAA + j, ONE + i);\r
4385             if(message[0] == '+' || message[0] == '~') {\r
4386                 sprintf(message, "%c%c%c+\n",\r
4387                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4388                         AAA + j, ONE + i);\r
4389             }\r
4390             if(appData.alphaRank) {\r
4391                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4392                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4393             }\r
4394             SendToProgram(message, cps);\r
4395           }\r
4396         }\r
4397       }\r
4398     \r
4399       SendToProgram(".\n", cps);\r
4400     }\r
4401     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4402 }\r
4403 \r
4404 int\r
4405 IsPromotion(fromX, fromY, toX, toY)\r
4406      int fromX, fromY, toX, toY;\r
4407 {\r
4408     /* [HGM] add Shogi promotions */\r
4409     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4410     ChessSquare piece;\r
4411 \r
4412     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4413       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4414    /* [HGM] Note to self: line above also weeds out drops */\r
4415     piece = boards[currentMove][fromY][fromX];\r
4416     if(gameInfo.variant == VariantShogi) {\r
4417         promotionZoneSize = 3;\r
4418         highestPromotingPiece = (int)WhiteKing;\r
4419         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4420            and if in normal chess we then allow promotion to King, why not\r
4421            allow promotion of other piece in Shogi?                         */\r
4422     }\r
4423     if((int)piece >= BlackPawn) {\r
4424         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4425              return FALSE;\r
4426         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4427     } else {\r
4428         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4429            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4430     }\r
4431     return ( (int)piece <= highestPromotingPiece );\r
4432 }\r
4433 \r
4434 int\r
4435 InPalace(row, column)\r
4436      int row, column;\r
4437 {   /* [HGM] for Xiangqi */\r
4438     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4439          column < (BOARD_WIDTH + 4)/2 &&\r
4440          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4441     return FALSE;\r
4442 }\r
4443 \r
4444 int\r
4445 PieceForSquare (x, y)\r
4446      int x;\r
4447      int y;\r
4448 {\r
4449   if (x < BOARD_LEFT || x >= BOARD_RGHT || y < 0 || y >= BOARD_HEIGHT)\r
4450      return -1;\r
4451   else\r
4452      return boards[currentMove][y][x];\r
4453 }\r
4454 \r
4455 int\r
4456 OKToStartUserMove(x, y)\r
4457      int x, y;\r
4458 {\r
4459     ChessSquare from_piece;\r
4460     int white_piece;\r
4461 \r
4462     if (matchMode) return FALSE;\r
4463     if (gameMode == EditPosition) return TRUE;\r
4464 \r
4465     if (x >= 0 && y >= 0)\r
4466       from_piece = boards[currentMove][y][x];\r
4467     else\r
4468       from_piece = EmptySquare;\r
4469 \r
4470     if (from_piece == EmptySquare) return FALSE;\r
4471 \r
4472     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4473       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4474 \r
4475     switch (gameMode) {\r
4476       case PlayFromGameFile:\r
4477       case AnalyzeFile:\r
4478       case TwoMachinesPlay:\r
4479       case EndOfGame:\r
4480         return FALSE;\r
4481 \r
4482       case IcsObserving:\r
4483       case IcsIdle:\r
4484         return FALSE;\r
4485 \r
4486       case MachinePlaysWhite:\r
4487       case IcsPlayingBlack:\r
4488         if (appData.zippyPlay) return FALSE;\r
4489         if (white_piece) {\r
4490             DisplayMoveError("You are playing Black");\r
4491             return FALSE;\r
4492         }\r
4493         break;\r
4494 \r
4495       case MachinePlaysBlack:\r
4496       case IcsPlayingWhite:\r
4497         if (appData.zippyPlay) return FALSE;\r
4498         if (!white_piece) {\r
4499             DisplayMoveError("You are playing White");\r
4500             return FALSE;\r
4501         }\r
4502         break;\r
4503 \r
4504       case EditGame:\r
4505         if (!white_piece && WhiteOnMove(currentMove)) {\r
4506             DisplayMoveError("It is White's turn");\r
4507             return FALSE;\r
4508         }           \r
4509         if (white_piece && !WhiteOnMove(currentMove)) {\r
4510             DisplayMoveError("It is Black's turn");\r
4511             return FALSE;\r
4512         }           \r
4513         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
4514             /* Editing correspondence game history */\r
4515             /* Could disallow this or prompt for confirmation */\r
4516             cmailOldMove = -1;\r
4517         }\r
4518         if (currentMove < forwardMostMove) {\r
4519             /* Discarding moves */\r
4520             /* Could prompt for confirmation here,\r
4521                but I don't think that's such a good idea */\r
4522             forwardMostMove = currentMove;\r
4523         }\r
4524         break;\r
4525 \r
4526       case BeginningOfGame:\r
4527         if (appData.icsActive) return FALSE;\r
4528         if (!appData.noChessProgram) {\r
4529             if (!white_piece) {\r
4530                 DisplayMoveError("You are playing White");\r
4531                 return FALSE;\r
4532             }\r
4533         }\r
4534         break;\r
4535         \r
4536       case Training:\r
4537         if (!white_piece && WhiteOnMove(currentMove)) {\r
4538             DisplayMoveError("It is White's turn");\r
4539             return FALSE;\r
4540         }           \r
4541         if (white_piece && !WhiteOnMove(currentMove)) {\r
4542             DisplayMoveError("It is Black's turn");\r
4543             return FALSE;\r
4544         }           \r
4545         break;\r
4546 \r
4547       default:\r
4548       case IcsExamining:\r
4549         break;\r
4550     }\r
4551     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
4552         && gameMode != AnalyzeFile && gameMode != Training) {\r
4553         DisplayMoveError("Displayed position is not current");\r
4554         return FALSE;\r
4555     }\r
4556     return TRUE;\r
4557 }\r
4558 \r
4559 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
4560 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
4561 int lastLoadGameUseList = FALSE;\r
4562 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
4563 ChessMove lastLoadGameStart = (ChessMove) 0;\r
4564 \r
4565 \r
4566 ChessMove\r
4567 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
4568      int fromX, fromY, toX, toY;\r
4569      int promoChar;\r
4570 {\r
4571     ChessMove moveType;\r
4572     ChessSquare pdown, pup;\r
4573 \r
4574     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
4575     if ((fromX == toX) && (fromY == toY)) {\r
4576         return ImpossibleMove;\r
4577     }\r
4578 \r
4579     /* [HGM] suppress all moves into holdings area and guard band */\r
4580     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
4581             return ImpossibleMove;\r
4582 \r
4583     /* [HGM] <sameColor> moved to here from winboard.c */\r
4584     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
4585     pdown = boards[currentMove][fromY][fromX];\r
4586     pup = boards[currentMove][toY][toX];\r
4587     if (    gameMode != EditPosition &&\r
4588             (WhitePawn <= pdown && pdown < BlackPawn &&\r
4589              WhitePawn <= pup && pup < BlackPawn  ||\r
4590              BlackPawn <= pdown && pdown < EmptySquare &&\r
4591              BlackPawn <= pup && pup < EmptySquare \r
4592             ) && !(gameInfo.variant == VariantFischeRandom &&\r
4593                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
4594                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
4595         )           )\r
4596          return ImpossibleMove;\r
4597 \r
4598     /* Check if the user is playing in turn.  This is complicated because we\r
4599        let the user "pick up" a piece before it is his turn.  So the piece he\r
4600        tried to pick up may have been captured by the time he puts it down!\r
4601        Therefore we use the color the user is supposed to be playing in this\r
4602        test, not the color of the piece that is currently on the starting\r
4603        square---except in EditGame mode, where the user is playing both\r
4604        sides; fortunately there the capture race can't happen.  (It can\r
4605        now happen in IcsExamining mode, but that's just too bad.  The user\r
4606        will get a somewhat confusing message in that case.)\r
4607        */\r
4608 \r
4609     switch (gameMode) {\r
4610       case PlayFromGameFile:\r
4611       case AnalyzeFile:\r
4612       case TwoMachinesPlay:\r
4613       case EndOfGame:\r
4614       case IcsObserving:\r
4615       case IcsIdle:\r
4616         /* We switched into a game mode where moves are not accepted,\r
4617            perhaps while the mouse button was down. */\r
4618         return ImpossibleMove;\r
4619 \r
4620       case MachinePlaysWhite:\r
4621         /* User is moving for Black */\r
4622         if (WhiteOnMove(currentMove)) {\r
4623             DisplayMoveError("It is White's turn");\r
4624             return ImpossibleMove;\r
4625         }\r
4626         break;\r
4627 \r
4628       case MachinePlaysBlack:\r
4629         /* User is moving for White */\r
4630         if (!WhiteOnMove(currentMove)) {\r
4631             DisplayMoveError("It is Black's turn");\r
4632             return ImpossibleMove;\r
4633         }\r
4634         break;\r
4635 \r
4636       case EditGame:\r
4637       case IcsExamining:\r
4638       case BeginningOfGame:\r
4639       case AnalyzeMode:\r
4640       case Training:\r
4641         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
4642             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
4643             /* User is moving for Black */\r
4644             if (WhiteOnMove(currentMove)) {\r
4645                 DisplayMoveError("It is White's turn");\r
4646                 return ImpossibleMove;\r
4647             }\r
4648         } else {\r
4649             /* User is moving for White */\r
4650             if (!WhiteOnMove(currentMove)) {\r
4651                 DisplayMoveError("It is Black's turn");\r
4652                 return ImpossibleMove;\r
4653             }\r
4654         }\r
4655         break;\r
4656 \r
4657       case IcsPlayingBlack:\r
4658         /* User is moving for Black */\r
4659         if (WhiteOnMove(currentMove)) {\r
4660             if (!appData.premove) {\r
4661                 DisplayMoveError("It is White's turn");\r
4662             } else if (toX >= 0 && toY >= 0) {\r
4663                 premoveToX = toX;\r
4664                 premoveToY = toY;\r
4665                 premoveFromX = fromX;\r
4666                 premoveFromY = fromY;\r
4667                 premovePromoChar = promoChar;\r
4668                 gotPremove = 1;\r
4669                 if (appData.debugMode) \r
4670                     fprintf(debugFP, "Got premove: fromX %d,"\r
4671                             "fromY %d, toX %d, toY %d\n",\r
4672                             fromX, fromY, toX, toY);\r
4673             }\r
4674             return ImpossibleMove;\r
4675         }\r
4676         break;\r
4677 \r
4678       case IcsPlayingWhite:\r
4679         /* User is moving for White */\r
4680         if (!WhiteOnMove(currentMove)) {\r
4681             if (!appData.premove) {\r
4682                 DisplayMoveError("It is Black's turn");\r
4683             } else if (toX >= 0 && toY >= 0) {\r
4684                 premoveToX = toX;\r
4685                 premoveToY = toY;\r
4686                 premoveFromX = fromX;\r
4687                 premoveFromY = fromY;\r
4688                 premovePromoChar = promoChar;\r
4689                 gotPremove = 1;\r
4690                 if (appData.debugMode) \r
4691                     fprintf(debugFP, "Got premove: fromX %d,"\r
4692                             "fromY %d, toX %d, toY %d\n",\r
4693                             fromX, fromY, toX, toY);\r
4694             }\r
4695             return ImpossibleMove;\r
4696         }\r
4697         break;\r
4698 \r
4699       default:\r
4700         break;\r
4701 \r
4702       case EditPosition:\r
4703         /* EditPosition, empty square, or different color piece;\r
4704            click-click move is possible */\r
4705         if (toX == -2 || toY == -2) {\r
4706             boards[0][fromY][fromX] = EmptySquare;\r
4707             DrawPosition(FALSE, boards[currentMove]);\r
4708         } else if (toX >= 0 && toY >= 0) {\r
4709             boards[0][toY][toX] = boards[0][fromY][fromX];\r
4710             boards[0][fromY][fromX] = EmptySquare;\r
4711             DrawPosition(FALSE, boards[currentMove]);\r
4712         }\r
4713         return ImpossibleMove;\r
4714     }\r
4715 \r
4716     /* [HGM] If move started in holdings, it means a drop */\r
4717     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
4718          if( pup != EmptySquare ) return ImpossibleMove;\r
4719          if(appData.testLegality) {\r
4720              /* it would be more logical if LegalityTest() also figured out\r
4721               * which drops are legal. For now we forbid pawns on back rank.\r
4722               * Shogi is on its own here...\r
4723               */\r
4724              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
4725                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
4726                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
4727          }\r
4728          return WhiteDrop; /* Not needed to specify white or black yet */\r
4729     }\r
4730 \r
4731     userOfferedDraw = FALSE;\r
4732         \r
4733     /* [HGM] always test for legality, to get promotion info */\r
4734     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
4735                           epStatus[currentMove], castlingRights[currentMove],\r
4736                                          fromY, fromX, toY, toX, promoChar);\r
4737 \r
4738     /* [HGM] but possibly ignore an IllegalMove result */\r
4739     if (appData.testLegality) {\r
4740         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
4741             DisplayMoveError("Illegal move");\r
4742             return ImpossibleMove;\r
4743         }\r
4744     }\r
4745 \r
4746     return moveType;\r
4747     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
4748        function is made into one that returns an OK move type if FinishMove\r
4749        should be called. This to give the calling driver routine the\r
4750        opportunity to finish the userMove input with a promotion popup,\r
4751        without bothering the user with this for invalid or illegal moves */\r
4752 \r
4753 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
4754 }\r
4755 \r
4756 /* Common tail of UserMoveEvent and DropMenuEvent */\r
4757 void\r
4758 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
4759      ChessMove moveType;\r
4760      int fromX, fromY, toX, toY;\r
4761      /*char*/int promoChar;\r
4762 {\r
4763     /* [HGM] <popupFix> kludge to avoid having know the exact promotion\r
4764        move type in caller when we know the move is a legal promotion */\r
4765     if(moveType == NormalMove)\r
4766         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
4767 \r
4768     /* [HGM] convert drag-and-drop piece drops to standard form */\r
4769     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
4770          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
4771          fromX = boards[currentMove][fromY][fromX];\r
4772          fromY = DROP_RANK;\r
4773     }\r
4774 \r
4775     /* [HGM] <popupFix> The following if has been moved here from\r
4776        UserMoveEvent(). Because it seemed to belon here (why not allow\r
4777        piece drops in training games?), and because it can only be\r
4778        performed after it is known to what we promote. */\r
4779     if (gameMode == Training) {\r
4780       /* compare the move played on the board to the next move in the\r
4781        * game. If they match, display the move and the opponent's response. \r
4782        * If they don't match, display an error message.\r
4783        */\r
4784       int saveAnimate;\r
4785       Board testBoard;\r
4786       CopyBoard(testBoard, boards[currentMove]);\r
4787       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
4788 \r
4789       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
4790         ForwardInner(currentMove+1);\r
4791 \r
4792         /* Autoplay the opponent's response.\r
4793          * if appData.animate was TRUE when Training mode was entered,\r
4794          * the response will be animated.\r
4795          */\r
4796         saveAnimate = appData.animate;\r
4797         appData.animate = animateTraining;\r
4798         ForwardInner(currentMove+1);\r
4799         appData.animate = saveAnimate;\r
4800 \r
4801         /* check for the end of the game */\r
4802         if (currentMove >= forwardMostMove) {\r
4803           gameMode = PlayFromGameFile;\r
4804           ModeHighlight();\r
4805           SetTrainingModeOff();\r
4806           DisplayInformation("End of game");\r
4807         }\r
4808       } else {\r
4809         DisplayError("Incorrect move", 0);\r
4810       }\r
4811       return;\r
4812     }\r
4813 \r
4814   /* Ok, now we know that the move is good, so we can kill\r
4815      the previous line in Analysis Mode */\r
4816   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
4817     forwardMostMove = currentMove;\r
4818   }\r
4819 \r
4820   /* If we need the chess program but it's dead, restart it */\r
4821   ResurrectChessProgram();\r
4822 \r
4823   /* A user move restarts a paused game*/\r
4824   if (pausing)\r
4825     PauseEvent();\r
4826 \r
4827   thinkOutput[0] = NULLCHAR;\r
4828 \r
4829   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
4830 \r
4831   if (gameMode == BeginningOfGame) {\r
4832     if (appData.noChessProgram) {\r
4833       gameMode = EditGame;\r
4834       SetGameInfo();\r
4835     } else {\r
4836       char buf[MSG_SIZ];\r
4837       gameMode = MachinePlaysBlack;\r
4838       SetGameInfo();\r
4839       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
4840       DisplayTitle(buf);\r
4841       if (first.sendName) {\r
4842         sprintf(buf, "name %s\n", gameInfo.white);\r
4843         SendToProgram(buf, &first);\r
4844       }\r
4845     }\r
4846     ModeHighlight();\r
4847   }\r
4848 \r
4849   /* Relay move to ICS or chess engine */\r
4850   if (appData.icsActive) {\r
4851     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
4852         gameMode == IcsExamining) {\r
4853       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
4854       ics_user_moved = 1;\r
4855     }\r
4856   } else {\r
4857     if (first.sendTime && (gameMode == BeginningOfGame ||\r
4858                            gameMode == MachinePlaysWhite ||\r
4859                            gameMode == MachinePlaysBlack)) {\r
4860       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
4861     }\r
4862     SendMoveToProgram(forwardMostMove-1, &first);\r
4863     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
4864       first.maybeThinking = TRUE;\r
4865     }\r
4866     if (currentMove == cmailOldMove + 1) {\r
4867       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
4868     }\r
4869   }\r
4870 \r
4871   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
4872 \r
4873   switch (gameMode) {\r
4874   case EditGame:\r
4875     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
4876                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
4877     case MT_NONE:\r
4878     case MT_CHECK:\r
4879       break;\r
4880     case MT_CHECKMATE:\r
4881       if (WhiteOnMove(currentMove)) {\r
4882         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
4883       } else {\r
4884         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
4885       }\r
4886       break;\r
4887     case MT_STALEMATE:\r
4888       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
4889       break;\r
4890     }\r
4891     break;\r
4892     \r
4893   case MachinePlaysBlack:\r
4894   case MachinePlaysWhite:\r
4895     /* disable certain menu options while machine is thinking */\r
4896     SetMachineThinkingEnables();\r
4897     break;\r
4898 \r
4899   default:\r
4900     break;\r
4901   }\r
4902 }\r
4903 \r
4904 void\r
4905 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
4906      int fromX, fromY, toX, toY;\r
4907      int promoChar;\r
4908 {\r
4909     /* [HGM] This routine was added to allow calling of its two logical\r
4910        parts from other modules in the old way. Before, UserMoveEvent()\r
4911        automatically called FinishMove() if the move was OK, and returned\r
4912        otherwise. I separated the two, in order to make it possible to\r
4913        slip a promotion popup in between. But that it always needs two\r
4914        calls, to the first part, (now called UserMoveTest() ), and to\r
4915        FinishMove if the first part succeeded. Calls that do not need\r
4916        to do anything in between, can call this routine the old way. \r
4917     */\r
4918     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
4919 \r
4920     if(moveType != ImpossibleMove)\r
4921         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
4922 }\r
4923 \r
4924 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
4925 {\r
4926     char * hint = lastHint;\r
4927     FrontEndProgramStats stats;\r
4928 \r
4929     stats.which = cps == &first ? 0 : 1;\r
4930     stats.depth = cpstats->depth;\r
4931     stats.nodes = cpstats->nodes;\r
4932     stats.score = cpstats->score;\r
4933     stats.time = cpstats->time;\r
4934     stats.pv = cpstats->movelist;\r
4935     stats.hint = lastHint;\r
4936     stats.an_move_index = 0;\r
4937     stats.an_move_count = 0;\r
4938 \r
4939     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
4940         stats.hint = cpstats->move_name;\r
4941         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
4942         stats.an_move_count = cpstats->nr_moves;\r
4943     }\r
4944 \r
4945     SetProgramStats( &stats );\r
4946 }\r
4947 \r
4948 void\r
4949 HandleMachineMove(message, cps)\r
4950      char *message;\r
4951      ChessProgramState *cps;\r
4952 {\r
4953     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
4954     char realname[MSG_SIZ];\r
4955     int fromX, fromY, toX, toY;\r
4956     ChessMove moveType;\r
4957     char promoChar;\r
4958     char *p;\r
4959     int machineWhite;\r
4960 \r
4961     /*\r
4962      * Kludge to ignore BEL characters\r
4963      */\r
4964     while (*message == '\007') message++;\r
4965 \r
4966     /*\r
4967      * [HGM] engine debug message: ignore lines starting with '#' character\r
4968      */\r
4969     if(cps->debug && *message == '#') return;\r
4970 \r
4971     /*\r
4972      * Look for book output\r
4973      */\r
4974     if (cps == &first && bookRequested) {\r
4975         if (message[0] == '\t' || message[0] == ' ') {\r
4976             /* Part of the book output is here; append it */\r
4977             strcat(bookOutput, message);\r
4978             strcat(bookOutput, "  \n");\r
4979             return;\r
4980         } else if (bookOutput[0] != NULLCHAR) {\r
4981             /* All of book output has arrived; display it */\r
4982             char *p = bookOutput;\r
4983             while (*p != NULLCHAR) {\r
4984                 if (*p == '\t') *p = ' ';\r
4985                 p++;\r
4986             }\r
4987             DisplayInformation(bookOutput);\r
4988             bookRequested = FALSE;\r
4989             /* Fall through to parse the current output */\r
4990         }\r
4991     }\r
4992 \r
4993     /*\r
4994      * Look for machine move.\r
4995      */\r
4996     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
4997         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
4998     {\r
4999         /* This method is only useful on engines that support ping */\r
5000         if (cps->lastPing != cps->lastPong) {\r
5001           if (gameMode == BeginningOfGame) {\r
5002             /* Extra move from before last new; ignore */\r
5003             if (appData.debugMode) {\r
5004                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5005             }\r
5006           } else {\r
5007             if (appData.debugMode) {\r
5008                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5009                         cps->which, gameMode);\r
5010             }\r
5011 \r
5012             SendToProgram("undo\n", cps);\r
5013           }\r
5014           return;\r
5015         }\r
5016 \r
5017         switch (gameMode) {\r
5018           case BeginningOfGame:\r
5019             /* Extra move from before last reset; ignore */\r
5020             if (appData.debugMode) {\r
5021                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5022             }\r
5023             return;\r
5024 \r
5025           case EndOfGame:\r
5026           case IcsIdle:\r
5027           default:\r
5028             /* Extra move after we tried to stop.  The mode test is\r
5029                not a reliable way of detecting this problem, but it's\r
5030                the best we can do on engines that don't support ping.\r
5031             */\r
5032             if (appData.debugMode) {\r
5033                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5034                         cps->which, gameMode);\r
5035             }\r
5036             SendToProgram("undo\n", cps);\r
5037             return;\r
5038 \r
5039           case MachinePlaysWhite:\r
5040           case IcsPlayingWhite:\r
5041             machineWhite = TRUE;\r
5042             break;\r
5043 \r
5044           case MachinePlaysBlack:\r
5045           case IcsPlayingBlack:\r
5046             machineWhite = FALSE;\r
5047             break;\r
5048 \r
5049           case TwoMachinesPlay:\r
5050             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5051             break;\r
5052         }\r
5053         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5054             if (appData.debugMode) {\r
5055                 fprintf(debugFP,\r
5056                         "Ignoring move out of turn by %s, gameMode %d"\r
5057                         ", forwardMost %d\n",\r
5058                         cps->which, gameMode, forwardMostMove);\r
5059             }\r
5060             return;\r
5061         }\r
5062 \r
5063     if (appData.debugMode) { int f = forwardMostMove;\r
5064         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5065                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5066     }\r
5067         AlphaRank(machineMove, 4);\r
5068         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5069                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5070             /* Machine move could not be parsed; ignore it. */\r
5071             sprintf(buf1, "Illegal move \"%s\" from %s machine",\r
5072                     machineMove, cps->which);\r
5073             DisplayError(buf1, 0);\r
5074             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",\r
5075                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5076             if (gameMode == TwoMachinesPlay) {\r
5077               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5078                        buf1, GE_XBOARD);\r
5079             }\r
5080             return;\r
5081         }\r
5082 \r
5083         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5084         /* So we have to redo legality test with true e.p. status here,  */\r
5085         /* to make sure an illegal e.p. capture does not slip through,   */\r
5086         /* to cause a forfeit on a justified illegal-move complaint      */\r
5087         /* of the opponent.                                              */\r
5088         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5089             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5090                                                               ) {\r
5091            ChessMove moveType;\r
5092            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5093                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5094                              fromY, fromX, toY, toX, promoChar);\r
5095             if (appData.debugMode) {\r
5096                 int i;\r
5097                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5098                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5099                 fprintf(debugFP, "castling rights\n");\r
5100             }\r
5101             if(moveType == IllegalMove) {\r
5102                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5103                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5104                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5105                            buf1, GE_XBOARD);\r
5106            } else if(gameInfo.variant != VariantFischeRandom)\r
5107            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5108               when they shouldn't (like TSCP-Gothic) */\r
5109            switch(moveType) {\r
5110              case WhiteASideCastleFR:\r
5111              case BlackASideCastleFR:\r
5112                toX+=2;\r
5113                currentMoveString[2]++;\r
5114                break;\r
5115              case WhiteHSideCastleFR:\r
5116              case BlackHSideCastleFR:\r
5117                toX--;\r
5118                currentMoveString[2]--;\r
5119                break;\r
5120            }\r
5121         }\r
5122         hintRequested = FALSE;\r
5123         lastHint[0] = NULLCHAR;\r
5124         bookRequested = FALSE;\r
5125         /* Program may be pondering now */\r
5126         cps->maybeThinking = TRUE;\r
5127         if (cps->sendTime == 2) cps->sendTime = 1;\r
5128         if (cps->offeredDraw) cps->offeredDraw--;\r
5129 \r
5130 #if ZIPPY\r
5131         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5132             first.initDone) {\r
5133           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5134           ics_user_moved = 1;\r
5135         }\r
5136 #endif\r
5137         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5138         strcpy(machineMove, currentMoveString);\r
5139         strcat(machineMove, "\n");\r
5140         strcpy(moveList[forwardMostMove], machineMove);\r
5141 \r
5142         /* [AS] Save move info and clear stats for next move */\r
5143         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5144         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5145         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5146         ClearProgramStats();\r
5147         thinkOutput[0] = NULLCHAR;\r
5148         hiddenThinkOutputState = 0;\r
5149 \r
5150         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5151 \r
5152         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5153         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5154             int count = 0;\r
5155 \r
5156             while( count < adjudicateLossPlies ) {\r
5157                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5158 \r
5159                 if( count & 1 ) {\r
5160                     score = -score; /* Flip score for winning side */\r
5161                 }\r
5162 \r
5163                 if( score > adjudicateLossThreshold ) {\r
5164                     break;\r
5165                 }\r
5166 \r
5167                 count++;\r
5168             }\r
5169 \r
5170             if( count >= adjudicateLossPlies ) {\r
5171                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5172 \r
5173                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5174                     "Xboard adjudication", \r
5175                     GE_XBOARD );\r
5176 \r
5177                 return;\r
5178             }\r
5179         }\r
5180 \r
5181 #ifdef ADJUDICATE // [HGM] some adjudications useful with buggy engines\r
5182 \r
5183         if( gameMode == TwoMachinesPlay && gameInfo.holdingsSize == 0) {\r
5184             int count = 0, epFile = epStatus[forwardMostMove];\r
5185 \r
5186             if(appData.testLegality && appData.checkMates) \r
5187             // don't wait for engine to announce game end if we can judge ourselves\r
5188             switch (MateTest(boards[forwardMostMove],\r
5189                                  PosFlags(forwardMostMove), epFile,\r
5190                                        castlingRights[forwardMostMove]) ) {\r
5191               case MT_NONE:\r
5192               case MT_CHECK:\r
5193               default:\r
5194                 break;\r
5195               case MT_STALEMATE:\r
5196                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5197                 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
5198                     GE_XBOARD );\r
5199                 break;\r
5200               case MT_CHECKMATE:\r
5201                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5202                 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5203                     "Xboard adjudication: Checkmate", \r
5204                     GE_XBOARD );\r
5205                 break;\r
5206             }\r
5207 \r
5208             if( appData.testLegality )\r
5209             {   /* [HGM] Some more adjudications for obstinate engines */\r
5210                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5211                     NrWQ=0, NrBQ=0, bishopsColor = 0,\r
5212                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;\r
5213                 static int moveCount;\r
5214 \r
5215                 /* First absolutely insufficient mating material. Count what is on board. */\r
5216                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5217                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5218                     int m=i;\r
5219 \r
5220                     switch((int) p)\r
5221                     {   /* count B,N,R and other of each side */\r
5222                         case WhiteKnight:\r
5223                              NrWN++; break;\r
5224                         case WhiteBishop:\r
5225                              bishopsColor |= 1 << ((i^j)&1);\r
5226                              NrWB++; break;\r
5227                         case BlackKnight:\r
5228                              NrBN++; break;\r
5229                         case BlackBishop:\r
5230                              bishopsColor |= 1 << ((i^j)&1);\r
5231                              NrBB++; break;\r
5232                         case WhiteRook:\r
5233                              NrWR++; break;\r
5234                         case BlackRook:\r
5235                              NrBR++; break;\r
5236                         case WhiteQueen:\r
5237                              NrWQ++; break;\r
5238                         case BlackQueen:\r
5239                              NrBQ++; break;\r
5240                         case EmptySquare: \r
5241                              break;\r
5242                         case BlackPawn:\r
5243                              m = 7-i;\r
5244                         case WhitePawn:\r
5245                              PawnAdvance += m; NrPawns++;\r
5246                     }\r
5247                     NrPieces += (p != EmptySquare);\r
5248                 }\r
5249 \r
5250                 if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2\r
5251                  || NrPieces == 4 && NrBB+NrWB==2 && bishopsColor != 3)\r
5252                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5253 \r
5254                      /* always flag draws, for judging claims */\r
5255                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5256 \r
5257                      if(appData.materialDraws) {\r
5258                          /* but only adjudicate them if adjudication enabled */\r
5259                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5260                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5261                          return;\r
5262                      }\r
5263                 }\r
5264 \r
5265                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5266                 if(NrPieces == 4 && \r
5267                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5268                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5269                    || NrWN==2 || NrBN==2     /* KNNK */\r
5270                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5271                   ) ) {\r
5272                      if(--moveCount < 0 && appData.trivialDraws)\r
5273                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5274                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5275                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5276                           return;\r
5277                      }\r
5278                 } else moveCount = 6;\r
5279 #if 0\r
5280     if (appData.debugMode) { int i;\r
5281       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5282               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5283               appData.drawRepeats);\r
5284       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5285            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5286 \r
5287     }\r
5288 #endif\r
5289                 /* Check for rep-draws */\r
5290                 count = 0;\r
5291                 for(k = forwardMostMove-2;\r
5292                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5293                         epStatus[k] < EP_UNKNOWN &&\r
5294                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5295                     k-=2)\r
5296                 {   int rights=0;\r
5297 #if 0\r
5298     if (appData.debugMode) {\r
5299       fprintf(debugFP, " loop\n");\r
5300     }\r
5301 #endif\r
5302                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5303 #if 0\r
5304     if (appData.debugMode) {\r
5305       fprintf(debugFP, "match\n");\r
5306     }\r
5307 #endif\r
5308                         /* compare castling rights */\r
5309                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5310                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5311                                 rights++; /* King lost rights, while rook still had them */\r
5312                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5313                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5314                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5315                                    rights++; /* but at least one rook lost them */\r
5316                         }\r
5317                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5318                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5319                                 rights++; \r
5320                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5321                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5322                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5323                                    rights++;\r
5324                         }\r
5325 #if 0\r
5326     if (appData.debugMode) {\r
5327       for(i=0; i<nrCastlingRights; i++)\r
5328       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5329     }\r
5330 \r
5331     if (appData.debugMode) {\r
5332       fprintf(debugFP, " %d %d\n", rights, k);\r
5333     }\r
5334 #endif\r
5335                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5336                             && appData.drawRepeats > 1) {\r
5337                              /* adjudicate after user-specified nr of repeats */\r
5338                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5339                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
5340                              return;\r
5341                         }\r
5342                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
5343                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
5344                     }\r
5345                 }\r
5346 \r
5347                 /* Now we test for 50-move draws. Determine ply count */\r
5348                 count = forwardMostMove;\r
5349                 /* look for last irreversble move */\r
5350                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
5351                     count--;\r
5352                 /* if we hit starting position, add initial plies */\r
5353                 if( count == backwardMostMove )\r
5354                     count -= initialRulePlies;\r
5355                 count = forwardMostMove - count; \r
5356                 if( count >= 100)\r
5357                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
5358                          /* this is used to judge if draw claims are legal */\r
5359                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
5360                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5361                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
5362                          return;\r
5363                 }\r
5364 \r
5365                 /* if draw offer is pending, treat it as a draw claim\r
5366                  * when draw condition present, to allow engines a way to\r
5367                  * claim draws before making their move to avoid a race\r
5368                  * condition occurring after their move\r
5369                  */\r
5370                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
5371                          char *p = NULL;\r
5372                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
5373                              p = "Draw claim: 50-move rule";\r
5374                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
5375                              p = "Draw claim: 3-fold repetition";\r
5376                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
5377                              p = "Draw claim: insufficient mating material";\r
5378                          if( p != NULL ) {\r
5379                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
5380                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5381                              return;\r
5382                          }\r
5383                 }\r
5384 \r
5385             }\r
5386 \r
5387 \r
5388         }\r
5389 #endif\r
5390         if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
5391             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5392 \r
5393             GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
5394 \r
5395             return;\r
5396         }\r
5397 \r
5398         if (gameMode == TwoMachinesPlay) {\r
5399             /* [HGM] relaying draw offers moved to after reception of move */\r
5400             /* and interpreting offer as claim if it brings draw condition */\r
5401             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
5402                 SendToProgram("draw\n", cps->other);\r
5403             }\r
5404             if (cps->other->sendTime) {\r
5405                 SendTimeRemaining(cps->other,\r
5406                                   cps->other->twoMachinesColor[0] == 'w');\r
5407             }\r
5408             SendMoveToProgram(forwardMostMove-1, cps->other);\r
5409             if (firstMove) {\r
5410                 firstMove = FALSE;\r
5411                 if (cps->other->useColors) {\r
5412                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
5413                 }\r
5414                 SendToProgram("go\n", cps->other);\r
5415             }\r
5416             cps->other->maybeThinking = TRUE;\r
5417         }\r
5418 \r
5419         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5420         \r
5421         if (!pausing && appData.ringBellAfterMoves) {\r
5422             RingBell();\r
5423         }\r
5424 \r
5425         /* \r
5426          * Reenable menu items that were disabled while\r
5427          * machine was thinking\r
5428          */\r
5429         if (gameMode != TwoMachinesPlay)\r
5430             SetUserThinkingEnables();\r
5431 \r
5432         return;\r
5433     }\r
5434 \r
5435     /* Set special modes for chess engines.  Later something general\r
5436      *  could be added here; for now there is just one kludge feature,\r
5437      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
5438      *  when "xboard" is given as an interactive command.\r
5439      */\r
5440     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
5441         cps->useSigint = FALSE;\r
5442         cps->useSigterm = FALSE;\r
5443     }\r
5444 \r
5445     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
5446      * want this, I was asked to put it in, and obliged.\r
5447      */\r
5448     if (!strncmp(message, "setboard ", 9)) {\r
5449         Board initial_position; int i;\r
5450 \r
5451         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
5452 \r
5453         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
5454             DisplayError("Bad FEN received from engine", 0);\r
5455             return ;\r
5456         } else {\r
5457            Reset(FALSE, FALSE);\r
5458            CopyBoard(boards[0], initial_position);\r
5459            initialRulePlies = FENrulePlies;\r
5460            epStatus[0] = FENepStatus;\r
5461            for( i=0; i<nrCastlingRights; i++ )\r
5462                 castlingRights[0][i] = FENcastlingRights[i];\r
5463            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
5464            else gameMode = MachinePlaysBlack;                 \r
5465            DrawPosition(FALSE, boards[currentMove]);\r
5466         }\r
5467         return;\r
5468     }\r
5469 \r
5470     /*\r
5471      * Look for communication commands\r
5472      */\r
5473     if (!strncmp(message, "telluser ", 9)) {\r
5474         DisplayNote(message + 9);\r
5475         return;\r
5476     }\r
5477     if (!strncmp(message, "tellusererror ", 14)) {\r
5478         DisplayError(message + 14, 0);\r
5479         return;\r
5480     }\r
5481     if (!strncmp(message, "tellopponent ", 13)) {\r
5482       if (appData.icsActive) {\r
5483         if (loggedOn) {\r
5484           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
5485           SendToICS(buf1);\r
5486         }\r
5487       } else {\r
5488         DisplayNote(message + 13);\r
5489       }\r
5490       return;\r
5491     }\r
5492     if (!strncmp(message, "tellothers ", 11)) {\r
5493       if (appData.icsActive) {\r
5494         if (loggedOn) {\r
5495           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
5496           SendToICS(buf1);\r
5497         }\r
5498       }\r
5499       return;\r
5500     }\r
5501     if (!strncmp(message, "tellall ", 8)) {\r
5502       if (appData.icsActive) {\r
5503         if (loggedOn) {\r
5504           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
5505           SendToICS(buf1);\r
5506         }\r
5507       } else {\r
5508         DisplayNote(message + 8);\r
5509       }\r
5510       return;\r
5511     }\r
5512     if (strncmp(message, "warning", 7) == 0) {\r
5513         /* Undocumented feature, use tellusererror in new code */\r
5514         DisplayError(message, 0);\r
5515         return;\r
5516     }\r
5517     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
5518         strcpy(realname, cps->tidy);\r
5519         strcat(realname, " query");\r
5520         AskQuestion(realname, buf2, buf1, cps->pr);\r
5521         return;\r
5522     }\r
5523     /* Commands from the engine directly to ICS.  We don't allow these to be \r
5524      *  sent until we are logged on. Crafty kibitzes have been known to \r
5525      *  interfere with the login process.\r
5526      */\r
5527     if (loggedOn) {\r
5528         if (!strncmp(message, "tellics ", 8)) {\r
5529             SendToICS(message + 8);\r
5530             SendToICS("\n");\r
5531             return;\r
5532         }\r
5533         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
5534             SendToICS(ics_prefix);\r
5535             SendToICS(message + 15);\r
5536             SendToICS("\n");\r
5537             return;\r
5538         }\r
5539         /* The following are for backward compatibility only */\r
5540         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
5541             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
5542             SendToICS(ics_prefix);\r
5543             SendToICS(message);\r
5544             SendToICS("\n");\r
5545             return;\r
5546         }\r
5547     }\r
5548     if (strncmp(message, "feature ", 8) == 0) {\r
5549       ParseFeatures(message+8, cps);\r
5550     }\r
5551     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
5552       return;\r
5553     }\r
5554     /*\r
5555      * If the move is illegal, cancel it and redraw the board.\r
5556      * Also deal with other error cases.  Matching is rather loose\r
5557      * here to accommodate engines written before the spec.\r
5558      */\r
5559     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
5560         strncmp(message, "Error", 5) == 0) {\r
5561         if (StrStr(message, "name") || \r
5562             StrStr(message, "rating") || StrStr(message, "?") ||\r
5563             StrStr(message, "result") || StrStr(message, "board") ||\r
5564             StrStr(message, "bk") || StrStr(message, "computer") ||\r
5565             StrStr(message, "variant") || StrStr(message, "hint") ||\r
5566             StrStr(message, "random") || StrStr(message, "depth") ||\r
5567             StrStr(message, "accepted")) {\r
5568             return;\r
5569         }\r
5570         if (StrStr(message, "protover")) {\r
5571           /* Program is responding to input, so it's apparently done\r
5572              initializing, and this error message indicates it is\r
5573              protocol version 1.  So we don't need to wait any longer\r
5574              for it to initialize and send feature commands. */\r
5575           FeatureDone(cps, 1);\r
5576           cps->protocolVersion = 1;\r
5577           return;\r
5578         }\r
5579         cps->maybeThinking = FALSE;\r
5580 \r
5581         if (StrStr(message, "draw")) {\r
5582             /* Program doesn't have "draw" command */\r
5583             cps->sendDrawOffers = 0;\r
5584             return;\r
5585         }\r
5586         if (cps->sendTime != 1 &&\r
5587             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
5588           /* Program apparently doesn't have "time" or "otim" command */\r
5589           cps->sendTime = 0;\r
5590           return;\r
5591         }\r
5592         if (StrStr(message, "analyze")) {\r
5593             cps->analysisSupport = FALSE;\r
5594             cps->analyzing = FALSE;\r
5595             Reset(FALSE, TRUE);\r
5596             sprintf(buf2, "%s does not support analysis", cps->tidy);\r
5597             DisplayError(buf2, 0);\r
5598             return;\r
5599         }\r
5600         if (StrStr(message, "(no matching move)st")) {\r
5601           /* Special kludge for GNU Chess 4 only */\r
5602           cps->stKludge = TRUE;\r
5603           SendTimeControl(cps, movesPerSession, timeControl,\r
5604                           timeIncrement, appData.searchDepth,\r
5605                           searchTime);\r
5606           return;\r
5607         }\r
5608         if (StrStr(message, "(no matching move)sd")) {\r
5609           /* Special kludge for GNU Chess 4 only */\r
5610           cps->sdKludge = TRUE;\r
5611           SendTimeControl(cps, movesPerSession, timeControl,\r
5612                           timeIncrement, appData.searchDepth,\r
5613                           searchTime);\r
5614           return;\r
5615         }\r
5616         if (!StrStr(message, "llegal")) {\r
5617             return;\r
5618         }\r
5619         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
5620             gameMode == IcsIdle) return;\r
5621         if (forwardMostMove <= backwardMostMove) return;\r
5622 #if 0\r
5623         /* Following removed: it caused a bug where a real illegal move\r
5624            message in analyze mored would be ignored. */\r
5625         if (cps == &first && programStats.ok_to_send == 0) {\r
5626             /* Bogus message from Crafty responding to "."  This filtering\r
5627                can miss some of the bad messages, but fortunately the bug \r
5628                is fixed in current Crafty versions, so it doesn't matter. */\r
5629             return;\r
5630         }\r
5631 #endif\r
5632         if (pausing) PauseEvent();\r
5633         if (gameMode == PlayFromGameFile) {\r
5634             /* Stop reading this game file */\r
5635             gameMode = EditGame;\r
5636             ModeHighlight();\r
5637         }\r
5638         currentMove = --forwardMostMove;\r
5639         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
5640         SwitchClocks();\r
5641         DisplayBothClocks();\r
5642         sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",\r
5643                 parseList[currentMove], cps->which);\r
5644         DisplayMoveError(buf1);\r
5645         DrawPosition(FALSE, boards[currentMove]);\r
5646 \r
5647         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
5648         /* only passes fully legal moves                            */\r
5649         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
5650             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
5651                                 "False illegal-move claim", GE_XBOARD );\r
5652         }\r
5653         return;\r
5654     }\r
5655     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
5656         /* Program has a broken "time" command that\r
5657            outputs a string not ending in newline.\r
5658            Don't use it. */\r
5659         cps->sendTime = 0;\r
5660     }\r
5661     \r
5662     /*\r
5663      * If chess program startup fails, exit with an error message.\r
5664      * Attempts to recover here are futile.\r
5665      */\r
5666     if ((StrStr(message, "unknown host") != NULL)\r
5667         || (StrStr(message, "No remote directory") != NULL)\r
5668         || (StrStr(message, "not found") != NULL)\r
5669         || (StrStr(message, "No such file") != NULL)\r
5670         || (StrStr(message, "can't alloc") != NULL)\r
5671         || (StrStr(message, "Permission denied") != NULL)) {\r
5672 \r
5673         cps->maybeThinking = FALSE;\r
5674         sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",\r
5675                 cps->which, cps->program, cps->host, message);\r
5676         RemoveInputSource(cps->isr);\r
5677         DisplayFatalError(buf1, 0, 1);\r
5678         return;\r
5679     }\r
5680     \r
5681     /* \r
5682      * Look for hint output\r
5683      */\r
5684     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
5685         if (cps == &first && hintRequested) {\r
5686             hintRequested = FALSE;\r
5687             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
5688                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5689                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
5690                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
5691                                     fromY, fromX, toY, toX, promoChar, buf1);\r
5692                 sprintf(buf2, "Hint: %s", buf1);\r
5693                 DisplayInformation(buf2);\r
5694             } else {\r
5695                 /* Hint move could not be parsed!? */\r
5696                 sprintf(buf2,\r
5697                         "Illegal hint move \"%s\"\nfrom %s chess program",\r
5698                         buf1, cps->which);\r
5699                 DisplayError(buf2, 0);\r
5700             }\r
5701         } else {\r
5702             strcpy(lastHint, buf1);\r
5703         }\r
5704         return;\r
5705     }\r
5706 \r
5707     /*\r
5708      * Ignore other messages if game is not in progress\r
5709      */\r
5710     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
5711         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
5712 \r
5713     /*\r
5714      * look for win, lose, draw, or draw offer\r
5715      */\r
5716     if (strncmp(message, "1-0", 3) == 0) {\r
5717         char *p, *q, *r = "";\r
5718         p = strchr(message, '{');\r
5719         if (p) {\r
5720             q = strchr(p, '}');\r
5721             if (q) {\r
5722                 *q = NULLCHAR;\r
5723                 r = p + 1;\r
5724             }\r
5725         }\r
5726         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
5727         return;\r
5728     } else if (strncmp(message, "0-1", 3) == 0) {\r
5729         char *p, *q, *r = "";\r
5730         p = strchr(message, '{');\r
5731         if (p) {\r
5732             q = strchr(p, '}');\r
5733             if (q) {\r
5734                 *q = NULLCHAR;\r
5735                 r = p + 1;\r
5736             }\r
5737         }\r
5738         /* Kludge for Arasan 4.1 bug */\r
5739         if (strcmp(r, "Black resigns") == 0) {\r
5740             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
5741             return;\r
5742         }\r
5743         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
5744         return;\r
5745     } else if (strncmp(message, "1/2", 3) == 0) {\r
5746         char *p, *q, *r = "";\r
5747         p = strchr(message, '{');\r
5748         if (p) {\r
5749             q = strchr(p, '}');\r
5750             if (q) {\r
5751                 *q = NULLCHAR;\r
5752                 r = p + 1;\r
5753             }\r
5754         }\r
5755             \r
5756         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
5757         return;\r
5758 \r
5759     } else if (strncmp(message, "White resign", 12) == 0) {\r
5760         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
5761         return;\r
5762     } else if (strncmp(message, "Black resign", 12) == 0) {\r
5763         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
5764         return;\r
5765     } else if (strncmp(message, "White matches", 13) == 0 ||\r
5766                strncmp(message, "Black matches", 13) == 0   ) {\r
5767         /* [HGM] ignore GNUShogi noises */\r
5768         return;\r
5769     } else if (strncmp(message, "White", 5) == 0 &&\r
5770                message[5] != '(' &&\r
5771                StrStr(message, "Black") == NULL) {\r
5772         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5773         return;\r
5774     } else if (strncmp(message, "Black", 5) == 0 &&\r
5775                message[5] != '(') {\r
5776         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5777         return;\r
5778     } else if (strcmp(message, "resign") == 0 ||\r
5779                strcmp(message, "computer resigns") == 0) {\r
5780         switch (gameMode) {\r
5781           case MachinePlaysBlack:\r
5782           case IcsPlayingBlack:\r
5783             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
5784             break;\r
5785           case MachinePlaysWhite:\r
5786           case IcsPlayingWhite:\r
5787             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
5788             break;\r
5789           case TwoMachinesPlay:\r
5790             if (cps->twoMachinesColor[0] == 'w')\r
5791               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
5792             else\r
5793               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
5794             break;\r
5795           default:\r
5796             /* can't happen */\r
5797             break;\r
5798         }\r
5799         return;\r
5800     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
5801         switch (gameMode) {\r
5802           case MachinePlaysBlack:\r
5803           case IcsPlayingBlack:\r
5804             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
5805             break;\r
5806           case MachinePlaysWhite:\r
5807           case IcsPlayingWhite:\r
5808             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
5809             break;\r
5810           case TwoMachinesPlay:\r
5811             if (cps->twoMachinesColor[0] == 'w')\r
5812               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5813             else\r
5814               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5815             break;\r
5816           default:\r
5817             /* can't happen */\r
5818             break;\r
5819         }\r
5820         return;\r
5821     } else if (strncmp(message, "computer mates", 14) == 0) {\r
5822         switch (gameMode) {\r
5823           case MachinePlaysBlack:\r
5824           case IcsPlayingBlack:\r
5825             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
5826             break;\r
5827           case MachinePlaysWhite:\r
5828           case IcsPlayingWhite:\r
5829             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
5830             break;\r
5831           case TwoMachinesPlay:\r
5832             if (cps->twoMachinesColor[0] == 'w')\r
5833               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5834             else\r
5835               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5836             break;\r
5837           default:\r
5838             /* can't happen */\r
5839             break;\r
5840         }\r
5841         return;\r
5842     } else if (strncmp(message, "checkmate", 9) == 0) {\r
5843         if (WhiteOnMove(forwardMostMove)) {\r
5844             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
5845         } else {\r
5846             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
5847         }\r
5848         return;\r
5849     } else if (strstr(message, "Draw") != NULL ||\r
5850                strstr(message, "game is a draw") != NULL) {\r
5851         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
5852         return;\r
5853     } else if (strstr(message, "offer") != NULL &&\r
5854                strstr(message, "draw") != NULL) {\r
5855 #if ZIPPY\r
5856         if (appData.zippyPlay && first.initDone) {\r
5857             /* Relay offer to ICS */\r
5858             SendToICS(ics_prefix);\r
5859             SendToICS("draw\n");\r
5860         }\r
5861 #endif\r
5862         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
5863         if (gameMode == TwoMachinesPlay) {\r
5864             if (cps->other->offeredDraw) {\r
5865                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
5866             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
5867             /* until after we also have move, to see if it is really claim */\r
5868             }\r
5869 #if 0\r
5870               else {\r
5871                 if (cps->other->sendDrawOffers) {\r
5872                     SendToProgram("draw\n", cps->other);\r
5873                 }\r
5874             }\r
5875 #endif\r
5876         } else if (gameMode == MachinePlaysWhite ||\r
5877                    gameMode == MachinePlaysBlack) {\r
5878           if (userOfferedDraw) {\r
5879             DisplayInformation("Machine accepts your draw offer");\r
5880             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
5881           } else {\r
5882             DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");\r
5883           }\r
5884         }\r
5885     }\r
5886 \r
5887     \r
5888     /*\r
5889      * Look for thinking output\r
5890      */\r
5891     if ( appData.showThinking) {\r
5892         int plylev, mvleft, mvtot, curscore, time;\r
5893         char mvname[MOVE_LEN];\r
5894         unsigned long nodes;\r
5895         char plyext;\r
5896         int ignore = FALSE;\r
5897         int prefixHint = FALSE;\r
5898         mvname[0] = NULLCHAR;\r
5899 \r
5900         switch (gameMode) {\r
5901           case MachinePlaysBlack:\r
5902           case IcsPlayingBlack:\r
5903             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
5904             break;\r
5905           case MachinePlaysWhite:\r
5906           case IcsPlayingWhite:\r
5907             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
5908             break;\r
5909           case AnalyzeMode:\r
5910           case AnalyzeFile:\r
5911             break;\r
5912           case TwoMachinesPlay:\r
5913             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
5914                 ignore = TRUE;\r
5915             }\r
5916             break;\r
5917           default:\r
5918             ignore = TRUE;\r
5919             break;\r
5920         }\r
5921 \r
5922         if (!ignore) {\r
5923             buf1[0] = NULLCHAR;\r
5924             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
5925                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
5926 \r
5927                 if (plyext != ' ' && plyext != '\t') {\r
5928                     time *= 100;\r
5929                 }\r
5930 \r
5931                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
5932                 if( cps->scoreIsAbsolute && \r
5933                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
5934                 {\r
5935                     curscore = -curscore;\r
5936                 }\r
5937 \r
5938 \r
5939                 programStats.depth = plylev;\r
5940                 programStats.nodes = nodes;\r
5941                 programStats.time = time;\r
5942                 programStats.score = curscore;\r
5943                 programStats.got_only_move = 0;\r
5944 \r
5945                 /* Buffer overflow protection */\r
5946                 if (buf1[0] != NULLCHAR) {\r
5947                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
5948                         && appData.debugMode) {\r
5949                         fprintf(debugFP,\r
5950                                 "PV is too long; using the first %d bytes.\n",\r
5951                                 sizeof(programStats.movelist) - 1);\r
5952                     }\r
5953 \r
5954                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
5955                 } else {\r
5956                     sprintf(programStats.movelist, " no PV\n");\r
5957                 }\r
5958 \r
5959                 if (programStats.seen_stat) {\r
5960                     programStats.ok_to_send = 1;\r
5961                 }\r
5962 \r
5963                 if (strchr(programStats.movelist, '(') != NULL) {\r
5964                     programStats.line_is_book = 1;\r
5965                     programStats.nr_moves = 0;\r
5966                     programStats.moves_left = 0;\r
5967                 } else {\r
5968                     programStats.line_is_book = 0;\r
5969                 }\r
5970 \r
5971                 SendProgramStatsToFrontend( cps, &programStats );\r
5972 \r
5973                 /* \r
5974                     [AS] Protect the thinkOutput buffer from overflow... this\r
5975                     is only useful if buf1 hasn't overflowed first!\r
5976                 */\r
5977                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
5978                         plylev, \r
5979                         (gameMode == TwoMachinesPlay ?\r
5980                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
5981                         ((double) curscore) / 100.0,\r
5982                         prefixHint ? lastHint : "",\r
5983                         prefixHint ? " " : "" );\r
5984 \r
5985                 if( buf1[0] != NULLCHAR ) {\r
5986                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
5987 \r
5988                     if( strlen(buf1) > max_len ) {\r
5989                         if( appData.debugMode) {\r
5990                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
5991                         }\r
5992                         buf1[max_len+1] = '\0';\r
5993                     }\r
5994 \r
5995                     strcat( thinkOutput, buf1 );\r
5996                 }\r
5997 \r
5998                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
5999                     DisplayMove(currentMove - 1);\r
6000                     DisplayAnalysis();\r
6001                 }\r
6002                 return;\r
6003 \r
6004             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6005                 /* crafty (9.25+) says "(only move) <move>"\r
6006                  * if there is only 1 legal move\r
6007                  */\r
6008                 sscanf(p, "(only move) %s", buf1);\r
6009                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6010                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6011                 programStats.depth = 1;\r
6012                 programStats.nr_moves = 1;\r
6013                 programStats.moves_left = 1;\r
6014                 programStats.nodes = 1;\r
6015                 programStats.time = 1;\r
6016                 programStats.got_only_move = 1;\r
6017 \r
6018                 /* Not really, but we also use this member to\r
6019                    mean "line isn't going to change" (Crafty\r
6020                    isn't searching, so stats won't change) */\r
6021                 programStats.line_is_book = 1;\r
6022 \r
6023                 SendProgramStatsToFrontend( cps, &programStats );\r
6024                 \r
6025                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
6026                     DisplayMove(currentMove - 1);\r
6027                     DisplayAnalysis();\r
6028                 }\r
6029                 return;\r
6030             } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",\r
6031                               &time, &nodes, &plylev, &mvleft,\r
6032                               &mvtot, mvname) >= 5) {\r
6033                 /* The stat01: line is from Crafty (9.29+) in response\r
6034                    to the "." command */\r
6035                 programStats.seen_stat = 1;\r
6036                 cps->maybeThinking = TRUE;\r
6037 \r
6038                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6039                   return;\r
6040 \r
6041                 programStats.depth = plylev;\r
6042                 programStats.time = time;\r
6043                 programStats.nodes = nodes;\r
6044                 programStats.moves_left = mvleft;\r
6045                 programStats.nr_moves = mvtot;\r
6046                 strcpy(programStats.move_name, mvname);\r
6047                 programStats.ok_to_send = 1;\r
6048                 programStats.movelist[0] = '\0';\r
6049 \r
6050                 SendProgramStatsToFrontend( cps, &programStats );\r
6051 \r
6052                 DisplayAnalysis();\r
6053                 return;\r
6054 \r
6055             } else if (strncmp(message,"++",2) == 0) {\r
6056                 /* Crafty 9.29+ outputs this */\r
6057                 programStats.got_fail = 2;\r
6058                 return;\r
6059 \r
6060             } else if (strncmp(message,"--",2) == 0) {\r
6061                 /* Crafty 9.29+ outputs this */\r
6062                 programStats.got_fail = 1;\r
6063                 return;\r
6064 \r
6065             } else if (thinkOutput[0] != NULLCHAR &&\r
6066                        strncmp(message, "    ", 4) == 0) {\r
6067                 unsigned message_len;\r
6068 \r
6069                 p = message;\r
6070                 while (*p && *p == ' ') p++;\r
6071 \r
6072                 message_len = strlen( p );\r
6073 \r
6074                 /* [AS] Avoid buffer overflow */\r
6075                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6076                     strcat(thinkOutput, " ");\r
6077                     strcat(thinkOutput, p);\r
6078                 }\r
6079 \r
6080                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6081                     strcat(programStats.movelist, " ");\r
6082                     strcat(programStats.movelist, p);\r
6083                 }\r
6084 \r
6085                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
6086                     DisplayMove(currentMove - 1);\r
6087                     DisplayAnalysis();\r
6088                 }\r
6089                 return;\r
6090             }\r
6091         }\r
6092         else {\r
6093             buf1[0] = NULLCHAR;\r
6094 \r
6095             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
6096                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6097             {\r
6098                 ChessProgramStats cpstats;\r
6099 \r
6100                 if (plyext != ' ' && plyext != '\t') {\r
6101                     time *= 100;\r
6102                 }\r
6103 \r
6104                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6105                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6106                     curscore = -curscore;\r
6107                 }\r
6108 \r
6109                 cpstats.depth = plylev;\r
6110                 cpstats.nodes = nodes;\r
6111                 cpstats.time = time;\r
6112                 cpstats.score = curscore;\r
6113                 cpstats.got_only_move = 0;\r
6114                 cpstats.movelist[0] = '\0';\r
6115 \r
6116                 if (buf1[0] != NULLCHAR) {\r
6117                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6118                 }\r
6119 \r
6120                 cpstats.ok_to_send = 0;\r
6121                 cpstats.line_is_book = 0;\r
6122                 cpstats.nr_moves = 0;\r
6123                 cpstats.moves_left = 0;\r
6124 \r
6125                 SendProgramStatsToFrontend( cps, &cpstats );\r
6126             }\r
6127         }\r
6128     }\r
6129 }\r
6130 \r
6131 \r
6132 /* Parse a game score from the character string "game", and\r
6133    record it as the history of the current game.  The game\r
6134    score is NOT assumed to start from the standard position. \r
6135    The display is not updated in any way.\r
6136    */\r
6137 void\r
6138 ParseGameHistory(game)\r
6139      char *game;\r
6140 {\r
6141     ChessMove moveType;\r
6142     int fromX, fromY, toX, toY, boardIndex;\r
6143     char promoChar;\r
6144     char *p, *q;\r
6145     char buf[MSG_SIZ];\r
6146 \r
6147     if (appData.debugMode)\r
6148       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6149 \r
6150     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6151     gameInfo.site = StrSave(appData.icsHost);\r
6152     gameInfo.date = PGNDate();\r
6153     gameInfo.round = StrSave("-");\r
6154 \r
6155     /* Parse out names of players */\r
6156     while (*game == ' ') game++;\r
6157     p = buf;\r
6158     while (*game != ' ') *p++ = *game++;\r
6159     *p = NULLCHAR;\r
6160     gameInfo.white = StrSave(buf);\r
6161     while (*game == ' ') game++;\r
6162     p = buf;\r
6163     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6164     *p = NULLCHAR;\r
6165     gameInfo.black = StrSave(buf);\r
6166 \r
6167     /* Parse moves */\r
6168     boardIndex = blackPlaysFirst ? 1 : 0;\r
6169     yynewstr(game);\r
6170     for (;;) {\r
6171         yyboardindex = boardIndex;\r
6172         moveType = (ChessMove) yylex();\r
6173         switch (moveType) {\r
6174           case IllegalMove:             /* maybe suicide chess, etc. */\r
6175   if (appData.debugMode) {\r
6176     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6177     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6178     setbuf(debugFP, NULL);\r
6179   }\r
6180           case WhitePromotionChancellor:\r
6181           case BlackPromotionChancellor:\r
6182           case WhitePromotionArchbishop:\r
6183           case BlackPromotionArchbishop:\r
6184           case WhitePromotionQueen:\r
6185           case BlackPromotionQueen:\r
6186           case WhitePromotionRook:\r
6187           case BlackPromotionRook:\r
6188           case WhitePromotionBishop:\r
6189           case BlackPromotionBishop:\r
6190           case WhitePromotionKnight:\r
6191           case BlackPromotionKnight:\r
6192           case WhitePromotionKing:\r
6193           case BlackPromotionKing:\r
6194           case NormalMove:\r
6195           case WhiteCapturesEnPassant:\r
6196           case BlackCapturesEnPassant:\r
6197           case WhiteKingSideCastle:\r
6198           case WhiteQueenSideCastle:\r
6199           case BlackKingSideCastle:\r
6200           case BlackQueenSideCastle:\r
6201           case WhiteKingSideCastleWild:\r
6202           case WhiteQueenSideCastleWild:\r
6203           case BlackKingSideCastleWild:\r
6204           case BlackQueenSideCastleWild:\r
6205           /* PUSH Fabien */\r
6206           case WhiteHSideCastleFR:\r
6207           case WhiteASideCastleFR:\r
6208           case BlackHSideCastleFR:\r
6209           case BlackASideCastleFR:\r
6210           /* POP Fabien */\r
6211             fromX = currentMoveString[0] - AAA;\r
6212             fromY = currentMoveString[1] - ONE;\r
6213             toX = currentMoveString[2] - AAA;\r
6214             toY = currentMoveString[3] - ONE;\r
6215             promoChar = currentMoveString[4];\r
6216             break;\r
6217           case WhiteDrop:\r
6218           case BlackDrop:\r
6219             fromX = moveType == WhiteDrop ?\r
6220               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6221             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6222             fromY = DROP_RANK;\r
6223             toX = currentMoveString[2] - AAA;\r
6224             toY = currentMoveString[3] - ONE;\r
6225             promoChar = NULLCHAR;\r
6226             break;\r
6227           case AmbiguousMove:\r
6228             /* bug? */\r
6229             sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);\r
6230   if (appData.debugMode) {\r
6231     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6232     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6233     setbuf(debugFP, NULL);\r
6234   }\r
6235             DisplayError(buf, 0);\r
6236             return;\r
6237           case ImpossibleMove:\r
6238             /* bug? */\r
6239             sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);\r
6240   if (appData.debugMode) {\r
6241     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6242     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6243     setbuf(debugFP, NULL);\r
6244   }\r
6245             DisplayError(buf, 0);\r
6246             return;\r
6247           case (ChessMove) 0:   /* end of file */\r
6248             if (boardIndex < backwardMostMove) {\r
6249                 /* Oops, gap.  How did that happen? */\r
6250                 DisplayError("Gap in move list", 0);\r
6251                 return;\r
6252             }\r
6253             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6254             if (boardIndex > forwardMostMove) {\r
6255                 forwardMostMove = boardIndex;\r
6256             }\r
6257             return;\r
6258           case ElapsedTime:\r
6259             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6260                 strcat(parseList[boardIndex-1], " ");\r
6261                 strcat(parseList[boardIndex-1], yy_text);\r
6262             }\r
6263             continue;\r
6264           case Comment:\r
6265           case PGNTag:\r
6266           case NAG:\r
6267           default:\r
6268             /* ignore */\r
6269             continue;\r
6270           case WhiteWins:\r
6271           case BlackWins:\r
6272           case GameIsDrawn:\r
6273           case GameUnfinished:\r
6274             if (gameMode == IcsExamining) {\r
6275                 if (boardIndex < backwardMostMove) {\r
6276                     /* Oops, gap.  How did that happen? */\r
6277                     return;\r
6278                 }\r
6279                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6280                 return;\r
6281             }\r
6282             gameInfo.result = moveType;\r
6283             p = strchr(yy_text, '{');\r
6284             if (p == NULL) p = strchr(yy_text, '(');\r
6285             if (p == NULL) {\r
6286                 p = yy_text;\r
6287                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
6288             } else {\r
6289                 q = strchr(p, *p == '{' ? '}' : ')');\r
6290                 if (q != NULL) *q = NULLCHAR;\r
6291                 p++;\r
6292             }\r
6293             gameInfo.resultDetails = StrSave(p);\r
6294             continue;\r
6295         }\r
6296         if (boardIndex >= forwardMostMove &&\r
6297             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
6298             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6299             return;\r
6300         }\r
6301         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
6302                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
6303                                  parseList[boardIndex]);\r
6304         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
6305         /* currentMoveString is set as a side-effect of yylex */\r
6306         strcpy(moveList[boardIndex], currentMoveString);\r
6307         strcat(moveList[boardIndex], "\n");\r
6308         boardIndex++;\r
6309         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
6310         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
6311                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
6312           case MT_NONE:\r
6313           case MT_STALEMATE:\r
6314           default:\r
6315             break;\r
6316           case MT_CHECK:\r
6317             if(gameInfo.variant != VariantShogi)\r
6318                 strcat(parseList[boardIndex - 1], "+");\r
6319             break;\r
6320           case MT_CHECKMATE:\r
6321             strcat(parseList[boardIndex - 1], "#");\r
6322             break;\r
6323         }\r
6324     }\r
6325 }\r
6326 \r
6327 \r
6328 /* Apply a move to the given board  */\r
6329 void\r
6330 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
6331      int fromX, fromY, toX, toY;\r
6332      int promoChar;\r
6333      Board board;\r
6334 {\r
6335   ChessSquare captured = board[toY][toX], piece, king; int p;\r
6336 \r
6337     /* [HGM] compute & store e.p. status and castling rights for new position */\r
6338     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
6339     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
6340     { int i, j;\r
6341 \r
6342       epStatus[p] = EP_NONE;\r
6343 \r
6344       if( board[toY][toX] != EmptySquare ) \r
6345            epStatus[p] = EP_CAPTURE;  \r
6346 \r
6347       if( board[fromY][fromX] == WhitePawn ) {\r
6348            epStatus[p] = EP_PAWN_MOVE; \r
6349            if( toY-fromY==2 &&\r
6350                (toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn ||\r
6351                 toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn ) )\r
6352               epStatus[p] = toX;\r
6353       } else \r
6354       if( board[fromY][fromX] == BlackPawn ) {\r
6355            epStatus[p] = EP_PAWN_MOVE; \r
6356            if( toY-fromY== -2 &&\r
6357                (toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn ||\r
6358                 toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn ) )\r
6359               epStatus[p] = toX;\r
6360        }\r
6361 \r
6362        for(i=0; i<nrCastlingRights; i++) {\r
6363            castlingRights[p][i] = castlingRights[p-1][i];\r
6364            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
6365               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
6366              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
6367        }\r
6368 \r
6369     }\r
6370 \r
6371   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
6372   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
6373        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
6374          \r
6375   if (fromX == toX && fromY == toY) return;\r
6376 \r
6377   if (fromY == DROP_RANK) {\r
6378         /* must be first */\r
6379         piece = board[toY][toX] = (ChessSquare) fromX;\r
6380   } else {\r
6381      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
6382      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
6383      if(gameInfo.variant == VariantKnightmate)\r
6384          king += (int) WhiteUnicorn - (int) WhiteKing;\r
6385 \r
6386     /* Code added by Tord: */\r
6387     /* FRC castling assumed when king captures friendly rook. */\r
6388     if (board[fromY][fromX] == WhiteKing &&\r
6389              board[toY][toX] == WhiteRook) {\r
6390       board[fromY][fromX] = EmptySquare;\r
6391       board[toY][toX] = EmptySquare;\r
6392       if(toX > fromX) {\r
6393         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
6394       } else {\r
6395         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
6396       }\r
6397     } else if (board[fromY][fromX] == BlackKing &&\r
6398                board[toY][toX] == BlackRook) {\r
6399       board[fromY][fromX] = EmptySquare;\r
6400       board[toY][toX] = EmptySquare;\r
6401       if(toX > fromX) {\r
6402         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
6403       } else {\r
6404         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
6405       }\r
6406     /* End of code added by Tord */\r
6407 \r
6408     } else if (board[fromY][fromX] == king\r
6409         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
6410         && toY == fromY && toX > fromX+1) {\r
6411         board[fromY][fromX] = EmptySquare;\r
6412         board[toY][toX] = king;\r
6413         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
6414         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
6415     } else if (board[fromY][fromX] == king\r
6416         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
6417                && toY == fromY && toX < fromX-1) {\r
6418         board[fromY][fromX] = EmptySquare;\r
6419         board[toY][toX] = king;\r
6420         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
6421         board[fromY][BOARD_LEFT] = EmptySquare;\r
6422     } else if (board[fromY][fromX] == WhitePawn\r
6423                && toY == BOARD_HEIGHT-1\r
6424                && gameInfo.variant != VariantXiangqi\r
6425                ) {\r
6426         /* white pawn promotion */\r
6427         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
6428         if (board[toY][toX] == EmptySquare) {\r
6429             board[toY][toX] = WhiteQueen;\r
6430         }\r
6431         if(gameInfo.variant==VariantBughouse ||\r
6432            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
6433             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
6434         board[fromY][fromX] = EmptySquare;\r
6435     } else if ((fromY == BOARD_HEIGHT-4)\r
6436                && (toX != fromX)\r
6437                && gameInfo.variant != VariantXiangqi\r
6438                && (board[fromY][fromX] == WhitePawn)\r
6439                && (board[toY][toX] == EmptySquare)) {\r
6440         board[fromY][fromX] = EmptySquare;\r
6441         board[toY][toX] = WhitePawn;\r
6442         captured = board[toY - 1][toX];\r
6443         board[toY - 1][toX] = EmptySquare;\r
6444     } else if (board[fromY][fromX] == king\r
6445         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
6446                && toY == fromY && toX > fromX+1) {\r
6447         board[fromY][fromX] = EmptySquare;\r
6448         board[toY][toX] = king;\r
6449         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
6450         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
6451     } else if (board[fromY][fromX] == king\r
6452         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
6453                && toY == fromY && toX < fromX-1) {\r
6454         board[fromY][fromX] = EmptySquare;\r
6455         board[toY][toX] = king;\r
6456         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
6457         board[fromY][BOARD_LEFT] = EmptySquare;\r
6458     } else if (fromY == 7 && fromX == 3\r
6459                && board[fromY][fromX] == BlackKing\r
6460                && toY == 7 && toX == 5) {\r
6461         board[fromY][fromX] = EmptySquare;\r
6462         board[toY][toX] = BlackKing;\r
6463         board[fromY][7] = EmptySquare;\r
6464         board[toY][4] = BlackRook;\r
6465     } else if (fromY == 7 && fromX == 3\r
6466                && board[fromY][fromX] == BlackKing\r
6467                && toY == 7 && toX == 1) {\r
6468         board[fromY][fromX] = EmptySquare;\r
6469         board[toY][toX] = BlackKing;\r
6470         board[fromY][0] = EmptySquare;\r
6471         board[toY][2] = BlackRook;\r
6472     } else if (board[fromY][fromX] == BlackPawn\r
6473                && toY == 0\r
6474                && gameInfo.variant != VariantXiangqi\r
6475                ) {\r
6476         /* black pawn promotion */\r
6477         board[0][toX] = CharToPiece(ToLower(promoChar));\r
6478         if (board[0][toX] == EmptySquare) {\r
6479             board[0][toX] = BlackQueen;\r
6480         }\r
6481         if(gameInfo.variant==VariantBughouse ||\r
6482            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
6483             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
6484         board[fromY][fromX] = EmptySquare;\r
6485     } else if ((fromY == 3)\r
6486                && (toX != fromX)\r
6487                && gameInfo.variant != VariantXiangqi\r
6488                && (board[fromY][fromX] == BlackPawn)\r
6489                && (board[toY][toX] == EmptySquare)) {\r
6490         board[fromY][fromX] = EmptySquare;\r
6491         board[toY][toX] = BlackPawn;\r
6492         captured = board[toY + 1][toX];\r
6493         board[toY + 1][toX] = EmptySquare;\r
6494     } else {\r
6495         board[toY][toX] = board[fromY][fromX];\r
6496         board[fromY][fromX] = EmptySquare;\r
6497     }\r
6498 \r
6499     /* [HGM] now we promote for Shogi, if needed */\r
6500     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
6501         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
6502   }\r
6503 \r
6504     if (gameInfo.holdingsWidth != 0) {\r
6505 \r
6506       /* !!A lot more code needs to be written to support holdings  */\r
6507       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
6508       /* penultimate board files, so they are automaticlly stored   */\r
6509       /* in the game history.                                       */\r
6510       if (fromY == DROP_RANK) {\r
6511         /* Delete from holdings, by decreasing count */\r
6512         /* and erasing image if necessary            */\r
6513         p = (int) fromX;\r
6514         if(p < (int) BlackPawn) { /* white drop */\r
6515              p -= (int)WhitePawn;\r
6516              if(p >= gameInfo.holdingsSize) p = 0;\r
6517              if(--board[p][BOARD_WIDTH-2] == 0)\r
6518                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
6519         } else {                  /* black drop */\r
6520              p -= (int)BlackPawn;\r
6521              if(p >= gameInfo.holdingsSize) p = 0;\r
6522              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
6523                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
6524         }\r
6525       }\r
6526       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
6527           && gameInfo.variant != VariantBughouse        ) {\r
6528         /* Add to holdings, if holdings exist */\r
6529         p = (int) captured;\r
6530         if (p >= (int) BlackPawn) {\r
6531           p -= (int)BlackPawn;\r
6532           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
6533                   /* in Shogi restore piece to its original  first */\r
6534                   captured = (ChessSquare) (DEMOTED captured);\r
6535                   p = DEMOTED p;\r
6536           }\r
6537           p = PieceToNumber((ChessSquare)p);\r
6538           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
6539           board[p][BOARD_WIDTH-2]++;\r
6540           board[p][BOARD_WIDTH-1] =\r
6541                                    BLACK_TO_WHITE captured;\r
6542         } else {\r
6543           p -= (int)WhitePawn;\r
6544           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
6545                   captured = (ChessSquare) (DEMOTED captured);\r
6546                   p = DEMOTED p;\r
6547           }\r
6548           p = PieceToNumber((ChessSquare)p);\r
6549           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
6550           board[BOARD_HEIGHT-1-p][1]++;\r
6551           board[BOARD_HEIGHT-1-p][0] =\r
6552                                   WHITE_TO_BLACK captured;\r
6553         }\r
6554       }\r
6555 \r
6556     } else if (gameInfo.variant == VariantAtomic) {\r
6557       if (captured != EmptySquare) {\r
6558         int y, x;\r
6559         for (y = toY-1; y <= toY+1; y++) {\r
6560           for (x = toX-1; x <= toX+1; x++) {\r
6561             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
6562                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
6563               board[y][x] = EmptySquare;\r
6564             }\r
6565           }\r
6566         }\r
6567         board[toY][toX] = EmptySquare;\r
6568       }\r
6569     }\r
6570     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
6571         /* [HGM] Shogi promotions */\r
6572         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
6573     }\r
6574 \r
6575 }\r
6576 \r
6577 /* Updates forwardMostMove */\r
6578 void\r
6579 MakeMove(fromX, fromY, toX, toY, promoChar)\r
6580      int fromX, fromY, toX, toY;\r
6581      int promoChar;\r
6582 {\r
6583     forwardMostMove++;\r
6584 \r
6585     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting */\r
6586         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
6587         piece = boards[forwardMostMove-1][fromY][fromX];\r
6588         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
6589         if(gameInfo.variant == VariantKnightmate)\r
6590             king += (int) WhiteUnicorn - (int) WhiteKing;\r
6591         if(forwardMostMove == 1) {\r
6592             if(blackPlaysFirst) \r
6593                 fprintf(serverMoves, "%s;", second.tidy);\r
6594             fprintf(serverMoves, "%s;", first.tidy);\r
6595             if(!blackPlaysFirst) \r
6596                 fprintf(serverMoves, "%s;", second.tidy);\r
6597         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
6598         lastLoadFlag = loadFlag;\r
6599         // print base move\r
6600         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
6601         // print castling suffix\r
6602         if( toY == fromY && piece == king ) {\r
6603             if(toX-fromX > 1)\r
6604                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
6605             if(fromX-toX >1)\r
6606                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
6607         }\r
6608         // e.p. suffix\r
6609         if( (boards[forwardMostMove-1][fromY][fromX] == WhitePawn ||\r
6610              boards[forwardMostMove-1][fromY][fromX] == BlackPawn   ) &&\r
6611              boards[forwardMostMove-1][toY][toX] == EmptySquare\r
6612              && fromX != toX )\r
6613                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
6614         // promotion suffix\r
6615         if(promoChar != NULLCHAR)\r
6616                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
6617         if(!loadFlag) {\r
6618             fprintf(serverMoves, "/%d/%d",\r
6619                pvInfoList[forwardMostMove-1].depth, pvInfoList[forwardMostMove-1].score);\r
6620             if(forwardMostMove & 1) timeLeft = whiteTimeRemaining/1000;\r
6621             else                    timeLeft = blackTimeRemaining/1000;\r
6622             fprintf(serverMoves, "/%d", timeLeft);\r
6623         }\r
6624         fflush(serverMoves);\r
6625     }\r
6626 \r
6627     if (forwardMostMove >= MAX_MOVES) {\r
6628       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
6629                         0, 1);\r
6630       return;\r
6631     }\r
6632     SwitchClocks();\r
6633     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
6634     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
6635     if (commentList[forwardMostMove] != NULL) {\r
6636         free(commentList[forwardMostMove]);\r
6637         commentList[forwardMostMove] = NULL;\r
6638     }\r
6639     CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);\r
6640     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);\r
6641     gameInfo.result = GameUnfinished;\r
6642     if (gameInfo.resultDetails != NULL) {\r
6643         free(gameInfo.resultDetails);\r
6644         gameInfo.resultDetails = NULL;\r
6645     }\r
6646     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
6647                               moveList[forwardMostMove - 1]);\r
6648     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
6649                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
6650                              fromY, fromX, toY, toX, promoChar,\r
6651                              parseList[forwardMostMove - 1]);\r
6652     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
6653                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
6654                             castlingRights[forwardMostMove]) ) {\r
6655       case MT_NONE:\r
6656       case MT_STALEMATE:\r
6657       default:\r
6658         break;\r
6659       case MT_CHECK:\r
6660         if(gameInfo.variant != VariantShogi)\r
6661             strcat(parseList[forwardMostMove - 1], "+");\r
6662         break;\r
6663       case MT_CHECKMATE:\r
6664         strcat(parseList[forwardMostMove - 1], "#");\r
6665         break;\r
6666     }\r
6667     if (appData.debugMode) {\r
6668         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
6669     }\r
6670 \r
6671 }\r
6672 \r
6673 /* Updates currentMove if not pausing */\r
6674 void\r
6675 ShowMove(fromX, fromY, toX, toY)\r
6676 {\r
6677     int instant = (gameMode == PlayFromGameFile) ?\r
6678         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
6679     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
6680         if (!instant) {\r
6681             if (forwardMostMove == currentMove + 1) {\r
6682                 AnimateMove(boards[forwardMostMove - 1],\r
6683                             fromX, fromY, toX, toY);\r
6684             }\r
6685             if (appData.highlightLastMove) {\r
6686                 SetHighlights(fromX, fromY, toX, toY);\r
6687             }\r
6688         }\r
6689         currentMove = forwardMostMove;\r
6690     }\r
6691 \r
6692     if (instant) return;\r
6693 \r
6694     DisplayMove(currentMove - 1);\r
6695     DrawPosition(FALSE, boards[currentMove]);\r
6696     DisplayBothClocks();\r
6697     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
6698 }\r
6699 \r
6700 \r
6701 void\r
6702 InitChessProgram(cps, setup)\r
6703      ChessProgramState *cps;\r
6704      int setup; /* [HGM] needed to setup FRC opening position */\r
6705 {\r
6706     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
6707     if (appData.noChessProgram) return;\r
6708     hintRequested = FALSE;\r
6709     bookRequested = FALSE;\r
6710     SendToProgram(cps->initString, cps);\r
6711     if (gameInfo.variant != VariantNormal &&\r
6712         gameInfo.variant != VariantLoadable\r
6713         /* [HGM] also send variant if board size non-standard */\r
6714         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8\r
6715                                             ) {\r
6716       char *v = VariantName(gameInfo.variant);\r
6717       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
6718         /* [HGM] in protocol 1 we have to assume all variants valid */\r
6719         sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);\r
6720         DisplayFatalError(buf, 0, 1);\r
6721         return;\r
6722       }\r
6723 \r
6724       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
6725       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
6726       if( gameInfo.variant == VariantXiangqi )\r
6727            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
6728       if( gameInfo.variant == VariantShogi )\r
6729            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
6730       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
6731            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
6732       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
6733            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
6734       if( gameInfo.variant == VariantCourier )\r
6735            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
6736 \r
6737       if(overruled) {\r
6738            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
6739                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
6740            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
6741            if(StrStr(cps->variants, b) == NULL) { \r
6742                // specific sized variant not known, check if general sizing allowed\r
6743                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
6744                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
6745                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
6746                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
6747                        DisplayFatalError(buf, 0, 1);\r
6748                        return;\r
6749                    }\r
6750                    /* [HGM] here we really should compare with the maximum supported board size */\r
6751                }\r
6752            }\r
6753       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
6754       sprintf(buf, "variant %s\n", b);\r
6755       SendToProgram(buf, cps);\r
6756       /* [HGM] send opening position in FRC to first engine */\r
6757       if(setup /* cps == &first && gameInfo.variant == VariantFischeRandom */) {\r
6758           SendToProgram("force\n", cps);\r
6759           SendBoard(cps, 0);\r
6760           /* engine is now in force mode! Set flag to wake it up after first move. */\r
6761           setboardSpoiledMachineBlack = 1;\r
6762       }\r
6763     }\r
6764     if (cps->sendICS) {\r
6765       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
6766       SendToProgram(buf, cps);\r
6767     }\r
6768     cps->maybeThinking = FALSE;\r
6769     cps->offeredDraw = 0;\r
6770     if (!appData.icsActive) {\r
6771         SendTimeControl(cps, movesPerSession, timeControl,\r
6772                         timeIncrement, appData.searchDepth,\r
6773                         searchTime);\r
6774     }\r
6775     if (appData.showThinking) {\r
6776         SendToProgram("post\n", cps);\r
6777     }\r
6778     SendToProgram("hard\n", cps);\r
6779     if (!appData.ponderNextMove) {\r
6780         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
6781            it without being sure what state we are in first.  "hard"\r
6782            is not a toggle, so that one is OK.\r
6783          */\r
6784         SendToProgram("easy\n", cps);\r
6785     }\r
6786     if (cps->usePing) {\r
6787       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
6788       SendToProgram(buf, cps);\r
6789     }\r
6790     cps->initDone = TRUE;\r
6791 }   \r
6792 \r
6793 \r
6794 void\r
6795 StartChessProgram(cps)\r
6796      ChessProgramState *cps;\r
6797 {\r
6798     char buf[MSG_SIZ];\r
6799     int err;\r
6800 \r
6801     if (appData.noChessProgram) return;\r
6802     cps->initDone = FALSE;\r
6803 \r
6804     if (strcmp(cps->host, "localhost") == 0) {\r
6805         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
6806     } else if (*appData.remoteShell == NULLCHAR) {\r
6807         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
6808     } else {\r
6809         if (*appData.remoteUser == NULLCHAR) {\r
6810             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
6811                     cps->program);\r
6812         } else {\r
6813             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
6814                     cps->host, appData.remoteUser, cps->program);\r
6815         }\r
6816         err = StartChildProcess(buf, "", &cps->pr);\r
6817     }\r
6818     \r
6819     if (err != 0) {\r
6820         sprintf(buf, "Startup failure on '%s'", cps->program);\r
6821         DisplayFatalError(buf, err, 1);\r
6822         cps->pr = NoProc;\r
6823         cps->isr = NULL;\r
6824         return;\r
6825     }\r
6826     \r
6827     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
6828     if (cps->protocolVersion > 1) {\r
6829       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
6830       SendToProgram(buf, cps);\r
6831     } else {\r
6832       SendToProgram("xboard\n", cps);\r
6833     }\r
6834 }\r
6835 \r
6836 \r
6837 void\r
6838 TwoMachinesEventIfReady P((void))\r
6839 {\r
6840   if (first.lastPing != first.lastPong) {\r
6841     DisplayMessage("", "Waiting for first chess program");\r
6842     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
6843     return;\r
6844   }\r
6845   if (second.lastPing != second.lastPong) {\r
6846     DisplayMessage("", "Waiting for second chess program");\r
6847     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
6848     return;\r
6849   }\r
6850   ThawUI();\r
6851   TwoMachinesEvent();\r
6852 }\r
6853 \r
6854 void\r
6855 NextMatchGame P((void))\r
6856 {\r
6857     Reset(FALSE, TRUE);\r
6858     if (*appData.loadGameFile != NULLCHAR) {\r
6859         LoadGameFromFile(appData.loadGameFile,\r
6860                          appData.loadGameIndex,\r
6861                          appData.loadGameFile, FALSE);\r
6862     } else if (*appData.loadPositionFile != NULLCHAR) {\r
6863         LoadPositionFromFile(appData.loadPositionFile,\r
6864                              appData.loadPositionIndex,\r
6865                              appData.loadPositionFile);\r
6866     }\r
6867     TwoMachinesEventIfReady();\r
6868 }\r
6869 \r
6870 void UserAdjudicationEvent( int result )\r
6871 {\r
6872     ChessMove gameResult = GameIsDrawn;\r
6873 \r
6874     if( result > 0 ) {\r
6875         gameResult = WhiteWins;\r
6876     }\r
6877     else if( result < 0 ) {\r
6878         gameResult = BlackWins;\r
6879     }\r
6880 \r
6881     if( gameMode == TwoMachinesPlay ) {\r
6882         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
6883     }\r
6884 }\r
6885 \r
6886 \r
6887 void\r
6888 GameEnds(result, resultDetails, whosays)\r
6889      ChessMove result;\r
6890      char *resultDetails;\r
6891      int whosays;\r
6892 {\r
6893     GameMode nextGameMode;\r
6894     int isIcsGame;\r
6895     char buf[MSG_SIZ];\r
6896 \r
6897     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
6898     endingGame = 1;\r
6899 \r
6900     if (appData.debugMode) {\r
6901       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
6902               result, resultDetails ? resultDetails : "(null)", whosays);\r
6903     }\r
6904 \r
6905     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
6906         /* If we are playing on ICS, the server decides when the\r
6907            game is over, but the engine can offer to draw, claim \r
6908            a draw, or resign. \r
6909          */\r
6910 #if ZIPPY\r
6911         if (appData.zippyPlay && first.initDone) {\r
6912             if (result == GameIsDrawn) {\r
6913                 /* In case draw still needs to be claimed */\r
6914                 SendToICS(ics_prefix);\r
6915                 SendToICS("draw\n");\r
6916             } else if (StrCaseStr(resultDetails, "resign")) {\r
6917                 SendToICS(ics_prefix);\r
6918                 SendToICS("resign\n");\r
6919             }\r
6920         }\r
6921 #endif\r
6922         endingGame = 0; /* [HGM] crash */\r
6923         return;\r
6924     }\r
6925 \r
6926     /* If we're loading the game from a file, stop */\r
6927     if (whosays == GE_FILE) {\r
6928       (void) StopLoadGameTimer();\r
6929       gameFileFP = NULL;\r
6930     }\r
6931 \r
6932     /* Cancel draw offers */\r
6933     first.offeredDraw = second.offeredDraw = 0;\r
6934 \r
6935     /* If this is an ICS game, only ICS can really say it's done;\r
6936        if not, anyone can. */\r
6937     isIcsGame = (gameMode == IcsPlayingWhite || \r
6938                  gameMode == IcsPlayingBlack || \r
6939                  gameMode == IcsObserving    || \r
6940                  gameMode == IcsExamining);\r
6941 \r
6942     if (!isIcsGame || whosays == GE_ICS) {\r
6943         /* OK -- not an ICS game, or ICS said it was done */\r
6944         StopClocks();\r
6945     if (appData.debugMode) {\r
6946       fprintf(debugFP, "GameEnds(%d, %s, %d) clock stopped\n",\r
6947               result, resultDetails ? resultDetails : "(null)", whosays);\r
6948     }\r
6949         if (!isIcsGame && !appData.noChessProgram) \r
6950           SetUserThinkingEnables();\r
6951     \r
6952         /* [HGM] if a machine claims the game end we verify this claim */\r
6953         if( appData.testLegality && gameMode == TwoMachinesPlay &&\r
6954             appData.testClaims && whosays >= GE_ENGINE1 ) {\r
6955                 char claimer;\r
6956 \r
6957     if (appData.debugMode) {\r
6958       fprintf(debugFP, "GameEnds(%d, %s, %d) test claims\n",\r
6959               result, resultDetails ? resultDetails : "(null)", whosays);\r
6960     }\r
6961                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
6962                                             first.twoMachinesColor[0] :\r
6963                                             second.twoMachinesColor[0] ;\r
6964                 if( gameInfo.holdingsWidth == 0 &&\r
6965                     (result == WhiteWins && claimer == 'w' ||\r
6966                      result == BlackWins && claimer == 'b'   ) ) {\r
6967                       /* Xboard immediately adjudicates all mates, so win claims must be false */\r
6968                       sprintf(buf, "False win claim: '%s'", resultDetails);\r
6969                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
6970                       resultDetails = buf;\r
6971                 } else\r
6972                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
6973                     && (forwardMostMove <= backwardMostMove ||\r
6974                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
6975                         (claimer=='b')==(forwardMostMove&1))\r
6976                                                                                   ) {\r
6977                       /* Draw that was not flagged by Xboard is false */\r
6978                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
6979                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
6980                       resultDetails = buf;\r
6981                 }\r
6982                 /* (Claiming a loss is accepted no questions asked!) */\r
6983         }\r
6984 \r
6985         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
6986             if(result==WhiteWins) c = '+';\r
6987             if(result==BlackWins) c = '-';\r
6988             if(resultDetails != NULL)\r
6989                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
6990         }\r
6991     if (appData.debugMode) {\r
6992       fprintf(debugFP, "GameEnds(%d, %s, %d) after test\n",\r
6993               result, resultDetails ? resultDetails : "(null)", whosays);\r
6994     }\r
6995         if (resultDetails != NULL) {\r
6996             gameInfo.result = result;\r
6997             gameInfo.resultDetails = StrSave(resultDetails);\r
6998 \r
6999             /* display last move only if game was not loaded from file */\r
7000             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7001                 DisplayMove(currentMove - 1);\r
7002     \r
7003             if (forwardMostMove != 0) {\r
7004                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7005                     if (*appData.saveGameFile != NULLCHAR) {\r
7006                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7007                     } else if (appData.autoSaveGames) {\r
7008                         AutoSaveGame();\r
7009                     }\r
7010                     if (*appData.savePositionFile != NULLCHAR) {\r
7011                         SavePositionToFile(appData.savePositionFile);\r
7012                     }\r
7013                 }\r
7014             }\r
7015 \r
7016             /* Tell program how game ended in case it is learning */\r
7017             /* [HGM] Moved this to after saving the PGN, just in case */\r
7018             /* engine died and we got here through time loss. In that */\r
7019             /* case we will get a fatal error writing the pipe, which */\r
7020             /* would otherwise lose us the PGN.                       */\r
7021             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7022             /* output during GameEnds should never be fatal anymore   */\r
7023             if (gameMode == MachinePlaysWhite ||\r
7024                 gameMode == MachinePlaysBlack ||\r
7025                 gameMode == TwoMachinesPlay ||\r
7026                 gameMode == IcsPlayingWhite ||\r
7027                 gameMode == IcsPlayingBlack ||\r
7028                 gameMode == BeginningOfGame) {\r
7029                 char buf[MSG_SIZ];\r
7030                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7031                         resultDetails);\r
7032                 if (first.pr != NoProc) {\r
7033                     SendToProgram(buf, &first);\r
7034                 }\r
7035                 if (second.pr != NoProc &&\r
7036                     gameMode == TwoMachinesPlay) {\r
7037                     SendToProgram(buf, &second);\r
7038                 }\r
7039             }\r
7040         }\r
7041 \r
7042         if (appData.icsActive) {\r
7043             if (appData.quietPlay &&\r
7044                 (gameMode == IcsPlayingWhite ||\r
7045                  gameMode == IcsPlayingBlack)) {\r
7046                 SendToICS(ics_prefix);\r
7047                 SendToICS("set shout 1\n");\r
7048             }\r
7049             nextGameMode = IcsIdle;\r
7050             ics_user_moved = FALSE;\r
7051             /* clean up premove.  It's ugly when the game has ended and the\r
7052              * premove highlights are still on the board.\r
7053              */\r
7054             if (gotPremove) {\r
7055               gotPremove = FALSE;\r
7056               ClearPremoveHighlights();\r
7057               DrawPosition(FALSE, boards[currentMove]);\r
7058             }\r
7059             if (whosays == GE_ICS) {\r
7060                 switch (result) {\r
7061                 case WhiteWins:\r
7062                     if (gameMode == IcsPlayingWhite)\r
7063                         PlayIcsWinSound();\r
7064                     else if(gameMode == IcsPlayingBlack)\r
7065                         PlayIcsLossSound();\r
7066                     break;\r
7067                 case BlackWins:\r
7068                     if (gameMode == IcsPlayingBlack)\r
7069                         PlayIcsWinSound();\r
7070                     else if(gameMode == IcsPlayingWhite)\r
7071                         PlayIcsLossSound();\r
7072                     break;\r
7073                 case GameIsDrawn:\r
7074                     PlayIcsDrawSound();\r
7075                     break;\r
7076                 default:\r
7077                     PlayIcsUnfinishedSound();\r
7078                 }\r
7079             }\r
7080         } else if (gameMode == EditGame ||\r
7081                    gameMode == PlayFromGameFile || \r
7082                    gameMode == AnalyzeMode || \r
7083                    gameMode == AnalyzeFile) {\r
7084             nextGameMode = gameMode;\r
7085         } else {\r
7086             nextGameMode = EndOfGame;\r
7087         }\r
7088         pausing = FALSE;\r
7089         ModeHighlight();\r
7090     } else {\r
7091         nextGameMode = gameMode;\r
7092     }\r
7093 \r
7094     if (appData.noChessProgram) {\r
7095         gameMode = nextGameMode;\r
7096         ModeHighlight();\r
7097         endingGame = 0; /* [HGM] crash */\r
7098         return;\r
7099     }\r
7100 \r
7101     if (first.reuse) {\r
7102         /* Put first chess program into idle state */\r
7103         if (first.pr != NoProc &&\r
7104             (gameMode == MachinePlaysWhite ||\r
7105              gameMode == MachinePlaysBlack ||\r
7106              gameMode == TwoMachinesPlay ||\r
7107              gameMode == IcsPlayingWhite ||\r
7108              gameMode == IcsPlayingBlack ||\r
7109              gameMode == BeginningOfGame)) {\r
7110             SendToProgram("force\n", &first);\r
7111             if (first.usePing) {\r
7112               char buf[MSG_SIZ];\r
7113               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7114               SendToProgram(buf, &first);\r
7115             }\r
7116         }\r
7117     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7118         /* Kill off first chess program */\r
7119         if (first.isr != NULL)\r
7120           RemoveInputSource(first.isr);\r
7121         first.isr = NULL;\r
7122     \r
7123         if (first.pr != NoProc) {\r
7124             ExitAnalyzeMode();\r
7125             DoSleep( appData.delayBeforeQuit );\r
7126             SendToProgram("quit\n", &first);\r
7127             DoSleep( appData.delayAfterQuit );\r
7128             DestroyChildProcess(first.pr, first.useSigterm);\r
7129         }\r
7130         first.pr = NoProc;\r
7131     }\r
7132     if (second.reuse) {\r
7133         /* Put second chess program into idle state */\r
7134         if (second.pr != NoProc &&\r
7135             gameMode == TwoMachinesPlay) {\r
7136             SendToProgram("force\n", &second);\r
7137             if (second.usePing) {\r
7138               char buf[MSG_SIZ];\r
7139               sprintf(buf, "ping %d\n", ++second.lastPing);\r
7140               SendToProgram(buf, &second);\r
7141             }\r
7142         }\r
7143     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7144         /* Kill off second chess program */\r
7145         if (second.isr != NULL)\r
7146           RemoveInputSource(second.isr);\r
7147         second.isr = NULL;\r
7148     \r
7149         if (second.pr != NoProc) {\r
7150             DoSleep( appData.delayBeforeQuit );\r
7151             SendToProgram("quit\n", &second);\r
7152             DoSleep( appData.delayAfterQuit );\r
7153             DestroyChildProcess(second.pr, second.useSigterm);\r
7154         }\r
7155         second.pr = NoProc;\r
7156     }\r
7157 \r
7158     if (matchMode && gameMode == TwoMachinesPlay) {\r
7159         switch (result) {\r
7160         case WhiteWins:\r
7161           if (first.twoMachinesColor[0] == 'w') {\r
7162             first.matchWins++;\r
7163           } else {\r
7164             second.matchWins++;\r
7165           }\r
7166           break;\r
7167         case BlackWins:\r
7168           if (first.twoMachinesColor[0] == 'b') {\r
7169             first.matchWins++;\r
7170           } else {\r
7171             second.matchWins++;\r
7172           }\r
7173           break;\r
7174         default:\r
7175           break;\r
7176         }\r
7177         if (matchGame < appData.matchGames) {\r
7178             char *tmp;\r
7179             tmp = first.twoMachinesColor;\r
7180             first.twoMachinesColor = second.twoMachinesColor;\r
7181             second.twoMachinesColor = tmp;\r
7182             gameMode = nextGameMode;\r
7183             matchGame++;\r
7184             if(appData.matchPause>10000 || appData.matchPause<10)\r
7185                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
7186             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
7187             endingGame = 0; /* [HGM] crash */\r
7188             return;\r
7189         } else {\r
7190             char buf[MSG_SIZ];\r
7191             gameMode = nextGameMode;\r
7192             sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",\r
7193                     first.tidy, second.tidy,\r
7194                     first.matchWins, second.matchWins,\r
7195                     appData.matchGames - (first.matchWins + second.matchWins));\r
7196             DisplayFatalError(buf, 0, 0);\r
7197         }\r
7198     }\r
7199     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
7200         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
7201       ExitAnalyzeMode();\r
7202     gameMode = nextGameMode;\r
7203     ModeHighlight();\r
7204     endingGame = 0;  /* [HGM] crash */\r
7205 }\r
7206 \r
7207 /* Assumes program was just initialized (initString sent).\r
7208    Leaves program in force mode. */\r
7209 void\r
7210 FeedMovesToProgram(cps, upto) \r
7211      ChessProgramState *cps;\r
7212      int upto;\r
7213 {\r
7214     int i;\r
7215     \r
7216     if (appData.debugMode)\r
7217       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
7218               startedFromSetupPosition ? "position and " : "",\r
7219               backwardMostMove, upto, cps->which);\r
7220     SendToProgram("force\n", cps);\r
7221     if (startedFromSetupPosition) {\r
7222         SendBoard(cps, backwardMostMove);\r
7223     if (appData.debugMode) {\r
7224         fprintf(debugFP, "feedMoves\n");\r
7225     }\r
7226     }\r
7227     for (i = backwardMostMove; i < upto; i++) {\r
7228         SendMoveToProgram(i, cps);\r
7229     }\r
7230 }\r
7231 \r
7232 \r
7233 void\r
7234 ResurrectChessProgram()\r
7235 {\r
7236      /* The chess program may have exited.\r
7237         If so, restart it and feed it all the moves made so far. */\r
7238 \r
7239     if (appData.noChessProgram || first.pr != NoProc) return;\r
7240     \r
7241     StartChessProgram(&first);\r
7242     if (appData.debugMode) {\r
7243         fprintf(debugFP, "From ResurrectChessProgram\n");\r
7244     }\r
7245     InitChessProgram(&first, FALSE);\r
7246     FeedMovesToProgram(&first, currentMove);\r
7247 \r
7248     if (!first.sendTime) {\r
7249         /* can't tell gnuchess what its clock should read,\r
7250            so we bow to its notion. */\r
7251         ResetClocks();\r
7252         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
7253         timeRemaining[1][currentMove] = blackTimeRemaining;\r
7254     }\r
7255 \r
7256     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
7257         first.analysisSupport) {\r
7258       SendToProgram("analyze\n", &first);\r
7259       first.analyzing = TRUE;\r
7260     }\r
7261 }\r
7262 \r
7263 /*\r
7264  * Button procedures\r
7265  */\r
7266 void\r
7267 Reset(redraw, init)\r
7268      int redraw, init;\r
7269 {\r
7270     int i;\r
7271 \r
7272     if (appData.debugMode) {\r
7273         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
7274                 redraw, init, gameMode);\r
7275     }\r
7276     pausing = pauseExamInvalid = FALSE;\r
7277     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
7278     firstMove = TRUE;\r
7279     whiteFlag = blackFlag = FALSE;\r
7280     userOfferedDraw = FALSE;\r
7281     hintRequested = bookRequested = FALSE;\r
7282     first.maybeThinking = FALSE;\r
7283     second.maybeThinking = FALSE;\r
7284     thinkOutput[0] = NULLCHAR;\r
7285     lastHint[0] = NULLCHAR;\r
7286     ClearGameInfo(&gameInfo);\r
7287     gameInfo.variant = StringToVariant(appData.variant);\r
7288     ics_user_moved = ics_clock_paused = FALSE;\r
7289     ics_getting_history = H_FALSE;\r
7290     ics_gamenum = -1;\r
7291     white_holding[0] = black_holding[0] = NULLCHAR;\r
7292     ClearProgramStats();\r
7293     \r
7294     ResetFrontEnd();\r
7295     ClearHighlights();\r
7296     flipView = appData.flipView;\r
7297     ClearPremoveHighlights();\r
7298     gotPremove = FALSE;\r
7299     alarmSounded = FALSE;\r
7300 \r
7301     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
7302     if(appData.serverMovesName != NULL) {\r
7303         /* [HGM] prepare to make moves file for broadcasting */\r
7304         clock_t t = clock();\r
7305         if(serverMoves != NULL) fclose(serverMoves);\r
7306         serverMoves = fopen(appData.serverMovesName, "r");\r
7307         if(serverMoves != NULL) {\r
7308             fclose(serverMoves);\r
7309             /* delay 15 sec before overwriting, so all clients can see end */\r
7310             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
7311         }\r
7312         serverMoves = fopen(appData.serverMovesName, "w");\r
7313     }\r
7314 \r
7315     ExitAnalyzeMode();\r
7316     gameMode = BeginningOfGame;\r
7317     ModeHighlight();\r
7318     InitPosition(redraw);\r
7319     for (i = 0; i < MAX_MOVES; i++) {\r
7320         if (commentList[i] != NULL) {\r
7321             free(commentList[i]);\r
7322             commentList[i] = NULL;\r
7323         }\r
7324     }\r
7325     ResetClocks();\r
7326     timeRemaining[0][0] = whiteTimeRemaining;\r
7327     timeRemaining[1][0] = blackTimeRemaining;\r
7328     if (first.pr == NULL) {\r
7329         StartChessProgram(&first);\r
7330     }\r
7331     if (init) {\r
7332     if (appData.debugMode) {\r
7333         fprintf(debugFP, "From Reset\n");\r
7334     }\r
7335 InitChessProgram(&first, startedFromSetupPosition);}\r
7336     DisplayTitle("");\r
7337     DisplayMessage("", "");\r
7338     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
7339 }\r
7340 \r
7341 void\r
7342 AutoPlayGameLoop()\r
7343 {\r
7344     for (;;) {\r
7345         if (!AutoPlayOneMove())\r
7346           return;\r
7347         if (matchMode || appData.timeDelay == 0)\r
7348           continue;\r
7349         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
7350           return;\r
7351         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
7352         break;\r
7353     }\r
7354 }\r
7355 \r
7356 \r
7357 int\r
7358 AutoPlayOneMove()\r
7359 {\r
7360     int fromX, fromY, toX, toY;\r
7361 \r
7362     if (appData.debugMode) {\r
7363       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
7364     }\r
7365 \r
7366     if (gameMode != PlayFromGameFile)\r
7367       return FALSE;\r
7368 \r
7369     if (currentMove >= forwardMostMove) {\r
7370       gameMode = EditGame;\r
7371       ModeHighlight();\r
7372 \r
7373       /* [AS] Clear current move marker at the end of a game */\r
7374       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
7375 \r
7376       return FALSE;\r
7377     }\r
7378     \r
7379     toX = moveList[currentMove][2] - AAA;\r
7380     toY = moveList[currentMove][3] - ONE;\r
7381 \r
7382     if (moveList[currentMove][1] == '@') {\r
7383         if (appData.highlightLastMove) {\r
7384             SetHighlights(-1, -1, toX, toY);\r
7385         }\r
7386     } else {\r
7387         fromX = moveList[currentMove][0] - AAA;\r
7388         fromY = moveList[currentMove][1] - ONE;\r
7389 \r
7390         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
7391 \r
7392         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
7393 \r
7394         if (appData.highlightLastMove) {\r
7395             SetHighlights(fromX, fromY, toX, toY);\r
7396         }\r
7397     }\r
7398     DisplayMove(currentMove);\r
7399     SendMoveToProgram(currentMove++, &first);\r
7400     DisplayBothClocks();\r
7401     DrawPosition(FALSE, boards[currentMove]);\r
7402     // [HGM] PV info: always display, routine tests if empty\r
7403     DisplayComment(currentMove - 1, commentList[currentMove]);\r
7404     return TRUE;\r
7405 }\r
7406 \r
7407 \r
7408 int\r
7409 LoadGameOneMove(readAhead)\r
7410      ChessMove readAhead;\r
7411 {\r
7412     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
7413     char promoChar = NULLCHAR;\r
7414     ChessMove moveType;\r
7415     char move[MSG_SIZ];\r
7416     char *p, *q;\r
7417     \r
7418     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
7419         gameMode != AnalyzeMode && gameMode != Training) {\r
7420         gameFileFP = NULL;\r
7421         return FALSE;\r
7422     }\r
7423     \r
7424     yyboardindex = forwardMostMove;\r
7425     if (readAhead != (ChessMove)0) {\r
7426       moveType = readAhead;\r
7427     } else {\r
7428       if (gameFileFP == NULL)\r
7429           return FALSE;\r
7430       moveType = (ChessMove) yylex();\r
7431     }\r
7432     \r
7433     done = FALSE;\r
7434     switch (moveType) {\r
7435       case Comment:\r
7436         if (appData.debugMode) \r
7437           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
7438         p = yy_text;\r
7439         if (*p == '{' || *p == '[' || *p == '(') {\r
7440             p[strlen(p) - 1] = NULLCHAR;\r
7441             p++;\r
7442         }\r
7443 \r
7444         /* append the comment but don't display it */\r
7445         while (*p == '\n') p++;\r
7446         AppendComment(currentMove, p);\r
7447         return TRUE;\r
7448 \r
7449       case WhiteCapturesEnPassant:\r
7450       case BlackCapturesEnPassant:\r
7451       case WhitePromotionChancellor:\r
7452       case BlackPromotionChancellor:\r
7453       case WhitePromotionArchbishop:\r
7454       case BlackPromotionArchbishop:\r
7455       case WhitePromotionQueen:\r
7456       case BlackPromotionQueen:\r
7457       case WhitePromotionRook:\r
7458       case BlackPromotionRook:\r
7459       case WhitePromotionBishop:\r
7460       case BlackPromotionBishop:\r
7461       case WhitePromotionKnight:\r
7462       case BlackPromotionKnight:\r
7463       case WhitePromotionKing:\r
7464       case BlackPromotionKing:\r
7465       case NormalMove:\r
7466       case WhiteKingSideCastle:\r
7467       case WhiteQueenSideCastle:\r
7468       case BlackKingSideCastle:\r
7469       case BlackQueenSideCastle:\r
7470       case WhiteKingSideCastleWild:\r
7471       case WhiteQueenSideCastleWild:\r
7472       case BlackKingSideCastleWild:\r
7473       case BlackQueenSideCastleWild:\r
7474       /* PUSH Fabien */\r
7475       case WhiteHSideCastleFR:\r
7476       case WhiteASideCastleFR:\r
7477       case BlackHSideCastleFR:\r
7478       case BlackASideCastleFR:\r
7479       /* POP Fabien */\r
7480         if (appData.debugMode)\r
7481           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
7482         fromX = currentMoveString[0] - AAA;\r
7483         fromY = currentMoveString[1] - ONE;\r
7484         toX = currentMoveString[2] - AAA;\r
7485         toY = currentMoveString[3] - ONE;\r
7486         promoChar = currentMoveString[4];\r
7487         break;\r
7488 \r
7489       case WhiteDrop:\r
7490       case BlackDrop:\r
7491         if (appData.debugMode)\r
7492           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
7493         fromX = moveType == WhiteDrop ?\r
7494           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
7495         (int) CharToPiece(ToLower(currentMoveString[0]));\r
7496         fromY = DROP_RANK;\r
7497         toX = currentMoveString[2] - AAA;\r
7498         toY = currentMoveString[3] - ONE;\r
7499         break;\r
7500 \r
7501       case WhiteWins:\r
7502       case BlackWins:\r
7503       case GameIsDrawn:\r
7504       case GameUnfinished:\r
7505         if (appData.debugMode)\r
7506           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
7507         p = strchr(yy_text, '{');\r
7508         if (p == NULL) p = strchr(yy_text, '(');\r
7509         if (p == NULL) {\r
7510             p = yy_text;\r
7511             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
7512         } else {\r
7513             q = strchr(p, *p == '{' ? '}' : ')');\r
7514             if (q != NULL) *q = NULLCHAR;\r
7515             p++;\r
7516         }\r
7517         GameEnds(moveType, p, GE_FILE);\r
7518         done = TRUE;\r
7519         if (cmailMsgLoaded) {\r
7520             ClearHighlights();\r
7521             flipView = WhiteOnMove(currentMove);\r
7522             if (moveType == GameUnfinished) flipView = !flipView;\r
7523             if (appData.debugMode)\r
7524               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
7525         }\r
7526         break;\r
7527 \r
7528       case (ChessMove) 0:       /* end of file */\r
7529         if (appData.debugMode)\r
7530           fprintf(debugFP, "Parser hit end of file\n");\r
7531         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
7532                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
7533           case MT_NONE:\r
7534           case MT_CHECK:\r
7535             break;\r
7536           case MT_CHECKMATE:\r
7537             if (WhiteOnMove(currentMove)) {\r
7538                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
7539             } else {\r
7540                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
7541             }\r
7542             break;\r
7543           case MT_STALEMATE:\r
7544             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
7545             break;\r
7546         }\r
7547         done = TRUE;\r
7548         break;\r
7549 \r
7550       case MoveNumberOne:\r
7551         if (lastLoadGameStart == GNUChessGame) {\r
7552             /* GNUChessGames have numbers, but they aren't move numbers */\r
7553             if (appData.debugMode)\r
7554               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
7555                       yy_text, (int) moveType);\r
7556             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
7557         }\r
7558         /* else fall thru */\r
7559 \r
7560       case XBoardGame:\r
7561       case GNUChessGame:\r
7562       case PGNTag:\r
7563         /* Reached start of next game in file */\r
7564         if (appData.debugMode)\r
7565           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
7566         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
7567                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
7568           case MT_NONE:\r
7569           case MT_CHECK:\r
7570             break;\r
7571           case MT_CHECKMATE:\r
7572             if (WhiteOnMove(currentMove)) {\r
7573                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
7574             } else {\r
7575                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
7576             }\r
7577             break;\r
7578           case MT_STALEMATE:\r
7579             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
7580             break;\r
7581         }\r
7582         done = TRUE;\r
7583         break;\r
7584 \r
7585       case PositionDiagram:     /* should not happen; ignore */\r
7586       case ElapsedTime:         /* ignore */\r
7587       case NAG:                 /* ignore */\r
7588         if (appData.debugMode)\r
7589           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
7590                   yy_text, (int) moveType);\r
7591         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
7592 \r
7593       case IllegalMove:\r
7594         if (appData.testLegality) {\r
7595             if (appData.debugMode)\r
7596               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
7597             sprintf(move, "Illegal move: %d.%s%s",\r
7598                     (forwardMostMove / 2) + 1,\r
7599                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
7600             DisplayError(move, 0);\r
7601             done = TRUE;\r
7602         } else {\r
7603             if (appData.debugMode)\r
7604               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
7605                       yy_text, currentMoveString);\r
7606             fromX = currentMoveString[0] - AAA;\r
7607             fromY = currentMoveString[1] - ONE;\r
7608             toX = currentMoveString[2] - AAA;\r
7609             toY = currentMoveString[3] - ONE;\r
7610             promoChar = currentMoveString[4];\r
7611         }\r
7612         break;\r
7613 \r
7614       case AmbiguousMove:\r
7615         if (appData.debugMode)\r
7616           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
7617         sprintf(move, "Ambiguous move: %d.%s%s",\r
7618                 (forwardMostMove / 2) + 1,\r
7619                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
7620         DisplayError(move, 0);\r
7621         done = TRUE;\r
7622         break;\r
7623 \r
7624       default:\r
7625       case ImpossibleMove:\r
7626         if (appData.debugMode)\r
7627           fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);\r
7628         sprintf(move, "Illegal move: %d.%s%s",\r
7629                 (forwardMostMove / 2) + 1,\r
7630                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
7631         DisplayError(move, 0);\r
7632         done = TRUE;\r
7633         break;\r
7634     }\r
7635 \r
7636     if (done) {\r
7637         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
7638             DrawPosition(FALSE, boards[currentMove]);\r
7639             DisplayBothClocks();\r
7640             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
7641               DisplayComment(currentMove - 1, commentList[currentMove]);\r
7642         }\r
7643         (void) StopLoadGameTimer();\r
7644         gameFileFP = NULL;\r
7645         cmailOldMove = forwardMostMove;\r
7646         return FALSE;\r
7647     } else {\r
7648         /* currentMoveString is set as a side-effect of yylex */\r
7649         strcat(currentMoveString, "\n");\r
7650         strcpy(moveList[forwardMostMove], currentMoveString);\r
7651         \r
7652         thinkOutput[0] = NULLCHAR;\r
7653         MakeMove(fromX, fromY, toX, toY, promoChar);\r
7654         currentMove = forwardMostMove;\r
7655         return TRUE;\r
7656     }\r
7657 }\r
7658 \r
7659 /* Load the nth game from the given file */\r
7660 int\r
7661 LoadGameFromFile(filename, n, title, useList)\r
7662      char *filename;\r
7663      int n;\r
7664      char *title;\r
7665      /*Boolean*/ int useList;\r
7666 {\r
7667     FILE *f;\r
7668     char buf[MSG_SIZ];\r
7669 \r
7670     if (strcmp(filename, "-") == 0) {\r
7671         f = stdin;\r
7672         title = "stdin";\r
7673     } else {\r
7674         f = fopen(filename, "rb");\r
7675         if (f == NULL) {\r
7676             sprintf(buf, "Can't open \"%s\"", filename);\r
7677             DisplayError(buf, errno);\r
7678             return FALSE;\r
7679         }\r
7680     }\r
7681     if (fseek(f, 0, 0) == -1) {\r
7682         /* f is not seekable; probably a pipe */\r
7683         useList = FALSE;\r
7684     }\r
7685     if (useList && n == 0) {\r
7686         int error = GameListBuild(f);\r
7687         if (error) {\r
7688             DisplayError("Cannot build game list", error);\r
7689         } else if (!ListEmpty(&gameList) &&\r
7690                    ((ListGame *) gameList.tailPred)->number > 1) {\r
7691             GameListPopUp(f, title);\r
7692             return TRUE;\r
7693         }\r
7694         GameListDestroy();\r
7695         n = 1;\r
7696     }\r
7697     if (n == 0) n = 1;\r
7698     return LoadGame(f, n, title, FALSE);\r
7699 }\r
7700 \r
7701 \r
7702 void\r
7703 MakeRegisteredMove()\r
7704 {\r
7705     int fromX, fromY, toX, toY;\r
7706     char promoChar;\r
7707     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
7708         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
7709           case CMAIL_MOVE:\r
7710           case CMAIL_DRAW:\r
7711             if (appData.debugMode)\r
7712               fprintf(debugFP, "Restoring %s for game %d\n",\r
7713                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
7714     \r
7715             thinkOutput[0] = NULLCHAR;\r
7716             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
7717             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
7718             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
7719             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
7720             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
7721             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
7722             MakeMove(fromX, fromY, toX, toY, promoChar);\r
7723             ShowMove(fromX, fromY, toX, toY);\r
7724               \r
7725             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
7726                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
7727               case MT_NONE:\r
7728               case MT_CHECK:\r
7729                 break;\r
7730                 \r
7731               case MT_CHECKMATE:\r
7732                 if (WhiteOnMove(currentMove)) {\r
7733                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
7734                 } else {\r
7735                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
7736                 }\r
7737                 break;\r
7738                 \r
7739               case MT_STALEMATE:\r
7740                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
7741                 break;\r
7742             }\r
7743 \r
7744             break;\r
7745             \r
7746           case CMAIL_RESIGN:\r
7747             if (WhiteOnMove(currentMove)) {\r
7748                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
7749             } else {\r
7750                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
7751             }\r
7752             break;\r
7753             \r
7754           case CMAIL_ACCEPT:\r
7755             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
7756             break;\r
7757               \r
7758           default:\r
7759             break;\r
7760         }\r
7761     }\r
7762 \r
7763     return;\r
7764 }\r
7765 \r
7766 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
7767 int\r
7768 CmailLoadGame(f, gameNumber, title, useList)\r
7769      FILE *f;\r
7770      int gameNumber;\r
7771      char *title;\r
7772      int useList;\r
7773 {\r
7774     int retVal;\r
7775 \r
7776     if (gameNumber > nCmailGames) {\r
7777         DisplayError("No more games in this message", 0);\r
7778         return FALSE;\r
7779     }\r
7780     if (f == lastLoadGameFP) {\r
7781         int offset = gameNumber - lastLoadGameNumber;\r
7782         if (offset == 0) {\r
7783             cmailMsg[0] = NULLCHAR;\r
7784             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
7785                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
7786                 nCmailMovesRegistered--;\r
7787             }\r
7788             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
7789             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
7790                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
7791             }\r
7792         } else {\r
7793             if (! RegisterMove()) return FALSE;\r
7794         }\r
7795     }\r
7796 \r
7797     retVal = LoadGame(f, gameNumber, title, useList);\r
7798 \r
7799     /* Make move registered during previous look at this game, if any */\r
7800     MakeRegisteredMove();\r
7801 \r
7802     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
7803         commentList[currentMove]\r
7804           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
7805         DisplayComment(currentMove - 1, commentList[currentMove]);\r
7806     }\r
7807 \r
7808     return retVal;\r
7809 }\r
7810 \r
7811 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
7812 int\r
7813 ReloadGame(offset)\r
7814      int offset;\r
7815 {\r
7816     int gameNumber = lastLoadGameNumber + offset;\r
7817     if (lastLoadGameFP == NULL) {\r
7818         DisplayError("No game has been loaded yet", 0);\r
7819         return FALSE;\r
7820     }\r
7821     if (gameNumber <= 0) {\r
7822         DisplayError("Can't back up any further", 0);\r
7823         return FALSE;\r
7824     }\r
7825     if (cmailMsgLoaded) {\r
7826         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
7827                              lastLoadGameTitle, lastLoadGameUseList);\r
7828     } else {\r
7829         return LoadGame(lastLoadGameFP, gameNumber,\r
7830                         lastLoadGameTitle, lastLoadGameUseList);\r
7831     }\r
7832 }\r
7833 \r
7834 \r
7835 \r
7836 /* Load the nth game from open file f */\r
7837 int\r
7838 LoadGame(f, gameNumber, title, useList)\r
7839      FILE *f;\r
7840      int gameNumber;\r
7841      char *title;\r
7842      int useList;\r
7843 {\r
7844     ChessMove cm;\r
7845     char buf[MSG_SIZ];\r
7846     int gn = gameNumber;\r
7847     ListGame *lg = NULL;\r
7848     int numPGNTags = 0;\r
7849     int err;\r
7850     GameMode oldGameMode;\r
7851     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
7852 \r
7853     if (appData.debugMode) \r
7854         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
7855 \r
7856     if (gameMode == Training )\r
7857         SetTrainingModeOff();\r
7858 \r
7859     oldGameMode = gameMode;\r
7860     if (gameMode != BeginningOfGame) {\r
7861       Reset(FALSE, TRUE);\r
7862     }\r
7863 \r
7864     gameFileFP = f;\r
7865     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
7866         fclose(lastLoadGameFP);\r
7867     }\r
7868 \r
7869     if (useList) {\r
7870         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
7871         \r
7872         if (lg) {\r
7873             fseek(f, lg->offset, 0);\r
7874             GameListHighlight(gameNumber);\r
7875             gn = 1;\r
7876         }\r
7877         else {\r
7878             DisplayError("Game number out of range", 0);\r
7879             return FALSE;\r
7880         }\r
7881     } else {\r
7882         GameListDestroy();\r
7883         if (fseek(f, 0, 0) == -1) {\r
7884             if (f == lastLoadGameFP ?\r
7885                 gameNumber == lastLoadGameNumber + 1 :\r
7886                 gameNumber == 1) {\r
7887                 gn = 1;\r
7888             } else {\r
7889                 DisplayError("Can't seek on game file", 0);\r
7890                 return FALSE;\r
7891             }\r
7892         }\r
7893     }\r
7894     lastLoadGameFP = f;\r
7895     lastLoadGameNumber = gameNumber;\r
7896     strcpy(lastLoadGameTitle, title);\r
7897     lastLoadGameUseList = useList;\r
7898 \r
7899     yynewfile(f);\r
7900 \r
7901     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
7902         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
7903                 lg->gameInfo.black);\r
7904             DisplayTitle(buf);\r
7905     } else if (*title != NULLCHAR) {\r
7906         if (gameNumber > 1) {\r
7907             sprintf(buf, "%s %d", title, gameNumber);\r
7908             DisplayTitle(buf);\r
7909         } else {\r
7910             DisplayTitle(title);\r
7911         }\r
7912     }\r
7913 \r
7914     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
7915         gameMode = PlayFromGameFile;\r
7916         ModeHighlight();\r
7917     }\r
7918 \r
7919     currentMove = forwardMostMove = backwardMostMove = 0;\r
7920     CopyBoard(boards[0], initialPosition);\r
7921     StopClocks();\r
7922 \r
7923     /*\r
7924      * Skip the first gn-1 games in the file.\r
7925      * Also skip over anything that precedes an identifiable \r
7926      * start of game marker, to avoid being confused by \r
7927      * garbage at the start of the file.  Currently \r
7928      * recognized start of game markers are the move number "1",\r
7929      * the pattern "gnuchess .* game", the pattern\r
7930      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
7931      * A game that starts with one of the latter two patterns\r
7932      * will also have a move number 1, possibly\r
7933      * following a position diagram.\r
7934      * 5-4-02: Let's try being more lenient and allowing a game to\r
7935      * start with an unnumbered move.  Does that break anything?\r
7936      */\r
7937     cm = lastLoadGameStart = (ChessMove) 0;\r
7938     while (gn > 0) {\r
7939         yyboardindex = forwardMostMove;\r
7940         cm = (ChessMove) yylex();\r
7941         switch (cm) {\r
7942           case (ChessMove) 0:\r
7943             if (cmailMsgLoaded) {\r
7944                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
7945             } else {\r
7946                 Reset(TRUE, TRUE);\r
7947                 DisplayError("Game not found in file", 0);\r
7948             }\r
7949             return FALSE;\r
7950 \r
7951           case GNUChessGame:\r
7952           case XBoardGame:\r
7953             gn--;\r
7954             lastLoadGameStart = cm;\r
7955             break;\r
7956             \r
7957           case MoveNumberOne:\r
7958             switch (lastLoadGameStart) {\r
7959               case GNUChessGame:\r
7960               case XBoardGame:\r
7961               case PGNTag:\r
7962                 break;\r
7963               case MoveNumberOne:\r
7964               case (ChessMove) 0:\r
7965                 gn--;           /* count this game */\r
7966                 lastLoadGameStart = cm;\r
7967                 break;\r
7968               default:\r
7969                 /* impossible */\r
7970                 break;\r
7971             }\r
7972             break;\r
7973 \r
7974           case PGNTag:\r
7975             switch (lastLoadGameStart) {\r
7976               case GNUChessGame:\r
7977               case PGNTag:\r
7978               case MoveNumberOne:\r
7979               case (ChessMove) 0:\r
7980                 gn--;           /* count this game */\r
7981                 lastLoadGameStart = cm;\r
7982                 break;\r
7983               case XBoardGame:\r
7984                 lastLoadGameStart = cm; /* game counted already */\r
7985                 break;\r
7986               default:\r
7987                 /* impossible */\r
7988                 break;\r
7989             }\r
7990             if (gn > 0) {\r
7991                 do {\r
7992                     yyboardindex = forwardMostMove;\r
7993                     cm = (ChessMove) yylex();\r
7994                 } while (cm == PGNTag || cm == Comment);\r
7995             }\r
7996             break;\r
7997 \r
7998           case WhiteWins:\r
7999           case BlackWins:\r
8000           case GameIsDrawn:\r
8001             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8002                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8003                     != CMAIL_OLD_RESULT) {\r
8004                     nCmailResults ++ ;\r
8005                     cmailResult[  CMAIL_MAX_GAMES\r
8006                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8007                 }\r
8008             }\r
8009             break;\r
8010 \r
8011           case NormalMove:\r
8012             /* Only a NormalMove can be at the start of a game\r
8013              * without a position diagram. */\r
8014             if (lastLoadGameStart == (ChessMove) 0) {\r
8015               gn--;\r
8016               lastLoadGameStart = MoveNumberOne;\r
8017             }\r
8018             break;\r
8019 \r
8020           default:\r
8021             break;\r
8022         }\r
8023     }\r
8024     \r
8025     if (appData.debugMode)\r
8026       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8027 \r
8028     if (cm == XBoardGame) {\r
8029         /* Skip any header junk before position diagram and/or move 1 */\r
8030         for (;;) {\r
8031             yyboardindex = forwardMostMove;\r
8032             cm = (ChessMove) yylex();\r
8033 \r
8034             if (cm == (ChessMove) 0 ||\r
8035                 cm == GNUChessGame || cm == XBoardGame) {\r
8036                 /* Empty game; pretend end-of-file and handle later */\r
8037                 cm = (ChessMove) 0;\r
8038                 break;\r
8039             }\r
8040 \r
8041             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8042                 cm == PGNTag || cm == Comment)\r
8043               break;\r
8044         }\r
8045     } else if (cm == GNUChessGame) {\r
8046         if (gameInfo.event != NULL) {\r
8047             free(gameInfo.event);\r
8048         }\r
8049         gameInfo.event = StrSave(yy_text);\r
8050     }   \r
8051 \r
8052     startedFromSetupPosition = FALSE;\r
8053     while (cm == PGNTag) {\r
8054         if (appData.debugMode) \r
8055           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8056         err = ParsePGNTag(yy_text, &gameInfo);\r
8057         if (!err) numPGNTags++;\r
8058 \r
8059         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8060         if(gameInfo.variant != oldVariant) {\r
8061             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8062             InitPosition(TRUE);\r
8063             oldVariant = gameInfo.variant;\r
8064             if (appData.debugMode) \r
8065               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8066         }\r
8067 \r
8068 \r
8069         if (gameInfo.fen != NULL) {\r
8070           Board initial_position;\r
8071           startedFromSetupPosition = TRUE;\r
8072           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8073             Reset(TRUE, TRUE);\r
8074             DisplayError("Bad FEN position in file", 0);\r
8075             return FALSE;\r
8076           }\r
8077           CopyBoard(boards[0], initial_position);\r
8078           /* [HGM] copy FEN attributes as well */\r
8079           {   int i;\r
8080               initialRulePlies = FENrulePlies;\r
8081               epStatus[0] = FENepStatus;\r
8082               for( i=0; i< nrCastlingRights; i++ )\r
8083                   castlingRights[0][i] = FENcastlingRights[i];\r
8084           }\r
8085           if (blackPlaysFirst) {\r
8086             currentMove = forwardMostMove = backwardMostMove = 1;\r
8087             CopyBoard(boards[1], initial_position);\r
8088             strcpy(moveList[0], "");\r
8089             strcpy(parseList[0], "");\r
8090             timeRemaining[0][1] = whiteTimeRemaining;\r
8091             timeRemaining[1][1] = blackTimeRemaining;\r
8092             if (commentList[0] != NULL) {\r
8093               commentList[1] = commentList[0];\r
8094               commentList[0] = NULL;\r
8095             }\r
8096           } else {\r
8097             currentMove = forwardMostMove = backwardMostMove = 0;\r
8098           }\r
8099           yyboardindex = forwardMostMove;\r
8100           free(gameInfo.fen);\r
8101           gameInfo.fen = NULL;\r
8102         }\r
8103 \r
8104         yyboardindex = forwardMostMove;\r
8105         cm = (ChessMove) yylex();\r
8106 \r
8107         /* Handle comments interspersed among the tags */\r
8108         while (cm == Comment) {\r
8109             char *p;\r
8110             if (appData.debugMode) \r
8111               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8112             p = yy_text;\r
8113             if (*p == '{' || *p == '[' || *p == '(') {\r
8114                 p[strlen(p) - 1] = NULLCHAR;\r
8115                 p++;\r
8116             }\r
8117             while (*p == '\n') p++;\r
8118             AppendComment(currentMove, p);\r
8119             yyboardindex = forwardMostMove;\r
8120             cm = (ChessMove) yylex();\r
8121         }\r
8122     }\r
8123 \r
8124     /* don't rely on existence of Event tag since if game was\r
8125      * pasted from clipboard the Event tag may not exist\r
8126      */\r
8127     if (numPGNTags > 0){\r
8128         char *tags;\r
8129         if (gameInfo.variant == VariantNormal) {\r
8130           gameInfo.variant = StringToVariant(gameInfo.event);\r
8131         }\r
8132         if (!matchMode) {\r
8133           if( appData.autoDisplayTags ) {\r
8134             tags = PGNTags(&gameInfo);\r
8135             TagsPopUp(tags, CmailMsg());\r
8136             free(tags);\r
8137           }\r
8138         }\r
8139     } else {\r
8140         /* Make something up, but don't display it now */\r
8141         SetGameInfo();\r
8142         TagsPopDown();\r
8143     }\r
8144 \r
8145     if (cm == PositionDiagram) {\r
8146         int i, j;\r
8147         char *p;\r
8148         Board initial_position;\r
8149 \r
8150         if (appData.debugMode)\r
8151           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
8152 \r
8153         if (!startedFromSetupPosition) {\r
8154             p = yy_text;\r
8155             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
8156               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
8157                 switch (*p) {\r
8158                   case '[':\r
8159                   case '-':\r
8160                   case ' ':\r
8161                   case '\t':\r
8162                   case '\n':\r
8163                   case '\r':\r
8164                     break;\r
8165                   default:\r
8166                     initial_position[i][j++] = CharToPiece(*p);\r
8167                     break;\r
8168                 }\r
8169             while (*p == ' ' || *p == '\t' ||\r
8170                    *p == '\n' || *p == '\r') p++;\r
8171         \r
8172             if (strncmp(p, "black", strlen("black"))==0)\r
8173               blackPlaysFirst = TRUE;\r
8174             else\r
8175               blackPlaysFirst = FALSE;\r
8176             startedFromSetupPosition = TRUE;\r
8177         \r
8178             CopyBoard(boards[0], initial_position);\r
8179             if (blackPlaysFirst) {\r
8180                 currentMove = forwardMostMove = backwardMostMove = 1;\r
8181                 CopyBoard(boards[1], initial_position);\r
8182                 strcpy(moveList[0], "");\r
8183                 strcpy(parseList[0], "");\r
8184                 timeRemaining[0][1] = whiteTimeRemaining;\r
8185                 timeRemaining[1][1] = blackTimeRemaining;\r
8186                 if (commentList[0] != NULL) {\r
8187                     commentList[1] = commentList[0];\r
8188                     commentList[0] = NULL;\r
8189                 }\r
8190             } else {\r
8191                 currentMove = forwardMostMove = backwardMostMove = 0;\r
8192             }\r
8193         }\r
8194         yyboardindex = forwardMostMove;\r
8195         cm = (ChessMove) yylex();\r
8196     }\r
8197 \r
8198     if (first.pr == NoProc) {\r
8199         StartChessProgram(&first);\r
8200     }\r
8201     if (appData.debugMode) {\r
8202         fprintf(debugFP, "From LoadGame\n");\r
8203     }\r
8204     InitChessProgram(&first, FALSE);\r
8205     SendToProgram("force\n", &first);\r
8206     if (startedFromSetupPosition) {\r
8207         SendBoard(&first, forwardMostMove);\r
8208     if (appData.debugMode) {\r
8209         fprintf(debugFP, "Load Game\n");\r
8210     }\r
8211         DisplayBothClocks();\r
8212     }      \r
8213 \r
8214     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
8215     loadFlag = appData.suppressLoadMoves;\r
8216 \r
8217     while (cm == Comment) {\r
8218         char *p;\r
8219         if (appData.debugMode) \r
8220           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8221         p = yy_text;\r
8222         if (*p == '{' || *p == '[' || *p == '(') {\r
8223             p[strlen(p) - 1] = NULLCHAR;\r
8224             p++;\r
8225         }\r
8226         while (*p == '\n') p++;\r
8227         AppendComment(currentMove, p);\r
8228         yyboardindex = forwardMostMove;\r
8229         cm = (ChessMove) yylex();\r
8230     }\r
8231 \r
8232     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
8233         cm == WhiteWins || cm == BlackWins ||\r
8234         cm == GameIsDrawn || cm == GameUnfinished) {\r
8235         DisplayMessage("", "No moves in game");\r
8236         if (cmailMsgLoaded) {\r
8237             if (appData.debugMode)\r
8238               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
8239             ClearHighlights();\r
8240             flipView = FALSE;\r
8241         }\r
8242         DrawPosition(FALSE, boards[currentMove]);\r
8243         DisplayBothClocks();\r
8244         gameMode = EditGame;\r
8245         ModeHighlight();\r
8246         gameFileFP = NULL;\r
8247         cmailOldMove = 0;\r
8248         return TRUE;\r
8249     }\r
8250 \r
8251     // [HGM] PV info: routine tests if comment empty\r
8252     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
8253         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8254     }\r
8255     if (!matchMode && appData.timeDelay != 0) \r
8256       DrawPosition(FALSE, boards[currentMove]);\r
8257 \r
8258     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
8259       programStats.ok_to_send = 1;\r
8260     }\r
8261 \r
8262     /* if the first token after the PGN tags is a move\r
8263      * and not move number 1, retrieve it from the parser \r
8264      */\r
8265     if (cm != MoveNumberOne)\r
8266         LoadGameOneMove(cm);\r
8267 \r
8268     /* load the remaining moves from the file */\r
8269     while (LoadGameOneMove((ChessMove)0)) {\r
8270       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
8271       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
8272     }\r
8273 \r
8274     /* rewind to the start of the game */\r
8275     currentMove = backwardMostMove;\r
8276 \r
8277     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8278 \r
8279     if (oldGameMode == AnalyzeFile ||\r
8280         oldGameMode == AnalyzeMode) {\r
8281       AnalyzeFileEvent();\r
8282     }\r
8283 \r
8284     if (matchMode || appData.timeDelay == 0) {\r
8285       ToEndEvent();\r
8286       gameMode = EditGame;\r
8287       ModeHighlight();\r
8288     } else if (appData.timeDelay > 0) {\r
8289       AutoPlayGameLoop();\r
8290     }\r
8291 \r
8292     if (appData.debugMode) \r
8293         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
8294 \r
8295     loadFlag = 0; /* [HGM] true game starts */\r
8296     return TRUE;\r
8297 }\r
8298 \r
8299 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
8300 int\r
8301 ReloadPosition(offset)\r
8302      int offset;\r
8303 {\r
8304     int positionNumber = lastLoadPositionNumber + offset;\r
8305     if (lastLoadPositionFP == NULL) {\r
8306         DisplayError("No position has been loaded yet", 0);\r
8307         return FALSE;\r
8308     }\r
8309     if (positionNumber <= 0) {\r
8310         DisplayError("Can't back up any further", 0);\r
8311         return FALSE;\r
8312     }\r
8313     return LoadPosition(lastLoadPositionFP, positionNumber,\r
8314                         lastLoadPositionTitle);\r
8315 }\r
8316 \r
8317 /* Load the nth position from the given file */\r
8318 int\r
8319 LoadPositionFromFile(filename, n, title)\r
8320      char *filename;\r
8321      int n;\r
8322      char *title;\r
8323 {\r
8324     FILE *f;\r
8325     char buf[MSG_SIZ];\r
8326 \r
8327     if (strcmp(filename, "-") == 0) {\r
8328         return LoadPosition(stdin, n, "stdin");\r
8329     } else {\r
8330         f = fopen(filename, "rb");\r
8331         if (f == NULL) {\r
8332             sprintf(buf, "Can't open \"%s\"", filename);\r
8333             DisplayError(buf, errno);\r
8334             return FALSE;\r
8335         } else {\r
8336             return LoadPosition(f, n, title);\r
8337         }\r
8338     }\r
8339 }\r
8340 \r
8341 /* Load the nth position from the given open file, and close it */\r
8342 int\r
8343 LoadPosition(f, positionNumber, title)\r
8344      FILE *f;\r
8345      int positionNumber;\r
8346      char *title;\r
8347 {\r
8348     char *p, line[MSG_SIZ];\r
8349     Board initial_position;\r
8350     int i, j, fenMode, pn;\r
8351     \r
8352     if (gameMode == Training )\r
8353         SetTrainingModeOff();\r
8354 \r
8355     if (gameMode != BeginningOfGame) {\r
8356         Reset(FALSE, TRUE);\r
8357     }\r
8358     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
8359         fclose(lastLoadPositionFP);\r
8360     }\r
8361     if (positionNumber == 0) positionNumber = 1;\r
8362     lastLoadPositionFP = f;\r
8363     lastLoadPositionNumber = positionNumber;\r
8364     strcpy(lastLoadPositionTitle, title);\r
8365     if (first.pr == NoProc) {\r
8366       StartChessProgram(&first);\r
8367     if (appData.debugMode) {\r
8368         fprintf(debugFP, "From LoadPosition\n");\r
8369     }\r
8370       InitChessProgram(&first, FALSE);\r
8371     }    \r
8372     pn = positionNumber;\r
8373     if (positionNumber < 0) {\r
8374         /* Negative position number means to seek to that byte offset */\r
8375         if (fseek(f, -positionNumber, 0) == -1) {\r
8376             DisplayError("Can't seek on position file", 0);\r
8377             return FALSE;\r
8378         };\r
8379         pn = 1;\r
8380     } else {\r
8381         if (fseek(f, 0, 0) == -1) {\r
8382             if (f == lastLoadPositionFP ?\r
8383                 positionNumber == lastLoadPositionNumber + 1 :\r
8384                 positionNumber == 1) {\r
8385                 pn = 1;\r
8386             } else {\r
8387                 DisplayError("Can't seek on position file", 0);\r
8388                 return FALSE;\r
8389             }\r
8390         }\r
8391     }\r
8392     /* See if this file is FEN or old-style xboard */\r
8393     if (fgets(line, MSG_SIZ, f) == NULL) {\r
8394         DisplayError("Position not found in file", 0);\r
8395         return FALSE;\r
8396     }\r
8397 #if 0\r
8398     switch (line[0]) {\r
8399       case '#':  case 'x':\r
8400       default:\r
8401         fenMode = FALSE;\r
8402         break;\r
8403       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
8404       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
8405       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
8406       case '7':  case '8':  case '9':\r
8407       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
8408       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
8409       case 'C':  case 'W':             case 'c':  case 'w': \r
8410         fenMode = TRUE;\r
8411         break;\r
8412     }\r
8413 #else\r
8414     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
8415     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
8416 #endif\r
8417 \r
8418     if (pn >= 2) {\r
8419         if (fenMode || line[0] == '#') pn--;\r
8420         while (pn > 0) {\r
8421             /* skip postions before number pn */\r
8422             if (fgets(line, MSG_SIZ, f) == NULL) {\r
8423                 Reset(TRUE, TRUE);\r
8424                 DisplayError("Position not found in file", 0);\r
8425                 return FALSE;\r
8426             }\r
8427             if (fenMode || line[0] == '#') pn--;\r
8428         }\r
8429     }\r
8430 \r
8431     if (fenMode) {\r
8432         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
8433             DisplayError("Bad FEN position in file", 0);\r
8434             return FALSE;\r
8435         }\r
8436     } else {\r
8437         (void) fgets(line, MSG_SIZ, f);\r
8438         (void) fgets(line, MSG_SIZ, f);\r
8439     \r
8440         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
8441             (void) fgets(line, MSG_SIZ, f);\r
8442             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
8443                 if (*p == ' ')\r
8444                   continue;\r
8445                 initial_position[i][j++] = CharToPiece(*p);\r
8446             }\r
8447         }\r
8448     \r
8449         blackPlaysFirst = FALSE;\r
8450         if (!feof(f)) {\r
8451             (void) fgets(line, MSG_SIZ, f);\r
8452             if (strncmp(line, "black", strlen("black"))==0)\r
8453               blackPlaysFirst = TRUE;\r
8454         }\r
8455     }\r
8456     startedFromSetupPosition = TRUE;\r
8457     \r
8458     SendToProgram("force\n", &first);\r
8459     CopyBoard(boards[0], initial_position);\r
8460           /* [HGM] copy FEN attributes as well */\r
8461           {   int i;\r
8462               initialRulePlies = FENrulePlies;\r
8463               epStatus[0] = FENepStatus;\r
8464               for( i=0; i< nrCastlingRights; i++ )\r
8465                   castlingRights[0][i] = FENcastlingRights[i];\r
8466           }\r
8467     if (blackPlaysFirst) {\r
8468         currentMove = forwardMostMove = backwardMostMove = 1;\r
8469         strcpy(moveList[0], "");\r
8470         strcpy(parseList[0], "");\r
8471         CopyBoard(boards[1], initial_position);\r
8472         DisplayMessage("", "Black to play");\r
8473     } else {\r
8474         currentMove = forwardMostMove = backwardMostMove = 0;\r
8475         DisplayMessage("", "White to play");\r
8476     }\r
8477     SendBoard(&first, forwardMostMove);\r
8478     if (appData.debugMode) {\r
8479         fprintf(debugFP, "Load Position\n");\r
8480     }\r
8481 \r
8482     if (positionNumber > 1) {\r
8483         sprintf(line, "%s %d", title, positionNumber);\r
8484         DisplayTitle(line);\r
8485     } else {\r
8486         DisplayTitle(title);\r
8487     }\r
8488     gameMode = EditGame;\r
8489     ModeHighlight();\r
8490     ResetClocks();\r
8491     timeRemaining[0][1] = whiteTimeRemaining;\r
8492     timeRemaining[1][1] = blackTimeRemaining;\r
8493     DrawPosition(FALSE, boards[currentMove]);\r
8494    \r
8495     return TRUE;\r
8496 }\r
8497 \r
8498 \r
8499 void\r
8500 CopyPlayerNameIntoFileName(dest, src)\r
8501      char **dest, *src;\r
8502 {\r
8503     while (*src != NULLCHAR && *src != ',') {\r
8504         if (*src == ' ') {\r
8505             *(*dest)++ = '_';\r
8506             src++;\r
8507         } else {\r
8508             *(*dest)++ = *src++;\r
8509         }\r
8510     }\r
8511 }\r
8512 \r
8513 char *DefaultFileName(ext)\r
8514      char *ext;\r
8515 {\r
8516     static char def[MSG_SIZ];\r
8517     char *p;\r
8518 \r
8519     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
8520         p = def;\r
8521         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
8522         *p++ = '-';\r
8523         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
8524         *p++ = '.';\r
8525         strcpy(p, ext);\r
8526     } else {\r
8527         def[0] = NULLCHAR;\r
8528     }\r
8529     return def;\r
8530 }\r
8531 \r
8532 /* Save the current game to the given file */\r
8533 int\r
8534 SaveGameToFile(filename, append)\r
8535      char *filename;\r
8536      int append;\r
8537 {\r
8538     FILE *f;\r
8539     char buf[MSG_SIZ];\r
8540 \r
8541     if (strcmp(filename, "-") == 0) {\r
8542         return SaveGame(stdout, 0, NULL);\r
8543     } else {\r
8544         f = fopen(filename, append ? "a" : "w");\r
8545         if (f == NULL) {\r
8546             sprintf(buf, "Can't open \"%s\"", filename);\r
8547             DisplayError(buf, errno);\r
8548             return FALSE;\r
8549         } else {\r
8550             return SaveGame(f, 0, NULL);\r
8551         }\r
8552     }\r
8553 }\r
8554 \r
8555 char *\r
8556 SavePart(str)\r
8557      char *str;\r
8558 {\r
8559     static char buf[MSG_SIZ];\r
8560     char *p;\r
8561     \r
8562     p = strchr(str, ' ');\r
8563     if (p == NULL) return str;\r
8564     strncpy(buf, str, p - str);\r
8565     buf[p - str] = NULLCHAR;\r
8566     return buf;\r
8567 }\r
8568 \r
8569 #define PGN_MAX_LINE 75\r
8570 \r
8571 #define PGN_SIDE_WHITE  0\r
8572 #define PGN_SIDE_BLACK  1\r
8573 \r
8574 /* [AS] */\r
8575 static int FindFirstMoveOutOfBook( int side )\r
8576 {\r
8577     int result = -1;\r
8578 \r
8579     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
8580         int index = backwardMostMove;\r
8581         int has_book_hit = 0;\r
8582 \r
8583         if( (index % 2) != side ) {\r
8584             index++;\r
8585         }\r
8586 \r
8587         while( index < forwardMostMove ) {\r
8588             /* Check to see if engine is in book */\r
8589             int depth = pvInfoList[index].depth;\r
8590             int score = pvInfoList[index].score;\r
8591             int in_book = 0;\r
8592 \r
8593             if( depth <= 2 ) {\r
8594                 in_book = 1;\r
8595             }\r
8596             else if( score == 0 && depth == 63 ) {\r
8597                 in_book = 1; /* Zappa */\r
8598             }\r
8599             else if( score == 2 && depth == 99 ) {\r
8600                 in_book = 1; /* Abrok */\r
8601             }\r
8602 \r
8603             has_book_hit += in_book;\r
8604 \r
8605             if( ! in_book ) {\r
8606                 result = index;\r
8607 \r
8608                 break;\r
8609             }\r
8610 \r
8611             index += 2;\r
8612         }\r
8613     }\r
8614 \r
8615     return result;\r
8616 }\r
8617 \r
8618 /* [AS] */\r
8619 void GetOutOfBookInfo( char * buf )\r
8620 {\r
8621     int oob[2];\r
8622     int i;\r
8623     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
8624 \r
8625     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
8626     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
8627 \r
8628     *buf = '\0';\r
8629 \r
8630     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
8631         for( i=0; i<2; i++ ) {\r
8632             int idx = oob[i];\r
8633 \r
8634             if( idx >= 0 ) {\r
8635                 if( i > 0 && oob[0] >= 0 ) {\r
8636                     strcat( buf, "   " );\r
8637                 }\r
8638 \r
8639                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
8640                 sprintf( buf+strlen(buf), "%s%.2f", \r
8641                     pvInfoList[idx].score >= 0 ? "+" : "",\r
8642                     pvInfoList[idx].score / 100.0 );\r
8643             }\r
8644         }\r
8645     }\r
8646 }\r
8647 \r
8648 /* Save game in PGN style and close the file */\r
8649 int\r
8650 SaveGamePGN(f)\r
8651      FILE *f;\r
8652 {\r
8653     int i, offset, linelen, newblock;\r
8654     time_t tm;\r
8655     char *movetext;\r
8656     char numtext[32];\r
8657     int movelen, numlen, blank;\r
8658     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
8659 \r
8660     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
8661     \r
8662     tm = time((time_t *) NULL);\r
8663     \r
8664     PrintPGNTags(f, &gameInfo);\r
8665     \r
8666     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
8667         char *fen = PositionToFEN(backwardMostMove, 1);\r
8668         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
8669         fprintf(f, "\n{--------------\n");\r
8670         PrintPosition(f, backwardMostMove);\r
8671         fprintf(f, "--------------}\n");\r
8672         free(fen);\r
8673     }\r
8674     else {\r
8675         /* [AS] Out of book annotation */\r
8676         if( appData.saveOutOfBookInfo ) {\r
8677             char buf[64];\r
8678 \r
8679             GetOutOfBookInfo( buf );\r
8680 \r
8681             if( buf[0] != '\0' ) {\r
8682                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
8683             }\r
8684         }\r
8685 \r
8686         fprintf(f, "\n");\r
8687     }\r
8688 \r
8689     i = backwardMostMove;\r
8690     linelen = 0;\r
8691     newblock = TRUE;\r
8692 \r
8693     while (i < forwardMostMove) {\r
8694         /* Print comments preceding this move */\r
8695         if (commentList[i] != NULL) {\r
8696             if (linelen > 0) fprintf(f, "\n");\r
8697             fprintf(f, "{\n%s}\n", commentList[i]);\r
8698             linelen = 0;\r
8699             newblock = TRUE;\r
8700         }\r
8701 \r
8702         /* Format move number */\r
8703         if ((i % 2) == 0) {\r
8704             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
8705         } else {\r
8706             if (newblock) {\r
8707                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
8708             } else {\r
8709                 numtext[0] = NULLCHAR;\r
8710             }\r
8711         }\r
8712         numlen = strlen(numtext);\r
8713         newblock = FALSE;\r
8714 \r
8715         /* Print move number */\r
8716         blank = linelen > 0 && numlen > 0;\r
8717         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
8718             fprintf(f, "\n");\r
8719             linelen = 0;\r
8720             blank = 0;\r
8721         }\r
8722         if (blank) {\r
8723             fprintf(f, " ");\r
8724             linelen++;\r
8725         }\r
8726         fprintf(f, numtext);\r
8727         linelen += numlen;\r
8728 \r
8729         /* Get move */\r
8730         movetext = SavePart(parseList[i]);\r
8731 \r
8732         /* [AS] Add PV info if present */\r
8733         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
8734             /* [HGM] add time */\r
8735             char buf[MSG_SIZ]; int seconds = 0;\r
8736 \r
8737             if(i >= backwardMostMove) {\r
8738                 /* take the time that changed */\r
8739                 seconds = timeRemaining[0][i] - timeRemaining[0][i+1];\r
8740               if(seconds <= 0)\r
8741                     seconds = timeRemaining[1][i] - timeRemaining[1][i+1];\r
8742             }\r
8743             seconds /= 1000;\r
8744             seconds = pvInfoList[i].time/100;\r
8745     if (appData.debugMode) {\r
8746         fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",\r
8747                 timeRemaining[0][i+1], timeRemaining[0][i],\r
8748                      timeRemaining[1][i+1], timeRemaining[1][i], seconds\r
8749         );\r
8750     }\r
8751 \r
8752             if( seconds < 0 ) buf[0] = 0; else\r
8753             if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0);\r
8754             else    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
8755 \r
8756             sprintf( move_buffer, "%s {%s%.2f/%d%s}", \r
8757                 movetext, \r
8758                 pvInfoList[i].score >= 0 ? "+" : "",\r
8759                 pvInfoList[i].score / 100.0,\r
8760                 pvInfoList[i].depth,\r
8761                 buf );\r
8762             movetext = move_buffer;\r
8763         }\r
8764 \r
8765         movelen = strlen(movetext);\r
8766 \r
8767         /* Print move */\r
8768         blank = linelen > 0 && movelen > 0;\r
8769         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
8770             fprintf(f, "\n");\r
8771             linelen = 0;\r
8772             blank = 0;\r
8773         }\r
8774         if (blank) {\r
8775             fprintf(f, " ");\r
8776             linelen++;\r
8777         }\r
8778         fprintf(f, movetext);\r
8779         linelen += movelen;\r
8780 \r
8781         i++;\r
8782     }\r
8783     \r
8784     /* Start a new line */\r
8785     if (linelen > 0) fprintf(f, "\n");\r
8786 \r
8787     /* Print comments after last move */\r
8788     if (commentList[i] != NULL) {\r
8789         fprintf(f, "{\n%s}\n", commentList[i]);\r
8790     }\r
8791 \r
8792     /* Print result */\r
8793     if (gameInfo.resultDetails != NULL &&\r
8794         gameInfo.resultDetails[0] != NULLCHAR) {\r
8795         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
8796                 PGNResult(gameInfo.result));\r
8797     } else {\r
8798         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
8799     }\r
8800 \r
8801     fclose(f);\r
8802     return TRUE;\r
8803 }\r
8804 \r
8805 /* Save game in old style and close the file */\r
8806 int\r
8807 SaveGameOldStyle(f)\r
8808      FILE *f;\r
8809 {\r
8810     int i, offset;\r
8811     time_t tm;\r
8812     \r
8813     tm = time((time_t *) NULL);\r
8814     \r
8815     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
8816     PrintOpponents(f);\r
8817     \r
8818     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
8819         fprintf(f, "\n[--------------\n");\r
8820         PrintPosition(f, backwardMostMove);\r
8821         fprintf(f, "--------------]\n");\r
8822     } else {\r
8823         fprintf(f, "\n");\r
8824     }\r
8825 \r
8826     i = backwardMostMove;\r
8827     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
8828 \r
8829     while (i < forwardMostMove) {\r
8830         if (commentList[i] != NULL) {\r
8831             fprintf(f, "[%s]\n", commentList[i]);\r
8832         }\r
8833 \r
8834         if ((i % 2) == 1) {\r
8835             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
8836             i++;\r
8837         } else {\r
8838             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
8839             i++;\r
8840             if (commentList[i] != NULL) {\r
8841                 fprintf(f, "\n");\r
8842                 continue;\r
8843             }\r
8844             if (i >= forwardMostMove) {\r
8845                 fprintf(f, "\n");\r
8846                 break;\r
8847             }\r
8848             fprintf(f, "%s\n", parseList[i]);\r
8849             i++;\r
8850         }\r
8851     }\r
8852     \r
8853     if (commentList[i] != NULL) {\r
8854         fprintf(f, "[%s]\n", commentList[i]);\r
8855     }\r
8856 \r
8857     /* This isn't really the old style, but it's close enough */\r
8858     if (gameInfo.resultDetails != NULL &&\r
8859         gameInfo.resultDetails[0] != NULLCHAR) {\r
8860         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
8861                 gameInfo.resultDetails);\r
8862     } else {\r
8863         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
8864     }\r
8865 \r
8866     fclose(f);\r
8867     return TRUE;\r
8868 }\r
8869 \r
8870 /* Save the current game to open file f and close the file */\r
8871 int\r
8872 SaveGame(f, dummy, dummy2)\r
8873      FILE *f;\r
8874      int dummy;\r
8875      char *dummy2;\r
8876 {\r
8877     if (gameMode == EditPosition) EditPositionDone();\r
8878     if (appData.oldSaveStyle)\r
8879       return SaveGameOldStyle(f);\r
8880     else\r
8881       return SaveGamePGN(f);\r
8882 }\r
8883 \r
8884 /* Save the current position to the given file */\r
8885 int\r
8886 SavePositionToFile(filename)\r
8887      char *filename;\r
8888 {\r
8889     FILE *f;\r
8890     char buf[MSG_SIZ];\r
8891 \r
8892     if (strcmp(filename, "-") == 0) {\r
8893         return SavePosition(stdout, 0, NULL);\r
8894     } else {\r
8895         f = fopen(filename, "a");\r
8896         if (f == NULL) {\r
8897             sprintf(buf, "Can't open \"%s\"", filename);\r
8898             DisplayError(buf, errno);\r
8899             return FALSE;\r
8900         } else {\r
8901             SavePosition(f, 0, NULL);\r
8902             return TRUE;\r
8903         }\r
8904     }\r
8905 }\r
8906 \r
8907 /* Save the current position to the given open file and close the file */\r
8908 int\r
8909 SavePosition(f, dummy, dummy2)\r
8910      FILE *f;\r
8911      int dummy;\r
8912      char *dummy2;\r
8913 {\r
8914     time_t tm;\r
8915     char *fen;\r
8916     \r
8917     if (appData.oldSaveStyle) {\r
8918         tm = time((time_t *) NULL);\r
8919     \r
8920         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
8921         PrintOpponents(f);\r
8922         fprintf(f, "[--------------\n");\r
8923         PrintPosition(f, currentMove);\r
8924         fprintf(f, "--------------]\n");\r
8925     } else {\r
8926         fen = PositionToFEN(currentMove, 1);\r
8927         fprintf(f, "%s\n", fen);\r
8928         free(fen);\r
8929     }\r
8930     fclose(f);\r
8931     return TRUE;\r
8932 }\r
8933 \r
8934 void\r
8935 ReloadCmailMsgEvent(unregister)\r
8936      int unregister;\r
8937 {\r
8938 #if !WIN32\r
8939     static char *inFilename = NULL;\r
8940     static char *outFilename;\r
8941     int i;\r
8942     struct stat inbuf, outbuf;\r
8943     int status;\r
8944     \r
8945     /* Any registered moves are unregistered if unregister is set, */\r
8946     /* i.e. invoked by the signal handler */\r
8947     if (unregister) {\r
8948         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
8949             cmailMoveRegistered[i] = FALSE;\r
8950             if (cmailCommentList[i] != NULL) {\r
8951                 free(cmailCommentList[i]);\r
8952                 cmailCommentList[i] = NULL;\r
8953             }\r
8954         }\r
8955         nCmailMovesRegistered = 0;\r
8956     }\r
8957 \r
8958     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
8959         cmailResult[i] = CMAIL_NOT_RESULT;\r
8960     }\r
8961     nCmailResults = 0;\r
8962 \r
8963     if (inFilename == NULL) {\r
8964         /* Because the filenames are static they only get malloced once  */\r
8965         /* and they never get freed                                      */\r
8966         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
8967         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
8968 \r
8969         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
8970         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
8971     }\r
8972     \r
8973     status = stat(outFilename, &outbuf);\r
8974     if (status < 0) {\r
8975         cmailMailedMove = FALSE;\r
8976     } else {\r
8977         status = stat(inFilename, &inbuf);\r
8978         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
8979     }\r
8980     \r
8981     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
8982        counts the games, notes how each one terminated, etc.\r
8983        \r
8984        It would be nice to remove this kludge and instead gather all\r
8985        the information while building the game list.  (And to keep it\r
8986        in the game list nodes instead of having a bunch of fixed-size\r
8987        parallel arrays.)  Note this will require getting each game's\r
8988        termination from the PGN tags, as the game list builder does\r
8989        not process the game moves.  --mann\r
8990        */\r
8991     cmailMsgLoaded = TRUE;\r
8992     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
8993     \r
8994     /* Load first game in the file or popup game menu */\r
8995     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
8996 \r
8997 #endif /* !WIN32 */\r
8998     return;\r
8999 }\r
9000 \r
9001 int\r
9002 RegisterMove()\r
9003 {\r
9004     FILE *f;\r
9005     char string[MSG_SIZ];\r
9006 \r
9007     if (   cmailMailedMove\r
9008         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9009         return TRUE;            /* Allow free viewing  */\r
9010     }\r
9011 \r
9012     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9013     /* with the move registered when the conditions for registering no   */\r
9014     /* longer hold                                                       */\r
9015     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9016         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9017         nCmailMovesRegistered --;\r
9018 \r
9019         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9020           {\r
9021               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9022               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9023           }\r
9024     }\r
9025 \r
9026     if (cmailOldMove == -1) {\r
9027         DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);\r
9028         return FALSE;\r
9029     }\r
9030 \r
9031     if (currentMove > cmailOldMove + 1) {\r
9032         DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);\r
9033         return FALSE;\r
9034     }\r
9035 \r
9036     if (currentMove < cmailOldMove) {\r
9037         DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);\r
9038         return FALSE;\r
9039     }\r
9040 \r
9041     if (forwardMostMove > currentMove) {\r
9042         /* Silently truncate extra moves */\r
9043         TruncateGame();\r
9044     }\r
9045 \r
9046     if (   (currentMove == cmailOldMove + 1)\r
9047         || (   (currentMove == cmailOldMove)\r
9048             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9049                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9050         if (gameInfo.result != GameUnfinished) {\r
9051             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9052         }\r
9053 \r
9054         if (commentList[currentMove] != NULL) {\r
9055             cmailCommentList[lastLoadGameNumber - 1]\r
9056               = StrSave(commentList[currentMove]);\r
9057         }\r
9058         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9059 \r
9060         if (appData.debugMode)\r
9061           fprintf(debugFP, "Saving %s for game %d\n",\r
9062                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9063 \r
9064         sprintf(string,\r
9065                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9066         \r
9067         f = fopen(string, "w");\r
9068         if (appData.oldSaveStyle) {\r
9069             SaveGameOldStyle(f); /* also closes the file */\r
9070             \r
9071             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9072             f = fopen(string, "w");\r
9073             SavePosition(f, 0, NULL); /* also closes the file */\r
9074         } else {\r
9075             fprintf(f, "{--------------\n");\r
9076             PrintPosition(f, currentMove);\r
9077             fprintf(f, "--------------}\n\n");\r
9078             \r
9079             SaveGame(f, 0, NULL); /* also closes the file*/\r
9080         }\r
9081         \r
9082         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9083         nCmailMovesRegistered ++;\r
9084     } else if (nCmailGames == 1) {\r
9085         DisplayError("You have not made a move yet", 0);\r
9086         return FALSE;\r
9087     }\r
9088 \r
9089     return TRUE;\r
9090 }\r
9091 \r
9092 void\r
9093 MailMoveEvent()\r
9094 {\r
9095 #if !WIN32\r
9096     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9097     FILE *commandOutput;\r
9098     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9099     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9100     int nBuffers;\r
9101     int i;\r
9102     int archived;\r
9103     char *arcDir;\r
9104 \r
9105     if (! cmailMsgLoaded) {\r
9106         DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);\r
9107         return;\r
9108     }\r
9109 \r
9110     if (nCmailGames == nCmailResults) {\r
9111         DisplayError("No unfinished games", 0);\r
9112         return;\r
9113     }\r
9114 \r
9115 #if CMAIL_PROHIBIT_REMAIL\r
9116     if (cmailMailedMove) {\r
9117         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
9118         DisplayError(msg, 0);\r
9119         return;\r
9120     }\r
9121 #endif\r
9122 \r
9123     if (! (cmailMailedMove || RegisterMove())) return;\r
9124     \r
9125     if (   cmailMailedMove\r
9126         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
9127         sprintf(string, partCommandString,\r
9128                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
9129         commandOutput = popen(string, "rb");\r
9130 \r
9131         if (commandOutput == NULL) {\r
9132             DisplayError("Failed to invoke cmail", 0);\r
9133         } else {\r
9134             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
9135                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
9136             }\r
9137             if (nBuffers > 1) {\r
9138                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
9139                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
9140                 nBytes = MSG_SIZ - 1;\r
9141             } else {\r
9142                 (void) memcpy(msg, buffer, nBytes);\r
9143             }\r
9144             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
9145 \r
9146             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
9147                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
9148 \r
9149                 archived = TRUE;\r
9150                 for (i = 0; i < nCmailGames; i ++) {\r
9151                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
9152                         archived = FALSE;\r
9153                     }\r
9154                 }\r
9155                 if (   archived\r
9156                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
9157                         != NULL)) {\r
9158                     sprintf(buffer, "%s/%s.%s.archive",\r
9159                             arcDir,\r
9160                             appData.cmailGameName,\r
9161                             gameInfo.date);\r
9162                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
9163                     cmailMsgLoaded = FALSE;\r
9164                 }\r
9165             }\r
9166 \r
9167             DisplayInformation(msg);\r
9168             pclose(commandOutput);\r
9169         }\r
9170     } else {\r
9171         if ((*cmailMsg) != '\0') {\r
9172             DisplayInformation(cmailMsg);\r
9173         }\r
9174     }\r
9175 \r
9176     return;\r
9177 #endif /* !WIN32 */\r
9178 }\r
9179 \r
9180 char *\r
9181 CmailMsg()\r
9182 {\r
9183 #if WIN32\r
9184     return NULL;\r
9185 #else\r
9186     int  prependComma = 0;\r
9187     char number[5];\r
9188     char string[MSG_SIZ];       /* Space for game-list */\r
9189     int  i;\r
9190     \r
9191     if (!cmailMsgLoaded) return "";\r
9192 \r
9193     if (cmailMailedMove) {\r
9194         sprintf(cmailMsg, "Waiting for reply from opponent\n");\r
9195     } else {\r
9196         /* Create a list of games left */\r
9197         sprintf(string, "[");\r
9198         for (i = 0; i < nCmailGames; i ++) {\r
9199             if (! (   cmailMoveRegistered[i]\r
9200                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
9201                 if (prependComma) {\r
9202                     sprintf(number, ",%d", i + 1);\r
9203                 } else {\r
9204                     sprintf(number, "%d", i + 1);\r
9205                     prependComma = 1;\r
9206                 }\r
9207                 \r
9208                 strcat(string, number);\r
9209             }\r
9210         }\r
9211         strcat(string, "]");\r
9212 \r
9213         if (nCmailMovesRegistered + nCmailResults == 0) {\r
9214             switch (nCmailGames) {\r
9215               case 1:\r
9216                 sprintf(cmailMsg,\r
9217                         "Still need to make move for game\n");\r
9218                 break;\r
9219                 \r
9220               case 2:\r
9221                 sprintf(cmailMsg,\r
9222                         "Still need to make moves for both games\n");\r
9223                 break;\r
9224                 \r
9225               default:\r
9226                 sprintf(cmailMsg,\r
9227                         "Still need to make moves for all %d games\n",\r
9228                         nCmailGames);\r
9229                 break;\r
9230             }\r
9231         } else {\r
9232             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
9233               case 1:\r
9234                 sprintf(cmailMsg,\r
9235                         "Still need to make a move for game %s\n",\r
9236                         string);\r
9237                 break;\r
9238                 \r
9239               case 0:\r
9240                 if (nCmailResults == nCmailGames) {\r
9241                     sprintf(cmailMsg, "No unfinished games\n");\r
9242                 } else {\r
9243                     sprintf(cmailMsg, "Ready to send mail\n");\r
9244                 }\r
9245                 break;\r
9246                 \r
9247               default:\r
9248                 sprintf(cmailMsg,\r
9249                         "Still need to make moves for games %s\n",\r
9250                         string);\r
9251             }\r
9252         }\r
9253     }\r
9254     return cmailMsg;\r
9255 #endif /* WIN32 */\r
9256 }\r
9257 \r
9258 void\r
9259 ResetGameEvent()\r
9260 {\r
9261     if (gameMode == Training)\r
9262       SetTrainingModeOff();\r
9263 \r
9264     Reset(TRUE, TRUE);\r
9265     cmailMsgLoaded = FALSE;\r
9266     if (appData.icsActive) {\r
9267       SendToICS(ics_prefix);\r
9268       SendToICS("refresh\n");\r
9269     }\r
9270 }\r
9271 \r
9272 void\r
9273 ExitEvent(status)\r
9274      int status;\r
9275 {\r
9276     exiting++;\r
9277     if (exiting > 2) {\r
9278       /* Give up on clean exit */\r
9279       exit(status);\r
9280     }\r
9281     if (exiting > 1) {\r
9282       /* Keep trying for clean exit */\r
9283       return;\r
9284     }\r
9285 \r
9286     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
9287 \r
9288     if (telnetISR != NULL) {\r
9289       RemoveInputSource(telnetISR);\r
9290     }\r
9291     if (icsPR != NoProc) {\r
9292       DestroyChildProcess(icsPR, TRUE);\r
9293     }\r
9294 #if 0\r
9295     /* Save game if resource set and not already saved by GameEnds() */\r
9296     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
9297                              && forwardMostMove > 0) {\r
9298       if (*appData.saveGameFile != NULLCHAR) {\r
9299         SaveGameToFile(appData.saveGameFile, TRUE);\r
9300       } else if (appData.autoSaveGames) {\r
9301         AutoSaveGame();\r
9302       }\r
9303       if (*appData.savePositionFile != NULLCHAR) {\r
9304         SavePositionToFile(appData.savePositionFile);\r
9305       }\r
9306     }\r
9307     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
9308 #else\r
9309     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
9310     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "aborted" : gameInfo.resultDetails, GE_PLAYER);\r
9311 #endif\r
9312     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
9313     /* make sure this other one finishes before killing it!                  */\r
9314     if(endingGame) { int count = 0;\r
9315         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
9316         while(endingGame && count++ < 10) DoSleep(1);\r
9317         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
9318     }\r
9319 \r
9320     /* Kill off chess programs */\r
9321     if (first.pr != NoProc) {\r
9322         ExitAnalyzeMode();\r
9323         \r
9324         DoSleep( appData.delayBeforeQuit );\r
9325         SendToProgram("quit\n", &first);\r
9326         DoSleep( appData.delayAfterQuit );\r
9327         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
9328     }\r
9329     if (second.pr != NoProc) {\r
9330         DoSleep( appData.delayBeforeQuit );\r
9331         SendToProgram("quit\n", &second);\r
9332         DoSleep( appData.delayAfterQuit );\r
9333         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
9334     }\r
9335     if (first.isr != NULL) {\r
9336         RemoveInputSource(first.isr);\r
9337     }\r
9338     if (second.isr != NULL) {\r
9339         RemoveInputSource(second.isr);\r
9340     }\r
9341 \r
9342     ShutDownFrontEnd();\r
9343     exit(status);\r
9344 }\r
9345 \r
9346 void\r
9347 PauseEvent()\r
9348 {\r
9349     if (appData.debugMode)\r
9350         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
9351     if (pausing) {\r
9352         pausing = FALSE;\r
9353         ModeHighlight();\r
9354         if (gameMode == MachinePlaysWhite ||\r
9355             gameMode == MachinePlaysBlack) {\r
9356             StartClocks();\r
9357         } else {\r
9358             DisplayBothClocks();\r
9359         }\r
9360         if (gameMode == PlayFromGameFile) {\r
9361             if (appData.timeDelay >= 0) \r
9362                 AutoPlayGameLoop();\r
9363         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
9364             Reset(FALSE, TRUE);\r
9365             SendToICS(ics_prefix);\r
9366             SendToICS("refresh\n");\r
9367         } else if (currentMove < forwardMostMove) {\r
9368             ForwardInner(forwardMostMove);\r
9369         }\r
9370         pauseExamInvalid = FALSE;\r
9371     } else {\r
9372         switch (gameMode) {\r
9373           default:\r
9374             return;\r
9375           case IcsExamining:\r
9376             pauseExamForwardMostMove = forwardMostMove;\r
9377             pauseExamInvalid = FALSE;\r
9378             /* fall through */\r
9379           case IcsObserving:\r
9380           case IcsPlayingWhite:\r
9381           case IcsPlayingBlack:\r
9382             pausing = TRUE;\r
9383             ModeHighlight();\r
9384             return;\r
9385           case PlayFromGameFile:\r
9386             (void) StopLoadGameTimer();\r
9387             pausing = TRUE;\r
9388             ModeHighlight();\r
9389             break;\r
9390           case BeginningOfGame:\r
9391             if (appData.icsActive) return;\r
9392             /* else fall through */\r
9393           case MachinePlaysWhite:\r
9394           case MachinePlaysBlack:\r
9395           case TwoMachinesPlay:\r
9396             if (forwardMostMove == 0)\r
9397               return;           /* don't pause if no one has moved */\r
9398             if ((gameMode == MachinePlaysWhite &&\r
9399                  !WhiteOnMove(forwardMostMove)) ||\r
9400                 (gameMode == MachinePlaysBlack &&\r
9401                  WhiteOnMove(forwardMostMove))) {\r
9402                 StopClocks();\r
9403             }\r
9404             pausing = TRUE;\r
9405             ModeHighlight();\r
9406             break;\r
9407         }\r
9408     }\r
9409 }\r
9410 \r
9411 void\r
9412 EditCommentEvent()\r
9413 {\r
9414     char title[MSG_SIZ];\r
9415 \r
9416     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
9417         strcpy(title, "Edit comment");\r
9418     } else {\r
9419         sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,\r
9420                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
9421                 parseList[currentMove - 1]);\r
9422     }\r
9423 \r
9424     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
9425 }\r
9426 \r
9427 \r
9428 void\r
9429 EditTagsEvent()\r
9430 {\r
9431     char *tags = PGNTags(&gameInfo);\r
9432     EditTagsPopUp(tags);\r
9433     free(tags);\r
9434 }\r
9435 \r
9436 void\r
9437 AnalyzeModeEvent()\r
9438 {\r
9439     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
9440       return;\r
9441 \r
9442     if (gameMode != AnalyzeFile) {\r
9443         EditGameEvent();\r
9444         if (gameMode != EditGame) return;\r
9445         ResurrectChessProgram();\r
9446         SendToProgram("analyze\n", &first);\r
9447         first.analyzing = TRUE;\r
9448         /*first.maybeThinking = TRUE;*/\r
9449         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
9450         AnalysisPopUp("Analysis",\r
9451                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
9452     }\r
9453     gameMode = AnalyzeMode;\r
9454     pausing = FALSE;\r
9455     ModeHighlight();\r
9456     SetGameInfo();\r
9457 \r
9458     StartAnalysisClock();\r
9459     GetTimeMark(&lastNodeCountTime);\r
9460     lastNodeCount = 0;\r
9461 }\r
9462 \r
9463 void\r
9464 AnalyzeFileEvent()\r
9465 {\r
9466     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
9467       return;\r
9468 \r
9469     if (gameMode != AnalyzeMode) {\r
9470         EditGameEvent();\r
9471         if (gameMode != EditGame) return;\r
9472         ResurrectChessProgram();\r
9473         SendToProgram("analyze\n", &first);\r
9474         first.analyzing = TRUE;\r
9475         /*first.maybeThinking = TRUE;*/\r
9476         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
9477         AnalysisPopUp("Analysis",\r
9478                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
9479     }\r
9480     gameMode = AnalyzeFile;\r
9481     pausing = FALSE;\r
9482     ModeHighlight();\r
9483     SetGameInfo();\r
9484 \r
9485     StartAnalysisClock();\r
9486     GetTimeMark(&lastNodeCountTime);\r
9487     lastNodeCount = 0;\r
9488 }\r
9489 \r
9490 void\r
9491 MachineWhiteEvent()\r
9492 {\r
9493     char buf[MSG_SIZ];\r
9494 \r
9495     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
9496       return;\r
9497 \r
9498 \r
9499     if (gameMode == PlayFromGameFile || \r
9500         gameMode == TwoMachinesPlay  || \r
9501         gameMode == Training         || \r
9502         gameMode == AnalyzeMode      || \r
9503         gameMode == EndOfGame)\r
9504         EditGameEvent();\r
9505 \r
9506     if (gameMode == EditPosition) \r
9507         EditPositionDone();\r
9508 \r
9509     if (!WhiteOnMove(currentMove)) {\r
9510         DisplayError("It is not White's turn", 0);\r
9511         return;\r
9512     }\r
9513   \r
9514     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
9515       ExitAnalyzeMode();\r
9516 \r
9517     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
9518         gameMode == AnalyzeFile)\r
9519         TruncateGame();\r
9520 \r
9521     ResurrectChessProgram();    /* in case it isn't running */\r
9522     gameMode = MachinePlaysWhite;\r
9523     pausing = FALSE;\r
9524     ModeHighlight();\r
9525     SetGameInfo();\r
9526     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
9527     DisplayTitle(buf);\r
9528     if (first.sendName) {\r
9529       sprintf(buf, "name %s\n", gameInfo.black);\r
9530       SendToProgram(buf, &first);\r
9531     }\r
9532     if (first.sendTime) {\r
9533       if (first.useColors) {\r
9534         SendToProgram("black\n", &first); /*gnu kludge*/\r
9535       }\r
9536       SendTimeRemaining(&first, TRUE);\r
9537     }\r
9538     if (first.useColors) {\r
9539       SendToProgram("white\ngo\n", &first);\r
9540     } else {\r
9541       SendToProgram("go\n", &first);\r
9542     }\r
9543     SetMachineThinkingEnables();\r
9544     first.maybeThinking = TRUE;\r
9545     StartClocks();\r
9546 \r
9547     if (appData.autoFlipView && !flipView) {\r
9548       flipView = !flipView;\r
9549       DrawPosition(FALSE, NULL);\r
9550     }\r
9551 }\r
9552 \r
9553 void\r
9554 MachineBlackEvent()\r
9555 {\r
9556     char buf[MSG_SIZ];\r
9557 \r
9558     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
9559         return;\r
9560 \r
9561 \r
9562     if (gameMode == PlayFromGameFile || \r
9563         gameMode == TwoMachinesPlay  || \r
9564         gameMode == Training         || \r
9565         gameMode == AnalyzeMode      || \r
9566         gameMode == EndOfGame)\r
9567         EditGameEvent();\r
9568 \r
9569     if (gameMode == EditPosition) \r
9570         EditPositionDone();\r
9571 \r
9572     if (WhiteOnMove(currentMove)) {\r
9573         DisplayError("It is not Black's turn", 0);\r
9574         return;\r
9575     }\r
9576     \r
9577     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
9578       ExitAnalyzeMode();\r
9579 \r
9580     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
9581         gameMode == AnalyzeFile)\r
9582         TruncateGame();\r
9583 \r
9584     ResurrectChessProgram();    /* in case it isn't running */\r
9585     gameMode = MachinePlaysBlack;\r
9586     pausing = FALSE;\r
9587     ModeHighlight();\r
9588     SetGameInfo();\r
9589     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
9590     DisplayTitle(buf);\r
9591     if (first.sendName) {\r
9592       sprintf(buf, "name %s\n", gameInfo.white);\r
9593       SendToProgram(buf, &first);\r
9594     }\r
9595     if (first.sendTime) {\r
9596       if (first.useColors) {\r
9597         SendToProgram("white\n", &first); /*gnu kludge*/\r
9598       }\r
9599       SendTimeRemaining(&first, FALSE);\r
9600     }\r
9601     if (first.useColors) {\r
9602       SendToProgram("black\ngo\n", &first);\r
9603     } else {\r
9604       SendToProgram("go\n", &first);\r
9605     }\r
9606     SetMachineThinkingEnables();\r
9607     first.maybeThinking = TRUE;\r
9608     StartClocks();\r
9609 \r
9610     if (appData.autoFlipView && flipView) {\r
9611       flipView = !flipView;\r
9612       DrawPosition(FALSE, NULL);\r
9613     }\r
9614 }\r
9615 \r
9616 \r
9617 void\r
9618 DisplayTwoMachinesTitle()\r
9619 {\r
9620     char buf[MSG_SIZ];\r
9621     if (appData.matchGames > 0) {\r
9622         if (first.twoMachinesColor[0] == 'w') {\r
9623             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
9624                     gameInfo.white, gameInfo.black,\r
9625                     first.matchWins, second.matchWins,\r
9626                     matchGame - 1 - (first.matchWins + second.matchWins));\r
9627         } else {\r
9628             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
9629                     gameInfo.white, gameInfo.black,\r
9630                     second.matchWins, first.matchWins,\r
9631                     matchGame - 1 - (first.matchWins + second.matchWins));\r
9632         }\r
9633     } else {\r
9634         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
9635     }\r
9636     DisplayTitle(buf);\r
9637 }\r
9638 \r
9639 void\r
9640 TwoMachinesEvent P((void))\r
9641 {\r
9642     int i;\r
9643     char buf[MSG_SIZ];\r
9644     ChessProgramState *onmove;\r
9645     \r
9646     if (appData.noChessProgram) return;\r
9647 \r
9648     switch (gameMode) {\r
9649       case TwoMachinesPlay:\r
9650         return;\r
9651       case MachinePlaysWhite:\r
9652       case MachinePlaysBlack:\r
9653         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
9654             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
9655             return;\r
9656         }\r
9657         /* fall through */\r
9658       case BeginningOfGame:\r
9659       case PlayFromGameFile:\r
9660       case EndOfGame:\r
9661         EditGameEvent();\r
9662         if (gameMode != EditGame) return;\r
9663         break;\r
9664       case EditPosition:\r
9665         EditPositionDone();\r
9666         break;\r
9667       case AnalyzeMode:\r
9668       case AnalyzeFile:\r
9669         ExitAnalyzeMode();\r
9670         break;\r
9671       case EditGame:\r
9672       default:\r
9673         break;\r
9674     }\r
9675 \r
9676     forwardMostMove = currentMove;\r
9677     ResurrectChessProgram();    /* in case first program isn't running */\r
9678 \r
9679     if (second.pr == NULL) {\r
9680         StartChessProgram(&second);\r
9681         if (second.protocolVersion == 1) {\r
9682           TwoMachinesEventIfReady();\r
9683         } else {\r
9684           /* kludge: allow timeout for initial "feature" command */\r
9685           FreezeUI();\r
9686           DisplayMessage("", "Starting second chess program");\r
9687           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
9688         }\r
9689         return;\r
9690     }\r
9691     DisplayMessage("", "");\r
9692     if (appData.debugMode) {\r
9693         fprintf(debugFP, "From TwoMachines\n");\r
9694     }\r
9695     InitChessProgram(&second, FALSE);\r
9696     SendToProgram("force\n", &second);\r
9697     if (startedFromSetupPosition) {\r
9698         SendBoard(&second, backwardMostMove);\r
9699     if (appData.debugMode) {\r
9700         fprintf(debugFP, "Two Machines\n");\r
9701     }\r
9702     }\r
9703     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
9704         SendMoveToProgram(i, &second);\r
9705     }\r
9706 \r
9707     gameMode = TwoMachinesPlay;\r
9708     pausing = FALSE;\r
9709     ModeHighlight();\r
9710     SetGameInfo();\r
9711     DisplayTwoMachinesTitle();\r
9712     firstMove = TRUE;\r
9713     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
9714         onmove = &first;\r
9715     } else {\r
9716         onmove = &second;\r
9717     }\r
9718 \r
9719     SendToProgram(first.computerString, &first);\r
9720     if (first.sendName) {\r
9721       sprintf(buf, "name %s\n", second.tidy);\r
9722       SendToProgram(buf, &first);\r
9723     }\r
9724     SendToProgram(second.computerString, &second);\r
9725     if (second.sendName) {\r
9726       sprintf(buf, "name %s\n", first.tidy);\r
9727       SendToProgram(buf, &second);\r
9728     }\r
9729 \r
9730     if (!first.sendTime || !second.sendTime) {\r
9731         ResetClocks();\r
9732         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9733         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9734     }\r
9735     if (onmove->sendTime) {\r
9736       if (onmove->useColors) {\r
9737         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
9738       }\r
9739       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
9740     }\r
9741     if (onmove->useColors) {\r
9742       SendToProgram(onmove->twoMachinesColor, onmove);\r
9743     }\r
9744     SendToProgram("go\n", onmove);\r
9745     onmove->maybeThinking = TRUE;\r
9746     SetMachineThinkingEnables();\r
9747 \r
9748     StartClocks();\r
9749 }\r
9750 \r
9751 void\r
9752 TrainingEvent()\r
9753 {\r
9754     if (gameMode == Training) {\r
9755       SetTrainingModeOff();\r
9756       gameMode = PlayFromGameFile;\r
9757       DisplayMessage("", "Training mode off");\r
9758     } else {\r
9759       gameMode = Training;\r
9760       animateTraining = appData.animate;\r
9761 \r
9762       /* make sure we are not already at the end of the game */\r
9763       if (currentMove < forwardMostMove) {\r
9764         SetTrainingModeOn();\r
9765         DisplayMessage("", "Training mode on");\r
9766       } else {\r
9767         gameMode = PlayFromGameFile;\r
9768         DisplayError("Already at end of game", 0);\r
9769       }\r
9770     }\r
9771     ModeHighlight();\r
9772 }\r
9773 \r
9774 void\r
9775 IcsClientEvent()\r
9776 {\r
9777     if (!appData.icsActive) return;\r
9778     switch (gameMode) {\r
9779       case IcsPlayingWhite:\r
9780       case IcsPlayingBlack:\r
9781       case IcsObserving:\r
9782       case IcsIdle:\r
9783       case BeginningOfGame:\r
9784       case IcsExamining:\r
9785         return;\r
9786 \r
9787       case EditGame:\r
9788         break;\r
9789 \r
9790       case EditPosition:\r
9791         EditPositionDone();\r
9792         break;\r
9793 \r
9794       case AnalyzeMode:\r
9795       case AnalyzeFile:\r
9796         ExitAnalyzeMode();\r
9797         break;\r
9798         \r
9799       default:\r
9800         EditGameEvent();\r
9801         break;\r
9802     }\r
9803 \r
9804     gameMode = IcsIdle;\r
9805     ModeHighlight();\r
9806     return;\r
9807 }\r
9808 \r
9809 \r
9810 void\r
9811 EditGameEvent()\r
9812 {\r
9813     int i;\r
9814 \r
9815     switch (gameMode) {\r
9816       case Training:\r
9817         SetTrainingModeOff();\r
9818         break;\r
9819       case MachinePlaysWhite:\r
9820       case MachinePlaysBlack:\r
9821       case BeginningOfGame:\r
9822         SendToProgram("force\n", &first);\r
9823         SetUserThinkingEnables();\r
9824         break;\r
9825       case PlayFromGameFile:\r
9826         (void) StopLoadGameTimer();\r
9827         if (gameFileFP != NULL) {\r
9828             gameFileFP = NULL;\r
9829         }\r
9830         break;\r
9831       case EditPosition:\r
9832         EditPositionDone();\r
9833         break;\r
9834       case AnalyzeMode:\r
9835       case AnalyzeFile:\r
9836         ExitAnalyzeMode();\r
9837         SendToProgram("force\n", &first);\r
9838         break;\r
9839       case TwoMachinesPlay:\r
9840         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
9841         ResurrectChessProgram();\r
9842         SetUserThinkingEnables();\r
9843         break;\r
9844       case EndOfGame:\r
9845         ResurrectChessProgram();\r
9846         break;\r
9847       case IcsPlayingBlack:\r
9848       case IcsPlayingWhite:\r
9849         DisplayError("Warning: You are still playing a game", 0);\r
9850         break;\r
9851       case IcsObserving:\r
9852         DisplayError("Warning: You are still observing a game", 0);\r
9853         break;\r
9854       case IcsExamining:\r
9855         DisplayError("Warning: You are still examining a game", 0);\r
9856         break;\r
9857       case IcsIdle:\r
9858         break;\r
9859       case EditGame:\r
9860       default:\r
9861         return;\r
9862     }\r
9863     \r
9864     pausing = FALSE;\r
9865     StopClocks();\r
9866     first.offeredDraw = second.offeredDraw = 0;\r
9867 \r
9868     if (gameMode == PlayFromGameFile) {\r
9869         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9870         blackTimeRemaining = timeRemaining[1][currentMove];\r
9871         DisplayTitle("");\r
9872     }\r
9873 \r
9874     if (gameMode == MachinePlaysWhite ||\r
9875         gameMode == MachinePlaysBlack ||\r
9876         gameMode == TwoMachinesPlay ||\r
9877         gameMode == EndOfGame) {\r
9878         i = forwardMostMove;\r
9879         while (i > currentMove) {\r
9880             SendToProgram("undo\n", &first);\r
9881             i--;\r
9882         }\r
9883         whiteTimeRemaining = timeRemaining[0][currentMove];\r
9884         blackTimeRemaining = timeRemaining[1][currentMove];\r
9885         DisplayBothClocks();\r
9886         if (whiteFlag || blackFlag) {\r
9887             whiteFlag = blackFlag = 0;\r
9888         }\r
9889         DisplayTitle("");\r
9890     }           \r
9891     \r
9892     gameMode = EditGame;\r
9893     ModeHighlight();\r
9894     SetGameInfo();\r
9895 }\r
9896 \r
9897 \r
9898 void\r
9899 EditPositionEvent()\r
9900 {\r
9901     if (gameMode == EditPosition) {\r
9902         EditGameEvent();\r
9903         return;\r
9904     }\r
9905     \r
9906     EditGameEvent();\r
9907     if (gameMode != EditGame) return;\r
9908     \r
9909     gameMode = EditPosition;\r
9910     ModeHighlight();\r
9911     SetGameInfo();\r
9912     if (currentMove > 0)\r
9913       CopyBoard(boards[0], boards[currentMove]);\r
9914     \r
9915     blackPlaysFirst = !WhiteOnMove(currentMove);\r
9916     ResetClocks();\r
9917     currentMove = forwardMostMove = backwardMostMove = 0;\r
9918     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9919     DisplayMove(-1);\r
9920 }\r
9921 \r
9922 void\r
9923 ExitAnalyzeMode()\r
9924 {\r
9925     if (first.analysisSupport && first.analyzing) {\r
9926       SendToProgram("exit\n", &first);\r
9927       first.analyzing = FALSE;\r
9928     }\r
9929     AnalysisPopDown();\r
9930     thinkOutput[0] = NULLCHAR;\r
9931 }\r
9932 \r
9933 void\r
9934 EditPositionDone()\r
9935 {\r
9936     startedFromSetupPosition = TRUE;\r
9937     if (appData.debugMode) {\r
9938         fprintf(debugFP, "From EditPosition\n");\r
9939     }\r
9940     InitChessProgram(&first, FALSE);\r
9941     SendToProgram("force\n", &first);\r
9942     if (blackPlaysFirst) {\r
9943         strcpy(moveList[0], "");\r
9944         strcpy(parseList[0], "");\r
9945         currentMove = forwardMostMove = backwardMostMove = 1;\r
9946         CopyBoard(boards[1], boards[0]);\r
9947     } else {\r
9948         currentMove = forwardMostMove = backwardMostMove = 0;\r
9949     }\r
9950     SendBoard(&first, forwardMostMove);\r
9951     if (appData.debugMode) {\r
9952         fprintf(debugFP, "EditPosDone\n");\r
9953     }\r
9954     DisplayTitle("");\r
9955     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
9956     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
9957     gameMode = EditGame;\r
9958     ModeHighlight();\r
9959     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
9960     ClearHighlights(); /* [AS] */\r
9961 }\r
9962 \r
9963 /* Pause for `ms' milliseconds */\r
9964 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
9965 void\r
9966 TimeDelay(ms)\r
9967      long ms;\r
9968 {\r
9969     TimeMark m1, m2;\r
9970 \r
9971     GetTimeMark(&m1);\r
9972     do {\r
9973         GetTimeMark(&m2);\r
9974     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
9975 }\r
9976 \r
9977 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
9978 void\r
9979 SendMultiLineToICS(buf)\r
9980      char *buf;\r
9981 {\r
9982     char temp[MSG_SIZ+1], *p;\r
9983     int len;\r
9984 \r
9985     len = strlen(buf);\r
9986     if (len > MSG_SIZ)\r
9987       len = MSG_SIZ;\r
9988   \r
9989     strncpy(temp, buf, len);\r
9990     temp[len] = 0;\r
9991 \r
9992     p = temp;\r
9993     while (*p) {\r
9994         if (*p == '\n' || *p == '\r')\r
9995           *p = ' ';\r
9996         ++p;\r
9997     }\r
9998 \r
9999     strcat(temp, "\n");\r
10000     SendToICS(temp);\r
10001     SendToPlayer(temp, strlen(temp));\r
10002 }\r
10003 \r
10004 void\r
10005 SetWhiteToPlayEvent()\r
10006 {\r
10007     if (gameMode == EditPosition) {\r
10008         blackPlaysFirst = FALSE;\r
10009         DisplayBothClocks();    /* works because currentMove is 0 */\r
10010     } else if (gameMode == IcsExamining) {\r
10011         SendToICS(ics_prefix);\r
10012         SendToICS("tomove white\n");\r
10013     }\r
10014 }\r
10015 \r
10016 void\r
10017 SetBlackToPlayEvent()\r
10018 {\r
10019     if (gameMode == EditPosition) {\r
10020         blackPlaysFirst = TRUE;\r
10021         currentMove = 1;        /* kludge */\r
10022         DisplayBothClocks();\r
10023         currentMove = 0;\r
10024     } else if (gameMode == IcsExamining) {\r
10025         SendToICS(ics_prefix);\r
10026         SendToICS("tomove black\n");\r
10027     }\r
10028 }\r
10029 \r
10030 void\r
10031 EditPositionMenuEvent(selection, x, y)\r
10032      ChessSquare selection;\r
10033      int x, y;\r
10034 {\r
10035     char buf[MSG_SIZ];\r
10036     ChessSquare piece = boards[0][y][x];\r
10037 \r
10038     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10039 \r
10040     switch (selection) {\r
10041       case ClearBoard:\r
10042         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10043             SendToICS(ics_prefix);\r
10044             SendToICS("bsetup clear\n");\r
10045         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10046             SendToICS(ics_prefix);\r
10047             SendToICS("clearboard\n");\r
10048         } else {\r
10049             for (x = 0; x < BOARD_WIDTH; x++) {\r
10050                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10051                     if (gameMode == IcsExamining) {\r
10052                         if (boards[currentMove][y][x] != EmptySquare) {\r
10053                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10054                                     AAA + x, ONE + y);\r
10055                             SendToICS(buf);\r
10056                         }\r
10057                     } else {\r
10058                         boards[0][y][x] = EmptySquare;\r
10059                     }\r
10060                 }\r
10061             }\r
10062         }\r
10063         if (gameMode == EditPosition) {\r
10064             DrawPosition(FALSE, boards[0]);\r
10065         }\r
10066         break;\r
10067 \r
10068       case WhitePlay:\r
10069         SetWhiteToPlayEvent();\r
10070         break;\r
10071 \r
10072       case BlackPlay:\r
10073         SetBlackToPlayEvent();\r
10074         break;\r
10075 \r
10076       case EmptySquare:\r
10077         if (gameMode == IcsExamining) {\r
10078             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
10079             SendToICS(buf);\r
10080         } else {\r
10081             boards[0][y][x] = EmptySquare;\r
10082             DrawPosition(FALSE, boards[0]);\r
10083         }\r
10084         break;\r
10085 \r
10086       case PromotePiece:\r
10087         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
10088            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
10089             selection = (ChessSquare) (PROMOTED piece);\r
10090         } else if(piece == EmptySquare) selection = WhiteSilver;\r
10091         else selection = (ChessSquare)((int)piece - 1);\r
10092         goto defaultlabel;\r
10093 \r
10094       case DemotePiece:\r
10095         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
10096            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
10097             selection = (ChessSquare) (DEMOTED piece);\r
10098         } else if(piece == EmptySquare) selection = BlackSilver;\r
10099         else selection = (ChessSquare)((int)piece + 1);       \r
10100         goto defaultlabel;\r
10101 \r
10102       case WhiteQueen:\r
10103       case BlackQueen:\r
10104         if(gameInfo.variant == VariantShatranj ||\r
10105            gameInfo.variant == VariantXiangqi  ||\r
10106            gameInfo.variant == VariantCourier    )\r
10107             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
10108         goto defaultlabel;\r
10109 \r
10110       case WhiteKing:\r
10111       case BlackKing:\r
10112         if(gameInfo.variant == VariantXiangqi)\r
10113             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
10114         if(gameInfo.variant == VariantKnightmate)\r
10115             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
10116       default:\r
10117         defaultlabel:\r
10118         if (gameMode == IcsExamining) {\r
10119             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
10120                     PieceToChar(selection), AAA + x, ONE + y);\r
10121             SendToICS(buf);\r
10122         } else {\r
10123             boards[0][y][x] = selection;\r
10124             DrawPosition(FALSE, boards[0]);\r
10125         }\r
10126         break;\r
10127     }\r
10128 }\r
10129 \r
10130 \r
10131 void\r
10132 DropMenuEvent(selection, x, y)\r
10133      ChessSquare selection;\r
10134      int x, y;\r
10135 {\r
10136     ChessMove moveType;\r
10137 \r
10138     switch (gameMode) {\r
10139       case IcsPlayingWhite:\r
10140       case MachinePlaysBlack:\r
10141         if (!WhiteOnMove(currentMove)) {\r
10142             DisplayMoveError("It is Black's turn");\r
10143             return;\r
10144         }\r
10145         moveType = WhiteDrop;\r
10146         break;\r
10147       case IcsPlayingBlack:\r
10148       case MachinePlaysWhite:\r
10149         if (WhiteOnMove(currentMove)) {\r
10150             DisplayMoveError("It is White's turn");\r
10151             return;\r
10152         }\r
10153         moveType = BlackDrop;\r
10154         break;\r
10155       case EditGame:\r
10156         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
10157         break;\r
10158       default:\r
10159         return;\r
10160     }\r
10161 \r
10162     if (moveType == BlackDrop && selection < BlackPawn) {\r
10163       selection = (ChessSquare) ((int) selection\r
10164                                  + (int) BlackPawn - (int) WhitePawn);\r
10165     }\r
10166     if (boards[currentMove][y][x] != EmptySquare) {\r
10167         DisplayMoveError("That square is occupied");\r
10168         return;\r
10169     }\r
10170 \r
10171     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
10172 }\r
10173 \r
10174 void\r
10175 AcceptEvent()\r
10176 {\r
10177     /* Accept a pending offer of any kind from opponent */\r
10178     \r
10179     if (appData.icsActive) {\r
10180         SendToICS(ics_prefix);\r
10181         SendToICS("accept\n");\r
10182     } else if (cmailMsgLoaded) {\r
10183         if (currentMove == cmailOldMove &&\r
10184             commentList[cmailOldMove] != NULL &&\r
10185             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
10186                    "Black offers a draw" : "White offers a draw")) {\r
10187             TruncateGame();\r
10188             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
10189             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
10190         } else {\r
10191             DisplayError("There is no pending offer on this move", 0);\r
10192             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
10193         }\r
10194     } else {\r
10195         /* Not used for offers from chess program */\r
10196     }\r
10197 }\r
10198 \r
10199 void\r
10200 DeclineEvent()\r
10201 {\r
10202     /* Decline a pending offer of any kind from opponent */\r
10203     \r
10204     if (appData.icsActive) {\r
10205         SendToICS(ics_prefix);\r
10206         SendToICS("decline\n");\r
10207     } else if (cmailMsgLoaded) {\r
10208         if (currentMove == cmailOldMove &&\r
10209             commentList[cmailOldMove] != NULL &&\r
10210             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
10211                    "Black offers a draw" : "White offers a draw")) {\r
10212 #ifdef NOTDEF\r
10213             AppendComment(cmailOldMove, "Draw declined");\r
10214             DisplayComment(cmailOldMove - 1, "Draw declined");\r
10215 #endif /*NOTDEF*/\r
10216         } else {\r
10217             DisplayError("There is no pending offer on this move", 0);\r
10218         }\r
10219     } else {\r
10220         /* Not used for offers from chess program */\r
10221     }\r
10222 }\r
10223 \r
10224 void\r
10225 RematchEvent()\r
10226 {\r
10227     /* Issue ICS rematch command */\r
10228     if (appData.icsActive) {\r
10229         SendToICS(ics_prefix);\r
10230         SendToICS("rematch\n");\r
10231     }\r
10232 }\r
10233 \r
10234 void\r
10235 CallFlagEvent()\r
10236 {\r
10237     /* Call your opponent's flag (claim a win on time) */\r
10238     if (appData.icsActive) {\r
10239         SendToICS(ics_prefix);\r
10240         SendToICS("flag\n");\r
10241     } else {\r
10242         switch (gameMode) {\r
10243           default:\r
10244             return;\r
10245           case MachinePlaysWhite:\r
10246             if (whiteFlag) {\r
10247                 if (blackFlag)\r
10248                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
10249                            GE_PLAYER);\r
10250                 else\r
10251                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
10252             } else {\r
10253                 DisplayError("Your opponent is not out of time", 0);\r
10254             }\r
10255             break;\r
10256           case MachinePlaysBlack:\r
10257             if (blackFlag) {\r
10258                 if (whiteFlag)\r
10259                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
10260                            GE_PLAYER);\r
10261                 else\r
10262                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
10263             } else {\r
10264                 DisplayError("Your opponent is not out of time", 0);\r
10265             }\r
10266             break;\r
10267         }\r
10268     }\r
10269 }\r
10270 \r
10271 void\r
10272 DrawEvent()\r
10273 {\r
10274     /* Offer draw or accept pending draw offer from opponent */\r
10275     \r
10276     if (appData.icsActive) {\r
10277         /* Note: tournament rules require draw offers to be\r
10278            made after you make your move but before you punch\r
10279            your clock.  Currently ICS doesn't let you do that;\r
10280            instead, you immediately punch your clock after making\r
10281            a move, but you can offer a draw at any time. */\r
10282         \r
10283         SendToICS(ics_prefix);\r
10284         SendToICS("draw\n");\r
10285     } else if (cmailMsgLoaded) {\r
10286         if (currentMove == cmailOldMove &&\r
10287             commentList[cmailOldMove] != NULL &&\r
10288             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
10289                    "Black offers a draw" : "White offers a draw")) {\r
10290             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
10291             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
10292         } else if (currentMove == cmailOldMove + 1) {\r
10293             char *offer = WhiteOnMove(cmailOldMove) ?\r
10294               "White offers a draw" : "Black offers a draw";\r
10295             AppendComment(currentMove, offer);\r
10296             DisplayComment(currentMove - 1, offer);\r
10297             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
10298         } else {\r
10299             DisplayError("You must make your move before offering a draw", 0);\r
10300             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
10301         }\r
10302     } else if (first.offeredDraw) {\r
10303         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
10304     } else {\r
10305         if (first.sendDrawOffers) {\r
10306             SendToProgram("draw\n", &first);\r
10307             userOfferedDraw = TRUE;\r
10308         }\r
10309     }\r
10310 }\r
10311 \r
10312 void\r
10313 AdjournEvent()\r
10314 {\r
10315     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
10316     \r
10317     if (appData.icsActive) {\r
10318         SendToICS(ics_prefix);\r
10319         SendToICS("adjourn\n");\r
10320     } else {\r
10321         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
10322     }\r
10323 }\r
10324 \r
10325 \r
10326 void\r
10327 AbortEvent()\r
10328 {\r
10329     /* Offer Abort or accept pending Abort offer from opponent */\r
10330     \r
10331     if (appData.icsActive) {\r
10332         SendToICS(ics_prefix);\r
10333         SendToICS("abort\n");\r
10334     } else {\r
10335         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
10336     }\r
10337 }\r
10338 \r
10339 void\r
10340 ResignEvent()\r
10341 {\r
10342     /* Resign.  You can do this even if it's not your turn. */\r
10343     \r
10344     if (appData.icsActive) {\r
10345         SendToICS(ics_prefix);\r
10346         SendToICS("resign\n");\r
10347     } else {\r
10348         switch (gameMode) {\r
10349           case MachinePlaysWhite:\r
10350             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
10351             break;\r
10352           case MachinePlaysBlack:\r
10353             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
10354             break;\r
10355           case EditGame:\r
10356             if (cmailMsgLoaded) {\r
10357                 TruncateGame();\r
10358                 if (WhiteOnMove(cmailOldMove)) {\r
10359                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
10360                 } else {\r
10361                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
10362                 }\r
10363                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
10364             }\r
10365             break;\r
10366           default:\r
10367             break;\r
10368         }\r
10369     }\r
10370 }\r
10371 \r
10372 \r
10373 void\r
10374 StopObservingEvent()\r
10375 {\r
10376     /* Stop observing current games */\r
10377     SendToICS(ics_prefix);\r
10378     SendToICS("unobserve\n");\r
10379 }\r
10380 \r
10381 void\r
10382 StopExaminingEvent()\r
10383 {\r
10384     /* Stop observing current game */\r
10385     SendToICS(ics_prefix);\r
10386     SendToICS("unexamine\n");\r
10387 }\r
10388 \r
10389 void\r
10390 ForwardInner(target)\r
10391      int target;\r
10392 {\r
10393     int limit;\r
10394 \r
10395     if (appData.debugMode)\r
10396         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
10397                 target, currentMove, forwardMostMove);\r
10398 \r
10399     if (gameMode == EditPosition)\r
10400       return;\r
10401 \r
10402     if (gameMode == PlayFromGameFile && !pausing)\r
10403       PauseEvent();\r
10404     \r
10405     if (gameMode == IcsExamining && pausing)\r
10406       limit = pauseExamForwardMostMove;\r
10407     else\r
10408       limit = forwardMostMove;\r
10409     \r
10410     if (target > limit) target = limit;\r
10411 \r
10412     if (target > 0 && moveList[target - 1][0]) {\r
10413         int fromX, fromY, toX, toY;\r
10414         toX = moveList[target - 1][2] - AAA;\r
10415         toY = moveList[target - 1][3] - ONE;\r
10416         if (moveList[target - 1][1] == '@') {\r
10417             if (appData.highlightLastMove) {\r
10418                 SetHighlights(-1, -1, toX, toY);\r
10419             }\r
10420         } else {\r
10421             fromX = moveList[target - 1][0] - AAA;\r
10422             fromY = moveList[target - 1][1] - ONE;\r
10423             if (target == currentMove + 1) {\r
10424                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
10425             }\r
10426             if (appData.highlightLastMove) {\r
10427                 SetHighlights(fromX, fromY, toX, toY);\r
10428             }\r
10429         }\r
10430     }\r
10431     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10432         gameMode == Training || gameMode == PlayFromGameFile || \r
10433         gameMode == AnalyzeFile) {\r
10434         while (currentMove < target) {\r
10435             SendMoveToProgram(currentMove++, &first);\r
10436         }\r
10437     } else {\r
10438         currentMove = target;\r
10439     }\r
10440     \r
10441     if (gameMode == EditGame || gameMode == EndOfGame) {\r
10442         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10443         blackTimeRemaining = timeRemaining[1][currentMove];\r
10444     }\r
10445     DisplayBothClocks();\r
10446     DisplayMove(currentMove - 1);\r
10447     DrawPosition(FALSE, boards[currentMove]);\r
10448     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
10449     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
10450         DisplayComment(currentMove - 1, commentList[currentMove]);\r
10451     }\r
10452 }\r
10453 \r
10454 \r
10455 void\r
10456 ForwardEvent()\r
10457 {\r
10458     if (gameMode == IcsExamining && !pausing) {\r
10459         SendToICS(ics_prefix);\r
10460         SendToICS("forward\n");\r
10461     } else {\r
10462         ForwardInner(currentMove + 1);\r
10463     }\r
10464 }\r
10465 \r
10466 void\r
10467 ToEndEvent()\r
10468 {\r
10469     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10470         /* to optimze, we temporarily turn off analysis mode while we feed\r
10471          * the remaining moves to the engine. Otherwise we get analysis output\r
10472          * after each move.\r
10473          */ \r
10474         if (first.analysisSupport) {\r
10475           SendToProgram("exit\nforce\n", &first);\r
10476           first.analyzing = FALSE;\r
10477         }\r
10478     }\r
10479         \r
10480     if (gameMode == IcsExamining && !pausing) {\r
10481         SendToICS(ics_prefix);\r
10482         SendToICS("forward 999999\n");\r
10483     } else {\r
10484         ForwardInner(forwardMostMove);\r
10485     }\r
10486 \r
10487     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10488         /* we have fed all the moves, so reactivate analysis mode */\r
10489         SendToProgram("analyze\n", &first);\r
10490         first.analyzing = TRUE;\r
10491         /*first.maybeThinking = TRUE;*/\r
10492         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10493     }\r
10494 }\r
10495 \r
10496 void\r
10497 BackwardInner(target)\r
10498      int target;\r
10499 {\r
10500     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
10501 \r
10502     if (appData.debugMode)\r
10503         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
10504                 target, currentMove, forwardMostMove);\r
10505 \r
10506     if (gameMode == EditPosition) return;\r
10507     if (currentMove <= backwardMostMove) {\r
10508         ClearHighlights();\r
10509         DrawPosition(full_redraw, boards[currentMove]);\r
10510         return;\r
10511     }\r
10512     if (gameMode == PlayFromGameFile && !pausing)\r
10513       PauseEvent();\r
10514     \r
10515     if (moveList[target][0]) {\r
10516         int fromX, fromY, toX, toY;\r
10517         toX = moveList[target][2] - AAA;\r
10518         toY = moveList[target][3] - ONE;\r
10519         if (moveList[target][1] == '@') {\r
10520             if (appData.highlightLastMove) {\r
10521                 SetHighlights(-1, -1, toX, toY);\r
10522             }\r
10523         } else {\r
10524             fromX = moveList[target][0] - AAA;\r
10525             fromY = moveList[target][1] - ONE;\r
10526             if (target == currentMove - 1) {\r
10527                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
10528             }\r
10529             if (appData.highlightLastMove) {\r
10530                 SetHighlights(fromX, fromY, toX, toY);\r
10531             }\r
10532         }\r
10533     }\r
10534     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
10535         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
10536         while (currentMove > target) {\r
10537             SendToProgram("undo\n", &first);\r
10538             currentMove--;\r
10539         }\r
10540     } else {\r
10541         currentMove = target;\r
10542     }\r
10543     \r
10544     if (gameMode == EditGame || gameMode == EndOfGame) {\r
10545         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10546         blackTimeRemaining = timeRemaining[1][currentMove];\r
10547     }\r
10548     DisplayBothClocks();\r
10549     DisplayMove(currentMove - 1);\r
10550     DrawPosition(full_redraw, boards[currentMove]);\r
10551     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
10552     // [HGM] PV info: routine tests if comment empty\r
10553     DisplayComment(currentMove - 1, commentList[currentMove]);\r
10554 }\r
10555 \r
10556 void\r
10557 BackwardEvent()\r
10558 {\r
10559     if (gameMode == IcsExamining && !pausing) {\r
10560         SendToICS(ics_prefix);\r
10561         SendToICS("backward\n");\r
10562     } else {\r
10563         BackwardInner(currentMove - 1);\r
10564     }\r
10565 }\r
10566 \r
10567 void\r
10568 ToStartEvent()\r
10569 {\r
10570     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10571         /* to optimze, we temporarily turn off analysis mode while we undo\r
10572          * all the moves. Otherwise we get analysis output after each undo.\r
10573          */ \r
10574         if (first.analysisSupport) {\r
10575           SendToProgram("exit\nforce\n", &first);\r
10576           first.analyzing = FALSE;\r
10577         }\r
10578     }\r
10579 \r
10580     if (gameMode == IcsExamining && !pausing) {\r
10581         SendToICS(ics_prefix);\r
10582         SendToICS("backward 999999\n");\r
10583     } else {\r
10584         BackwardInner(backwardMostMove);\r
10585     }\r
10586 \r
10587     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10588         /* we have fed all the moves, so reactivate analysis mode */\r
10589         SendToProgram("analyze\n", &first);\r
10590         first.analyzing = TRUE;\r
10591         /*first.maybeThinking = TRUE;*/\r
10592         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10593     }\r
10594 }\r
10595 \r
10596 void\r
10597 ToNrEvent(int to)\r
10598 {\r
10599   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
10600   if (to >= forwardMostMove) to = forwardMostMove;\r
10601   if (to <= backwardMostMove) to = backwardMostMove;\r
10602   if (to < currentMove) {\r
10603     BackwardInner(to);\r
10604   } else {\r
10605     ForwardInner(to);\r
10606   }\r
10607 }\r
10608 \r
10609 void\r
10610 RevertEvent()\r
10611 {\r
10612     if (gameMode != IcsExamining) {\r
10613         DisplayError("You are not examining a game", 0);\r
10614         return;\r
10615     }\r
10616     if (pausing) {\r
10617         DisplayError("You can't revert while pausing", 0);\r
10618         return;\r
10619     }\r
10620     SendToICS(ics_prefix);\r
10621     SendToICS("revert\n");\r
10622 }\r
10623 \r
10624 void\r
10625 RetractMoveEvent()\r
10626 {\r
10627     switch (gameMode) {\r
10628       case MachinePlaysWhite:\r
10629       case MachinePlaysBlack:\r
10630         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10631             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
10632             return;\r
10633         }\r
10634         if (forwardMostMove < 2) return;\r
10635         currentMove = forwardMostMove = forwardMostMove - 2;\r
10636         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10637         blackTimeRemaining = timeRemaining[1][currentMove];\r
10638         DisplayBothClocks();\r
10639         DisplayMove(currentMove - 1);\r
10640         ClearHighlights();/*!! could figure this out*/\r
10641         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
10642         SendToProgram("remove\n", &first);\r
10643         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
10644         break;\r
10645 \r
10646       case BeginningOfGame:\r
10647       default:\r
10648         break;\r
10649 \r
10650       case IcsPlayingWhite:\r
10651       case IcsPlayingBlack:\r
10652         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
10653             SendToICS(ics_prefix);\r
10654             SendToICS("takeback 2\n");\r
10655         } else {\r
10656             SendToICS(ics_prefix);\r
10657             SendToICS("takeback 1\n");\r
10658         }\r
10659         break;\r
10660     }\r
10661 }\r
10662 \r
10663 void\r
10664 MoveNowEvent()\r
10665 {\r
10666     ChessProgramState *cps;\r
10667 \r
10668     switch (gameMode) {\r
10669       case MachinePlaysWhite:\r
10670         if (!WhiteOnMove(forwardMostMove)) {\r
10671             DisplayError("It is your turn", 0);\r
10672             return;\r
10673         }\r
10674         cps = &first;\r
10675         break;\r
10676       case MachinePlaysBlack:\r
10677         if (WhiteOnMove(forwardMostMove)) {\r
10678             DisplayError("It is your turn", 0);\r
10679             return;\r
10680         }\r
10681         cps = &first;\r
10682         break;\r
10683       case TwoMachinesPlay:\r
10684         if (WhiteOnMove(forwardMostMove) ==\r
10685             (first.twoMachinesColor[0] == 'w')) {\r
10686             cps = &first;\r
10687         } else {\r
10688             cps = &second;\r
10689         }\r
10690         break;\r
10691       case BeginningOfGame:\r
10692       default:\r
10693         return;\r
10694     }\r
10695     SendToProgram("?\n", cps);\r
10696 }\r
10697 \r
10698 void\r
10699 TruncateGameEvent()\r
10700 {\r
10701     EditGameEvent();\r
10702     if (gameMode != EditGame) return;\r
10703     TruncateGame();\r
10704 }\r
10705 \r
10706 void\r
10707 TruncateGame()\r
10708 {\r
10709     if (forwardMostMove > currentMove) {\r
10710         if (gameInfo.resultDetails != NULL) {\r
10711             free(gameInfo.resultDetails);\r
10712             gameInfo.resultDetails = NULL;\r
10713             gameInfo.result = GameUnfinished;\r
10714         }\r
10715         forwardMostMove = currentMove;\r
10716         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
10717                    currentMove-1);\r
10718     }\r
10719 }\r
10720 \r
10721 void\r
10722 HintEvent()\r
10723 {\r
10724     if (appData.noChessProgram) return;\r
10725     switch (gameMode) {\r
10726       case MachinePlaysWhite:\r
10727         if (WhiteOnMove(forwardMostMove)) {\r
10728             DisplayError("Wait until your turn", 0);\r
10729             return;\r
10730         }\r
10731         break;\r
10732       case BeginningOfGame:\r
10733       case MachinePlaysBlack:\r
10734         if (!WhiteOnMove(forwardMostMove)) {\r
10735             DisplayError("Wait until your turn", 0);\r
10736             return;\r
10737         }\r
10738         break;\r
10739       default:\r
10740         DisplayError("No hint available", 0);\r
10741         return;\r
10742     }\r
10743     SendToProgram("hint\n", &first);\r
10744     hintRequested = TRUE;\r
10745 }\r
10746 \r
10747 void\r
10748 BookEvent()\r
10749 {\r
10750     if (appData.noChessProgram) return;\r
10751     switch (gameMode) {\r
10752       case MachinePlaysWhite:\r
10753         if (WhiteOnMove(forwardMostMove)) {\r
10754             DisplayError("Wait until your turn", 0);\r
10755             return;\r
10756         }\r
10757         break;\r
10758       case BeginningOfGame:\r
10759       case MachinePlaysBlack:\r
10760         if (!WhiteOnMove(forwardMostMove)) {\r
10761             DisplayError("Wait until your turn", 0);\r
10762             return;\r
10763         }\r
10764         break;\r
10765       case EditPosition:\r
10766         EditPositionDone();\r
10767         break;\r
10768       case TwoMachinesPlay:\r
10769         return;\r
10770       default:\r
10771         break;\r
10772     }\r
10773     SendToProgram("bk\n", &first);\r
10774     bookOutput[0] = NULLCHAR;\r
10775     bookRequested = TRUE;\r
10776 }\r
10777 \r
10778 void\r
10779 AboutGameEvent()\r
10780 {\r
10781     char *tags = PGNTags(&gameInfo);\r
10782     TagsPopUp(tags, CmailMsg());\r
10783     free(tags);\r
10784 }\r
10785 \r
10786 /* end button procedures */\r
10787 \r
10788 void\r
10789 PrintPosition(fp, move)\r
10790      FILE *fp;\r
10791      int move;\r
10792 {\r
10793     int i, j;\r
10794     \r
10795     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
10796         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
10797             char c = PieceToChar(boards[move][i][j]);\r
10798             fputc(c == 'x' ? '.' : c, fp);\r
10799             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
10800         }\r
10801     }\r
10802     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
10803       fprintf(fp, "white to play\n");\r
10804     else\r
10805       fprintf(fp, "black to play\n");\r
10806 }\r
10807 \r
10808 void\r
10809 PrintOpponents(fp)\r
10810      FILE *fp;\r
10811 {\r
10812     if (gameInfo.white != NULL) {\r
10813         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
10814     } else {\r
10815         fprintf(fp, "\n");\r
10816     }\r
10817 }\r
10818 \r
10819 /* Find last component of program's own name, using some heuristics */\r
10820 void\r
10821 TidyProgramName(prog, host, buf)\r
10822      char *prog, *host, buf[MSG_SIZ];\r
10823 {\r
10824     char *p, *q;\r
10825     int local = (strcmp(host, "localhost") == 0);\r
10826     while (!local && (p = strchr(prog, ';')) != NULL) {\r
10827         p++;\r
10828         while (*p == ' ') p++;\r
10829         prog = p;\r
10830     }\r
10831     if (*prog == '"' || *prog == '\'') {\r
10832         q = strchr(prog + 1, *prog);\r
10833     } else {\r
10834         q = strchr(prog, ' ');\r
10835     }\r
10836     if (q == NULL) q = prog + strlen(prog);\r
10837     p = q;\r
10838     while (p >= prog && *p != '/' && *p != '\\') p--;\r
10839     p++;\r
10840     if(p == prog && *p == '"') p++;\r
10841     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
10842     memcpy(buf, p, q - p);\r
10843     buf[q - p] = NULLCHAR;\r
10844     if (!local) {\r
10845         strcat(buf, "@");\r
10846         strcat(buf, host);\r
10847     }\r
10848 }\r
10849 \r
10850 char *\r
10851 TimeControlTagValue()\r
10852 {\r
10853     char buf[MSG_SIZ];\r
10854     if (!appData.clockMode) {\r
10855         strcpy(buf, "-");\r
10856     } else if (movesPerSession > 0) {\r
10857         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
10858     } else if (timeIncrement == 0) {\r
10859         sprintf(buf, "%ld", timeControl/1000);\r
10860     } else {\r
10861         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
10862     }\r
10863     return StrSave(buf);\r
10864 }\r
10865 \r
10866 void\r
10867 SetGameInfo()\r
10868 {\r
10869     /* This routine is used only for certain modes */\r
10870     VariantClass v = gameInfo.variant;\r
10871     ClearGameInfo(&gameInfo);\r
10872     gameInfo.variant = v;\r
10873 \r
10874     switch (gameMode) {\r
10875       case MachinePlaysWhite:\r
10876         gameInfo.event = StrSave( appData.pgnEventHeader );\r
10877         gameInfo.site = StrSave(HostName());\r
10878         gameInfo.date = PGNDate();\r
10879         gameInfo.round = StrSave("-");\r
10880         gameInfo.white = StrSave(first.tidy);\r
10881         gameInfo.black = StrSave(UserName());\r
10882         gameInfo.timeControl = TimeControlTagValue();\r
10883         break;\r
10884 \r
10885       case MachinePlaysBlack:\r
10886         gameInfo.event = StrSave( appData.pgnEventHeader );\r
10887         gameInfo.site = StrSave(HostName());\r
10888         gameInfo.date = PGNDate();\r
10889         gameInfo.round = StrSave("-");\r
10890         gameInfo.white = StrSave(UserName());\r
10891         gameInfo.black = StrSave(first.tidy);\r
10892         gameInfo.timeControl = TimeControlTagValue();\r
10893         break;\r
10894 \r
10895       case TwoMachinesPlay:\r
10896         gameInfo.event = StrSave( appData.pgnEventHeader );\r
10897         gameInfo.site = StrSave(HostName());\r
10898         gameInfo.date = PGNDate();\r
10899         if (matchGame > 0) {\r
10900             char buf[MSG_SIZ];\r
10901             sprintf(buf, "%d", matchGame);\r
10902             gameInfo.round = StrSave(buf);\r
10903         } else {\r
10904             gameInfo.round = StrSave("-");\r
10905         }\r
10906         if (first.twoMachinesColor[0] == 'w') {\r
10907             gameInfo.white = StrSave(first.tidy);\r
10908             gameInfo.black = StrSave(second.tidy);\r
10909         } else {\r
10910             gameInfo.white = StrSave(second.tidy);\r
10911             gameInfo.black = StrSave(first.tidy);\r
10912         }\r
10913         gameInfo.timeControl = TimeControlTagValue();\r
10914         break;\r
10915 \r
10916       case EditGame:\r
10917         gameInfo.event = StrSave("Edited game");\r
10918         gameInfo.site = StrSave(HostName());\r
10919         gameInfo.date = PGNDate();\r
10920         gameInfo.round = StrSave("-");\r
10921         gameInfo.white = StrSave("-");\r
10922         gameInfo.black = StrSave("-");\r
10923         break;\r
10924 \r
10925       case EditPosition:\r
10926         gameInfo.event = StrSave("Edited position");\r
10927         gameInfo.site = StrSave(HostName());\r
10928         gameInfo.date = PGNDate();\r
10929         gameInfo.round = StrSave("-");\r
10930         gameInfo.white = StrSave("-");\r
10931         gameInfo.black = StrSave("-");\r
10932         break;\r
10933 \r
10934       case IcsPlayingWhite:\r
10935       case IcsPlayingBlack:\r
10936       case IcsObserving:\r
10937       case IcsExamining:\r
10938         break;\r
10939 \r
10940       case PlayFromGameFile:\r
10941         gameInfo.event = StrSave("Game from non-PGN file");\r
10942         gameInfo.site = StrSave(HostName());\r
10943         gameInfo.date = PGNDate();\r
10944         gameInfo.round = StrSave("-");\r
10945         gameInfo.white = StrSave("?");\r
10946         gameInfo.black = StrSave("?");\r
10947         break;\r
10948 \r
10949       default:\r
10950         break;\r
10951     }\r
10952 }\r
10953 \r
10954 void\r
10955 ReplaceComment(index, text)\r
10956      int index;\r
10957      char *text;\r
10958 {\r
10959     int len;\r
10960 \r
10961     while (*text == '\n') text++;\r
10962     len = strlen(text);\r
10963     while (len > 0 && text[len - 1] == '\n') len--;\r
10964 \r
10965     if (commentList[index] != NULL)\r
10966       free(commentList[index]);\r
10967 \r
10968     if (len == 0) {\r
10969         commentList[index] = NULL;\r
10970         return;\r
10971     }\r
10972     commentList[index] = (char *) malloc(len + 2);\r
10973     strncpy(commentList[index], text, len);\r
10974     commentList[index][len] = '\n';\r
10975     commentList[index][len + 1] = NULLCHAR;\r
10976 }\r
10977 \r
10978 void\r
10979 CrushCRs(text)\r
10980      char *text;\r
10981 {\r
10982   char *p = text;\r
10983   char *q = text;\r
10984   char ch;\r
10985 \r
10986   do {\r
10987     ch = *p++;\r
10988     if (ch == '\r') continue;\r
10989     *q++ = ch;\r
10990   } while (ch != '\0');\r
10991 }\r
10992 \r
10993 void\r
10994 AppendComment(index, text)\r
10995      int index;\r
10996      char *text;\r
10997 {\r
10998     int oldlen, len;\r
10999     char *old;\r
11000 \r
11001     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11002 \r
11003     CrushCRs(text);\r
11004     while (*text == '\n') text++;\r
11005     len = strlen(text);\r
11006     while (len > 0 && text[len - 1] == '\n') len--;\r
11007 \r
11008     if (len == 0) return;\r
11009 \r
11010     if (commentList[index] != NULL) {\r
11011         old = commentList[index];\r
11012         oldlen = strlen(old);\r
11013         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11014         strcpy(commentList[index], old);\r
11015         free(old);\r
11016         strncpy(&commentList[index][oldlen], text, len);\r
11017         commentList[index][oldlen + len] = '\n';\r
11018         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11019     } else {\r
11020         commentList[index] = (char *) malloc(len + 2);\r
11021         strncpy(commentList[index], text, len);\r
11022         commentList[index][len] = '\n';\r
11023         commentList[index][len + 1] = NULLCHAR;\r
11024     }\r
11025 }\r
11026 \r
11027 static char * FindStr( char * text, char * sub_text )\r
11028 {\r
11029     char * result = strstr( text, sub_text );\r
11030 \r
11031     if( result != NULL ) {\r
11032         result += strlen( sub_text );\r
11033     }\r
11034 \r
11035     return result;\r
11036 }\r
11037 \r
11038 /* [AS] Try to extract PV info from PGN comment */\r
11039 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11040 char *GetInfoFromComment( int index, char * text )\r
11041 {\r
11042     char * sep = text;\r
11043 \r
11044     if( text != NULL && index > 0 ) {\r
11045         int score = 0;\r
11046         int depth = 0;\r
11047         int time = -1, sec = 0;\r
11048         char * s_eval = FindStr( text, "[%eval " );\r
11049         char * s_emt = FindStr( text, "[%emt " );\r
11050 \r
11051         if( s_eval != NULL || s_emt != NULL ) {\r
11052             /* New style */\r
11053             char delim;\r
11054 \r
11055             if( s_eval != NULL ) {\r
11056                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11057                     return text;\r
11058                 }\r
11059 \r
11060                 if( delim != ']' ) {\r
11061                     return text;\r
11062                 }\r
11063             }\r
11064 \r
11065             if( s_emt != NULL ) {\r
11066             }\r
11067         }\r
11068         else {\r
11069             /* We expect something like: [+|-]nnn.nn/dd */\r
11070             int score_lo = 0;\r
11071 \r
11072             sep = strchr( text, '/' );\r
11073             if( sep == NULL || sep < (text+4) ) {\r
11074                 return text;\r
11075             }\r
11076 \r
11077             time = -1; sec = -1;\r
11078             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
11079                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
11080                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
11081                 return text;\r
11082             }\r
11083 \r
11084             if( score_lo < 0 || score_lo >= 100 ) {\r
11085                 return text;\r
11086             }\r
11087 \r
11088             if(sec >= 0) time = 60*time + sec;\r
11089             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
11090 \r
11091             /* [HGM] PV time: now locate end of PV info */\r
11092             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
11093             if(time >= 0)\r
11094             while( *++sep >= '0' && *sep <= '9'); // strip time\r
11095             if(sec >= 0)\r
11096             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
11097             while(*sep == ' ') sep++;\r
11098         }\r
11099 \r
11100         if( depth <= 0 ) {\r
11101             return text;\r
11102         }\r
11103 \r
11104         if( time < 0 ) {\r
11105             time = -1;\r
11106         }\r
11107 \r
11108         pvInfoList[index-1].depth = depth;\r
11109         pvInfoList[index-1].score = score;\r
11110         pvInfoList[index-1].time  = time;\r
11111     }\r
11112     return sep;\r
11113 }\r
11114 \r
11115 void\r
11116 SendToProgram(message, cps)\r
11117      char *message;\r
11118      ChessProgramState *cps;\r
11119 {\r
11120     int count, outCount, error;\r
11121     char buf[MSG_SIZ];\r
11122 \r
11123     if (cps->pr == NULL) return;\r
11124     Attention(cps);\r
11125     \r
11126     if (appData.debugMode) {\r
11127         TimeMark now;\r
11128         GetTimeMark(&now);\r
11129         fprintf(debugFP, "%ld >%-6s: %s", \r
11130                 SubtractTimeMarks(&now, &programStartTime),\r
11131                 cps->which, message);\r
11132     }\r
11133     \r
11134     count = strlen(message);\r
11135     outCount = OutputToProcess(cps->pr, message, count, &error);\r
11136     if (outCount < count && !exiting \r
11137                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
11138         sprintf(buf, "Error writing to %s chess program", cps->which);\r
11139         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
11140             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
11141                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
11142                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
11143             } else {\r
11144                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
11145             }\r
11146             gameInfo.resultDetails = buf;\r
11147         }\r
11148         DisplayFatalError(buf, error, 1);\r
11149     }\r
11150 }\r
11151 \r
11152 void\r
11153 ReceiveFromProgram(isr, closure, message, count, error)\r
11154      InputSourceRef isr;\r
11155      VOIDSTAR closure;\r
11156      char *message;\r
11157      int count;\r
11158      int error;\r
11159 {\r
11160     char *end_str;\r
11161     char buf[MSG_SIZ];\r
11162     ChessProgramState *cps = (ChessProgramState *)closure;\r
11163 \r
11164     if (isr != cps->isr) return; /* Killed intentionally */\r
11165     if (count <= 0) {\r
11166         if (count == 0) {\r
11167             sprintf(buf,\r
11168                     "Error: %s chess program (%s) exited unexpectedly",\r
11169                     cps->which, cps->program);\r
11170         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
11171                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
11172                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
11173                     sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
11174                 } else {\r
11175                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
11176                 }\r
11177                 gameInfo.resultDetails = buf;\r
11178             }\r
11179             RemoveInputSource(cps->isr);\r
11180             DisplayFatalError(buf, 0, 1);\r
11181         } else {\r
11182             sprintf(buf,\r
11183                     "Error reading from %s chess program (%s)",\r
11184                     cps->which, cps->program);\r
11185             RemoveInputSource(cps->isr);\r
11186 \r
11187             /* [AS] Program is misbehaving badly... kill it */\r
11188             if( count == -2 ) {\r
11189                 DestroyChildProcess( cps->pr, 9 );\r
11190                 cps->pr = NoProc;\r
11191             }\r
11192 \r
11193             DisplayFatalError(buf, error, 1);\r
11194         }\r
11195         return;\r
11196     }\r
11197     \r
11198     if ((end_str = strchr(message, '\r')) != NULL)\r
11199       *end_str = NULLCHAR;\r
11200     if ((end_str = strchr(message, '\n')) != NULL)\r
11201       *end_str = NULLCHAR;\r
11202     \r
11203     if (appData.debugMode) {\r
11204         TimeMark now;\r
11205         GetTimeMark(&now);\r
11206         fprintf(debugFP, "%ld <%-6s: %s\n", \r
11207                 SubtractTimeMarks(&now, &programStartTime),\r
11208                 cps->which, message);\r
11209     }\r
11210     HandleMachineMove(message, cps);\r
11211 }\r
11212 \r
11213 \r
11214 void\r
11215 SendTimeControl(cps, mps, tc, inc, sd, st)\r
11216      ChessProgramState *cps;\r
11217      int mps, inc, sd, st;\r
11218      long tc;\r
11219 {\r
11220     char buf[MSG_SIZ];\r
11221     int seconds;\r
11222 \r
11223     if( timeControl_2 > 0 ) {\r
11224         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
11225             tc = timeControl_2;\r
11226         }\r
11227     }\r
11228     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
11229     inc /= cps->timeOdds;\r
11230     st  /= cps->timeOdds;\r
11231 \r
11232     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
11233 \r
11234     if (st > 0) {\r
11235       /* Set exact time per move, normally using st command */\r
11236       if (cps->stKludge) {\r
11237         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
11238         seconds = st % 60;\r
11239         if (seconds == 0) {\r
11240           sprintf(buf, "level 1 %d\n", st/60);\r
11241         } else {\r
11242           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
11243         }\r
11244       } else {\r
11245         sprintf(buf, "st %d\n", st);\r
11246       }\r
11247     } else {\r
11248       /* Set conventional or incremental time control, using level command */\r
11249       if (seconds == 0) {\r
11250         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
11251            Fixed in later versions, but still avoid :seconds\r
11252            when seconds is 0. */\r
11253         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
11254       } else {\r
11255         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
11256                 seconds, inc/1000);\r
11257       }\r
11258     }\r
11259     SendToProgram(buf, cps);\r
11260 \r
11261     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
11262     /* Orthogonally, limit search to given depth */\r
11263     if (sd > 0) {\r
11264       if (cps->sdKludge) {\r
11265         sprintf(buf, "depth\n%d\n", sd);\r
11266       } else {\r
11267         sprintf(buf, "sd %d\n", sd);\r
11268       }\r
11269       SendToProgram(buf, cps);\r
11270     }\r
11271 }\r
11272 \r
11273 ChessProgramState *WhitePlayer()\r
11274 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
11275 {\r
11276     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')\r
11277         return &second;\r
11278     return &first;\r
11279 }\r
11280 \r
11281 void\r
11282 SendTimeRemaining(cps, machineWhite)\r
11283      ChessProgramState *cps;\r
11284      int /*boolean*/ machineWhite;\r
11285 {\r
11286     char message[MSG_SIZ];\r
11287     long time, otime;\r
11288 \r
11289     /* Note: this routine must be called when the clocks are stopped\r
11290        or when they have *just* been set or switched; otherwise\r
11291        it will be off by the time since the current tick started.\r
11292     */\r
11293     if (machineWhite) {\r
11294         time = whiteTimeRemaining / 10;\r
11295         otime = blackTimeRemaining / 10;\r
11296     } else {\r
11297         time = blackTimeRemaining / 10;\r
11298         otime = whiteTimeRemaining / 10;\r
11299     }\r
11300     /* [HGM] translate opponent's time by time-odds factor */\r
11301     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
11302     if (appData.debugMode) {\r
11303         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
11304     }\r
11305 \r
11306     if (time <= 0) time = 1;\r
11307     if (otime <= 0) otime = 1;\r
11308     \r
11309     sprintf(message, "time %ld\n", time);\r
11310     SendToProgram(message, cps);\r
11311 \r
11312     sprintf(message, "otim %ld\n", otime);\r
11313     SendToProgram(message, cps);\r
11314 }\r
11315 \r
11316 int\r
11317 BoolFeature(p, name, loc, cps)\r
11318      char **p;\r
11319      char *name;\r
11320      int *loc;\r
11321      ChessProgramState *cps;\r
11322 {\r
11323   char buf[MSG_SIZ];\r
11324   int len = strlen(name);\r
11325   int val;\r
11326   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
11327     (*p) += len + 1;\r
11328     sscanf(*p, "%d", &val);\r
11329     *loc = (val != 0);\r
11330     while (**p && **p != ' ') (*p)++;\r
11331     sprintf(buf, "accepted %s\n", name);\r
11332     SendToProgram(buf, cps);\r
11333     return TRUE;\r
11334   }\r
11335   return FALSE;\r
11336 }\r
11337 \r
11338 int\r
11339 IntFeature(p, name, loc, cps)\r
11340      char **p;\r
11341      char *name;\r
11342      int *loc;\r
11343      ChessProgramState *cps;\r
11344 {\r
11345   char buf[MSG_SIZ];\r
11346   int len = strlen(name);\r
11347   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
11348     (*p) += len + 1;\r
11349     sscanf(*p, "%d", loc);\r
11350     while (**p && **p != ' ') (*p)++;\r
11351     sprintf(buf, "accepted %s\n", name);\r
11352     SendToProgram(buf, cps);\r
11353     return TRUE;\r
11354   }\r
11355   return FALSE;\r
11356 }\r
11357 \r
11358 int\r
11359 StringFeature(p, name, loc, cps)\r
11360      char **p;\r
11361      char *name;\r
11362      char loc[];\r
11363      ChessProgramState *cps;\r
11364 {\r
11365   char buf[MSG_SIZ];\r
11366   int len = strlen(name);\r
11367   if (strncmp((*p), name, len) == 0\r
11368       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
11369     (*p) += len + 2;\r
11370     sscanf(*p, "%[^\"]", loc);\r
11371     while (**p && **p != '\"') (*p)++;\r
11372     if (**p == '\"') (*p)++;\r
11373     sprintf(buf, "accepted %s\n", name);\r
11374     SendToProgram(buf, cps);\r
11375     return TRUE;\r
11376   }\r
11377   return FALSE;\r
11378 }\r
11379 \r
11380 void\r
11381 FeatureDone(cps, val)\r
11382      ChessProgramState* cps;\r
11383      int val;\r
11384 {\r
11385   DelayedEventCallback cb = GetDelayedEvent();\r
11386   if ((cb == InitBackEnd3 && cps == &first) ||\r
11387       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
11388     CancelDelayedEvent();\r
11389     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
11390   }\r
11391   cps->initDone = val;\r
11392 }\r
11393 \r
11394 /* Parse feature command from engine */\r
11395 void\r
11396 ParseFeatures(args, cps)\r
11397      char* args;\r
11398      ChessProgramState *cps;  \r
11399 {\r
11400   char *p = args;\r
11401   char *q;\r
11402   int val;\r
11403   char buf[MSG_SIZ];\r
11404 \r
11405   for (;;) {\r
11406     while (*p == ' ') p++;\r
11407     if (*p == NULLCHAR) return;\r
11408 \r
11409     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
11410     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
11411     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
11412     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
11413     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
11414     if (BoolFeature(&p, "reuse", &val, cps)) {\r
11415       /* Engine can disable reuse, but can't enable it if user said no */\r
11416       if (!val) cps->reuse = FALSE;\r
11417       continue;\r
11418     }\r
11419     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
11420     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
11421       if (gameMode == TwoMachinesPlay) {\r
11422         DisplayTwoMachinesTitle();\r
11423       } else {\r
11424         DisplayTitle("");\r
11425       }\r
11426       continue;\r
11427     }\r
11428     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
11429     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
11430     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
11431     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
11432     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
11433     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
11434     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
11435     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
11436     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
11437     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
11438     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
11439     if (IntFeature(&p, "done", &val, cps)) {\r
11440       FeatureDone(cps, val);\r
11441       continue;\r
11442     }\r
11443     /* Added by Tord: */\r
11444     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
11445     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
11446     /* End of additions by Tord */\r
11447 \r
11448     /* unknown feature: complain and skip */\r
11449     q = p;\r
11450     while (*q && *q != '=') q++;\r
11451     sprintf(buf, "rejected %.*s\n", q-p, p);\r
11452     SendToProgram(buf, cps);\r
11453     p = q;\r
11454     if (*p == '=') {\r
11455       p++;\r
11456       if (*p == '\"') {\r
11457         p++;\r
11458         while (*p && *p != '\"') p++;\r
11459         if (*p == '\"') p++;\r
11460       } else {\r
11461         while (*p && *p != ' ') p++;\r
11462       }\r
11463     }\r
11464   }\r
11465 \r
11466 }\r
11467 \r
11468 void\r
11469 PeriodicUpdatesEvent(newState)\r
11470      int newState;\r
11471 {\r
11472     if (newState == appData.periodicUpdates)\r
11473       return;\r
11474 \r
11475     appData.periodicUpdates=newState;\r
11476 \r
11477     /* Display type changes, so update it now */\r
11478     DisplayAnalysis();\r
11479 \r
11480     /* Get the ball rolling again... */\r
11481     if (newState) {\r
11482         AnalysisPeriodicEvent(1);\r
11483         StartAnalysisClock();\r
11484     }\r
11485 }\r
11486 \r
11487 void\r
11488 PonderNextMoveEvent(newState)\r
11489      int newState;\r
11490 {\r
11491     if (newState == appData.ponderNextMove) return;\r
11492     if (gameMode == EditPosition) EditPositionDone();\r
11493     if (newState) {\r
11494         SendToProgram("hard\n", &first);\r
11495         if (gameMode == TwoMachinesPlay) {\r
11496             SendToProgram("hard\n", &second);\r
11497         }\r
11498     } else {\r
11499         SendToProgram("easy\n", &first);\r
11500         thinkOutput[0] = NULLCHAR;\r
11501         if (gameMode == TwoMachinesPlay) {\r
11502             SendToProgram("easy\n", &second);\r
11503         }\r
11504     }\r
11505     appData.ponderNextMove = newState;\r
11506 }\r
11507 \r
11508 void\r
11509 ShowThinkingEvent(newState)\r
11510      int newState;\r
11511 {\r
11512     if (newState == appData.showThinking) return;\r
11513     if (gameMode == EditPosition) EditPositionDone();\r
11514     if (newState) {\r
11515         SendToProgram("post\n", &first);\r
11516         if (gameMode == TwoMachinesPlay) {\r
11517             SendToProgram("post\n", &second);\r
11518         }\r
11519     } else {\r
11520         SendToProgram("nopost\n", &first);\r
11521         thinkOutput[0] = NULLCHAR;\r
11522         if (gameMode == TwoMachinesPlay) {\r
11523             SendToProgram("nopost\n", &second);\r
11524         }\r
11525     }\r
11526     appData.showThinking = newState;\r
11527 }\r
11528 \r
11529 void\r
11530 AskQuestionEvent(title, question, replyPrefix, which)\r
11531      char *title; char *question; char *replyPrefix; char *which;\r
11532 {\r
11533   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
11534   if (pr == NoProc) return;\r
11535   AskQuestion(title, question, replyPrefix, pr);\r
11536 }\r
11537 \r
11538 void\r
11539 DisplayMove(moveNumber)\r
11540      int moveNumber;\r
11541 {\r
11542     char message[MSG_SIZ];\r
11543     char res[MSG_SIZ];\r
11544     char cpThinkOutput[MSG_SIZ];\r
11545 \r
11546     if (moveNumber == forwardMostMove - 1 || \r
11547         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11548 \r
11549         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
11550 \r
11551         if (strchr(cpThinkOutput, '\n')) {\r
11552             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
11553         }\r
11554     } else {\r
11555         *cpThinkOutput = NULLCHAR;\r
11556     }\r
11557 \r
11558     /* [AS] Hide thinking from human user */\r
11559     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
11560         *cpThinkOutput = NULLCHAR;\r
11561         if( thinkOutput[0] != NULLCHAR ) {\r
11562             int i;\r
11563 \r
11564             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
11565                 cpThinkOutput[i] = '.';\r
11566             }\r
11567             cpThinkOutput[i] = NULLCHAR;\r
11568             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
11569         }\r
11570     }\r
11571 \r
11572     if (moveNumber == forwardMostMove - 1 &&\r
11573         gameInfo.resultDetails != NULL) {\r
11574         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
11575             sprintf(res, " %s", PGNResult(gameInfo.result));\r
11576         } else {\r
11577             sprintf(res, " {%s} %s",\r
11578                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
11579         }\r
11580     } else {\r
11581         res[0] = NULLCHAR;\r
11582     }\r
11583     \r
11584     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
11585         DisplayMessage(res, cpThinkOutput);\r
11586     } else {\r
11587         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
11588                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
11589                 parseList[moveNumber], res);\r
11590         DisplayMessage(message, cpThinkOutput);\r
11591     }\r
11592 }\r
11593 \r
11594 void\r
11595 DisplayAnalysisText(text)\r
11596      char *text;\r
11597 {\r
11598     char buf[MSG_SIZ];\r
11599 \r
11600     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11601         sprintf(buf, "Analysis (%s)", first.tidy);\r
11602         AnalysisPopUp(buf, text);\r
11603     }\r
11604 }\r
11605 \r
11606 static int\r
11607 only_one_move(str)\r
11608      char *str;\r
11609 {\r
11610     while (*str && isspace(*str)) ++str;\r
11611     while (*str && !isspace(*str)) ++str;\r
11612     if (!*str) return 1;\r
11613     while (*str && isspace(*str)) ++str;\r
11614     if (!*str) return 1;\r
11615     return 0;\r
11616 }\r
11617 \r
11618 void\r
11619 DisplayAnalysis()\r
11620 {\r
11621     char buf[MSG_SIZ];\r
11622     char lst[MSG_SIZ / 2];\r
11623     double nps;\r
11624     static char *xtra[] = { "", " (--)", " (++)" };\r
11625     int h, m, s, cs;\r
11626   \r
11627     if (programStats.time == 0) {\r
11628         programStats.time = 1;\r
11629     }\r
11630   \r
11631     if (programStats.got_only_move) {\r
11632         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
11633     } else {\r
11634         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
11635 \r
11636         nps = (((double)programStats.nodes) /\r
11637                (((double)programStats.time)/100.0));\r
11638 \r
11639         cs = programStats.time % 100;\r
11640         s = programStats.time / 100;\r
11641         h = (s / (60*60));\r
11642         s = s - h*60*60;\r
11643         m = (s/60);\r
11644         s = s - m*60;\r
11645 \r
11646         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
11647           if (programStats.move_name[0] != NULLCHAR) {\r
11648             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
11649                     programStats.depth,\r
11650                     programStats.nr_moves-programStats.moves_left,\r
11651                     programStats.nr_moves, programStats.move_name,\r
11652                     ((float)programStats.score)/100.0, lst,\r
11653                     only_one_move(lst)?\r
11654                     xtra[programStats.got_fail] : "",\r
11655                     programStats.nodes, (int)nps, h, m, s, cs);\r
11656           } else {\r
11657             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
11658                     programStats.depth,\r
11659                     programStats.nr_moves-programStats.moves_left,\r
11660                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
11661                     lst,\r
11662                     only_one_move(lst)?\r
11663                     xtra[programStats.got_fail] : "",\r
11664                     programStats.nodes, (int)nps, h, m, s, cs);\r
11665           }\r
11666         } else {\r
11667             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
11668                     programStats.depth,\r
11669                     ((float)programStats.score)/100.0,\r
11670                     lst,\r
11671                     only_one_move(lst)?\r
11672                     xtra[programStats.got_fail] : "",\r
11673                     programStats.nodes, (int)nps, h, m, s, cs);\r
11674         }\r
11675     }\r
11676     DisplayAnalysisText(buf);\r
11677 }\r
11678 \r
11679 void\r
11680 DisplayComment(moveNumber, text)\r
11681      int moveNumber;\r
11682      char *text;\r
11683 {\r
11684     char title[MSG_SIZ];\r
11685     char buf[8000]; // comment can be long!\r
11686     int score, depth;\r
11687 \r
11688     if( appData.autoDisplayComment ) {\r
11689         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
11690             strcpy(title, "Comment");\r
11691         } else {\r
11692             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
11693                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
11694                     parseList[moveNumber]);\r
11695         }\r
11696     } else title[0] = 0;\r
11697 \r
11698     // [HGM] PV info: display PV info together with (or as) comment\r
11699     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
11700         if(text == NULL) text = "";                                           \r
11701         score = pvInfoList[moveNumber].score;\r
11702         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
11703                               depth, pvInfoList[moveNumber].time, text);\r
11704         CommentPopUp(title, buf);\r
11705     } else\r
11706     if (text != NULL)\r
11707         CommentPopUp(title, text);\r
11708 }\r
11709 \r
11710 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
11711  * might be busy thinking or pondering.  It can be omitted if your\r
11712  * gnuchess is configured to stop thinking immediately on any user\r
11713  * input.  However, that gnuchess feature depends on the FIONREAD\r
11714  * ioctl, which does not work properly on some flavors of Unix.\r
11715  */\r
11716 void\r
11717 Attention(cps)\r
11718      ChessProgramState *cps;\r
11719 {\r
11720 #if ATTENTION\r
11721     if (!cps->useSigint) return;\r
11722     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
11723     switch (gameMode) {\r
11724       case MachinePlaysWhite:\r
11725       case MachinePlaysBlack:\r
11726       case TwoMachinesPlay:\r
11727       case IcsPlayingWhite:\r
11728       case IcsPlayingBlack:\r
11729       case AnalyzeMode:\r
11730       case AnalyzeFile:\r
11731         /* Skip if we know it isn't thinking */\r
11732         if (!cps->maybeThinking) return;\r
11733         if (appData.debugMode)\r
11734           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
11735         InterruptChildProcess(cps->pr);\r
11736         cps->maybeThinking = FALSE;\r
11737         break;\r
11738       default:\r
11739         break;\r
11740     }\r
11741 #endif /*ATTENTION*/\r
11742 }\r
11743 \r
11744 int\r
11745 CheckFlags()\r
11746 {\r
11747     if (whiteTimeRemaining <= 0) {\r
11748         if (!whiteFlag) {\r
11749             whiteFlag = TRUE;\r
11750             if (appData.icsActive) {\r
11751                 if (appData.autoCallFlag &&\r
11752                     gameMode == IcsPlayingBlack && !blackFlag) {\r
11753                   SendToICS(ics_prefix);\r
11754                   SendToICS("flag\n");\r
11755                 }\r
11756             } else {\r
11757                 if (blackFlag) {\r
11758                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
11759                 } else {\r
11760                     if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell");\r
11761                     if (appData.autoCallFlag) {\r
11762                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
11763                         return TRUE;\r
11764                     }\r
11765                 }\r
11766             }\r
11767         }\r
11768     }\r
11769     if (blackTimeRemaining <= 0) {\r
11770         if (!blackFlag) {\r
11771             blackFlag = TRUE;\r
11772             if (appData.icsActive) {\r
11773                 if (appData.autoCallFlag &&\r
11774                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
11775                   SendToICS(ics_prefix);\r
11776                   SendToICS("flag\n");\r
11777                 }\r
11778             } else {\r
11779                 if (whiteFlag) {\r
11780                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
11781                 } else {\r
11782                     if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell");\r
11783                     if (appData.autoCallFlag) {\r
11784                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
11785                         return TRUE;\r
11786                     }\r
11787                 }\r
11788             }\r
11789         }\r
11790     }\r
11791     return FALSE;\r
11792 }\r
11793 \r
11794 void\r
11795 CheckTimeControl()\r
11796 {\r
11797     if (!appData.clockMode || appData.icsActive ||\r
11798         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
11799 \r
11800     /*\r
11801      * add time to clocks when time control is achieved ([HGM] now also used fot increment)\r
11802      */\r
11803     if ( !WhiteOnMove(forwardMostMove) )\r
11804         /* White made time control */\r
11805         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
11806         /* [HGM] time odds: correct new time quota for time odds! */\r
11807                                             / WhitePlayer()->timeOdds;\r
11808       else\r
11809         /* Black made time control */\r
11810         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
11811                                             / WhitePlayer()->other->timeOdds;\r
11812 }\r
11813 \r
11814 void\r
11815 DisplayBothClocks()\r
11816 {\r
11817     int wom = gameMode == EditPosition ?\r
11818       !blackPlaysFirst : WhiteOnMove(currentMove);\r
11819     DisplayWhiteClock(whiteTimeRemaining, wom);\r
11820     DisplayBlackClock(blackTimeRemaining, !wom);\r
11821 }\r
11822 \r
11823 \r
11824 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
11825    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
11826    to use other calls if you don't.  Clocks will be less accurate if\r
11827    you have neither ftime nor gettimeofday.\r
11828 */\r
11829 \r
11830 /* Get the current time as a TimeMark */\r
11831 void\r
11832 GetTimeMark(tm)\r
11833      TimeMark *tm;\r
11834 {\r
11835 #if HAVE_GETTIMEOFDAY\r
11836 \r
11837     struct timeval timeVal;\r
11838     struct timezone timeZone;\r
11839 \r
11840     gettimeofday(&timeVal, &timeZone);\r
11841     tm->sec = (long) timeVal.tv_sec; \r
11842     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
11843 \r
11844 #else /*!HAVE_GETTIMEOFDAY*/\r
11845 #if HAVE_FTIME\r
11846 \r
11847 #include <sys/timeb.h>\r
11848     struct timeb timeB;\r
11849 \r
11850     ftime(&timeB);\r
11851     tm->sec = (long) timeB.time;\r
11852     tm->ms = (int) timeB.millitm;\r
11853 \r
11854 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
11855     tm->sec = (long) time(NULL);\r
11856     tm->ms = 0;\r
11857 #endif\r
11858 #endif\r
11859 }\r
11860 \r
11861 /* Return the difference in milliseconds between two\r
11862    time marks.  We assume the difference will fit in a long!\r
11863 */\r
11864 long\r
11865 SubtractTimeMarks(tm2, tm1)\r
11866      TimeMark *tm2, *tm1;\r
11867 {\r
11868     return 1000L*(tm2->sec - tm1->sec) +\r
11869            (long) (tm2->ms - tm1->ms);\r
11870 }\r
11871 \r
11872 \r
11873 /*\r
11874  * Code to manage the game clocks.\r
11875  *\r
11876  * In tournament play, black starts the clock and then white makes a move.\r
11877  * We give the human user a slight advantage if he is playing white---the\r
11878  * clocks don't run until he makes his first move, so it takes zero time.\r
11879  * Also, we don't account for network lag, so we could get out of sync\r
11880  * with GNU Chess's clock -- but then, referees are always right.  \r
11881  */\r
11882 \r
11883 static TimeMark tickStartTM;\r
11884 static long intendedTickLength;\r
11885 \r
11886 long\r
11887 NextTickLength(timeRemaining)\r
11888      long timeRemaining;\r
11889 {\r
11890     long nominalTickLength, nextTickLength;\r
11891 \r
11892     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
11893       nominalTickLength = 100L;\r
11894     else\r
11895       nominalTickLength = 1000L;\r
11896     nextTickLength = timeRemaining % nominalTickLength;\r
11897     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
11898 \r
11899     return nextTickLength;\r
11900 }\r
11901 \r
11902 /* Adjust clock one minute up or down */\r
11903 void\r
11904 AdjustClock(Boolean which, int dir)\r
11905 {\r
11906     if(which) blackTimeRemaining += 60000*dir;\r
11907     else      whiteTimeRemaining += 60000*dir;\r
11908     DisplayBothClocks();\r
11909 }\r
11910 \r
11911 /* Stop clocks and reset to a fresh time control */\r
11912 void\r
11913 ResetClocks() \r
11914 {\r
11915     (void) StopClockTimer();\r
11916     if (appData.icsActive) {\r
11917         whiteTimeRemaining = blackTimeRemaining = 0;\r
11918     } else { /* [HGM] correct new time quote for time odds */\r
11919         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
11920         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
11921     }\r
11922     if (whiteFlag || blackFlag) {\r
11923         DisplayTitle("");\r
11924         whiteFlag = blackFlag = FALSE;\r
11925     }\r
11926     DisplayBothClocks();\r
11927 }\r
11928 \r
11929 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
11930 \r
11931 /* Decrement running clock by amount of time that has passed */\r
11932 void\r
11933 DecrementClocks()\r
11934 {\r
11935     long timeRemaining;\r
11936     long lastTickLength, fudge;\r
11937     TimeMark now;\r
11938 \r
11939     if (!appData.clockMode) return;\r
11940     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
11941         \r
11942     GetTimeMark(&now);\r
11943 \r
11944     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
11945 \r
11946     /* Fudge if we woke up a little too soon */\r
11947     fudge = intendedTickLength - lastTickLength;\r
11948     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
11949 \r
11950     if (WhiteOnMove(forwardMostMove)) {\r
11951         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
11952         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
11953                           WhiteOnMove(currentMove));\r
11954     } else {\r
11955         timeRemaining = blackTimeRemaining -= lastTickLength;\r
11956         DisplayBlackClock(blackTimeRemaining - fudge,\r
11957                           !WhiteOnMove(currentMove));\r
11958     }\r
11959 \r
11960     if (CheckFlags()) return;\r
11961         \r
11962     tickStartTM = now;\r
11963     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
11964     StartClockTimer(intendedTickLength);\r
11965 \r
11966     /* if the time remaining has fallen below the alarm threshold, sound the\r
11967      * alarm. if the alarm has sounded and (due to a takeback or time control\r
11968      * with increment) the time remaining has increased to a level above the\r
11969      * threshold, reset the alarm so it can sound again. \r
11970      */\r
11971     \r
11972     if (appData.icsActive && appData.icsAlarm) {\r
11973 \r
11974         /* make sure we are dealing with the user's clock */\r
11975         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
11976                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
11977            )) return;\r
11978 \r
11979         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
11980             alarmSounded = FALSE;\r
11981         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
11982             PlayAlarmSound();\r
11983             alarmSounded = TRUE;\r
11984         }\r
11985     }\r
11986 }\r
11987 \r
11988 \r
11989 /* A player has just moved, so stop the previously running\r
11990    clock and (if in clock mode) start the other one.\r
11991    We redisplay both clocks in case we're in ICS mode, because\r
11992    ICS gives us an update to both clocks after every move.\r
11993    Note that this routine is called *after* forwardMostMove\r
11994    is updated, so the last fractional tick must be subtracted\r
11995    from the color that is *not* on move now.\r
11996 */\r
11997 void\r
11998 SwitchClocks()\r
11999 {\r
12000     long lastTickLength;\r
12001     TimeMark now;\r
12002     int flagged = FALSE;\r
12003 \r
12004     GetTimeMark(&now);\r
12005 \r
12006     if (StopClockTimer() && appData.clockMode) {\r
12007         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
12008         if (WhiteOnMove(forwardMostMove)) {\r
12009             blackTimeRemaining -= lastTickLength;\r
12010            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
12011             if(pvInfoList[forwardMostMove-1].time == -1)\r
12012                  pvInfoList[forwardMostMove-1].time = \r
12013                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
12014         } else {\r
12015             whiteTimeRemaining -= lastTickLength;\r
12016            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
12017             if(pvInfoList[forwardMostMove-1].time == -1)\r
12018                  pvInfoList[forwardMostMove-1].time = \r
12019                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
12020         }\r
12021         flagged = CheckFlags();\r
12022     }\r
12023     CheckTimeControl();\r
12024 \r
12025     if (flagged || !appData.clockMode) return;\r
12026 \r
12027     switch (gameMode) {\r
12028       case MachinePlaysBlack:\r
12029       case MachinePlaysWhite:\r
12030       case BeginningOfGame:\r
12031         if (pausing) return;\r
12032         break;\r
12033 \r
12034       case EditGame:\r
12035       case PlayFromGameFile:\r
12036       case IcsExamining:\r
12037         return;\r
12038 \r
12039       default:\r
12040         break;\r
12041     }\r
12042 \r
12043     tickStartTM = now;\r
12044     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
12045       whiteTimeRemaining : blackTimeRemaining);\r
12046     StartClockTimer(intendedTickLength);\r
12047 }\r
12048         \r
12049 \r
12050 /* Stop both clocks */\r
12051 void\r
12052 StopClocks()\r
12053 {       \r
12054     long lastTickLength;\r
12055     TimeMark now;\r
12056 \r
12057     if (!StopClockTimer()) return;\r
12058     if (!appData.clockMode) return;\r
12059 \r
12060     GetTimeMark(&now);\r
12061 \r
12062     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
12063     if (WhiteOnMove(forwardMostMove)) {\r
12064         whiteTimeRemaining -= lastTickLength;\r
12065         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
12066     } else {\r
12067         blackTimeRemaining -= lastTickLength;\r
12068         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
12069     }\r
12070     CheckFlags();\r
12071 }\r
12072         \r
12073 /* Start clock of player on move.  Time may have been reset, so\r
12074    if clock is already running, stop and restart it. */\r
12075 void\r
12076 StartClocks()\r
12077 {\r
12078     (void) StopClockTimer(); /* in case it was running already */\r
12079     DisplayBothClocks();\r
12080     if (CheckFlags()) return;\r
12081 \r
12082     if (!appData.clockMode) return;\r
12083     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
12084 \r
12085     GetTimeMark(&tickStartTM);\r
12086     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
12087       whiteTimeRemaining : blackTimeRemaining);\r
12088     StartClockTimer(intendedTickLength);\r
12089 }\r
12090 \r
12091 char *\r
12092 TimeString(ms)\r
12093      long ms;\r
12094 {\r
12095     long second, minute, hour, day;\r
12096     char *sign = "";\r
12097     static char buf[32];\r
12098     \r
12099     if (ms > 0 && ms <= 9900) {\r
12100       /* convert milliseconds to tenths, rounding up */\r
12101       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
12102 \r
12103       sprintf(buf, " %03.1f ", tenths/10.0);\r
12104       return buf;\r
12105     }\r
12106 \r
12107     /* convert milliseconds to seconds, rounding up */\r
12108     /* use floating point to avoid strangeness of integer division\r
12109        with negative dividends on many machines */\r
12110     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
12111 \r
12112     if (second < 0) {\r
12113         sign = "-";\r
12114         second = -second;\r
12115     }\r
12116     \r
12117     day = second / (60 * 60 * 24);\r
12118     second = second % (60 * 60 * 24);\r
12119     hour = second / (60 * 60);\r
12120     second = second % (60 * 60);\r
12121     minute = second / 60;\r
12122     second = second % 60;\r
12123     \r
12124     if (day > 0)\r
12125       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
12126               sign, day, hour, minute, second);\r
12127     else if (hour > 0)\r
12128       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
12129     else\r
12130       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
12131     \r
12132     return buf;\r
12133 }\r
12134 \r
12135 \r
12136 /*\r
12137  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
12138  */\r
12139 char *\r
12140 StrStr(string, match)\r
12141      char *string, *match;\r
12142 {\r
12143     int i, length;\r
12144     \r
12145     length = strlen(match);\r
12146     \r
12147     for (i = strlen(string) - length; i >= 0; i--, string++)\r
12148       if (!strncmp(match, string, length))\r
12149         return string;\r
12150     \r
12151     return NULL;\r
12152 }\r
12153 \r
12154 char *\r
12155 StrCaseStr(string, match)\r
12156      char *string, *match;\r
12157 {\r
12158     int i, j, length;\r
12159     \r
12160     length = strlen(match);\r
12161     \r
12162     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
12163         for (j = 0; j < length; j++) {\r
12164             if (ToLower(match[j]) != ToLower(string[j]))\r
12165               break;\r
12166         }\r
12167         if (j == length) return string;\r
12168     }\r
12169 \r
12170     return NULL;\r
12171 }\r
12172 \r
12173 #ifndef _amigados\r
12174 int\r
12175 StrCaseCmp(s1, s2)\r
12176      char *s1, *s2;\r
12177 {\r
12178     char c1, c2;\r
12179     \r
12180     for (;;) {\r
12181         c1 = ToLower(*s1++);\r
12182         c2 = ToLower(*s2++);\r
12183         if (c1 > c2) return 1;\r
12184         if (c1 < c2) return -1;\r
12185         if (c1 == NULLCHAR) return 0;\r
12186     }\r
12187 }\r
12188 \r
12189 \r
12190 int\r
12191 ToLower(c)\r
12192      int c;\r
12193 {\r
12194     return isupper(c) ? tolower(c) : c;\r
12195 }\r
12196 \r
12197 \r
12198 int\r
12199 ToUpper(c)\r
12200      int c;\r
12201 {\r
12202     return islower(c) ? toupper(c) : c;\r
12203 }\r
12204 #endif /* !_amigados    */\r
12205 \r
12206 char *\r
12207 StrSave(s)\r
12208      char *s;\r
12209 {\r
12210     char *ret;\r
12211 \r
12212     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
12213         strcpy(ret, s);\r
12214     }\r
12215     return ret;\r
12216 }\r
12217 \r
12218 char *\r
12219 StrSavePtr(s, savePtr)\r
12220      char *s, **savePtr;\r
12221 {\r
12222     if (*savePtr) {\r
12223         free(*savePtr);\r
12224     }\r
12225     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
12226         strcpy(*savePtr, s);\r
12227     }\r
12228     return(*savePtr);\r
12229 }\r
12230 \r
12231 char *\r
12232 PGNDate()\r
12233 {\r
12234     time_t clock;\r
12235     struct tm *tm;\r
12236     char buf[MSG_SIZ];\r
12237 \r
12238     clock = time((time_t *)NULL);\r
12239     tm = localtime(&clock);\r
12240     sprintf(buf, "%04d.%02d.%02d",\r
12241             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
12242     return StrSave(buf);\r
12243 }\r
12244 \r
12245 \r
12246 char *\r
12247 PositionToFEN(move, useFEN960)\r
12248      int move;\r
12249      int useFEN960;\r
12250 {\r
12251     int i, j, fromX, fromY, toX, toY;\r
12252     int whiteToPlay;\r
12253     char buf[128];\r
12254     char *p, *q;\r
12255     int emptycount;\r
12256     ChessSquare piece;\r
12257 \r
12258     whiteToPlay = (gameMode == EditPosition) ?\r
12259       !blackPlaysFirst : (move % 2 == 0);\r
12260     p = buf;\r
12261 \r
12262     /* Piece placement data */\r
12263     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
12264         emptycount = 0;\r
12265         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
12266             if (boards[move][i][j] == EmptySquare) {\r
12267                 emptycount++;\r
12268             } else { ChessSquare piece = boards[move][i][j];\r
12269                 if (emptycount > 0) {\r
12270                     if(emptycount<10) /* [HGM] can be >= 10 */\r
12271                         *p++ = '0' + emptycount;\r
12272                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
12273                     emptycount = 0;\r
12274                 }\r
12275                 if(PieceToChar(piece) == '+') {\r
12276                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
12277                     *p++ = '+';\r
12278                     piece = (ChessSquare)(DEMOTED piece);\r
12279                 } \r
12280                 *p++ = PieceToChar(piece);\r
12281                 if(p[-1] == '~') {\r
12282                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
12283                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
12284                     *p++ = '~';\r
12285                 }\r
12286             }\r
12287         }\r
12288         if (emptycount > 0) {\r
12289             if(emptycount<10) /* [HGM] can be >= 10 */\r
12290                 *p++ = '0' + emptycount;\r
12291             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
12292             emptycount = 0;\r
12293         }\r
12294         *p++ = '/';\r
12295     }\r
12296     *(p - 1) = ' ';\r
12297 \r
12298     /* [HGM] print Crazyhouse or Shogi holdings */\r
12299     if( gameInfo.holdingsWidth ) {\r
12300         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
12301         q = p;\r
12302         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
12303             piece = boards[move][i][BOARD_WIDTH-1];\r
12304             if( piece != EmptySquare )\r
12305               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
12306                   *p++ = PieceToChar(piece);\r
12307         }\r
12308         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
12309             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
12310             if( piece != EmptySquare )\r
12311               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
12312                   *p++ = PieceToChar(piece);\r
12313         }\r
12314 \r
12315         if( q == p ) *p++ = '-';\r
12316         *p++ = ']';\r
12317         *p++ = ' ';\r
12318     }\r
12319 \r
12320     /* Active color */\r
12321     *p++ = whiteToPlay ? 'w' : 'b';\r
12322     *p++ = ' ';\r
12323 \r
12324   if(nrCastlingRights) {\r
12325      q = p;\r
12326      if(gameInfo.variant == VariantFischeRandom) {\r
12327        /* [HGM] write directly from rights */\r
12328            if(castlingRights[move][2] >= 0 &&\r
12329               castlingRights[move][0] >= 0   )\r
12330                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
12331            if(castlingRights[move][2] >= 0 &&\r
12332               castlingRights[move][1] >= 0   )\r
12333                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
12334            if(castlingRights[move][5] >= 0 &&\r
12335               castlingRights[move][3] >= 0   )\r
12336                 *p++ = castlingRights[move][3] + AAA;\r
12337            if(castlingRights[move][5] >= 0 &&\r
12338               castlingRights[move][4] >= 0   )\r
12339                 *p++ = castlingRights[move][4] + AAA;\r
12340      } else {\r
12341 \r
12342         /* [HGM] write true castling rights */\r
12343         if( nrCastlingRights == 6 ) {\r
12344             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
12345                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
12346             if(castlingRights[move][1] == BOARD_LEFT &&\r
12347                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
12348             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
12349                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
12350             if(castlingRights[move][4] == BOARD_LEFT &&\r
12351                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
12352         }\r
12353      }\r
12354      if (q == p) *p++ = '-'; /* No castling rights */\r
12355      *p++ = ' ';\r
12356   }\r
12357 \r
12358   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
12359      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
12360     /* En passant target square */\r
12361     if (move > backwardMostMove) {\r
12362         fromX = moveList[move - 1][0] - AAA;\r
12363         fromY = moveList[move - 1][1] - ONE;\r
12364         toX = moveList[move - 1][2] - AAA;\r
12365         toY = moveList[move - 1][3] - ONE;\r
12366         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
12367             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
12368             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
12369             fromX == toX) {\r
12370             /* 2-square pawn move just happened */\r
12371             *p++ = toX + AAA;\r
12372             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
12373         } else {\r
12374             *p++ = '-';\r
12375         }\r
12376     } else {\r
12377         *p++ = '-';\r
12378     }\r
12379     *p++ = ' ';\r
12380   }\r
12381 \r
12382     /* [HGM] find reversible plies */\r
12383     {   int i = 0, j=move;\r
12384 \r
12385         if (appData.debugMode) { int k;\r
12386             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
12387             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
12388                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
12389 \r
12390         }\r
12391 \r
12392         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
12393         if( j == backwardMostMove ) i += initialRulePlies;\r
12394         sprintf(p, "%d ", i);\r
12395         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
12396     }\r
12397     /* Fullmove number */\r
12398     sprintf(p, "%d", (move / 2) + 1);\r
12399     \r
12400     return StrSave(buf);\r
12401 }\r
12402 \r
12403 Boolean\r
12404 ParseFEN(board, blackPlaysFirst, fen)\r
12405     Board board;\r
12406      int *blackPlaysFirst;\r
12407      char *fen;\r
12408 {\r
12409     int i, j;\r
12410     char *p;\r
12411     int emptycount;\r
12412     ChessSquare piece;\r
12413 \r
12414     p = fen;\r
12415 \r
12416     /* [HGM] by default clear Crazyhouse holdings, if present */\r
12417     if(gameInfo.holdingsWidth) {\r
12418        for(i=0; i<BOARD_HEIGHT; i++) {\r
12419            board[i][0]             = EmptySquare; /* black holdings */\r
12420            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
12421            board[i][1]             = (ChessSquare) 0; /* black counts */\r
12422            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
12423        }\r
12424     }\r
12425 \r
12426     /* Piece placement data */\r
12427     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
12428         j = 0;\r
12429         for (;;) {\r
12430             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
12431                 if (*p == '/') p++;\r
12432                 emptycount = gameInfo.boardWidth - j;\r
12433                 while (emptycount--)\r
12434                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
12435                 break;\r
12436 #if(BOARD_SIZE >= 10)\r
12437             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
12438                 p++; emptycount=10;\r
12439                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
12440                 while (emptycount--)\r
12441                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
12442 #endif\r
12443             } else if (isdigit(*p)) {\r
12444                 emptycount = *p++ - '0';\r
12445                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
12446                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
12447                 while (emptycount--)\r
12448                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
12449             } else if (*p == '+' || isalpha(*p)) {\r
12450                 if (j >= gameInfo.boardWidth) return FALSE;\r
12451                 if(*p=='+') {\r
12452                     piece = CharToPiece(*++p);\r
12453                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
12454                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
12455                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
12456                 } else piece = CharToPiece(*p++);\r
12457 \r
12458                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
12459                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
12460                     piece = (ChessSquare) (PROMOTED piece);\r
12461                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
12462                     p++;\r
12463                 }\r
12464                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
12465             } else {\r
12466                 return FALSE;\r
12467             }\r
12468         }\r
12469     }\r
12470     while (*p == '/' || *p == ' ') p++;\r
12471 \r
12472     /* [HGM] look for Crazyhouse holdings here */\r
12473     while(*p==' ') p++;\r
12474     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
12475         if(*p == '[') p++;\r
12476         if(*p == '-' ) *p++; /* empty holdings */ else {\r
12477             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
12478             /* if we would allow FEN reading to set board size, we would   */\r
12479             /* have to add holdings and shift the board read so far here   */\r
12480             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
12481                 *p++;\r
12482                 if((int) piece >= (int) BlackPawn ) {\r
12483                     i = (int)piece - (int)BlackPawn;\r
12484                     if( i >= BOARD_HEIGHT ) return FALSE;\r
12485                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
12486                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
12487                 } else {\r
12488                     i = (int)piece - (int)WhitePawn;\r
12489                     if( i >= BOARD_HEIGHT ) return FALSE;\r
12490                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
12491                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
12492                 }\r
12493             }\r
12494         }\r
12495         if(*p == ']') *p++;\r
12496     }\r
12497 \r
12498     while(*p == ' ') p++;\r
12499 \r
12500     /* Active color */\r
12501     switch (*p++) {\r
12502       case 'w':\r
12503         *blackPlaysFirst = FALSE;\r
12504         break;\r
12505       case 'b': \r
12506         *blackPlaysFirst = TRUE;\r
12507         break;\r
12508       default:\r
12509         return FALSE;\r
12510     }\r
12511 \r
12512     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
12513     /* return the extra info in global variiables             */\r
12514 \r
12515     /* set defaults in case FEN is incomplete */\r
12516     FENepStatus = EP_UNKNOWN;\r
12517     for(i=0; i<nrCastlingRights; i++ ) {\r
12518         FENcastlingRights[i] =\r
12519             gameInfo.variant == VariantFischeRandom ? -1 : initialRights[i];\r
12520     }   /* assume possible unless obviously impossible */\r
12521     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
12522     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
12523     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
12524     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
12525     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
12526     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
12527     FENrulePlies = 0;\r
12528 \r
12529     while(*p==' ') p++;\r
12530     if(nrCastlingRights) {\r
12531       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
12532           /* castling indicator present, so default becomes no castlings */\r
12533           for(i=0; i<nrCastlingRights; i++ ) {\r
12534                  FENcastlingRights[i] = -1;\r
12535           }\r
12536       }\r
12537       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
12538              gameInfo.variant == VariantFischeRandom &&\r
12539              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
12540              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
12541         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
12542 \r
12543         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
12544             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
12545             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
12546         }\r
12547         switch(c) {\r
12548           case'K':\r
12549               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
12550               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
12551               FENcastlingRights[2] = whiteKingFile;\r
12552               break;\r
12553           case'Q':\r
12554               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
12555               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
12556               FENcastlingRights[2] = whiteKingFile;\r
12557               break;\r
12558           case'k':\r
12559               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
12560               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
12561               FENcastlingRights[5] = blackKingFile;\r
12562               break;\r
12563           case'q':\r
12564               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
12565               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
12566               FENcastlingRights[5] = blackKingFile;\r
12567           case '-':\r
12568               break;\r
12569           default: /* FRC castlings */\r
12570               if(c >= 'a') { /* black rights */\r
12571                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
12572                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
12573                   if(i == BOARD_RGHT) break;\r
12574                   FENcastlingRights[5] = i;\r
12575                   c -= AAA;\r
12576                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
12577                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
12578                   if(c > i)\r
12579                       FENcastlingRights[3] = c;\r
12580                   else\r
12581                       FENcastlingRights[4] = c;\r
12582               } else { /* white rights */\r
12583                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
12584                     if(board[0][i] == WhiteKing) break;\r
12585                   if(i == BOARD_RGHT) break;\r
12586                   FENcastlingRights[2] = i;\r
12587                   c -= AAA - 'a' + 'A';\r
12588                   if(board[0][c] >= WhiteKing) break;\r
12589                   if(c > i)\r
12590                       FENcastlingRights[0] = c;\r
12591                   else\r
12592                       FENcastlingRights[1] = c;\r
12593               }\r
12594         }\r
12595       }\r
12596     if (appData.debugMode) {\r
12597         fprintf(debugFP, "FEN castling rights:");\r
12598         for(i=0; i<nrCastlingRights; i++)\r
12599         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
12600         fprintf(debugFP, "\n");\r
12601     }\r
12602 \r
12603       while(*p==' ') p++;\r
12604     }\r
12605 \r
12606     /* read e.p. field in games that know e.p. capture */\r
12607     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
12608        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
12609       if(*p=='-') {\r
12610         p++; FENepStatus = EP_NONE;\r
12611       } else {\r
12612          char c = *p++ - AAA;\r
12613 \r
12614          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
12615          if(*p >= '0' && *p <='9') *p++;\r
12616          FENepStatus = c;\r
12617       }\r
12618     }\r
12619 \r
12620 \r
12621     if(sscanf(p, "%d", &i) == 1) {\r
12622         FENrulePlies = i; /* 50-move ply counter */\r
12623         /* (The move number is still ignored)    */\r
12624     }\r
12625 \r
12626     return TRUE;\r
12627 }\r
12628       \r
12629 void\r
12630 EditPositionPasteFEN(char *fen)\r
12631 {\r
12632   if (fen != NULL) {\r
12633     Board initial_position;\r
12634 \r
12635     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
12636       DisplayError("Bad FEN position in clipboard", 0);\r
12637       return ;\r
12638     } else {\r
12639       int savedBlackPlaysFirst = blackPlaysFirst;\r
12640       EditPositionEvent();\r
12641       blackPlaysFirst = savedBlackPlaysFirst;\r
12642       CopyBoard(boards[0], initial_position);\r
12643           /* [HGM] copy FEN attributes as well */\r
12644           {   int i;\r
12645               initialRulePlies = FENrulePlies;\r
12646               epStatus[0] = FENepStatus;\r
12647               for( i=0; i<nrCastlingRights; i++ )\r
12648                   castlingRights[0][i] = FENcastlingRights[i];\r
12649           }\r
12650       EditPositionDone();\r
12651       DisplayBothClocks();\r
12652       DrawPosition(FALSE, boards[currentMove]);\r
12653     }\r
12654   }\r
12655 }\r