changes from H.G. Muller; version 4.3.14
[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 typedef int BOOL;\r
60 \r
61 #endif\r
62 \r
63 #include "config.h"\r
64 \r
65 #include <assert.h>\r
66 #include <stdio.h>\r
67 #include <ctype.h>\r
68 #include <errno.h>\r
69 #include <sys/types.h>\r
70 #include <sys/stat.h>\r
71 #include <math.h>\r
72 \r
73 #if STDC_HEADERS\r
74 # include <stdlib.h>\r
75 # include <string.h>\r
76 #else /* not STDC_HEADERS */\r
77 # if HAVE_STRING_H\r
78 #  include <string.h>\r
79 # else /* not HAVE_STRING_H */\r
80 #  include <strings.h>\r
81 # endif /* not HAVE_STRING_H */\r
82 #endif /* not STDC_HEADERS */\r
83 \r
84 #if HAVE_SYS_FCNTL_H\r
85 # include <sys/fcntl.h>\r
86 #else /* not HAVE_SYS_FCNTL_H */\r
87 # if HAVE_FCNTL_H\r
88 #  include <fcntl.h>\r
89 # endif /* HAVE_FCNTL_H */\r
90 #endif /* not HAVE_SYS_FCNTL_H */\r
91 \r
92 #if TIME_WITH_SYS_TIME\r
93 # include <sys/time.h>\r
94 # include <time.h>\r
95 #else\r
96 # if HAVE_SYS_TIME_H\r
97 #  include <sys/time.h>\r
98 # else\r
99 #  include <time.h>\r
100 # endif\r
101 #endif\r
102 \r
103 #if defined(_amigados) && !defined(__GNUC__)\r
104 struct timezone {\r
105     int tz_minuteswest;\r
106     int tz_dsttime;\r
107 };\r
108 extern int gettimeofday(struct timeval *, struct timezone *);\r
109 #endif\r
110 \r
111 #if HAVE_UNISTD_H\r
112 # include <unistd.h>\r
113 #endif\r
114 \r
115 #include "common.h"\r
116 #include "frontend.h"\r
117 #include "backend.h"\r
118 #include "parser.h"\r
119 #include "moves.h"\r
120 #if ZIPPY\r
121 # include "zippy.h"\r
122 #endif\r
123 #include "backendz.h"\r
124 \r
125 /* A point in time */\r
126 typedef struct {\r
127     long sec;  /* Assuming this is >= 32 bits */\r
128     int ms;    /* Assuming this is >= 16 bits */\r
129 } TimeMark;\r
130 \r
131 /* Search stats from chessprogram */\r
132 typedef struct {\r
133   char movelist[2*MSG_SIZ]; /* Last PV we were sent */\r
134   int depth;              /* Current search depth */\r
135   int nr_moves;           /* Total nr of root moves */\r
136   int moves_left;         /* Moves remaining to be searched */\r
137   char move_name[MOVE_LEN];  /* Current move being searched, if provided */\r
138   unsigned long nodes;    /* # of nodes searched */\r
139   int time;               /* Search time (centiseconds) */\r
140   int score;              /* Score (centipawns) */\r
141   int got_only_move;      /* If last msg was "(only move)" */\r
142   int got_fail;           /* 0 - nothing, 1 - got "--", 2 - got "++" */\r
143   int ok_to_send;         /* handshaking between send & recv */\r
144   int line_is_book;       /* 1 if movelist is book moves */\r
145   int seen_stat;          /* 1 if we've seen the stat01: line */\r
146 } ChessProgramStats;\r
147 \r
148 int establish P((void));\r
149 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,\r
150                          char *buf, int count, int error));\r
151 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,\r
152                       char *buf, int count, int error));\r
153 void SendToICS P((char *s));\r
154 void SendToICSDelayed P((char *s, long msdelay));\r
155 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,\r
156                       int toX, int toY));\r
157 void InitPosition P((int redraw));\r
158 void HandleMachineMove P((char *message, ChessProgramState *cps));\r
159 int AutoPlayOneMove P((void));\r
160 int LoadGameOneMove P((ChessMove readAhead));\r
161 int LoadGameFromFile P((char *filename, int n, char *title, int useList));\r
162 int LoadPositionFromFile P((char *filename, int n, char *title));\r
163 int SavePositionToFile P((char *filename));\r
164 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,\r
165                   Board board));\r
166 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));\r
167 void ShowMove P((int fromX, int fromY, int toX, int toY));\r
168 void FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,\r
169                    /*char*/int promoChar));\r
170 void BackwardInner P((int target));\r
171 void ForwardInner P((int target));\r
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));\r
173 void EditPositionDone P((void));\r
174 void PrintOpponents P((FILE *fp));\r
175 void PrintPosition P((FILE *fp, int move));\r
176 void StartChessProgram P((ChessProgramState *cps));\r
177 void SendToProgram P((char *message, ChessProgramState *cps));\r
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));\r
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,\r
180                            char *buf, int count, int error));\r
181 void SendTimeControl P((ChessProgramState *cps,\r
182                         int mps, long tc, int inc, int sd, int st));\r
183 char *TimeControlTagValue P((void));\r
184 void Attention P((ChessProgramState *cps));\r
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));\r
186 void ResurrectChessProgram P((void));\r
187 void DisplayComment P((int moveNumber, char *text));\r
188 void DisplayMove P((int moveNumber));\r
189 void DisplayAnalysis P((void));\r
190 \r
191 void ParseGameHistory P((char *game));\r
192 void ParseBoard12 P((char *string));\r
193 void StartClocks P((void));\r
194 void SwitchClocks P((void));\r
195 void StopClocks P((void));\r
196 void ResetClocks P((void));\r
197 char *PGNDate P((void));\r
198 void SetGameInfo P((void));\r
199 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));\r
200 int RegisterMove P((void));\r
201 void MakeRegisteredMove P((void));\r
202 void TruncateGame P((void));\r
203 int looking_at P((char *, int *, char *));\r
204 void CopyPlayerNameIntoFileName P((char **, char *));\r
205 char *SavePart P((char *));\r
206 int SaveGameOldStyle P((FILE *));\r
207 int SaveGamePGN P((FILE *));\r
208 void GetTimeMark P((TimeMark *));\r
209 long SubtractTimeMarks P((TimeMark *, TimeMark *));\r
210 int CheckFlags P((void));\r
211 long NextTickLength P((long));\r
212 void CheckTimeControl P((void));\r
213 void show_bytes P((FILE *, char *, int));\r
214 int string_to_rating P((char *str));\r
215 void ParseFeatures P((char* args, ChessProgramState *cps));\r
216 void InitBackEnd3 P((void));\r
217 void FeatureDone P((ChessProgramState* cps, int val));\r
218 void InitChessProgram P((ChessProgramState *cps, int setup));\r
219 \r
220 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment\r
221 \r
222 extern int tinyLayout, smallLayout;\r
223 static ChessProgramStats programStats;\r
224 static int exiting = 0; /* [HGM] moved to top */\r
225 static int setboardSpoiledMachineBlack = 0, errorExitFlag = 0;\r
226 extern int startedFromPositionFile;\r
227 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */\r
228 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */\r
229 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */\r
230 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */\r
231 \r
232 /* States for ics_getting_history */\r
233 #define H_FALSE 0\r
234 #define H_REQUESTED 1\r
235 #define H_GOT_REQ_HEADER 2\r
236 #define H_GOT_UNREQ_HEADER 3\r
237 #define H_GETTING_MOVES 4\r
238 #define H_GOT_UNWANTED_HEADER 5\r
239 \r
240 /* whosays values for GameEnds */\r
241 #define GE_ICS 0\r
242 #define GE_ENGINE 1\r
243 #define GE_PLAYER 2\r
244 #define GE_FILE 3\r
245 #define GE_XBOARD 4\r
246 #define GE_ENGINE1 5\r
247 #define GE_ENGINE2 6\r
248 \r
249 /* Maximum number of games in a cmail message */\r
250 #define CMAIL_MAX_GAMES 20\r
251 \r
252 /* Different types of move when calling RegisterMove */\r
253 #define CMAIL_MOVE   0\r
254 #define CMAIL_RESIGN 1\r
255 #define CMAIL_DRAW   2\r
256 #define CMAIL_ACCEPT 3\r
257 \r
258 /* Different types of result to remember for each game */\r
259 #define CMAIL_NOT_RESULT 0\r
260 #define CMAIL_OLD_RESULT 1\r
261 #define CMAIL_NEW_RESULT 2\r
262 \r
263 /* Telnet protocol constants */\r
264 #define TN_WILL 0373\r
265 #define TN_WONT 0374\r
266 #define TN_DO   0375\r
267 #define TN_DONT 0376\r
268 #define TN_IAC  0377\r
269 #define TN_ECHO 0001\r
270 #define TN_SGA  0003\r
271 #define TN_PORT 23\r
272 \r
273 /* [AS] */\r
274 static char * safeStrCpy( char * dst, const char * src, size_t count )\r
275 {\r
276     assert( dst != NULL );\r
277     assert( src != NULL );\r
278     assert( count > 0 );\r
279 \r
280     strncpy( dst, src, count );\r
281     dst[ count-1 ] = '\0';\r
282     return dst;\r
283 }\r
284 \r
285 static char * safeStrCat( char * dst, const char * src, size_t count )\r
286 {\r
287     size_t  dst_len;\r
288 \r
289     assert( dst != NULL );\r
290     assert( src != NULL );\r
291     assert( count > 0 );\r
292 \r
293     dst_len = strlen(dst);\r
294 \r
295     assert( count > dst_len ); /* Buffer size must be greater than current length */\r
296 \r
297     safeStrCpy( dst + dst_len, src, count - dst_len );\r
298 \r
299     return dst;\r
300 }\r
301 \r
302 /* Fake up flags for now, as we aren't keeping track of castling\r
303    availability yet. [HGM] Change of logic: the flag now only\r
304    indicates the type of castlings allowed by the rule of the game.\r
305    The actual rights themselves are maintained in the array\r
306    castlingRights, as part of the game history, and are not probed\r
307    by this function.\r
308  */\r
309 int\r
310 PosFlags(index)\r
311 {\r
312   int flags = F_ALL_CASTLE_OK;\r
313   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;\r
314   switch (gameInfo.variant) {\r
315   case VariantSuicide:\r
316   case VariantGiveaway:\r
317     flags |= F_IGNORE_CHECK;\r
318     flags &= ~F_ALL_CASTLE_OK;\r
319     break;\r
320   case VariantAtomic:\r
321     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
322     break;\r
323   case VariantKriegspiel:\r
324     flags |= F_KRIEGSPIEL_CAPTURE;\r
325     break;\r
326   case VariantCapaRandom: \r
327   case VariantFischeRandom:\r
328     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */\r
329   case VariantNoCastle:\r
330   case VariantShatranj:\r
331   case VariantCourier:\r
332     flags &= ~F_ALL_CASTLE_OK;\r
333     break;\r
334   default:\r
335     break;\r
336   }\r
337   return flags;\r
338 }\r
339 \r
340 FILE *gameFileFP, *debugFP;\r
341 \r
342 /* \r
343     [AS] Note: sometimes, the sscanf() function is used to parse the input\r
344     into a fixed-size buffer. Because of this, we must be prepared to\r
345     receive strings as long as the size of the input buffer, which is currently\r
346     set to 4K for Windows and 8K for the rest.\r
347     So, we must either allocate sufficiently large buffers here, or\r
348     reduce the size of the input buffer in the input reading part.\r
349 */\r
350 \r
351 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
352 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
353 char thinkOutput1[MSG_SIZ*10];\r
354 \r
355 ChessProgramState first, second;\r
356 \r
357 /* premove variables */\r
358 int premoveToX = 0;\r
359 int premoveToY = 0;\r
360 int premoveFromX = 0;\r
361 int premoveFromY = 0;\r
362 int premovePromoChar = 0;\r
363 int gotPremove = 0;\r
364 Boolean alarmSounded;\r
365 /* end premove variables */\r
366 \r
367 #define ICS_GENERIC 0\r
368 #define ICS_ICC 1\r
369 #define ICS_FICS 2\r
370 #define ICS_CHESSNET 3 /* not really supported */\r
371 int ics_type = ICS_GENERIC;\r
372 char *ics_prefix = "$";\r
373 \r
374 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
375 int pauseExamForwardMostMove = 0;\r
376 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
377 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
378 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
379 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
380 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
381 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
382 int whiteFlag = FALSE, blackFlag = FALSE;\r
383 int userOfferedDraw = FALSE;\r
384 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
385 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
386 int cmailMoveType[CMAIL_MAX_GAMES];\r
387 long ics_clock_paused = 0;\r
388 ProcRef icsPR = NoProc, cmailPR = NoProc;\r
389 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
390 GameMode gameMode = BeginningOfGame;\r
391 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
392 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
393 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
394 int hiddenThinkOutputState = 0; /* [AS] */\r
395 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
396 int adjudicateLossPlies = 6;\r
397 char white_holding[64], black_holding[64];\r
398 TimeMark lastNodeCountTime;\r
399 long lastNodeCount=0;\r
400 int have_sent_ICS_logon = 0;\r
401 int movesPerSession;\r
402 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
403 long timeControl_2; /* [AS] Allow separate time controls */\r
404 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */\r
405 long timeRemaining[2][MAX_MOVES];\r
406 int matchGame = 0;\r
407 TimeMark programStartTime;\r
408 char ics_handle[MSG_SIZ];\r
409 int have_set_title = 0;\r
410 \r
411 /* animateTraining preserves the state of appData.animate\r
412  * when Training mode is activated. This allows the\r
413  * response to be animated when appData.animate == TRUE and\r
414  * appData.animateDragging == TRUE.\r
415  */\r
416 Boolean animateTraining;\r
417 \r
418 GameInfo gameInfo;\r
419 \r
420 AppData appData;\r
421 \r
422 Board boards[MAX_MOVES];\r
423 /* [HGM] Following 7 needed for accurate legality tests: */\r
424 char  epStatus[MAX_MOVES];\r
425 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
426 char  castlingRank[BOARD_SIZE]; // and corresponding ranks\r
427 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];\r
428 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
429 int   initialRulePlies, FENrulePlies;\r
430 char  FENepStatus;\r
431 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)\r
432 int loadFlag = 0; \r
433 int shuffleOpenings;\r
434 \r
435 ChessSquare  FIDEArray[2][BOARD_SIZE] = {\r
436     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
437         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
438     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
439         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
440 };\r
441 \r
442 ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
443     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
444         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
445     { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
446         BlackKing, BlackKing, BlackKnight, BlackRook }\r
447 };\r
448 \r
449 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {\r
450     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
451         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
452     { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
453         BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
454 };\r
455 \r
456 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
457     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
458         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
459     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
460         BlackKing, BlackBishop, BlackKnight, BlackRook }\r
461 };\r
462 \r
463 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
464     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,\r
465         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
466     { BlackRook, BlackKnight, BlackAlfil, BlackKing,\r
467         BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
468 };\r
469 \r
470 \r
471 #if (BOARD_SIZE>=10)\r
472 ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
473     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
474         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
475     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
476         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
477 };\r
478 \r
479 ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
480     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
481         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
482     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
483         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
484 };\r
485 \r
486 ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
487     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, \r
488         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
489     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, \r
490         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
491 };\r
492 \r
493 ChessSquare JanusArray[2][BOARD_SIZE] = {\r
494     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, \r
495         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },\r
496     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, \r
497         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }\r
498 };\r
499 \r
500 #ifdef GOTHIC\r
501 ChessSquare GothicArray[2][BOARD_SIZE] = {\r
502     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
503         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },\r
504     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
505         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }\r
506 };\r
507 #else // !GOTHIC\r
508 #define GothicArray CapablancaArray\r
509 #endif // !GOTHIC\r
510 \r
511 #ifdef FALCON\r
512 ChessSquare FalconArray[2][BOARD_SIZE] = {\r
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, \r
514         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },\r
515     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, \r
516         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }\r
517 };\r
518 #else // !FALCON\r
519 #define FalconArray CapablancaArray\r
520 #endif // !FALCON\r
521 \r
522 #else // !(BOARD_SIZE>=10)\r
523 #define XiangqiPosition FIDEArray\r
524 #define CapablancaArray FIDEArray\r
525 #define GothicArray FIDEArray\r
526 #endif // !(BOARD_SIZE>=10)\r
527 \r
528 #if (BOARD_SIZE>=12)\r
529 ChessSquare CourierArray[2][BOARD_SIZE] = {\r
530     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
531         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
532     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
533         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
534 };\r
535 #else // !(BOARD_SIZE>=12)\r
536 #define CourierArray CapablancaArray\r
537 #endif // !(BOARD_SIZE>=12)\r
538 \r
539 \r
540 Board initialPosition;\r
541 \r
542 \r
543 /* Convert str to a rating. Checks for special cases of "----",\r
544 \r
545    "++++", etc. Also strips ()'s */\r
546 int\r
547 string_to_rating(str)\r
548   char *str;\r
549 {\r
550   while(*str && !isdigit(*str)) ++str;\r
551   if (!*str)\r
552     return 0;   /* One of the special "no rating" cases */\r
553   else\r
554     return atoi(str);\r
555 }\r
556 \r
557 void\r
558 ClearProgramStats()\r
559 {\r
560     /* Init programStats */\r
561     programStats.movelist[0] = 0;\r
562     programStats.depth = 0;\r
563     programStats.nr_moves = 0;\r
564     programStats.moves_left = 0;\r
565     programStats.nodes = 0;\r
566     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output\r
567     programStats.score = 0;\r
568     programStats.got_only_move = 0;\r
569     programStats.got_fail = 0;\r
570     programStats.line_is_book = 0;\r
571 }\r
572 \r
573 void\r
574 InitBackEnd1()\r
575 {\r
576     int matched, min, sec;\r
577 \r
578     GetTimeMark(&programStartTime);\r
579 \r
580     ClearProgramStats();\r
581     programStats.ok_to_send = 1;\r
582     programStats.seen_stat = 0;\r
583 \r
584     /*\r
585      * Initialize game list\r
586      */\r
587     ListNew(&gameList);\r
588 \r
589 \r
590     /*\r
591      * Internet chess server status\r
592      */\r
593     if (appData.icsActive) {\r
594         appData.matchMode = FALSE;\r
595         appData.matchGames = 0;\r
596 #if ZIPPY       \r
597         appData.noChessProgram = !appData.zippyPlay;\r
598 #else\r
599         appData.zippyPlay = FALSE;\r
600         appData.zippyTalk = FALSE;\r
601         appData.noChessProgram = TRUE;\r
602 #endif\r
603         if (*appData.icsHelper != NULLCHAR) {\r
604             appData.useTelnet = TRUE;\r
605             appData.telnetProgram = appData.icsHelper;\r
606         }\r
607     } else {\r
608         appData.zippyTalk = appData.zippyPlay = FALSE;\r
609     }\r
610 \r
611     /* [AS] Initialize pv info list [HGM] and game state */\r
612     {\r
613         int i, j;\r
614 \r
615         for( i=0; i<MAX_MOVES; i++ ) {\r
616             pvInfoList[i].depth = -1;\r
617             epStatus[i]=EP_NONE;\r
618             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
619         }\r
620     }\r
621 \r
622     /*\r
623      * Parse timeControl resource\r
624      */\r
625     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
626                           appData.movesPerSession)) {\r
627         char buf[MSG_SIZ];\r
628         sprintf(buf, "bad timeControl option %s", appData.timeControl);\r
629         DisplayFatalError(buf, 0, 2);\r
630     }\r
631 \r
632     /*\r
633      * Parse searchTime resource\r
634      */\r
635     if (*appData.searchTime != NULLCHAR) {\r
636         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
637         if (matched == 1) {\r
638             searchTime = min * 60;\r
639         } else if (matched == 2) {\r
640             searchTime = min * 60 + sec;\r
641         } else {\r
642             char buf[MSG_SIZ];\r
643             sprintf(buf, "bad searchTime option %s", appData.searchTime);\r
644             DisplayFatalError(buf, 0, 2);\r
645         }\r
646     }\r
647 \r
648     /* [AS] Adjudication threshold */\r
649     adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
650     \r
651     first.which = "first";\r
652     second.which = "second";\r
653     first.maybeThinking = second.maybeThinking = FALSE;\r
654     first.pr = second.pr = NoProc;\r
655     first.isr = second.isr = NULL;\r
656     first.sendTime = second.sendTime = 2;\r
657     first.sendDrawOffers = 1;\r
658     if (appData.firstPlaysBlack) {\r
659         first.twoMachinesColor = "black\n";\r
660         second.twoMachinesColor = "white\n";\r
661     } else {\r
662         first.twoMachinesColor = "white\n";\r
663         second.twoMachinesColor = "black\n";\r
664     }\r
665     first.program = appData.firstChessProgram;\r
666     second.program = appData.secondChessProgram;\r
667     first.host = appData.firstHost;\r
668     second.host = appData.secondHost;\r
669     first.dir = appData.firstDirectory;\r
670     second.dir = appData.secondDirectory;\r
671     first.other = &second;\r
672     second.other = &first;\r
673     first.initString = appData.initString;\r
674     second.initString = appData.secondInitString;\r
675     first.computerString = appData.firstComputerString;\r
676     second.computerString = appData.secondComputerString;\r
677     first.useSigint = second.useSigint = TRUE;\r
678     first.useSigterm = second.useSigterm = TRUE;\r
679     first.reuse = appData.reuseFirst;\r
680     second.reuse = appData.reuseSecond;\r
681     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second\r
682     second.nps = appData.secondNPS;\r
683     first.useSetboard = second.useSetboard = FALSE;\r
684     first.useSAN = second.useSAN = FALSE;\r
685     first.usePing = second.usePing = FALSE;\r
686     first.lastPing = second.lastPing = 0;\r
687     first.lastPong = second.lastPong = 0;\r
688     first.usePlayother = second.usePlayother = FALSE;\r
689     first.useColors = second.useColors = TRUE;\r
690     first.useUsermove = second.useUsermove = FALSE;\r
691     first.sendICS = second.sendICS = FALSE;\r
692     first.sendName = second.sendName = appData.icsActive;\r
693     first.sdKludge = second.sdKludge = FALSE;\r
694     first.stKludge = second.stKludge = FALSE;\r
695     TidyProgramName(first.program, first.host, first.tidy);\r
696     TidyProgramName(second.program, second.host, second.tidy);\r
697     first.matchWins = second.matchWins = 0;\r
698     strcpy(first.variants, appData.variant);\r
699     strcpy(second.variants, appData.variant);\r
700     first.analysisSupport = second.analysisSupport = 2; /* detect */\r
701     first.analyzing = second.analyzing = FALSE;\r
702     first.initDone = second.initDone = FALSE;\r
703 \r
704     /* New features added by Tord: */\r
705     first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
706     first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
707     /* End of new features added by Tord. */\r
708 \r
709     /* [HGM] time odds: set factor for each machine */\r
710     first.timeOdds  = appData.firstTimeOdds;\r
711     second.timeOdds = appData.secondTimeOdds;\r
712     { int norm = 1;\r
713         if(appData.timeOddsMode) {\r
714             norm = first.timeOdds;\r
715             if(norm > second.timeOdds) norm = second.timeOdds;\r
716         }\r
717         first.timeOdds /= norm;\r
718         second.timeOdds /= norm;\r
719     }\r
720 \r
721     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
722     first.accumulateTC = appData.firstAccumulateTC;\r
723     second.accumulateTC = appData.secondAccumulateTC;\r
724     first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
725 \r
726     /* [HGM] debug */\r
727     first.debug = second.debug = FALSE;\r
728     first.supportsNPS = second.supportsNPS = UNKNOWN;\r
729 \r
730     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
731     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
732     first.isUCI = appData.firstIsUCI; /* [AS] */\r
733     second.isUCI = appData.secondIsUCI; /* [AS] */\r
734     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
735     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
736 \r
737     if (appData.firstProtocolVersion > PROTOVER ||\r
738         appData.firstProtocolVersion < 1) {\r
739       char buf[MSG_SIZ];\r
740       sprintf(buf, "protocol version %d not supported",\r
741               appData.firstProtocolVersion);\r
742       DisplayFatalError(buf, 0, 2);\r
743     } else {\r
744       first.protocolVersion = appData.firstProtocolVersion;\r
745     }\r
746 \r
747     if (appData.secondProtocolVersion > PROTOVER ||\r
748         appData.secondProtocolVersion < 1) {\r
749       char buf[MSG_SIZ];\r
750       sprintf(buf, "protocol version %d not supported",\r
751               appData.secondProtocolVersion);\r
752       DisplayFatalError(buf, 0, 2);\r
753     } else {\r
754       second.protocolVersion = appData.secondProtocolVersion;\r
755     }\r
756 \r
757     if (appData.icsActive) {\r
758         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */\r
759     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
760         appData.clockMode = FALSE;\r
761         first.sendTime = second.sendTime = 0;\r
762     }\r
763     \r
764 #if ZIPPY\r
765     /* Override some settings from environment variables, for backward\r
766        compatibility.  Unfortunately it's not feasible to have the env\r
767        vars just set defaults, at least in xboard.  Ugh.\r
768     */\r
769     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
770       ZippyInit();\r
771     }\r
772 #endif\r
773     \r
774     if (appData.noChessProgram) {\r
775         programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
776                                         + strlen(PATCHLEVEL));\r
777         sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
778     } else {\r
779         char *p, *q;\r
780         q = first.program;\r
781         while (*q != ' ' && *q != NULLCHAR) q++;\r
782         p = q;\r
783         while (p > first.program && *(p-1) != '/') p--;\r
784         programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
785                                         + strlen(PATCHLEVEL) + (q - p));\r
786         sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
787         strncat(programVersion, p, q - p);\r
788     }\r
789 \r
790     if (!appData.icsActive) {\r
791       char buf[MSG_SIZ];\r
792       /* Check for variants that are supported only in ICS mode,\r
793          or not at all.  Some that are accepted here nevertheless\r
794          have bugs; see comments below.\r
795       */\r
796       VariantClass variant = StringToVariant(appData.variant);\r
797       switch (variant) {\r
798       case VariantBughouse:     /* need four players and two boards */\r
799       case VariantKriegspiel:   /* need to hide pieces and move details */\r
800       /* case VariantFischeRandom: (Fabien: moved below) */\r
801         sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);\r
802         DisplayFatalError(buf, 0, 2);\r
803         return;\r
804 \r
805       case VariantUnknown:\r
806       case VariantLoadable:\r
807       case Variant29:\r
808       case Variant30:\r
809       case Variant31:\r
810       case Variant32:\r
811       case Variant33:\r
812       case Variant34:\r
813       case Variant35:\r
814       case Variant36:\r
815       default:\r
816         sprintf(buf, "Unknown variant name %s", appData.variant);\r
817         DisplayFatalError(buf, 0, 2);\r
818         return;\r
819 \r
820       case VariantXiangqi:    /* [HGM] repetition rules not implemented */\r
821       case VariantFairy:      /* [HGM] TestLegality definitely off! */\r
822       case VariantGothic:     /* [HGM] should work */\r
823       case VariantCapablanca: /* [HGM] should work */\r
824       case VariantCourier:    /* [HGM] initial forced moves not implemented */\r
825       case VariantShogi:      /* [HGM] drops not tested for legality */\r
826       case VariantKnightmate: /* [HGM] should work */\r
827       case VariantCylinder:   /* [HGM] untested */\r
828       case VariantFalcon:     /* [HGM] untested */\r
829       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
830                                  offboard interposition not understood */\r
831       case VariantNormal:     /* definitely works! */\r
832       case VariantWildCastle: /* pieces not automatically shuffled */\r
833       case VariantNoCastle:   /* pieces not automatically shuffled */\r
834       case VariantFischeRandom: /* [HGM] works and shuffles pieces */\r
835       case VariantLosers:     /* should work except for win condition,\r
836                                  and doesn't know captures are mandatory */\r
837       case VariantSuicide:    /* should work except for win condition,\r
838                                  and doesn't know captures are mandatory */\r
839       case VariantGiveaway:   /* should work except for win condition,\r
840                                  and doesn't know captures are mandatory */\r
841       case VariantTwoKings:   /* should work */\r
842       case VariantAtomic:     /* should work except for win condition */\r
843       case Variant3Check:     /* should work except for win condition */\r
844       case VariantShatranj:   /* should work except for all win conditions */\r
845       case VariantBerolina:   /* might work if TestLegality is off */\r
846       case VariantCapaRandom: /* should work */\r
847         break;\r
848       }\r
849     }\r
850 }\r
851 \r
852 int NextIntegerFromString( char ** str, long * value )\r
853 {\r
854     int result = -1;\r
855     char * s = *str;\r
856 \r
857     while( *s == ' ' || *s == '\t' ) {\r
858         s++;\r
859     }\r
860 \r
861     *value = 0;\r
862 \r
863     if( *s >= '0' && *s <= '9' ) {\r
864         while( *s >= '0' && *s <= '9' ) {\r
865             *value = *value * 10 + (*s - '0');\r
866             s++;\r
867         }\r
868 \r
869         result = 0;\r
870     }\r
871 \r
872     *str = s;\r
873 \r
874     return result;\r
875 }\r
876 \r
877 int NextTimeControlFromString( char ** str, long * value )\r
878 {\r
879     long temp;\r
880     int result = NextIntegerFromString( str, &temp );\r
881 \r
882     if( result == 0 ) {\r
883         *value = temp * 60; /* Minutes */\r
884         if( **str == ':' ) {\r
885             (*str)++;\r
886             result = NextIntegerFromString( str, &temp );\r
887             *value += temp; /* Seconds */\r
888         }\r
889     }\r
890 \r
891     return result;\r
892 }\r
893 \r
894 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
895 {   /* [HGM] routine added to read '+moves/time' for secondary time control */\r
896     int result = -1; long temp, temp2;\r
897 \r
898     if(**str != '+') return -1; // old params remain in force!\r
899     (*str)++;\r
900     if( NextTimeControlFromString( str, &temp ) ) return -1;\r
901 \r
902     if(**str != '/') {\r
903         /* time only: incremental or sudden-death time control */\r
904         if(**str == '+') { /* increment follows; read it */\r
905             (*str)++;\r
906             if(result = NextIntegerFromString( str, &temp2)) return -1;\r
907             *inc = temp2 * 1000;\r
908         } else *inc = 0;\r
909         *moves = 0; *tc = temp * 1000; \r
910         return 0;\r
911     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */\r
912 \r
913     (*str)++; /* classical time control */\r
914     result = NextTimeControlFromString( str, &temp2);\r
915     if(result == 0) {\r
916         *moves = temp/60;\r
917         *tc    = temp2 * 1000;\r
918         *inc   = 0;\r
919     }\r
920     return result;\r
921 }\r
922 \r
923 int GetTimeQuota(int movenr)\r
924 {   /* [HGM] get time to add from the multi-session time-control string */\r
925     int moves=1; /* kludge to force reading of first session */\r
926     long time, increment;\r
927     char *s = fullTimeControlString;\r
928 \r
929     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
930     do {\r
931         if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
932         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
933         if(movenr == -1) return time;    /* last move before new session     */\r
934         if(!moves) return increment;     /* current session is incremental   */\r
935         if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
936     } while(movenr >= -1);               /* try again for next session       */\r
937 \r
938     return 0; // no new time quota on this move\r
939 }\r
940 \r
941 int\r
942 ParseTimeControl(tc, ti, mps)\r
943      char *tc;\r
944      int ti;\r
945      int mps;\r
946 {\r
947 #if 0\r
948     int matched, min, sec;\r
949 \r
950     matched = sscanf(tc, "%d:%d", &min, &sec);\r
951     if (matched == 1) {\r
952         timeControl = min * 60 * 1000;\r
953     } else if (matched == 2) {\r
954         timeControl = (min * 60 + sec) * 1000;\r
955     } else {\r
956         return FALSE;\r
957     }\r
958 #else\r
959     long tc1;\r
960     long tc2;\r
961     char buf[MSG_SIZ];\r
962 \r
963     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
964     if(ti > 0) {\r
965         if(mps)\r
966              sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
967         else sprintf(buf, "+%s+%d", tc, ti);\r
968     } else {\r
969         if(mps)\r
970              sprintf(buf, "+%d/%s", mps, tc);\r
971         else sprintf(buf, "+%s", tc);\r
972     }\r
973     fullTimeControlString = StrSave(buf);\r
974 \r
975     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
976         return FALSE;\r
977     }\r
978 \r
979     if( *tc == '/' ) {\r
980         /* Parse second time control */\r
981         tc++;\r
982 \r
983         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
984             return FALSE;\r
985         }\r
986 \r
987         if( tc2 == 0 ) {\r
988             return FALSE;\r
989         }\r
990 \r
991         timeControl_2 = tc2 * 1000;\r
992     }\r
993     else {\r
994         timeControl_2 = 0;\r
995     }\r
996 \r
997     if( tc1 == 0 ) {\r
998         return FALSE;\r
999     }\r
1000 \r
1001     timeControl = tc1 * 1000;\r
1002 #endif\r
1003 \r
1004     if (ti >= 0) {\r
1005         timeIncrement = ti * 1000;  /* convert to ms */\r
1006         movesPerSession = 0;\r
1007     } else {\r
1008         timeIncrement = 0;\r
1009         movesPerSession = mps;\r
1010     }\r
1011     return TRUE;\r
1012 }\r
1013 \r
1014 void\r
1015 InitBackEnd2()\r
1016 {\r
1017     if (appData.debugMode) {\r
1018         fprintf(debugFP, "%s\n", programVersion);\r
1019     }\r
1020 \r
1021     if (appData.matchGames > 0) {\r
1022         appData.matchMode = TRUE;\r
1023     } else if (appData.matchMode) {\r
1024         appData.matchGames = 1;\r
1025     }\r
1026     Reset(TRUE, FALSE);\r
1027     if (appData.noChessProgram || first.protocolVersion == 1) {\r
1028       InitBackEnd3();\r
1029     } else {\r
1030       /* kludge: allow timeout for initial "feature" commands */\r
1031       FreezeUI();\r
1032       DisplayMessage("", "Starting chess program");\r
1033       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
1034     }\r
1035 }\r
1036 \r
1037 void\r
1038 InitBackEnd3 P((void))\r
1039 {\r
1040     GameMode initialMode;\r
1041     char buf[MSG_SIZ];\r
1042     int err;\r
1043 \r
1044     InitChessProgram(&first, startedFromSetupPosition);\r
1045 \r
1046     if (appData.icsActive) {\r
1047         err = establish();\r
1048         if (err != 0) {\r
1049             if (*appData.icsCommPort != NULLCHAR) {\r
1050                 sprintf(buf, "Could not open comm port %s",  \r
1051                         appData.icsCommPort);\r
1052             } else {\r
1053                 sprintf(buf, "Could not connect to host %s, port %s",  \r
1054                         appData.icsHost, appData.icsPort);\r
1055             }\r
1056             DisplayFatalError(buf, err, 1);\r
1057             return;\r
1058         }\r
1059         SetICSMode();\r
1060         telnetISR =\r
1061           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
1062         fromUserISR =\r
1063           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
1064     } else if (appData.noChessProgram) {\r
1065         SetNCPMode();\r
1066     } else {\r
1067         SetGNUMode();\r
1068     }\r
1069 \r
1070     if (*appData.cmailGameName != NULLCHAR) {\r
1071         SetCmailMode();\r
1072         OpenLoopback(&cmailPR);\r
1073         cmailISR =\r
1074           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
1075     }\r
1076     \r
1077     ThawUI();\r
1078     DisplayMessage("", "");\r
1079     if (StrCaseCmp(appData.initialMode, "") == 0) {\r
1080       initialMode = BeginningOfGame;\r
1081     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
1082       initialMode = TwoMachinesPlay;\r
1083     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
1084       initialMode = AnalyzeFile; \r
1085     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
1086       initialMode = AnalyzeMode;\r
1087     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
1088       initialMode = MachinePlaysWhite;\r
1089     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
1090       initialMode = MachinePlaysBlack;\r
1091     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
1092       initialMode = EditGame;\r
1093     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
1094       initialMode = EditPosition;\r
1095     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
1096       initialMode = Training;\r
1097     } else {\r
1098       sprintf(buf, "Unknown initialMode %s", appData.initialMode);\r
1099       DisplayFatalError(buf, 0, 2);\r
1100       return;\r
1101     }\r
1102 \r
1103     if (appData.matchMode) {\r
1104         /* Set up machine vs. machine match */\r
1105         if (appData.noChessProgram) {\r
1106             DisplayFatalError("Can't have a match with no chess programs",\r
1107                               0, 2);\r
1108             return;\r
1109         }\r
1110         matchMode = TRUE;\r
1111         matchGame = 1;\r
1112         if (*appData.loadGameFile != NULLCHAR) {\r
1113             if (!LoadGameFromFile(appData.loadGameFile,\r
1114                                   appData.loadGameIndex,\r
1115                                   appData.loadGameFile, FALSE)) {\r
1116                 DisplayFatalError("Bad game file", 0, 1);\r
1117                 return;\r
1118             }\r
1119         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1120             if (!LoadPositionFromFile(appData.loadPositionFile,\r
1121                                       appData.loadPositionIndex,\r
1122                                       appData.loadPositionFile)) {\r
1123                 DisplayFatalError("Bad position file", 0, 1);\r
1124                 return;\r
1125             }\r
1126         }\r
1127         TwoMachinesEvent();\r
1128     } else if (*appData.cmailGameName != NULLCHAR) {\r
1129         /* Set up cmail mode */\r
1130         ReloadCmailMsgEvent(TRUE);\r
1131     } else {\r
1132         /* Set up other modes */\r
1133         if (initialMode == AnalyzeFile) {\r
1134           if (*appData.loadGameFile == NULLCHAR) {\r
1135             DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);\r
1136             return;\r
1137           }\r
1138         }\r
1139         if (*appData.loadGameFile != NULLCHAR) {\r
1140             (void) LoadGameFromFile(appData.loadGameFile,\r
1141                                     appData.loadGameIndex,\r
1142                                     appData.loadGameFile, TRUE);\r
1143         } else if (*appData.loadPositionFile != NULLCHAR) {\r
1144             (void) LoadPositionFromFile(appData.loadPositionFile,\r
1145                                         appData.loadPositionIndex,\r
1146                                         appData.loadPositionFile);\r
1147             /* [HGM] try to make self-starting even after FEN load */\r
1148             /* to allow automatic setup of fairy variants with wtm */\r
1149             if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
1150                 gameMode = BeginningOfGame;\r
1151                 setboardSpoiledMachineBlack = 1;\r
1152             }\r
1153             /* [HGM] loadPos: make that every new game uses the setup */\r
1154             /* from file as long as we do not switch variant          */\r
1155             if(!blackPlaysFirst) { int i;\r
1156                 startedFromPositionFile = TRUE;\r
1157                 CopyBoard(filePosition, boards[0]);\r
1158                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
1159             }\r
1160         }\r
1161         if (initialMode == AnalyzeMode) {\r
1162           if (appData.noChessProgram) {\r
1163             DisplayFatalError("Analysis mode requires a chess engine", 0, 2);\r
1164             return;\r
1165           }\r
1166           if (appData.icsActive) {\r
1167             DisplayFatalError("Analysis mode does not work with ICS mode",0,2);\r
1168             return;\r
1169           }\r
1170           AnalyzeModeEvent();\r
1171         } else if (initialMode == AnalyzeFile) {\r
1172           ShowThinkingEvent(TRUE);\r
1173           AnalyzeFileEvent();\r
1174           AnalysisPeriodicEvent(1);\r
1175         } else if (initialMode == MachinePlaysWhite) {\r
1176           if (appData.noChessProgram) {\r
1177             DisplayFatalError("MachineWhite mode requires a chess engine",\r
1178                               0, 2);\r
1179             return;\r
1180           }\r
1181           if (appData.icsActive) {\r
1182             DisplayFatalError("MachineWhite mode does not work with ICS mode",\r
1183                               0, 2);\r
1184             return;\r
1185           }\r
1186           MachineWhiteEvent();\r
1187         } else if (initialMode == MachinePlaysBlack) {\r
1188           if (appData.noChessProgram) {\r
1189             DisplayFatalError("MachineBlack mode requires a chess engine",\r
1190                               0, 2);\r
1191             return;\r
1192           }\r
1193           if (appData.icsActive) {\r
1194             DisplayFatalError("MachineBlack mode does not work with ICS mode",\r
1195                               0, 2);\r
1196             return;\r
1197           }\r
1198           MachineBlackEvent();\r
1199         } else if (initialMode == TwoMachinesPlay) {\r
1200           if (appData.noChessProgram) {\r
1201             DisplayFatalError("TwoMachines mode requires a chess engine",\r
1202                               0, 2);\r
1203             return;\r
1204           }\r
1205           if (appData.icsActive) {\r
1206             DisplayFatalError("TwoMachines mode does not work with ICS mode",\r
1207                               0, 2);\r
1208             return;\r
1209           }\r
1210           TwoMachinesEvent();\r
1211         } else if (initialMode == EditGame) {\r
1212           EditGameEvent();\r
1213         } else if (initialMode == EditPosition) {\r
1214           EditPositionEvent();\r
1215         } else if (initialMode == Training) {\r
1216           if (*appData.loadGameFile == NULLCHAR) {\r
1217             DisplayFatalError("Training mode requires a game file", 0, 2);\r
1218             return;\r
1219           }\r
1220           TrainingEvent();\r
1221         }\r
1222     }\r
1223 }\r
1224 \r
1225 /*\r
1226  * Establish will establish a contact to a remote host.port.\r
1227  * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
1228  *  used to talk to the host.\r
1229  * Returns 0 if okay, error code if not.\r
1230  */\r
1231 int\r
1232 establish()\r
1233 {\r
1234     char buf[MSG_SIZ];\r
1235 \r
1236     if (*appData.icsCommPort != NULLCHAR) {\r
1237         /* Talk to the host through a serial comm port */\r
1238         return OpenCommPort(appData.icsCommPort, &icsPR);\r
1239 \r
1240     } else if (*appData.gateway != NULLCHAR) {\r
1241         if (*appData.remoteShell == NULLCHAR) {\r
1242             /* Use the rcmd protocol to run telnet program on a gateway host */\r
1243             sprintf(buf, "%s %s %s",\r
1244                     appData.telnetProgram, appData.icsHost, appData.icsPort);\r
1245             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
1246 \r
1247         } else {\r
1248             /* Use the rsh program to run telnet program on a gateway host */\r
1249             if (*appData.remoteUser == NULLCHAR) {\r
1250                 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,\r
1251                         appData.gateway, appData.telnetProgram,\r
1252                         appData.icsHost, appData.icsPort);\r
1253             } else {\r
1254                 sprintf(buf, "%s %s -l %s %s %s %s",\r
1255                         appData.remoteShell, appData.gateway, \r
1256                         appData.remoteUser, appData.telnetProgram,\r
1257                         appData.icsHost, appData.icsPort);\r
1258             }\r
1259             return StartChildProcess(buf, "", &icsPR);\r
1260 \r
1261         }\r
1262     } else if (appData.useTelnet) {\r
1263         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
1264 \r
1265     } else {\r
1266         /* TCP socket interface differs somewhat between\r
1267            Unix and NT; handle details in the front end.\r
1268            */\r
1269         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
1270     }\r
1271 }\r
1272 \r
1273 void\r
1274 show_bytes(fp, buf, count)\r
1275      FILE *fp;\r
1276      char *buf;\r
1277      int count;\r
1278 {\r
1279     while (count--) {\r
1280         if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
1281             fprintf(fp, "\\%03o", *buf & 0xff);\r
1282         } else {\r
1283             putc(*buf, fp);\r
1284         }\r
1285         buf++;\r
1286     }\r
1287     fflush(fp);\r
1288 }\r
1289 \r
1290 /* Returns an errno value */\r
1291 int\r
1292 OutputMaybeTelnet(pr, message, count, outError)\r
1293      ProcRef pr;\r
1294      char *message;\r
1295      int count;\r
1296      int *outError;\r
1297 {\r
1298     char buf[8192], *p, *q, *buflim;\r
1299     int left, newcount, outcount;\r
1300 \r
1301     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
1302         *appData.gateway != NULLCHAR) {\r
1303         if (appData.debugMode) {\r
1304             fprintf(debugFP, ">ICS: ");\r
1305             show_bytes(debugFP, message, count);\r
1306             fprintf(debugFP, "\n");\r
1307         }\r
1308         return OutputToProcess(pr, message, count, outError);\r
1309     }\r
1310 \r
1311     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
1312     p = message;\r
1313     q = buf;\r
1314     left = count;\r
1315     newcount = 0;\r
1316     while (left) {\r
1317         if (q >= buflim) {\r
1318             if (appData.debugMode) {\r
1319                 fprintf(debugFP, ">ICS: ");\r
1320                 show_bytes(debugFP, buf, newcount);\r
1321                 fprintf(debugFP, "\n");\r
1322             }\r
1323             outcount = OutputToProcess(pr, buf, newcount, outError);\r
1324             if (outcount < newcount) return -1; /* to be sure */\r
1325             q = buf;\r
1326             newcount = 0;\r
1327         }\r
1328         if (*p == '\n') {\r
1329             *q++ = '\r';\r
1330             newcount++;\r
1331         } else if (((unsigned char) *p) == TN_IAC) {\r
1332             *q++ = (char) TN_IAC;\r
1333             newcount ++;\r
1334         }\r
1335         *q++ = *p++;\r
1336         newcount++;\r
1337         left--;\r
1338     }\r
1339     if (appData.debugMode) {\r
1340         fprintf(debugFP, ">ICS: ");\r
1341         show_bytes(debugFP, buf, newcount);\r
1342         fprintf(debugFP, "\n");\r
1343     }\r
1344     outcount = OutputToProcess(pr, buf, newcount, outError);\r
1345     if (outcount < newcount) return -1; /* to be sure */\r
1346     return count;\r
1347 }\r
1348 \r
1349 void\r
1350 read_from_player(isr, closure, message, count, error)\r
1351      InputSourceRef isr;\r
1352      VOIDSTAR closure;\r
1353      char *message;\r
1354      int count;\r
1355      int error;\r
1356 {\r
1357     int outError, outCount;\r
1358     static int gotEof = 0;\r
1359 \r
1360     /* Pass data read from player on to ICS */\r
1361     if (count > 0) {\r
1362         gotEof = 0;\r
1363         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
1364         if (outCount < count) {\r
1365             DisplayFatalError("Error writing to ICS", outError, 1);\r
1366         }\r
1367     } else if (count < 0) {\r
1368         RemoveInputSource(isr);\r
1369         DisplayFatalError("Error reading from keyboard", error, 1);\r
1370     } else if (gotEof++ > 0) {\r
1371         RemoveInputSource(isr);\r
1372         DisplayFatalError("Got end of file from keyboard", 0, 0);\r
1373     }\r
1374 }\r
1375 \r
1376 void\r
1377 SendToICS(s)\r
1378      char *s;\r
1379 {\r
1380     int count, outCount, outError;\r
1381 \r
1382     if (icsPR == NULL) return;\r
1383 \r
1384     count = strlen(s);\r
1385     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
1386     if (outCount < count) {\r
1387         DisplayFatalError("Error writing to ICS", outError, 1);\r
1388     }\r
1389 }\r
1390 \r
1391 /* This is used for sending logon scripts to the ICS. Sending\r
1392    without a delay causes problems when using timestamp on ICC\r
1393    (at least on my machine). */\r
1394 void\r
1395 SendToICSDelayed(s,msdelay)\r
1396      char *s;\r
1397      long msdelay;\r
1398 {\r
1399     int count, outCount, outError;\r
1400 \r
1401     if (icsPR == NULL) return;\r
1402 \r
1403     count = strlen(s);\r
1404     if (appData.debugMode) {\r
1405         fprintf(debugFP, ">ICS: ");\r
1406         show_bytes(debugFP, s, count);\r
1407         fprintf(debugFP, "\n");\r
1408     }\r
1409     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
1410                                       msdelay);\r
1411     if (outCount < count) {\r
1412         DisplayFatalError("Error writing to ICS", outError, 1);\r
1413     }\r
1414 }\r
1415 \r
1416 \r
1417 /* Remove all highlighting escape sequences in s\r
1418    Also deletes any suffix starting with '(' \r
1419    */\r
1420 char *\r
1421 StripHighlightAndTitle(s)\r
1422      char *s;\r
1423 {\r
1424     static char retbuf[MSG_SIZ];\r
1425     char *p = retbuf;\r
1426 \r
1427     while (*s != NULLCHAR) {\r
1428         while (*s == '\033') {\r
1429             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1430             if (*s != NULLCHAR) s++;\r
1431         }\r
1432         while (*s != NULLCHAR && *s != '\033') {\r
1433             if (*s == '(' || *s == '[') {\r
1434                 *p = NULLCHAR;\r
1435                 return retbuf;\r
1436             }\r
1437             *p++ = *s++;\r
1438         }\r
1439     }\r
1440     *p = NULLCHAR;\r
1441     return retbuf;\r
1442 }\r
1443 \r
1444 /* Remove all highlighting escape sequences in s */\r
1445 char *\r
1446 StripHighlight(s)\r
1447      char *s;\r
1448 {\r
1449     static char retbuf[MSG_SIZ];\r
1450     char *p = retbuf;\r
1451 \r
1452     while (*s != NULLCHAR) {\r
1453         while (*s == '\033') {\r
1454             while (*s != NULLCHAR && !isalpha(*s)) s++;\r
1455             if (*s != NULLCHAR) s++;\r
1456         }\r
1457         while (*s != NULLCHAR && *s != '\033') {\r
1458             *p++ = *s++;\r
1459         }\r
1460     }\r
1461     *p = NULLCHAR;\r
1462     return retbuf;\r
1463 }\r
1464 \r
1465 char *variantNames[] = VARIANT_NAMES;\r
1466 char *\r
1467 VariantName(v)\r
1468      VariantClass v;\r
1469 {\r
1470     return variantNames[v];\r
1471 }\r
1472 \r
1473 \r
1474 /* Identify a variant from the strings the chess servers use or the\r
1475    PGN Variant tag names we use. */\r
1476 VariantClass\r
1477 StringToVariant(e)\r
1478      char *e;\r
1479 {\r
1480     char *p;\r
1481     int wnum = -1;\r
1482     VariantClass v = VariantNormal;\r
1483     int i, found = FALSE;\r
1484     char buf[MSG_SIZ];\r
1485 \r
1486     if (!e) return v;\r
1487 \r
1488     /* [HGM] skip over optional board-size prefixes */\r
1489     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
1490         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
1491         while( *e++ != '_');\r
1492     }\r
1493 \r
1494     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
1495       if (StrCaseStr(e, variantNames[i])) {\r
1496         v = (VariantClass) i;\r
1497         found = TRUE;\r
1498         break;\r
1499       }\r
1500     }\r
1501 \r
1502     if (!found) {\r
1503       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
1504           || StrCaseStr(e, "wild/fr")) {\r
1505         v = VariantFischeRandom;\r
1506       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
1507                  (i = 1, p = StrCaseStr(e, "w"))) {\r
1508         p += i;\r
1509         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
1510         if (isdigit(*p)) {\r
1511           wnum = atoi(p);\r
1512         } else {\r
1513           wnum = -1;\r
1514         }\r
1515         switch (wnum) {\r
1516         case 0: /* FICS only, actually */\r
1517         case 1:\r
1518           /* Castling legal even if K starts on d-file */\r
1519           v = VariantWildCastle;\r
1520           break;\r
1521         case 2:\r
1522         case 3:\r
1523         case 4:\r
1524           /* Castling illegal even if K & R happen to start in\r
1525              normal positions. */\r
1526           v = VariantNoCastle;\r
1527           break;\r
1528         case 5:\r
1529         case 7:\r
1530         case 8:\r
1531         case 10:\r
1532         case 11:\r
1533         case 12:\r
1534         case 13:\r
1535         case 14:\r
1536         case 15:\r
1537         case 18:\r
1538         case 19:\r
1539           /* Castling legal iff K & R start in normal positions */\r
1540           v = VariantNormal;\r
1541           break;\r
1542         case 6:\r
1543         case 20:\r
1544         case 21:\r
1545           /* Special wilds for position setup; unclear what to do here */\r
1546           v = VariantLoadable;\r
1547           break;\r
1548         case 9:\r
1549           /* Bizarre ICC game */\r
1550           v = VariantTwoKings;\r
1551           break;\r
1552         case 16:\r
1553           v = VariantKriegspiel;\r
1554           break;\r
1555         case 17:\r
1556           v = VariantLosers;\r
1557           break;\r
1558         case 22:\r
1559           v = VariantFischeRandom;\r
1560           break;\r
1561         case 23:\r
1562           v = VariantCrazyhouse;\r
1563           break;\r
1564         case 24:\r
1565           v = VariantBughouse;\r
1566           break;\r
1567         case 25:\r
1568           v = Variant3Check;\r
1569           break;\r
1570         case 26:\r
1571           /* Not quite the same as FICS suicide! */\r
1572           v = VariantGiveaway;\r
1573           break;\r
1574         case 27:\r
1575           v = VariantAtomic;\r
1576           break;\r
1577         case 28:\r
1578           v = VariantShatranj;\r
1579           break;\r
1580 \r
1581         /* Temporary names for future ICC types.  The name *will* change in \r
1582            the next xboard/WinBoard release after ICC defines it. */\r
1583         case 29:\r
1584           v = Variant29;\r
1585           break;\r
1586         case 30:\r
1587           v = Variant30;\r
1588           break;\r
1589         case 31:\r
1590           v = Variant31;\r
1591           break;\r
1592         case 32:\r
1593           v = Variant32;\r
1594           break;\r
1595         case 33:\r
1596           v = Variant33;\r
1597           break;\r
1598         case 34:\r
1599           v = Variant34;\r
1600           break;\r
1601         case 35:\r
1602           v = Variant35;\r
1603           break;\r
1604         case 36:\r
1605           v = Variant36;\r
1606           break;\r
1607         case 37:\r
1608           v = VariantShogi;\r
1609           break;\r
1610         case 38:\r
1611           v = VariantXiangqi;\r
1612           break;\r
1613         case 39:\r
1614           v = VariantCourier;\r
1615           break;\r
1616         case 40:\r
1617           v = VariantGothic;\r
1618           break;\r
1619         case 41:\r
1620           v = VariantCapablanca;\r
1621           break;\r
1622         case 42:\r
1623           v = VariantKnightmate;\r
1624           break;\r
1625         case 43:\r
1626           v = VariantFairy;\r
1627           break;\r
1628         case 44:\r
1629           v = VariantCylinder;\r
1630           break;\r
1631         case 45:\r
1632           v = VariantFalcon;\r
1633           break;\r
1634         case 46:\r
1635           v = VariantCapaRandom;\r
1636           break;\r
1637         case 47:\r
1638           v = VariantBerolina;\r
1639           break;\r
1640         case 48:\r
1641           v = VariantJanus;\r
1642           break;\r
1643         case -1:\r
1644           /* Found "wild" or "w" in the string but no number;\r
1645              must assume it's normal chess. */\r
1646           v = VariantNormal;\r
1647           break;\r
1648         default:\r
1649           sprintf(buf, "Unknown wild type %d", wnum);\r
1650           DisplayError(buf, 0);\r
1651           v = VariantUnknown;\r
1652           break;\r
1653         }\r
1654       }\r
1655     }\r
1656     if (appData.debugMode) {\r
1657       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",\r
1658               e, wnum, VariantName(v));\r
1659     }\r
1660     return v;\r
1661 }\r
1662 \r
1663 static int leftover_start = 0, leftover_len = 0;\r
1664 char star_match[STAR_MATCH_N][MSG_SIZ];\r
1665 \r
1666 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
1667    advance *index beyond it, and set leftover_start to the new value of\r
1668    *index; else return FALSE.  If pattern contains the character '*', it\r
1669    matches any sequence of characters not containing '\r', '\n', or the\r
1670    character following the '*' (if any), and the matched sequence(s) are\r
1671    copied into star_match.\r
1672    */\r
1673 int\r
1674 looking_at(buf, index, pattern)\r
1675      char *buf;\r
1676      int *index;\r
1677      char *pattern;\r
1678 {\r
1679     char *bufp = &buf[*index], *patternp = pattern;\r
1680     int star_count = 0;\r
1681     char *matchp = star_match[0];\r
1682     \r
1683     for (;;) {\r
1684         if (*patternp == NULLCHAR) {\r
1685             *index = leftover_start = bufp - buf;\r
1686             *matchp = NULLCHAR;\r
1687             return TRUE;\r
1688         }\r
1689         if (*bufp == NULLCHAR) return FALSE;\r
1690         if (*patternp == '*') {\r
1691             if (*bufp == *(patternp + 1)) {\r
1692                 *matchp = NULLCHAR;\r
1693                 matchp = star_match[++star_count];\r
1694                 patternp += 2;\r
1695                 bufp++;\r
1696                 continue;\r
1697             } else if (*bufp == '\n' || *bufp == '\r') {\r
1698                 patternp++;\r
1699                 if (*patternp == NULLCHAR)\r
1700                   continue;\r
1701                 else\r
1702                   return FALSE;\r
1703             } else {\r
1704                 *matchp++ = *bufp++;\r
1705                 continue;\r
1706             }\r
1707         }\r
1708         if (*patternp != *bufp) return FALSE;\r
1709         patternp++;\r
1710         bufp++;\r
1711     }\r
1712 }\r
1713 \r
1714 void\r
1715 SendToPlayer(data, length)\r
1716      char *data;\r
1717      int length;\r
1718 {\r
1719     int error, outCount;\r
1720     outCount = OutputToProcess(NoProc, data, length, &error);\r
1721     if (outCount < length) {\r
1722         DisplayFatalError("Error writing to display", error, 1);\r
1723     }\r
1724 }\r
1725 \r
1726 void\r
1727 PackHolding(packed, holding)\r
1728      char packed[];\r
1729      char *holding;\r
1730 {\r
1731     char *p = holding;\r
1732     char *q = packed;\r
1733     int runlength = 0;\r
1734     int curr = 9999;\r
1735     do {\r
1736         if (*p == curr) {\r
1737             runlength++;\r
1738         } else {\r
1739             switch (runlength) {\r
1740               case 0:\r
1741                 break;\r
1742               case 1:\r
1743                 *q++ = curr;\r
1744                 break;\r
1745               case 2:\r
1746                 *q++ = curr;\r
1747                 *q++ = curr;\r
1748                 break;\r
1749               default:\r
1750                 sprintf(q, "%d", runlength);\r
1751                 while (*q) q++;\r
1752                 *q++ = curr;\r
1753                 break;\r
1754             }\r
1755             runlength = 1;\r
1756             curr = *p;\r
1757         }\r
1758     } while (*p++);\r
1759     *q = NULLCHAR;\r
1760 }\r
1761 \r
1762 /* Telnet protocol requests from the front end */\r
1763 void\r
1764 TelnetRequest(ddww, option)\r
1765      unsigned char ddww, option;\r
1766 {\r
1767     unsigned char msg[3];\r
1768     int outCount, outError;\r
1769 \r
1770     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
1771 \r
1772     if (appData.debugMode) {\r
1773         char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
1774         switch (ddww) {\r
1775           case TN_DO:\r
1776             ddwwStr = "DO";\r
1777             break;\r
1778           case TN_DONT:\r
1779             ddwwStr = "DONT";\r
1780             break;\r
1781           case TN_WILL:\r
1782             ddwwStr = "WILL";\r
1783             break;\r
1784           case TN_WONT:\r
1785             ddwwStr = "WONT";\r
1786             break;\r
1787           default:\r
1788             ddwwStr = buf1;\r
1789             sprintf(buf1, "%d", ddww);\r
1790             break;\r
1791         }\r
1792         switch (option) {\r
1793           case TN_ECHO:\r
1794             optionStr = "ECHO";\r
1795             break;\r
1796           default:\r
1797             optionStr = buf2;\r
1798             sprintf(buf2, "%d", option);\r
1799             break;\r
1800         }\r
1801         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
1802     }\r
1803     msg[0] = TN_IAC;\r
1804     msg[1] = ddww;\r
1805     msg[2] = option;\r
1806     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
1807     if (outCount < 3) {\r
1808         DisplayFatalError("Error writing to ICS", outError, 1);\r
1809     }\r
1810 }\r
1811 \r
1812 void\r
1813 DoEcho()\r
1814 {\r
1815     if (!appData.icsActive) return;\r
1816     TelnetRequest(TN_DO, TN_ECHO);\r
1817 }\r
1818 \r
1819 void\r
1820 DontEcho()\r
1821 {\r
1822     if (!appData.icsActive) return;\r
1823     TelnetRequest(TN_DONT, TN_ECHO);\r
1824 }\r
1825 \r
1826 void\r
1827 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
1828 {\r
1829     /* put the holdings sent to us by the server on the board holdings area */\r
1830     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
1831     char p;\r
1832     ChessSquare piece;\r
1833 \r
1834     if(gameInfo.holdingsWidth < 2)  return;\r
1835 \r
1836     if( (int)lowestPiece >= BlackPawn ) {\r
1837         holdingsColumn = 0;\r
1838         countsColumn = 1;\r
1839         holdingsStartRow = BOARD_HEIGHT-1;\r
1840         direction = -1;\r
1841     } else {\r
1842         holdingsColumn = BOARD_WIDTH-1;\r
1843         countsColumn = BOARD_WIDTH-2;\r
1844         holdingsStartRow = 0;\r
1845         direction = 1;\r
1846     }\r
1847 \r
1848     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
1849         board[i][holdingsColumn] = EmptySquare;\r
1850         board[i][countsColumn]   = (ChessSquare) 0;\r
1851     }\r
1852     while( (p=*holdings++) != NULLCHAR ) {\r
1853         piece = CharToPiece( ToUpper(p) );\r
1854         if(piece == EmptySquare) continue;\r
1855         /*j = (int) piece - (int) WhitePawn;*/\r
1856         j = PieceToNumber(piece);\r
1857         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
1858         if(j < 0) continue;               /* should not happen */\r
1859         piece = (ChessSquare) ( j + (int)lowestPiece );\r
1860         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
1861         board[holdingsStartRow+j*direction][countsColumn]++;\r
1862     }\r
1863 \r
1864 }\r
1865 \r
1866 \r
1867 void\r
1868 VariantSwitch(Board board, VariantClass newVariant)\r
1869 {\r
1870    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
1871    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;\r
1872    Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
1873 \r
1874    startedFromPositionFile = FALSE;\r
1875    if(gameInfo.variant == newVariant) return;\r
1876 \r
1877    /* [HGM] This routine is called each time an assignment is made to\r
1878     * gameInfo.variant during a game, to make sure the board sizes\r
1879     * are set to match the new variant. If that means adding or deleting\r
1880     * holdings, we shift the playing board accordingly\r
1881     * This kludge is needed because in ICS observe mode, we get boards\r
1882     * of an ongoing game without knowing the variant, and learn about the\r
1883     * latter only later. This can be because of the move list we requested,\r
1884     * in which case the game history is refilled from the beginning anyway,\r
1885     * but also when receiving holdings of a crazyhouse game. In the latter\r
1886     * case we want to add those holdings to the already received position.\r
1887     */\r
1888 \r
1889 \r
1890   if (appData.debugMode) {\r
1891     fprintf(debugFP, "Switch board from %s to %s\n",\r
1892                VariantName(gameInfo.variant), VariantName(newVariant));\r
1893     setbuf(debugFP, NULL);\r
1894   }\r
1895     shuffleOpenings = 0;       /* [HGM] shuffle */\r
1896     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
1897     switch(newVariant) {\r
1898             case VariantShogi:\r
1899               newWidth = 9;  newHeight = 9;\r
1900               gameInfo.holdingsSize = 7;\r
1901             case VariantBughouse:\r
1902             case VariantCrazyhouse:\r
1903               newHoldingsWidth = 2; break;\r
1904             default:\r
1905               newHoldingsWidth = gameInfo.holdingsSize = 0;\r
1906     }\r
1907 \r
1908     if(newWidth  != gameInfo.boardWidth  ||\r
1909        newHeight != gameInfo.boardHeight ||\r
1910        newHoldingsWidth != gameInfo.holdingsWidth ) {\r
1911 \r
1912         /* shift position to new playing area, if needed */\r
1913         if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
1914            for(i=0; i<BOARD_HEIGHT; i++) \r
1915                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
1916                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1917                                                      board[i][j];\r
1918            for(i=0; i<newHeight; i++) {\r
1919                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
1920                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
1921            }\r
1922         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
1923            for(i=0; i<BOARD_HEIGHT; i++)\r
1924                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
1925                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
1926                                                  board[i][j];\r
1927         }\r
1928 \r
1929         gameInfo.boardWidth  = newWidth;\r
1930         gameInfo.boardHeight = newHeight;\r
1931         gameInfo.holdingsWidth = newHoldingsWidth;\r
1932         gameInfo.variant = newVariant;\r
1933         InitDrawingSizes(-2, 0);\r
1934 \r
1935         /* [HGM] The following should definitely be solved in a better way */\r
1936 #if 0\r
1937         CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
1938         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
1939         saveEP = epStatus[0];\r
1940 #endif\r
1941         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */\r
1942 #if 0\r
1943         epStatus[0] = saveEP;\r
1944         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
1945         CopyBoard(tempBoard, board); /* restore position received from ICS   */\r
1946 #endif\r
1947     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
1948 \r
1949     forwardMostMove = oldForwardMostMove;\r
1950     backwardMostMove = oldBackwardMostMove;\r
1951     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */\r
1952 }\r
1953 \r
1954 static int loggedOn = FALSE;\r
1955 \r
1956 /*-- Game start info cache: --*/\r
1957 int gs_gamenum;\r
1958 char gs_kind[MSG_SIZ];\r
1959 static char player1Name[128] = "";\r
1960 static char player2Name[128] = "";\r
1961 static int player1Rating = -1;\r
1962 static int player2Rating = -1;\r
1963 /*----------------------------*/\r
1964 \r
1965 ColorClass curColor = ColorNormal;\r
1966 \r
1967 void\r
1968 read_from_ics(isr, closure, data, count, error)\r
1969      InputSourceRef isr;\r
1970      VOIDSTAR closure;\r
1971      char *data;\r
1972      int count;\r
1973      int error;\r
1974 {\r
1975 #define BUF_SIZE 8192\r
1976 #define STARTED_NONE 0\r
1977 #define STARTED_MOVES 1\r
1978 #define STARTED_BOARD 2\r
1979 #define STARTED_OBSERVE 3\r
1980 #define STARTED_HOLDINGS 4\r
1981 #define STARTED_CHATTER 5\r
1982 #define STARTED_COMMENT 6\r
1983 #define STARTED_MOVES_NOHIDE 7\r
1984     \r
1985     static int started = STARTED_NONE;\r
1986     static char parse[20000];\r
1987     static int parse_pos = 0;\r
1988     static char buf[BUF_SIZE + 1];\r
1989     static int firstTime = TRUE, intfSet = FALSE;\r
1990     static ColorClass prevColor = ColorNormal;\r
1991     static int savingComment = FALSE;\r
1992     char str[500];\r
1993     int i, oldi;\r
1994     int buf_len;\r
1995     int next_out;\r
1996     int tkind;\r
1997     char *p;\r
1998 \r
1999 #ifdef WIN32\r
2000     if (appData.debugMode) {\r
2001       if (!error) {\r
2002         fprintf(debugFP, "<ICS: ");\r
2003         show_bytes(debugFP, data, count);\r
2004         fprintf(debugFP, "\n");\r
2005       }\r
2006     }\r
2007 #endif\r
2008 \r
2009     if (appData.debugMode) { int f = forwardMostMove;\r
2010         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
2011                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
2012     }\r
2013     if (count > 0) {\r
2014         /* If last read ended with a partial line that we couldn't parse,\r
2015            prepend it to the new read and try again. */\r
2016         if (leftover_len > 0) {\r
2017             for (i=0; i<leftover_len; i++)\r
2018               buf[i] = buf[leftover_start + i];\r
2019         }\r
2020 \r
2021         /* Copy in new characters, removing nulls and \r's */\r
2022         buf_len = leftover_len;\r
2023         for (i = 0; i < count; i++) {\r
2024             if (data[i] != NULLCHAR && data[i] != '\r')\r
2025               buf[buf_len++] = data[i];\r
2026         }\r
2027 \r
2028         buf[buf_len] = NULLCHAR;\r
2029         next_out = leftover_len;\r
2030         leftover_start = 0;\r
2031         \r
2032         i = 0;\r
2033         while (i < buf_len) {\r
2034             /* Deal with part of the TELNET option negotiation\r
2035                protocol.  We refuse to do anything beyond the\r
2036                defaults, except that we allow the WILL ECHO option,\r
2037                which ICS uses to turn off password echoing when we are\r
2038                directly connected to it.  We reject this option\r
2039                if localLineEditing mode is on (always on in xboard)\r
2040                and we are talking to port 23, which might be a real\r
2041                telnet server that will try to keep WILL ECHO on permanently.\r
2042              */\r
2043             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
2044                 static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
2045                 unsigned char option;\r
2046                 oldi = i;\r
2047                 switch ((unsigned char) buf[++i]) {\r
2048                   case TN_WILL:\r
2049                     if (appData.debugMode)\r
2050                       fprintf(debugFP, "\n<WILL ");\r
2051                     switch (option = (unsigned char) buf[++i]) {\r
2052                       case TN_ECHO:\r
2053                         if (appData.debugMode)\r
2054                           fprintf(debugFP, "ECHO ");\r
2055                         /* Reply only if this is a change, according\r
2056                            to the protocol rules. */\r
2057                         if (remoteEchoOption) break;\r
2058                         if (appData.localLineEditing &&\r
2059                             atoi(appData.icsPort) == TN_PORT) {\r
2060                             TelnetRequest(TN_DONT, TN_ECHO);\r
2061                         } else {\r
2062                             EchoOff();\r
2063                             TelnetRequest(TN_DO, TN_ECHO);\r
2064                             remoteEchoOption = TRUE;\r
2065                         }\r
2066                         break;\r
2067                       default:\r
2068                         if (appData.debugMode)\r
2069                           fprintf(debugFP, "%d ", option);\r
2070                         /* Whatever this is, we don't want it. */\r
2071                         TelnetRequest(TN_DONT, option);\r
2072                         break;\r
2073                     }\r
2074                     break;\r
2075                   case TN_WONT:\r
2076                     if (appData.debugMode)\r
2077                       fprintf(debugFP, "\n<WONT ");\r
2078                     switch (option = (unsigned char) buf[++i]) {\r
2079                       case TN_ECHO:\r
2080                         if (appData.debugMode)\r
2081                           fprintf(debugFP, "ECHO ");\r
2082                         /* Reply only if this is a change, according\r
2083                            to the protocol rules. */\r
2084                         if (!remoteEchoOption) break;\r
2085                         EchoOn();\r
2086                         TelnetRequest(TN_DONT, TN_ECHO);\r
2087                         remoteEchoOption = FALSE;\r
2088                         break;\r
2089                       default:\r
2090                         if (appData.debugMode)\r
2091                           fprintf(debugFP, "%d ", (unsigned char) option);\r
2092                         /* Whatever this is, it must already be turned\r
2093                            off, because we never agree to turn on\r
2094                            anything non-default, so according to the\r
2095                            protocol rules, we don't reply. */\r
2096                         break;\r
2097                     }\r
2098                     break;\r
2099                   case TN_DO:\r
2100                     if (appData.debugMode)\r
2101                       fprintf(debugFP, "\n<DO ");\r
2102                     switch (option = (unsigned char) buf[++i]) {\r
2103                       default:\r
2104                         /* Whatever this is, we refuse to do it. */\r
2105                         if (appData.debugMode)\r
2106                           fprintf(debugFP, "%d ", option);\r
2107                         TelnetRequest(TN_WONT, option);\r
2108                         break;\r
2109                     }\r
2110                     break;\r
2111                   case TN_DONT:\r
2112                     if (appData.debugMode)\r
2113                       fprintf(debugFP, "\n<DONT ");\r
2114                     switch (option = (unsigned char) buf[++i]) {\r
2115                       default:\r
2116                         if (appData.debugMode)\r
2117                           fprintf(debugFP, "%d ", option);\r
2118                         /* Whatever this is, we are already not doing\r
2119                            it, because we never agree to do anything\r
2120                            non-default, so according to the protocol\r
2121                            rules, we don't reply. */\r
2122                         break;\r
2123                     }\r
2124                     break;\r
2125                   case TN_IAC:\r
2126                     if (appData.debugMode)\r
2127                       fprintf(debugFP, "\n<IAC ");\r
2128                     /* Doubled IAC; pass it through */\r
2129                     i--;\r
2130                     break;\r
2131                   default:\r
2132                     if (appData.debugMode)\r
2133                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
2134                     /* Drop all other telnet commands on the floor */\r
2135                     break;\r
2136                 }\r
2137                 if (oldi > next_out)\r
2138                   SendToPlayer(&buf[next_out], oldi - next_out);\r
2139                 if (++i > next_out)\r
2140                   next_out = i;\r
2141                 continue;\r
2142             }\r
2143                 \r
2144             /* OK, this at least will *usually* work */\r
2145             if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
2146                 loggedOn = TRUE;\r
2147             }\r
2148             \r
2149             if (loggedOn && !intfSet) {\r
2150                 if (ics_type == ICS_ICC) {\r
2151                   sprintf(str,\r
2152                           "/set-quietly interface %s\n/set-quietly style 12\n",\r
2153                           programVersion);\r
2154 \r
2155                 } else if (ics_type == ICS_CHESSNET) {\r
2156                   sprintf(str, "/style 12\n");\r
2157                 } else {\r
2158                   strcpy(str, "alias $ @\n$set interface ");\r
2159                   strcat(str, programVersion);\r
2160                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
2161 #ifdef WIN32\r
2162                   strcat(str, "$iset nohighlight 1\n");\r
2163 #endif\r
2164                   strcat(str, "$iset lock 1\n$style 12\n");\r
2165                 }\r
2166                 SendToICS(str);\r
2167                 intfSet = TRUE;\r
2168             }\r
2169 \r
2170             if (started == STARTED_COMMENT) {\r
2171                 /* Accumulate characters in comment */\r
2172                 parse[parse_pos++] = buf[i];\r
2173                 if (buf[i] == '\n') {\r
2174                     parse[parse_pos] = NULLCHAR;\r
2175                     AppendComment(forwardMostMove, StripHighlight(parse));\r
2176                     started = STARTED_NONE;\r
2177                 } else {\r
2178                     /* Don't match patterns against characters in chatter */\r
2179                     i++;\r
2180                     continue;\r
2181                 }\r
2182             }\r
2183             if (started == STARTED_CHATTER) {\r
2184                 if (buf[i] != '\n') {\r
2185                     /* Don't match patterns against characters in chatter */\r
2186                     i++;\r
2187                     continue;\r
2188                 }\r
2189                 started = STARTED_NONE;\r
2190             }\r
2191 \r
2192             /* Kludge to deal with rcmd protocol */\r
2193             if (firstTime && looking_at(buf, &i, "\001*")) {\r
2194                 DisplayFatalError(&buf[1], 0, 1);\r
2195                 continue;\r
2196             } else {\r
2197                 firstTime = FALSE;\r
2198             }\r
2199 \r
2200             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
2201                 ics_type = ICS_ICC;\r
2202                 ics_prefix = "/";\r
2203                 if (appData.debugMode)\r
2204                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2205                 continue;\r
2206             }\r
2207             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
2208                 ics_type = ICS_FICS;\r
2209                 ics_prefix = "$";\r
2210                 if (appData.debugMode)\r
2211                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2212                 continue;\r
2213             }\r
2214             if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
2215                 ics_type = ICS_CHESSNET;\r
2216                 ics_prefix = "/";\r
2217                 if (appData.debugMode)\r
2218                   fprintf(debugFP, "ics_type %d\n", ics_type);\r
2219                 continue;\r
2220             }\r
2221 \r
2222             if (!loggedOn &&\r
2223                 (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
2224                  looking_at(buf, &i, "Logging you in as \"*\"") ||\r
2225                  looking_at(buf, &i, "will be \"*\""))) {\r
2226               strcpy(ics_handle, star_match[0]);\r
2227               continue;\r
2228             }\r
2229 \r
2230             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
2231               char buf[MSG_SIZ];\r
2232               sprintf(buf, "%s@%s", ics_handle, appData.icsHost);\r
2233               DisplayIcsInteractionTitle(buf);\r
2234               have_set_title = TRUE;\r
2235             }\r
2236 \r
2237             /* skip finger notes */\r
2238             if (started == STARTED_NONE &&\r
2239                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
2240                  (buf[i] == '1' && buf[i+1] == '0')) &&\r
2241                 buf[i+2] == ':' && buf[i+3] == ' ') {\r
2242               started = STARTED_CHATTER;\r
2243               i += 3;\r
2244               continue;\r
2245             }\r
2246 \r
2247             /* skip formula vars */\r
2248             if (started == STARTED_NONE &&\r
2249                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
2250               started = STARTED_CHATTER;\r
2251               i += 3;\r
2252               continue;\r
2253             }\r
2254 \r
2255             oldi = i;\r
2256             if (appData.zippyTalk || appData.zippyPlay) {\r
2257 #if ZIPPY\r
2258                 if (ZippyControl(buf, &i) ||\r
2259                     ZippyConverse(buf, &i) ||\r
2260                     (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
2261                     loggedOn = TRUE;\r
2262                     continue;\r
2263                 }\r
2264 #endif\r
2265             } else {\r
2266                 if (/* Don't color "message" or "messages" output */\r
2267                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
2268                     looking_at(buf, &i, "*. * at *:*: ") ||\r
2269                     looking_at(buf, &i, "--* (*:*): ") ||\r
2270                     /* Regular tells and says */\r
2271                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
2272                     looking_at(buf, &i, "* (your partner) tells you: ") ||\r
2273                     looking_at(buf, &i, "* says: ") ||\r
2274                     /* Message notifications (same color as tells) */\r
2275                     looking_at(buf, &i, "* has left a message ") ||\r
2276                     looking_at(buf, &i, "* just sent you a message:\n") ||\r
2277                     /* Whispers and kibitzes */\r
2278                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
2279                     looking_at(buf, &i, "* kibitzes: ") ||\r
2280                     /* Channel tells */\r
2281                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
2282 \r
2283                   if (tkind == 1 && strchr(star_match[0], ':')) {\r
2284                       /* Avoid "tells you:" spoofs in channels */\r
2285                      tkind = 3;\r
2286                   }\r
2287                   if (star_match[0][0] == NULLCHAR ||\r
2288                       strchr(star_match[0], ' ') ||\r
2289                       (tkind == 3 && strchr(star_match[1], ' '))) {\r
2290                     /* Reject bogus matches */\r
2291                     i = oldi;\r
2292                   } else {\r
2293                     if (appData.colorize) {\r
2294                       if (oldi > next_out) {\r
2295                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2296                         next_out = oldi;\r
2297                       }\r
2298                       switch (tkind) {\r
2299                       case 1:\r
2300                         Colorize(ColorTell, FALSE);\r
2301                         curColor = ColorTell;\r
2302                         break;\r
2303                       case 2:\r
2304                         Colorize(ColorKibitz, FALSE);\r
2305                         curColor = ColorKibitz;\r
2306                         break;\r
2307                       case 3:\r
2308                         p = strrchr(star_match[1], '(');\r
2309                         if (p == NULL) {\r
2310                           p = star_match[1];\r
2311                         } else {\r
2312                           p++;\r
2313                         }\r
2314                         if (atoi(p) == 1) {\r
2315                           Colorize(ColorChannel1, FALSE);\r
2316                           curColor = ColorChannel1;\r
2317                         } else {\r
2318                           Colorize(ColorChannel, FALSE);\r
2319                           curColor = ColorChannel;\r
2320                         }\r
2321                         break;\r
2322                       case 5:\r
2323                         curColor = ColorNormal;\r
2324                         break;\r
2325                       }\r
2326                     }\r
2327                     if (started == STARTED_NONE && appData.autoComment &&\r
2328                         (gameMode == IcsObserving ||\r
2329                          gameMode == IcsPlayingWhite ||\r
2330                          gameMode == IcsPlayingBlack)) {\r
2331                       parse_pos = i - oldi;\r
2332                       memcpy(parse, &buf[oldi], parse_pos);\r
2333                       parse[parse_pos] = NULLCHAR;\r
2334                       started = STARTED_COMMENT;\r
2335                       savingComment = TRUE;\r
2336                     } else {\r
2337                       started = STARTED_CHATTER;\r
2338                       savingComment = FALSE;\r
2339                     }\r
2340                     loggedOn = TRUE;\r
2341                     continue;\r
2342                   }\r
2343                 }\r
2344 \r
2345                 if (looking_at(buf, &i, "* s-shouts: ") ||\r
2346                     looking_at(buf, &i, "* c-shouts: ")) {\r
2347                     if (appData.colorize) {\r
2348                         if (oldi > next_out) {\r
2349                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2350                             next_out = oldi;\r
2351                         }\r
2352                         Colorize(ColorSShout, FALSE);\r
2353                         curColor = ColorSShout;\r
2354                     }\r
2355                     loggedOn = TRUE;\r
2356                     started = STARTED_CHATTER;\r
2357                     continue;\r
2358                 }\r
2359 \r
2360                 if (looking_at(buf, &i, "--->")) {\r
2361                     loggedOn = TRUE;\r
2362                     continue;\r
2363                 }\r
2364 \r
2365                 if (looking_at(buf, &i, "* shouts: ") ||\r
2366                     looking_at(buf, &i, "--> ")) {\r
2367                     if (appData.colorize) {\r
2368                         if (oldi > next_out) {\r
2369                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2370                             next_out = oldi;\r
2371                         }\r
2372                         Colorize(ColorShout, FALSE);\r
2373                         curColor = ColorShout;\r
2374                     }\r
2375                     loggedOn = TRUE;\r
2376                     started = STARTED_CHATTER;\r
2377                     continue;\r
2378                 }\r
2379 \r
2380                 if (looking_at( buf, &i, "Challenge:")) {\r
2381                     if (appData.colorize) {\r
2382                         if (oldi > next_out) {\r
2383                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2384                             next_out = oldi;\r
2385                         }\r
2386                         Colorize(ColorChallenge, FALSE);\r
2387                         curColor = ColorChallenge;\r
2388                     }\r
2389                     loggedOn = TRUE;\r
2390                     continue;\r
2391                 }\r
2392 \r
2393                 if (looking_at(buf, &i, "* offers you") ||\r
2394                     looking_at(buf, &i, "* offers to be") ||\r
2395                     looking_at(buf, &i, "* would like to") ||\r
2396                     looking_at(buf, &i, "* requests to") ||\r
2397                     looking_at(buf, &i, "Your opponent offers") ||\r
2398                     looking_at(buf, &i, "Your opponent requests")) {\r
2399 \r
2400                     if (appData.colorize) {\r
2401                         if (oldi > next_out) {\r
2402                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2403                             next_out = oldi;\r
2404                         }\r
2405                         Colorize(ColorRequest, FALSE);\r
2406                         curColor = ColorRequest;\r
2407                     }\r
2408                     continue;\r
2409                 }\r
2410 \r
2411                 if (looking_at(buf, &i, "* (*) seeking")) {\r
2412                     if (appData.colorize) {\r
2413                         if (oldi > next_out) {\r
2414                             SendToPlayer(&buf[next_out], oldi - next_out);\r
2415                             next_out = oldi;\r
2416                         }\r
2417                         Colorize(ColorSeek, FALSE);\r
2418                         curColor = ColorSeek;\r
2419                     }\r
2420                     continue;\r
2421                 }\r
2422             }\r
2423 \r
2424             if (looking_at(buf, &i, "\\   ")) {\r
2425                 if (prevColor != ColorNormal) {\r
2426                     if (oldi > next_out) {\r
2427                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2428                         next_out = oldi;\r
2429                     }\r
2430                     Colorize(prevColor, TRUE);\r
2431                     curColor = prevColor;\r
2432                 }\r
2433                 if (savingComment) {\r
2434                     parse_pos = i - oldi;\r
2435                     memcpy(parse, &buf[oldi], parse_pos);\r
2436                     parse[parse_pos] = NULLCHAR;\r
2437                     started = STARTED_COMMENT;\r
2438                 } else {\r
2439                     started = STARTED_CHATTER;\r
2440                 }\r
2441                 continue;\r
2442             }\r
2443 \r
2444             if (looking_at(buf, &i, "Black Strength :") ||\r
2445                 looking_at(buf, &i, "<<< style 10 board >>>") ||\r
2446                 looking_at(buf, &i, "<10>") ||\r
2447                 looking_at(buf, &i, "#@#")) {\r
2448                 /* Wrong board style */\r
2449                 loggedOn = TRUE;\r
2450                 SendToICS(ics_prefix);\r
2451                 SendToICS("set style 12\n");\r
2452                 SendToICS(ics_prefix);\r
2453                 SendToICS("refresh\n");\r
2454                 continue;\r
2455             }\r
2456             \r
2457             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
2458                 ICSInitScript();\r
2459                 have_sent_ICS_logon = 1;\r
2460                 continue;\r
2461             }\r
2462               \r
2463             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
2464                 (looking_at(buf, &i, "\n<12> ") ||\r
2465                  looking_at(buf, &i, "<12> "))) {\r
2466                 loggedOn = TRUE;\r
2467                 if (oldi > next_out) {\r
2468                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2469                 }\r
2470                 next_out = i;\r
2471                 started = STARTED_BOARD;\r
2472                 parse_pos = 0;\r
2473                 continue;\r
2474             }\r
2475 \r
2476             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
2477                 looking_at(buf, &i, "<b1> ")) {\r
2478                 if (oldi > next_out) {\r
2479                     SendToPlayer(&buf[next_out], oldi - next_out);\r
2480                 }\r
2481                 next_out = i;\r
2482                 started = STARTED_HOLDINGS;\r
2483                 parse_pos = 0;\r
2484                 continue;\r
2485             }\r
2486 \r
2487             if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
2488                 loggedOn = TRUE;\r
2489                 /* Header for a move list -- first line */\r
2490 \r
2491                 switch (ics_getting_history) {\r
2492                   case H_FALSE:\r
2493                     switch (gameMode) {\r
2494                       case IcsIdle:\r
2495                       case BeginningOfGame:\r
2496                         /* User typed "moves" or "oldmoves" while we\r
2497                            were idle.  Pretend we asked for these\r
2498                            moves and soak them up so user can step\r
2499                            through them and/or save them.\r
2500                            */\r
2501                         Reset(FALSE, TRUE);\r
2502                         gameMode = IcsObserving;\r
2503                         ModeHighlight();\r
2504                         ics_gamenum = -1;\r
2505                         ics_getting_history = H_GOT_UNREQ_HEADER;\r
2506                         break;\r
2507                       case EditGame: /*?*/\r
2508                       case EditPosition: /*?*/\r
2509                         /* Should above feature work in these modes too? */\r
2510                         /* For now it doesn't */\r
2511                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2512                         break;\r
2513                       default:\r
2514                         ics_getting_history = H_GOT_UNWANTED_HEADER;\r
2515                         break;\r
2516                     }\r
2517                     break;\r
2518                   case H_REQUESTED:\r
2519                     /* Is this the right one? */\r
2520                     if (gameInfo.white && gameInfo.black &&\r
2521                         strcmp(gameInfo.white, star_match[0]) == 0 &&\r
2522                         strcmp(gameInfo.black, star_match[2]) == 0) {\r
2523                         /* All is well */\r
2524                         ics_getting_history = H_GOT_REQ_HEADER;\r
2525                     }\r
2526                     break;\r
2527                   case H_GOT_REQ_HEADER:\r
2528                   case H_GOT_UNREQ_HEADER:\r
2529                   case H_GOT_UNWANTED_HEADER:\r
2530                   case H_GETTING_MOVES:\r
2531                     /* Should not happen */\r
2532                     DisplayError("Error gathering move list: two headers", 0);\r
2533                     ics_getting_history = H_FALSE;\r
2534                     break;\r
2535                 }\r
2536 \r
2537                 /* Save player ratings into gameInfo if needed */\r
2538                 if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
2539                      ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
2540                     (gameInfo.whiteRating == -1 ||\r
2541                      gameInfo.blackRating == -1)) {\r
2542 \r
2543                     gameInfo.whiteRating = string_to_rating(star_match[1]);\r
2544                     gameInfo.blackRating = string_to_rating(star_match[3]);\r
2545                     if (appData.debugMode)\r
2546                       fprintf(debugFP, "Ratings from header: W %d, B %d\n", \r
2547                               gameInfo.whiteRating, gameInfo.blackRating);\r
2548                 }\r
2549                 continue;\r
2550             }\r
2551 \r
2552             if (looking_at(buf, &i,\r
2553               "* * match, initial time: * minute*, increment: * second")) {\r
2554                 /* Header for a move list -- second line */\r
2555                 /* Initial board will follow if this is a wild game */\r
2556                 if (gameInfo.event != NULL) free(gameInfo.event);\r
2557                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
2558                 gameInfo.event = StrSave(str);\r
2559                 /* [HGM] we switched variant. Translate boards if needed. */\r
2560                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
2561                 continue;\r
2562             }\r
2563 \r
2564             if (looking_at(buf, &i, "Move  ")) {\r
2565                 /* Beginning of a move list */\r
2566                 switch (ics_getting_history) {\r
2567                   case H_FALSE:\r
2568                     /* Normally should not happen */\r
2569                     /* Maybe user hit reset while we were parsing */\r
2570                     break;\r
2571                   case H_REQUESTED:\r
2572                     /* Happens if we are ignoring a move list that is not\r
2573                      * the one we just requested.  Common if the user\r
2574                      * tries to observe two games without turning off\r
2575                      * getMoveList */\r
2576                     break;\r
2577                   case H_GETTING_MOVES:\r
2578                     /* Should not happen */\r
2579                     DisplayError("Error gathering move list: nested", 0);\r
2580                     ics_getting_history = H_FALSE;\r
2581                     break;\r
2582                   case H_GOT_REQ_HEADER:\r
2583                     ics_getting_history = H_GETTING_MOVES;\r
2584                     started = STARTED_MOVES;\r
2585                     parse_pos = 0;\r
2586                     if (oldi > next_out) {\r
2587                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2588                     }\r
2589                     break;\r
2590                   case H_GOT_UNREQ_HEADER:\r
2591                     ics_getting_history = H_GETTING_MOVES;\r
2592                     started = STARTED_MOVES_NOHIDE;\r
2593                     parse_pos = 0;\r
2594                     break;\r
2595                   case H_GOT_UNWANTED_HEADER:\r
2596                     ics_getting_history = H_FALSE;\r
2597                     break;\r
2598                 }\r
2599                 continue;\r
2600             }                           \r
2601             \r
2602             if (looking_at(buf, &i, "% ") ||\r
2603                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
2604                  && looking_at(buf, &i, "}*"))) {\r
2605                 savingComment = FALSE;\r
2606                 switch (started) {\r
2607                   case STARTED_MOVES:\r
2608                   case STARTED_MOVES_NOHIDE:\r
2609                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
2610                     parse[parse_pos + i - oldi] = NULLCHAR;\r
2611                     ParseGameHistory(parse);\r
2612 #if ZIPPY\r
2613                     if (appData.zippyPlay && first.initDone) {\r
2614                         FeedMovesToProgram(&first, forwardMostMove);\r
2615                         if (gameMode == IcsPlayingWhite) {\r
2616                             if (WhiteOnMove(forwardMostMove)) {\r
2617                                 if (first.sendTime) {\r
2618                                   if (first.useColors) {\r
2619                                     SendToProgram("black\n", &first); \r
2620                                   }\r
2621                                   SendTimeRemaining(&first, TRUE);\r
2622                                 }\r
2623                                 if (first.useColors) {\r
2624                                   SendToProgram("white\ngo\n", &first);\r
2625                                 } else {\r
2626                                   SendToProgram("go\n", &first);\r
2627                                 }\r
2628                                 first.maybeThinking = TRUE;\r
2629                             } else {\r
2630                                 if (first.usePlayother) {\r
2631                                   if (first.sendTime) {\r
2632                                     SendTimeRemaining(&first, TRUE);\r
2633                                   }\r
2634                                   SendToProgram("playother\n", &first);\r
2635                                   firstMove = FALSE;\r
2636                                 } else {\r
2637                                   firstMove = TRUE;\r
2638                                 }\r
2639                             }\r
2640                         } else if (gameMode == IcsPlayingBlack) {\r
2641                             if (!WhiteOnMove(forwardMostMove)) {\r
2642                                 if (first.sendTime) {\r
2643                                   if (first.useColors) {\r
2644                                     SendToProgram("white\n", &first);\r
2645                                   }\r
2646                                   SendTimeRemaining(&first, FALSE);\r
2647                                 }\r
2648                                 if (first.useColors) {\r
2649                                   SendToProgram("black\ngo\n", &first);\r
2650                                 } else {\r
2651                                   SendToProgram("go\n", &first);\r
2652                                 }\r
2653                                 first.maybeThinking = TRUE;\r
2654                             } else {\r
2655                                 if (first.usePlayother) {\r
2656                                   if (first.sendTime) {\r
2657                                     SendTimeRemaining(&first, FALSE);\r
2658                                   }\r
2659                                   SendToProgram("playother\n", &first);\r
2660                                   firstMove = FALSE;\r
2661                                 } else {\r
2662                                   firstMove = TRUE;\r
2663                                 }\r
2664                             }\r
2665                         }                       \r
2666                     }\r
2667 #endif\r
2668                     if (gameMode == IcsObserving && ics_gamenum == -1) {\r
2669                         /* Moves came from oldmoves or moves command\r
2670                            while we weren't doing anything else.\r
2671                            */\r
2672                         currentMove = forwardMostMove;\r
2673                         ClearHighlights();/*!!could figure this out*/\r
2674                         flipView = appData.flipView;\r
2675                         DrawPosition(FALSE, boards[currentMove]);\r
2676                         DisplayBothClocks();\r
2677                         sprintf(str, "%s vs. %s",\r
2678                                 gameInfo.white, gameInfo.black);\r
2679                         DisplayTitle(str);\r
2680                         gameMode = IcsIdle;\r
2681                     } else {\r
2682                         /* Moves were history of an active game */\r
2683                         if (gameInfo.resultDetails != NULL) {\r
2684                             free(gameInfo.resultDetails);\r
2685                             gameInfo.resultDetails = NULL;\r
2686                         }\r
2687                     }\r
2688                     HistorySet(parseList, backwardMostMove,\r
2689                                forwardMostMove, currentMove-1);\r
2690                     DisplayMove(currentMove - 1);\r
2691                     if (started == STARTED_MOVES) next_out = i;\r
2692                     started = STARTED_NONE;\r
2693                     ics_getting_history = H_FALSE;\r
2694                     break;\r
2695 \r
2696                   case STARTED_OBSERVE:\r
2697                     started = STARTED_NONE;\r
2698                     SendToICS(ics_prefix);\r
2699                     SendToICS("refresh\n");\r
2700                     break;\r
2701 \r
2702                   default:\r
2703                     break;\r
2704                 }\r
2705                 continue;\r
2706             }\r
2707             \r
2708             if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
2709                  started == STARTED_HOLDINGS ||\r
2710                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
2711                 /* Accumulate characters in move list or board */\r
2712                 parse[parse_pos++] = buf[i];\r
2713             }\r
2714             \r
2715             /* Start of game messages.  Mostly we detect start of game\r
2716                when the first board image arrives.  On some versions\r
2717                of the ICS, though, we need to do a "refresh" after starting\r
2718                to observe in order to get the current board right away. */\r
2719             if (looking_at(buf, &i, "Adding game * to observation list")) {\r
2720                 started = STARTED_OBSERVE;\r
2721                 continue;\r
2722             }\r
2723 \r
2724             /* Handle auto-observe */\r
2725             if (appData.autoObserve &&\r
2726                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
2727                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
2728                 char *player;\r
2729                 /* Choose the player that was highlighted, if any. */\r
2730                 if (star_match[0][0] == '\033' ||\r
2731                     star_match[1][0] != '\033') {\r
2732                     player = star_match[0];\r
2733                 } else {\r
2734                     player = star_match[2];\r
2735                 }\r
2736                 sprintf(str, "%sobserve %s\n",\r
2737                         ics_prefix, StripHighlightAndTitle(player));\r
2738                 SendToICS(str);\r
2739 \r
2740                 /* Save ratings from notify string */\r
2741                 strcpy(player1Name, star_match[0]);\r
2742                 player1Rating = string_to_rating(star_match[1]);\r
2743                 strcpy(player2Name, star_match[2]);\r
2744                 player2Rating = string_to_rating(star_match[3]);\r
2745 \r
2746                 if (appData.debugMode)\r
2747                   fprintf(debugFP, \r
2748                           "Ratings from 'Game notification:' %s %d, %s %d\n",\r
2749                           player1Name, player1Rating,\r
2750                           player2Name, player2Rating);\r
2751 \r
2752                 continue;\r
2753             }\r
2754 \r
2755             /* Deal with automatic examine mode after a game,\r
2756                and with IcsObserving -> IcsExamining transition */\r
2757             if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
2758                 looking_at(buf, &i, "has made you an examiner of game *")) {\r
2759 \r
2760                 int gamenum = atoi(star_match[0]);\r
2761                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
2762                     gamenum == ics_gamenum) {\r
2763                     /* We were already playing or observing this game;\r
2764                        no need to refetch history */\r
2765                     gameMode = IcsExamining;\r
2766                     if (pausing) {\r
2767                         pauseExamForwardMostMove = forwardMostMove;\r
2768                     } else if (currentMove < forwardMostMove) {\r
2769                         ForwardInner(forwardMostMove);\r
2770                     }\r
2771                 } else {\r
2772                     /* I don't think this case really can happen */\r
2773                     SendToICS(ics_prefix);\r
2774                     SendToICS("refresh\n");\r
2775                 }\r
2776                 continue;\r
2777             }    \r
2778             \r
2779             /* Error messages */\r
2780             if (ics_user_moved) {\r
2781                 if (looking_at(buf, &i, "Illegal move") ||\r
2782                     looking_at(buf, &i, "Not a legal move") ||\r
2783                     looking_at(buf, &i, "Your king is in check") ||\r
2784                     looking_at(buf, &i, "It isn't your turn") ||\r
2785                     looking_at(buf, &i, "It is not your move")) {\r
2786                     /* Illegal move */\r
2787                     ics_user_moved = 0;\r
2788                     if (forwardMostMove > backwardMostMove) {\r
2789                         currentMove = --forwardMostMove;\r
2790                         DisplayMove(currentMove - 1); /* before DMError */\r
2791                         DisplayMoveError("Illegal move (rejected by ICS)");\r
2792                         DrawPosition(FALSE, boards[currentMove]);\r
2793                         SwitchClocks();\r
2794                         DisplayBothClocks();\r
2795                     }\r
2796                     continue;\r
2797                 }\r
2798             }\r
2799 \r
2800             if (looking_at(buf, &i, "still have time") ||\r
2801                 looking_at(buf, &i, "not out of time") ||\r
2802                 looking_at(buf, &i, "either player is out of time") ||\r
2803                 looking_at(buf, &i, "has timeseal; checking")) {\r
2804                 /* We must have called his flag a little too soon */\r
2805                 whiteFlag = blackFlag = FALSE;\r
2806                 continue;\r
2807             }\r
2808 \r
2809             if (looking_at(buf, &i, "added * seconds to") ||\r
2810                 looking_at(buf, &i, "seconds were added to")) {\r
2811                 /* Update the clocks */\r
2812                 SendToICS(ics_prefix);\r
2813                 SendToICS("refresh\n");\r
2814                 continue;\r
2815             }\r
2816 \r
2817             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
2818                 ics_clock_paused = TRUE;\r
2819                 StopClocks();\r
2820                 continue;\r
2821             }\r
2822 \r
2823             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
2824                 ics_clock_paused = FALSE;\r
2825                 StartClocks();\r
2826                 continue;\r
2827             }\r
2828 \r
2829             /* Grab player ratings from the Creating: message.\r
2830                Note we have to check for the special case when\r
2831                the ICS inserts things like [white] or [black]. */\r
2832             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
2833                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
2834                 /* star_matches:\r
2835                    0    player 1 name (not necessarily white)\r
2836                    1    player 1 rating\r
2837                    2    empty, white, or black (IGNORED)\r
2838                    3    player 2 name (not necessarily black)\r
2839                    4    player 2 rating\r
2840                    \r
2841                    The names/ratings are sorted out when the game\r
2842                    actually starts (below).\r
2843                 */\r
2844                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
2845                 player1Rating = string_to_rating(star_match[1]);\r
2846                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
2847                 player2Rating = string_to_rating(star_match[4]);\r
2848 \r
2849                 if (appData.debugMode)\r
2850                   fprintf(debugFP, \r
2851                           "Ratings from 'Creating:' %s %d, %s %d\n",\r
2852                           player1Name, player1Rating,\r
2853                           player2Name, player2Rating);\r
2854 \r
2855                 continue;\r
2856             }\r
2857             \r
2858             /* Improved generic start/end-of-game messages */\r
2859             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
2860                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
2861                 /* If tkind == 0: */\r
2862                 /* star_match[0] is the game number */\r
2863                 /*           [1] is the white player's name */\r
2864                 /*           [2] is the black player's name */\r
2865                 /* For end-of-game: */\r
2866                 /*           [3] is the reason for the game end */\r
2867                 /*           [4] is a PGN end game-token, preceded by " " */\r
2868                 /* For start-of-game: */\r
2869                 /*           [3] begins with "Creating" or "Continuing" */\r
2870                 /*           [4] is " *" or empty (don't care). */\r
2871                 int gamenum = atoi(star_match[0]);\r
2872                 char *whitename, *blackname, *why, *endtoken;\r
2873                 ChessMove endtype = (ChessMove) 0;\r
2874 \r
2875                 if (tkind == 0) {\r
2876                   whitename = star_match[1];\r
2877                   blackname = star_match[2];\r
2878                   why = star_match[3];\r
2879                   endtoken = star_match[4];\r
2880                 } else {\r
2881                   whitename = star_match[1];\r
2882                   blackname = star_match[3];\r
2883                   why = star_match[5];\r
2884                   endtoken = star_match[6];\r
2885                 }\r
2886 \r
2887                 /* Game start messages */\r
2888                 if (strncmp(why, "Creating ", 9) == 0 ||\r
2889                     strncmp(why, "Continuing ", 11) == 0) {\r
2890                     gs_gamenum = gamenum;\r
2891                     strcpy(gs_kind, strchr(why, ' ') + 1);\r
2892 #if ZIPPY\r
2893                     if (appData.zippyPlay) {\r
2894                         ZippyGameStart(whitename, blackname);\r
2895                     }\r
2896 #endif /*ZIPPY*/\r
2897                     continue;\r
2898                 }\r
2899 \r
2900                 /* Game end messages */\r
2901                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
2902                     ics_gamenum != gamenum) {\r
2903                     continue;\r
2904                 }\r
2905                 while (endtoken[0] == ' ') endtoken++;\r
2906                 switch (endtoken[0]) {\r
2907                   case '*':\r
2908                   default:\r
2909                     endtype = GameUnfinished;\r
2910                     break;\r
2911                   case '0':\r
2912                     endtype = BlackWins;\r
2913                     break;\r
2914                   case '1':\r
2915                     if (endtoken[1] == '/')\r
2916                       endtype = GameIsDrawn;\r
2917                     else\r
2918                       endtype = WhiteWins;\r
2919                     break;\r
2920                 }\r
2921                 GameEnds(endtype, why, GE_ICS);\r
2922 #if ZIPPY\r
2923                 if (appData.zippyPlay && first.initDone) {\r
2924                     ZippyGameEnd(endtype, why);\r
2925                     if (first.pr == NULL) {\r
2926                       /* Start the next process early so that we'll\r
2927                          be ready for the next challenge */\r
2928                       StartChessProgram(&first);\r
2929                     }\r
2930                     /* Send "new" early, in case this command takes\r
2931                        a long time to finish, so that we'll be ready\r
2932                        for the next challenge. */\r
2933                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
2934                     Reset(TRUE, TRUE);\r
2935                 }\r
2936 #endif /*ZIPPY*/\r
2937                 continue;\r
2938             }\r
2939 \r
2940             if (looking_at(buf, &i, "Removing game * from observation") ||\r
2941                 looking_at(buf, &i, "no longer observing game *") ||\r
2942                 looking_at(buf, &i, "Game * (*) has no examiners")) {\r
2943                 if (gameMode == IcsObserving &&\r
2944                     atoi(star_match[0]) == ics_gamenum)\r
2945                   {\r
2946                       StopClocks();\r
2947                       gameMode = IcsIdle;\r
2948                       ics_gamenum = -1;\r
2949                       ics_user_moved = FALSE;\r
2950                   }\r
2951                 continue;\r
2952             }\r
2953 \r
2954             if (looking_at(buf, &i, "no longer examining game *")) {\r
2955                 if (gameMode == IcsExamining &&\r
2956                     atoi(star_match[0]) == ics_gamenum)\r
2957                   {\r
2958                       gameMode = IcsIdle;\r
2959                       ics_gamenum = -1;\r
2960                       ics_user_moved = FALSE;\r
2961                   }\r
2962                 continue;\r
2963             }\r
2964 \r
2965             /* Advance leftover_start past any newlines we find,\r
2966                so only partial lines can get reparsed */\r
2967             if (looking_at(buf, &i, "\n")) {\r
2968                 prevColor = curColor;\r
2969                 if (curColor != ColorNormal) {\r
2970                     if (oldi > next_out) {\r
2971                         SendToPlayer(&buf[next_out], oldi - next_out);\r
2972                         next_out = oldi;\r
2973                     }\r
2974                     Colorize(ColorNormal, FALSE);\r
2975                     curColor = ColorNormal;\r
2976                 }\r
2977                 if (started == STARTED_BOARD) {\r
2978                     started = STARTED_NONE;\r
2979                     parse[parse_pos] = NULLCHAR;\r
2980                     ParseBoard12(parse);\r
2981                     ics_user_moved = 0;\r
2982 \r
2983                     /* Send premove here */\r
2984                     if (appData.premove) {\r
2985                       char str[MSG_SIZ];\r
2986                       if (currentMove == 0 &&\r
2987                           gameMode == IcsPlayingWhite &&\r
2988                           appData.premoveWhite) {\r
2989                         sprintf(str, "%s%s\n", ics_prefix,\r
2990                                 appData.premoveWhiteText);\r
2991                         if (appData.debugMode)\r
2992                           fprintf(debugFP, "Sending premove:\n");\r
2993                         SendToICS(str);\r
2994                       } else if (currentMove == 1 &&\r
2995                                  gameMode == IcsPlayingBlack &&\r
2996                                  appData.premoveBlack) {\r
2997                         sprintf(str, "%s%s\n", ics_prefix,\r
2998                                 appData.premoveBlackText);\r
2999                         if (appData.debugMode)\r
3000                           fprintf(debugFP, "Sending premove:\n");\r
3001                         SendToICS(str);\r
3002                       } else if (gotPremove) {\r
3003                         gotPremove = 0;\r
3004                         ClearPremoveHighlights();\r
3005                         if (appData.debugMode)\r
3006                           fprintf(debugFP, "Sending premove:\n");\r
3007                           UserMoveEvent(premoveFromX, premoveFromY, \r
3008                                         premoveToX, premoveToY, \r
3009                                         premovePromoChar);\r
3010                       }\r
3011                     }\r
3012 \r
3013                     /* Usually suppress following prompt */\r
3014                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
3015                         if (looking_at(buf, &i, "*% ")) {\r
3016                             savingComment = FALSE;\r
3017                         }\r
3018                     }\r
3019                     next_out = i;\r
3020                 } else if (started == STARTED_HOLDINGS) {\r
3021                     int gamenum;\r
3022                     char new_piece[MSG_SIZ];\r
3023                     started = STARTED_NONE;\r
3024                     parse[parse_pos] = NULLCHAR;\r
3025                     if (appData.debugMode)\r
3026                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
3027                                                         parse, currentMove);\r
3028                     if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
3029                         gamenum == ics_gamenum) {\r
3030                         if (gameInfo.variant == VariantNormal) {\r
3031                           /* [HGM] We seem to switch variant during a game!\r
3032                            * Presumably no holdings were displayed, so we have\r
3033                            * to move the position two files to the right to\r
3034                            * create room for them!\r
3035                            */\r
3036                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
3037                           /* Get a move list just to see the header, which\r
3038                              will tell us whether this is really bug or zh */\r
3039                           if (ics_getting_history == H_FALSE) {\r
3040                             ics_getting_history = H_REQUESTED;\r
3041                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3042                             SendToICS(str);\r
3043                           }\r
3044                         }\r
3045                         new_piece[0] = NULLCHAR;\r
3046                         sscanf(parse, "game %d white [%s black [%s <- %s",\r
3047                                &gamenum, white_holding, black_holding,\r
3048                                new_piece);\r
3049                         white_holding[strlen(white_holding)-1] = NULLCHAR;\r
3050                         black_holding[strlen(black_holding)-1] = NULLCHAR;\r
3051                         /* [HGM] copy holdings to board holdings area */\r
3052                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
3053                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
3054 #if ZIPPY\r
3055                         if (appData.zippyPlay && first.initDone) {\r
3056                             ZippyHoldings(white_holding, black_holding,\r
3057                                           new_piece);\r
3058                         }\r
3059 #endif /*ZIPPY*/\r
3060                         if (tinyLayout || smallLayout) {\r
3061                             char wh[16], bh[16];\r
3062                             PackHolding(wh, white_holding);\r
3063                             PackHolding(bh, black_holding);\r
3064                             sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
3065                                     gameInfo.white, gameInfo.black);\r
3066                         } else {\r
3067                             sprintf(str, "%s [%s] vs. %s [%s]",\r
3068                                     gameInfo.white, white_holding,\r
3069                                     gameInfo.black, black_holding);\r
3070                         }\r
3071 \r
3072                         DrawPosition(FALSE, boards[currentMove]);\r
3073                         DisplayTitle(str);\r
3074                     }\r
3075                     /* Suppress following prompt */\r
3076                     if (looking_at(buf, &i, "*% ")) {\r
3077                         savingComment = FALSE;\r
3078                     }\r
3079                     next_out = i;\r
3080                 }\r
3081                 continue;\r
3082             }\r
3083 \r
3084             i++;                /* skip unparsed character and loop back */\r
3085         }\r
3086         \r
3087         if (started != STARTED_MOVES && started != STARTED_BOARD &&\r
3088             started != STARTED_HOLDINGS && i > next_out) {\r
3089             SendToPlayer(&buf[next_out], i - next_out);\r
3090             next_out = i;\r
3091         }\r
3092         \r
3093         leftover_len = buf_len - leftover_start;\r
3094         /* if buffer ends with something we couldn't parse,\r
3095            reparse it after appending the next read */\r
3096         \r
3097     } else if (count == 0) {\r
3098         RemoveInputSource(isr);\r
3099         DisplayFatalError("Connection closed by ICS", 0, 0);\r
3100     } else {\r
3101         DisplayFatalError("Error reading from ICS", error, 1);\r
3102     }\r
3103 }\r
3104 \r
3105 \r
3106 /* Board style 12 looks like this:\r
3107    \r
3108    <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
3109    \r
3110  * The "<12> " is stripped before it gets to this routine.  The two\r
3111  * trailing 0's (flip state and clock ticking) are later addition, and\r
3112  * some chess servers may not have them, or may have only the first.\r
3113  * Additional trailing fields may be added in the future.  \r
3114  */\r
3115 \r
3116 #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
3117 \r
3118 #define RELATION_OBSERVING_PLAYED    0\r
3119 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */\r
3120 #define RELATION_PLAYING_MYMOVE      1\r
3121 #define RELATION_PLAYING_NOTMYMOVE  -1\r
3122 #define RELATION_EXAMINING           2\r
3123 #define RELATION_ISOLATED_BOARD     -3\r
3124 #define RELATION_STARTING_POSITION  -4   /* FICS only */\r
3125 \r
3126 void\r
3127 ParseBoard12(string)\r
3128      char *string;\r
3129\r
3130     GameMode newGameMode;\r
3131     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;\r
3132     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;\r
3133     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
3134     char to_play, board_chars[72];\r
3135     char move_str[500], str[500], elapsed_time[500];\r
3136     char black[32], white[32];\r
3137     Board board;\r
3138     int prevMove = currentMove;\r
3139     int ticking = 2;\r
3140     ChessMove moveType;\r
3141     int fromX, fromY, toX, toY;\r
3142     char promoChar;\r
3143 \r
3144     fromX = fromY = toX = toY = -1;\r
3145     \r
3146     newGame = FALSE;\r
3147 \r
3148     if (appData.debugMode)\r
3149       fprintf(debugFP, "Parsing board: %s\n", string);\r
3150 \r
3151     move_str[0] = NULLCHAR;\r
3152     elapsed_time[0] = NULLCHAR;\r
3153     n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,\r
3154                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
3155                &gamenum, white, black, &relation, &basetime, &increment,\r
3156                &white_stren, &black_stren, &white_time, &black_time,\r
3157                &moveNum, str, elapsed_time, move_str, &ics_flip,\r
3158                &ticking);\r
3159 \r
3160     if (n < 22) {\r
3161         sprintf(str, "Failed to parse board string:\n\"%s\"", string);\r
3162         DisplayError(str, 0);\r
3163         return;\r
3164     }\r
3165 \r
3166     /* Convert the move number to internal form */\r
3167     moveNum = (moveNum - 1) * 2;\r
3168     if (to_play == 'B') moveNum++;\r
3169     if (moveNum >= MAX_MOVES) {\r
3170       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
3171                         0, 1);\r
3172       return;\r
3173     }\r
3174     \r
3175     switch (relation) {\r
3176       case RELATION_OBSERVING_PLAYED:\r
3177       case RELATION_OBSERVING_STATIC:\r
3178         if (gamenum == -1) {\r
3179             /* Old ICC buglet */\r
3180             relation = RELATION_OBSERVING_STATIC;\r
3181         }\r
3182         newGameMode = IcsObserving;\r
3183         break;\r
3184       case RELATION_PLAYING_MYMOVE:\r
3185       case RELATION_PLAYING_NOTMYMOVE:\r
3186         newGameMode =\r
3187           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
3188             IcsPlayingWhite : IcsPlayingBlack;\r
3189         break;\r
3190       case RELATION_EXAMINING:\r
3191         newGameMode = IcsExamining;\r
3192         break;\r
3193       case RELATION_ISOLATED_BOARD:\r
3194       default:\r
3195         /* Just display this board.  If user was doing something else,\r
3196            we will forget about it until the next board comes. */ \r
3197         newGameMode = IcsIdle;\r
3198         break;\r
3199       case RELATION_STARTING_POSITION:\r
3200         newGameMode = gameMode;\r
3201         break;\r
3202     }\r
3203     \r
3204     /* Modify behavior for initial board display on move listing\r
3205        of wild games.\r
3206        */\r
3207     switch (ics_getting_history) {\r
3208       case H_FALSE:\r
3209       case H_REQUESTED:\r
3210         break;\r
3211       case H_GOT_REQ_HEADER:\r
3212       case H_GOT_UNREQ_HEADER:\r
3213         /* This is the initial position of the current game */\r
3214         gamenum = ics_gamenum;\r
3215         moveNum = 0;            /* old ICS bug workaround */\r
3216         if (to_play == 'B') {\r
3217           startedFromSetupPosition = TRUE;\r
3218           blackPlaysFirst = TRUE;\r
3219           moveNum = 1;\r
3220           if (forwardMostMove == 0) forwardMostMove = 1;\r
3221           if (backwardMostMove == 0) backwardMostMove = 1;\r
3222           if (currentMove == 0) currentMove = 1;\r
3223         }\r
3224         newGameMode = gameMode;\r
3225         relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
3226         break;\r
3227       case H_GOT_UNWANTED_HEADER:\r
3228         /* This is an initial board that we don't want */\r
3229         return;\r
3230       case H_GETTING_MOVES:\r
3231         /* Should not happen */\r
3232         DisplayError("Error gathering move list: extra board", 0);\r
3233         ics_getting_history = H_FALSE;\r
3234         return;\r
3235     }\r
3236     \r
3237     /* Take action if this is the first board of a new game, or of a\r
3238        different game than is currently being displayed.  */\r
3239     if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
3240         relation == RELATION_ISOLATED_BOARD) {\r
3241         \r
3242         /* Forget the old game and get the history (if any) of the new one */\r
3243         if (gameMode != BeginningOfGame) {\r
3244           Reset(FALSE, TRUE);\r
3245         }\r
3246         newGame = TRUE;\r
3247         if (appData.autoRaiseBoard) BoardToTop();\r
3248         prevMove = -3;\r
3249         if (gamenum == -1) {\r
3250             newGameMode = IcsIdle;\r
3251         } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
3252                    appData.getMoveList) {\r
3253             /* Need to get game history */\r
3254             ics_getting_history = H_REQUESTED;\r
3255             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3256             SendToICS(str);\r
3257         }\r
3258         \r
3259         /* Initially flip the board to have black on the bottom if playing\r
3260            black or if the ICS flip flag is set, but let the user change\r
3261            it with the Flip View button. */\r
3262         flipView = appData.autoFlipView ? \r
3263           (newGameMode == IcsPlayingBlack) || ics_flip :\r
3264           appData.flipView;\r
3265         \r
3266         /* Done with values from previous mode; copy in new ones */\r
3267         gameMode = newGameMode;\r
3268         ModeHighlight();\r
3269         ics_gamenum = gamenum;\r
3270         if (gamenum == gs_gamenum) {\r
3271             int klen = strlen(gs_kind);\r
3272             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
3273             sprintf(str, "ICS %s", gs_kind);\r
3274             gameInfo.event = StrSave(str);\r
3275         } else {\r
3276             gameInfo.event = StrSave("ICS game");\r
3277         }\r
3278         gameInfo.site = StrSave(appData.icsHost);\r
3279         gameInfo.date = PGNDate();\r
3280         gameInfo.round = StrSave("-");\r
3281         gameInfo.white = StrSave(white);\r
3282         gameInfo.black = StrSave(black);\r
3283         timeControl = basetime * 60 * 1000;\r
3284         timeControl_2 = 0;\r
3285         timeIncrement = increment * 1000;\r
3286         movesPerSession = 0;\r
3287         gameInfo.timeControl = TimeControlTagValue();\r
3288         VariantSwitch(board, StringToVariant(gameInfo.event) );\r
3289   if (appData.debugMode) {\r
3290     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
3291     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
3292     setbuf(debugFP, NULL);\r
3293   }\r
3294 \r
3295         gameInfo.outOfBook = NULL;\r
3296         \r
3297         /* Do we have the ratings? */\r
3298         if (strcmp(player1Name, white) == 0 &&\r
3299             strcmp(player2Name, black) == 0) {\r
3300             if (appData.debugMode)\r
3301               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3302                       player1Rating, player2Rating);\r
3303             gameInfo.whiteRating = player1Rating;\r
3304             gameInfo.blackRating = player2Rating;\r
3305         } else if (strcmp(player2Name, white) == 0 &&\r
3306                    strcmp(player1Name, black) == 0) {\r
3307             if (appData.debugMode)\r
3308               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
3309                       player2Rating, player1Rating);\r
3310             gameInfo.whiteRating = player2Rating;\r
3311             gameInfo.blackRating = player1Rating;\r
3312         }\r
3313         player1Name[0] = player2Name[0] = NULLCHAR;\r
3314 \r
3315         /* Silence shouts if requested */\r
3316         if (appData.quietPlay &&\r
3317             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
3318             SendToICS(ics_prefix);\r
3319             SendToICS("set shout 0\n");\r
3320         }\r
3321     }\r
3322     \r
3323     /* Deal with midgame name changes */\r
3324     if (!newGame) {\r
3325         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
3326             if (gameInfo.white) free(gameInfo.white);\r
3327             gameInfo.white = StrSave(white);\r
3328         }\r
3329         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
3330             if (gameInfo.black) free(gameInfo.black);\r
3331             gameInfo.black = StrSave(black);\r
3332         }\r
3333     }\r
3334     \r
3335     /* Throw away game result if anything actually changes in examine mode */\r
3336     if (gameMode == IcsExamining && !newGame) {\r
3337         gameInfo.result = GameUnfinished;\r
3338         if (gameInfo.resultDetails != NULL) {\r
3339             free(gameInfo.resultDetails);\r
3340             gameInfo.resultDetails = NULL;\r
3341         }\r
3342     }\r
3343     \r
3344     /* In pausing && IcsExamining mode, we ignore boards coming\r
3345        in if they are in a different variation than we are. */\r
3346     if (pauseExamInvalid) return;\r
3347     if (pausing && gameMode == IcsExamining) {\r
3348         if (moveNum <= pauseExamForwardMostMove) {\r
3349             pauseExamInvalid = TRUE;\r
3350             forwardMostMove = pauseExamForwardMostMove;\r
3351             return;\r
3352         }\r
3353     }\r
3354     \r
3355     /* Parse the board */\r
3356     for (k = 0; k < 8; k++) {\r
3357       for (j = 0; j < 8; j++)\r
3358         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(7-k)*9 + j]);\r
3359       if(gameInfo.holdingsWidth > 1) {\r
3360            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
3361            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
3362       }\r
3363     }\r
3364     CopyBoard(boards[moveNum], board);\r
3365     if (moveNum == 0) {\r
3366         startedFromSetupPosition =\r
3367           !CompareBoards(board, initialPosition);\r
3368         if(startedFromSetupPosition)\r
3369             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
3370     }\r
3371 \r
3372     /* [HGM] Set castling rights. Take the outermost Rooks,\r
3373        to make it also work for FRC opening positions. Note that board12\r
3374        is really defective for later FRC positions, as it has no way to\r
3375        indicate which Rook can castle if they are on the same side of King.\r
3376        For the initial position we grant rights to the outermost Rooks,\r
3377        and remember thos rights, and we then copy them on positions\r
3378        later in an FRC game. This means WB might not recognize castlings with\r
3379        Rooks that have moved back to their original position as illegal,\r
3380        but in ICS mode that is not its job anyway.\r
3381     */\r
3382     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
3383     { int i, j;\r
3384 \r
3385         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3386             if(board[0][i] == WhiteRook) j = i;\r
3387         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3388         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3389             if(board[0][i] == WhiteRook) j = i;\r
3390         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3391         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
3392             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3393         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3394         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
3395             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
3396         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
3397 \r
3398         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3399             if(board[0][k] == WhiteKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
3400         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
3401             if(board[BOARD_HEIGHT-1][k] == BlackKing)\r
3402                 initialRights[5] = castlingRights[moveNum][5] = k;\r
3403     } else { int r;\r
3404         r = castlingRights[moveNum][0] = initialRights[0];\r
3405         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
3406         r = castlingRights[moveNum][1] = initialRights[1];\r
3407         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
3408         r = castlingRights[moveNum][3] = initialRights[3];\r
3409         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
3410         r = castlingRights[moveNum][4] = initialRights[4];\r
3411         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
3412         /* wildcastle kludge: always assume King has rights */\r
3413         r = castlingRights[moveNum][2] = initialRights[2];\r
3414         r = castlingRights[moveNum][5] = initialRights[5];\r
3415     }\r
3416     /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
3417     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
3418 \r
3419     \r
3420     if (ics_getting_history == H_GOT_REQ_HEADER ||\r
3421         ics_getting_history == H_GOT_UNREQ_HEADER) {\r
3422         /* This was an initial position from a move list, not\r
3423            the current position */\r
3424         return;\r
3425     }\r
3426     \r
3427     /* Update currentMove and known move number limits */\r
3428     newMove = newGame || moveNum > forwardMostMove;\r
3429     if (newGame) {\r
3430         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3431         if (gameMode == IcsExamining && moveNum == 0) {\r
3432           /* Workaround for ICS limitation: we are not told the wild\r
3433              type when starting to examine a game.  But if we ask for\r
3434              the move list, the move list header will tell us */\r
3435             ics_getting_history = H_REQUESTED;\r
3436             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3437             SendToICS(str);\r
3438         }\r
3439     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
3440                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
3441         forwardMostMove = moveNum;\r
3442         if (!pausing || currentMove > forwardMostMove)\r
3443           currentMove = forwardMostMove;\r
3444     } else {\r
3445         /* New part of history that is not contiguous with old part */ \r
3446         if (pausing && gameMode == IcsExamining) {\r
3447             pauseExamInvalid = TRUE;\r
3448             forwardMostMove = pauseExamForwardMostMove;\r
3449             return;\r
3450         }\r
3451         forwardMostMove = backwardMostMove = currentMove = moveNum;\r
3452         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
3453             ics_getting_history = H_REQUESTED;\r
3454             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
3455             SendToICS(str);\r
3456         }\r
3457     }\r
3458     \r
3459     /* Update the clocks */\r
3460     if (strchr(elapsed_time, '.')) {\r
3461       /* Time is in ms */\r
3462       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
3463       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
3464     } else {\r
3465       /* Time is in seconds */\r
3466       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
3467       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
3468     }\r
3469       \r
3470 \r
3471 #if ZIPPY\r
3472     if (appData.zippyPlay && newGame &&\r
3473         gameMode != IcsObserving && gameMode != IcsIdle &&\r
3474         gameMode != IcsExamining)\r
3475       ZippyFirstBoard(moveNum, basetime, increment);\r
3476 #endif\r
3477     \r
3478     /* Put the move on the move list, first converting\r
3479        to canonical algebraic form. */\r
3480     if (moveNum > 0) {\r
3481   if (appData.debugMode) {\r
3482     if (appData.debugMode) { int f = forwardMostMove;\r
3483         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
3484                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
3485     }\r
3486     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
3487     fprintf(debugFP, "moveNum = %d\n", moveNum);\r
3488     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
3489     setbuf(debugFP, NULL);\r
3490   }\r
3491         if (moveNum <= backwardMostMove) {\r
3492             /* We don't know what the board looked like before\r
3493                this move.  Punt. */\r
3494             strcpy(parseList[moveNum - 1], move_str);\r
3495             strcat(parseList[moveNum - 1], " ");\r
3496             strcat(parseList[moveNum - 1], elapsed_time);\r
3497             moveList[moveNum - 1][0] = NULLCHAR;\r
3498         } else if (ParseOneMove(move_str, moveNum - 1, &moveType,\r
3499                                 &fromX, &fromY, &toX, &toY, &promoChar)) {\r
3500             (void) CoordsToAlgebraic(boards[moveNum - 1],\r
3501                                      PosFlags(moveNum - 1), EP_UNKNOWN,\r
3502                                      fromY, fromX, toY, toX, promoChar,\r
3503                                      parseList[moveNum-1]);\r
3504             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
3505                              castlingRights[moveNum]) ) {\r
3506               case MT_NONE:\r
3507               case MT_STALEMATE:\r
3508               default:\r
3509                 break;\r
3510               case MT_CHECK:\r
3511                 if(gameInfo.variant != VariantShogi)\r
3512                     strcat(parseList[moveNum - 1], "+");\r
3513                 break;\r
3514               case MT_CHECKMATE:\r
3515                 strcat(parseList[moveNum - 1], "#");\r
3516                 break;\r
3517             }\r
3518             strcat(parseList[moveNum - 1], " ");\r
3519             strcat(parseList[moveNum - 1], elapsed_time);\r
3520             /* currentMoveString is set as a side-effect of ParseOneMove */\r
3521             strcpy(moveList[moveNum - 1], currentMoveString);\r
3522             strcat(moveList[moveNum - 1], "\n");\r
3523         } else if (strcmp(move_str, "none") == 0) {\r
3524             /* Again, we don't know what the board looked like;\r
3525                this is really the start of the game. */\r
3526             parseList[moveNum - 1][0] = NULLCHAR;\r
3527             moveList[moveNum - 1][0] = NULLCHAR;\r
3528             backwardMostMove = moveNum;\r
3529             startedFromSetupPosition = TRUE;\r
3530             fromX = fromY = toX = toY = -1;\r
3531         } else {\r
3532             /* Move from ICS was illegal!?  Punt. */\r
3533   if (appData.debugMode) {\r
3534     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
3535     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
3536   }\r
3537 #if 0\r
3538             if (appData.testLegality && appData.debugMode) {\r
3539                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
3540                 DisplayError(str, 0);\r
3541             }\r
3542 #endif\r
3543             strcpy(parseList[moveNum - 1], move_str);\r
3544             strcat(parseList[moveNum - 1], " ");\r
3545             strcat(parseList[moveNum - 1], elapsed_time);\r
3546             moveList[moveNum - 1][0] = NULLCHAR;\r
3547             fromX = fromY = toX = toY = -1;\r
3548         }\r
3549   if (appData.debugMode) {\r
3550     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
3551     setbuf(debugFP, NULL);\r
3552   }\r
3553 \r
3554 #if ZIPPY\r
3555         /* Send move to chess program (BEFORE animating it). */\r
3556         if (appData.zippyPlay && !newGame && newMove && \r
3557            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
3558 \r
3559             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
3560                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
3561                 if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3562                     sprintf(str, "Couldn't parse move \"%s\" from ICS",\r
3563                             move_str);\r
3564                     DisplayError(str, 0);\r
3565                 } else {\r
3566                     if (first.sendTime) {\r
3567                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
3568                     }\r
3569                     SendMoveToProgram(moveNum - 1, &first);\r
3570                     if (firstMove) {\r
3571                         firstMove = FALSE;\r
3572                         if (first.useColors) {\r
3573                           SendToProgram(gameMode == IcsPlayingWhite ?\r
3574                                         "white\ngo\n" :\r
3575                                         "black\ngo\n", &first);\r
3576                         } else {\r
3577                           SendToProgram("go\n", &first);\r
3578                         }\r
3579                         first.maybeThinking = TRUE;\r
3580                     }\r
3581                 }\r
3582             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
3583               if (moveList[moveNum - 1][0] == NULLCHAR) {\r
3584                 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);\r
3585                 DisplayError(str, 0);\r
3586               } else {\r
3587                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
3588                 SendMoveToProgram(moveNum - 1, &first);\r
3589               }\r
3590             }\r
3591         }\r
3592 #endif\r
3593     }\r
3594 \r
3595     if (moveNum > 0 && !gotPremove) {\r
3596         /* If move comes from a remote source, animate it.  If it\r
3597            isn't remote, it will have already been animated. */\r
3598         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
3599             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
3600         }\r
3601         if (!pausing && appData.highlightLastMove) {\r
3602             SetHighlights(fromX, fromY, toX, toY);\r
3603         }\r
3604     }\r
3605     \r
3606     /* Start the clocks */\r
3607     whiteFlag = blackFlag = FALSE;\r
3608     appData.clockMode = !(basetime == 0 && increment == 0);\r
3609     if (ticking == 0) {\r
3610       ics_clock_paused = TRUE;\r
3611       StopClocks();\r
3612     } else if (ticking == 1) {\r
3613       ics_clock_paused = FALSE;\r
3614     }\r
3615     if (gameMode == IcsIdle ||\r
3616         relation == RELATION_OBSERVING_STATIC ||\r
3617         relation == RELATION_EXAMINING ||\r
3618         ics_clock_paused)\r
3619       DisplayBothClocks();\r
3620     else\r
3621       StartClocks();\r
3622     \r
3623     /* Display opponents and material strengths */\r
3624     if (gameInfo.variant != VariantBughouse &&\r
3625         gameInfo.variant != VariantCrazyhouse) {\r
3626         if (tinyLayout || smallLayout) {\r
3627             if(gameInfo.variant == VariantNormal)\r
3628                 sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
3629                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3630                     basetime, increment);\r
3631             else\r
3632                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
3633                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3634                     basetime, increment, (int) gameInfo.variant);\r
3635         } else {\r
3636             if(gameInfo.variant == VariantNormal)\r
3637                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
3638                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3639                     basetime, increment);\r
3640             else\r
3641                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
3642                     gameInfo.white, white_stren, gameInfo.black, black_stren,\r
3643                     basetime, increment, VariantName(gameInfo.variant));\r
3644         }\r
3645         DisplayTitle(str);\r
3646   if (appData.debugMode) {\r
3647     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
3648   }\r
3649     }\r
3650 \r
3651    \r
3652     /* Display the board */\r
3653     if (!pausing) {\r
3654       \r
3655       if (appData.premove)\r
3656           if (!gotPremove || \r
3657              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
3658              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
3659               ClearPremoveHighlights();\r
3660 \r
3661       DrawPosition(FALSE, boards[currentMove]);\r
3662       DisplayMove(moveNum - 1);\r
3663       if (appData.ringBellAfterMoves && !ics_user_moved)\r
3664         RingBell();\r
3665     }\r
3666 \r
3667     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
3668 }\r
3669 \r
3670 void\r
3671 GetMoveListEvent()\r
3672 {\r
3673     char buf[MSG_SIZ];\r
3674     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
3675         ics_getting_history = H_REQUESTED;\r
3676         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
3677         SendToICS(buf);\r
3678     }\r
3679 }\r
3680 \r
3681 void\r
3682 AnalysisPeriodicEvent(force)\r
3683      int force;\r
3684 {\r
3685     if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
3686          && !force) || !appData.periodicUpdates)\r
3687       return;\r
3688 \r
3689     /* Send . command to Crafty to collect stats */\r
3690     SendToProgram(".\n", &first);\r
3691 \r
3692     /* Don't send another until we get a response (this makes\r
3693        us stop sending to old Crafty's which don't understand\r
3694        the "." command (sending illegal cmds resets node count & time,\r
3695        which looks bad)) */\r
3696     programStats.ok_to_send = 0;\r
3697 }\r
3698 \r
3699 void\r
3700 SendMoveToProgram(moveNum, cps)\r
3701      int moveNum;\r
3702      ChessProgramState *cps;\r
3703 {\r
3704     char buf[MSG_SIZ];\r
3705 \r
3706     if (cps->useUsermove) {\r
3707       SendToProgram("usermove ", cps);\r
3708     }\r
3709     if (cps->useSAN) {\r
3710       char *space;\r
3711       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
3712         int len = space - parseList[moveNum];\r
3713         memcpy(buf, parseList[moveNum], len);\r
3714         buf[len++] = '\n';\r
3715         buf[len] = NULLCHAR;\r
3716       } else {\r
3717         sprintf(buf, "%s\n", parseList[moveNum]);\r
3718       }\r
3719       SendToProgram(buf, cps);\r
3720     } else {\r
3721       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
3722        * the engine. It would be nice to have a better way to identify castle \r
3723        * moves here. */\r
3724       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
3725                                                                          && cps->useOOCastle) {\r
3726   if (appData.debugMode) {\r
3727     fprintf(debugFP, "Tord's FRC castling code\n");\r
3728   }\r
3729         int fromX = moveList[moveNum][0] - AAA; \r
3730         int fromY = moveList[moveNum][1] - ONE;\r
3731         int toX = moveList[moveNum][2] - AAA; \r
3732         int toY = moveList[moveNum][3] - ONE;\r
3733         if((boards[moveNum][fromY][fromX] == WhiteKing \r
3734             && boards[moveNum][toY][toX] == WhiteRook)\r
3735            || (boards[moveNum][fromY][fromX] == BlackKing \r
3736                && boards[moveNum][toY][toX] == BlackRook)) {\r
3737           if(toX > fromX) SendToProgram("O-O\n", cps);\r
3738           else SendToProgram("O-O-O\n", cps);\r
3739         }\r
3740         else SendToProgram(moveList[moveNum], cps);\r
3741       }\r
3742       else SendToProgram(moveList[moveNum], cps);\r
3743       /* End of additions by Tord */\r
3744     }\r
3745 \r
3746     /* [HGM] setting up the opening has brought engine in force mode! */\r
3747     /*       Send 'go' if we are in a mode where machine should play. */\r
3748     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
3749         (gameMode == TwoMachinesPlay   ||\r
3750 #ifdef ZIPPY\r
3751          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||\r
3752 #endif\r
3753          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
3754         SendToProgram("go\n", cps);\r
3755   if (appData.debugMode) {\r
3756     fprintf(debugFP, "(extra)\n");\r
3757   }\r
3758     }\r
3759     setboardSpoiledMachineBlack = 0;\r
3760 }\r
3761 \r
3762 void\r
3763 SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
3764      ChessMove moveType;\r
3765      int fromX, fromY, toX, toY;\r
3766 {\r
3767     char user_move[MSG_SIZ];\r
3768 \r
3769     switch (moveType) {\r
3770       default:\r
3771         sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",\r
3772                 (int)moveType, fromX, fromY, toX, toY);\r
3773         DisplayError(user_move + strlen("say "), 0);\r
3774         break;\r
3775       case WhiteKingSideCastle:\r
3776       case BlackKingSideCastle:\r
3777       case WhiteQueenSideCastleWild:\r
3778       case BlackQueenSideCastleWild:\r
3779       /* PUSH Fabien */\r
3780       case WhiteHSideCastleFR:\r
3781       case BlackHSideCastleFR:\r
3782       /* POP Fabien */\r
3783         sprintf(user_move, "o-o\n");\r
3784         break;\r
3785       case WhiteQueenSideCastle:\r
3786       case BlackQueenSideCastle:\r
3787       case WhiteKingSideCastleWild:\r
3788       case BlackKingSideCastleWild:\r
3789       /* PUSH Fabien */\r
3790       case WhiteASideCastleFR:\r
3791       case BlackASideCastleFR:\r
3792       /* POP Fabien */\r
3793         sprintf(user_move, "o-o-o\n");\r
3794         break;\r
3795       case WhitePromotionQueen:\r
3796       case BlackPromotionQueen:\r
3797       case WhitePromotionRook:\r
3798       case BlackPromotionRook:\r
3799       case WhitePromotionBishop:\r
3800       case BlackPromotionBishop:\r
3801       case WhitePromotionKnight:\r
3802       case BlackPromotionKnight:\r
3803       case WhitePromotionKing:\r
3804       case BlackPromotionKing:\r
3805       case WhitePromotionChancellor:\r
3806       case BlackPromotionChancellor:\r
3807       case WhitePromotionArchbishop:\r
3808       case BlackPromotionArchbishop:\r
3809         if(gameInfo.variant == VariantShatranj)\r
3810             sprintf(user_move, "%c%c%c%c=%c\n",\r
3811                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
3812                 PieceToChar(WhiteFerz));\r
3813         else\r
3814             sprintf(user_move, "%c%c%c%c=%c\n",\r
3815                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
3816                 PieceToChar(PromoPiece(moveType)));\r
3817         break;\r
3818       case WhiteDrop:\r
3819       case BlackDrop:\r
3820         sprintf(user_move, "%c@%c%c\n",\r
3821                 ToUpper(PieceToChar((ChessSquare) fromX)),\r
3822                 AAA + toX, ONE + toY);\r
3823         break;\r
3824       case NormalMove:\r
3825       case WhiteCapturesEnPassant:\r
3826       case BlackCapturesEnPassant:\r
3827       case IllegalMove:  /* could be a variant we don't quite understand */\r
3828         sprintf(user_move, "%c%c%c%c\n",\r
3829                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
3830         break;\r
3831     }\r
3832     SendToICS(user_move);\r
3833 }\r
3834 \r
3835 void\r
3836 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
3837      int rf, ff, rt, ft;\r
3838      char promoChar;\r
3839      char move[7];\r
3840 {\r
3841     if (rf == DROP_RANK) {\r
3842         sprintf(move, "%c@%c%c\n",\r
3843                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
3844     } else {\r
3845         if (promoChar == 'x' || promoChar == NULLCHAR) {\r
3846             sprintf(move, "%c%c%c%c\n",\r
3847                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
3848         } else {\r
3849             sprintf(move, "%c%c%c%c%c\n",\r
3850                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
3851         }\r
3852     }\r
3853     AlphaRank(move, 4);\r
3854 }\r
3855 \r
3856 void\r
3857 ProcessICSInitScript(f)\r
3858      FILE *f;\r
3859 {\r
3860     char buf[MSG_SIZ];\r
3861 \r
3862     while (fgets(buf, MSG_SIZ, f)) {\r
3863         SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
3864     }\r
3865 \r
3866     fclose(f);\r
3867 }\r
3868 \r
3869 \r
3870 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
3871 void\r
3872 AlphaRank(char *move, int n)\r
3873 {\r
3874     char *p = move, c; int x, y;\r
3875 \r
3876     if( !appData.alphaRank ) return;\r
3877 \r
3878     if (appData.debugMode) {\r
3879         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
3880     }\r
3881 \r
3882     if(move[1]=='*' && \r
3883        move[2]>='0' && move[2]<='9' &&\r
3884        move[3]>='a' && move[3]<='x'    ) {\r
3885         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
3886         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
3887     } else\r
3888     if(move[0]>='0' && move[0]<='9' &&\r
3889        move[1]>='a' && move[1]<='x' &&\r
3890        move[2]>='0' && move[2]<='9' &&\r
3891        move[3]>='a' && move[3]<='x'    ) {\r
3892         /* input move, Shogi -> normal */\r
3893         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;\r
3894         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
3895         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;\r
3896         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
3897     } else\r
3898     if(move[1]=='@' &&\r
3899        move[3]>='0' && move[3]<='9' &&\r
3900        move[2]>='a' && move[2]<='x'    ) {\r
3901         move[1] = '*';\r
3902         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
3903         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
3904     } else\r
3905     if(\r
3906        move[0]>='a' && move[0]<='x' &&\r
3907        move[3]>='0' && move[3]<='9' &&\r
3908        move[2]>='a' && move[2]<='x'    ) {\r
3909          /* output move, normal -> Shogi */\r
3910         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
3911         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
3912         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
3913         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
3914         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
3915     }\r
3916     if (appData.debugMode) {\r
3917         fprintf(debugFP, "   out = '%s'\n", move);\r
3918     }\r
3919 }\r
3920 \r
3921 /* Parser for moves from gnuchess, ICS, or user typein box */\r
3922 Boolean\r
3923 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
3924      char *move;\r
3925      int moveNum;\r
3926      ChessMove *moveType;\r
3927      int *fromX, *fromY, *toX, *toY;\r
3928      char *promoChar;\r
3929 {       \r
3930     if (appData.debugMode) {\r
3931         fprintf(debugFP, "move to parse: %s\n", move);\r
3932     }\r
3933     *moveType = yylexstr(moveNum, move);\r
3934 \r
3935     switch (*moveType) {\r
3936       case WhitePromotionChancellor:\r
3937       case BlackPromotionChancellor:\r
3938       case WhitePromotionArchbishop:\r
3939       case BlackPromotionArchbishop:\r
3940       case WhitePromotionQueen:\r
3941       case BlackPromotionQueen:\r
3942       case WhitePromotionRook:\r
3943       case BlackPromotionRook:\r
3944       case WhitePromotionBishop:\r
3945       case BlackPromotionBishop:\r
3946       case WhitePromotionKnight:\r
3947       case BlackPromotionKnight:\r
3948       case WhitePromotionKing:\r
3949       case BlackPromotionKing:\r
3950       case NormalMove:\r
3951       case WhiteCapturesEnPassant:\r
3952       case BlackCapturesEnPassant:\r
3953       case WhiteKingSideCastle:\r
3954       case WhiteQueenSideCastle:\r
3955       case BlackKingSideCastle:\r
3956       case BlackQueenSideCastle:\r
3957       case WhiteKingSideCastleWild:\r
3958       case WhiteQueenSideCastleWild:\r
3959       case BlackKingSideCastleWild:\r
3960       case BlackQueenSideCastleWild:\r
3961       /* Code added by Tord: */\r
3962       case WhiteHSideCastleFR:\r
3963       case WhiteASideCastleFR:\r
3964       case BlackHSideCastleFR:\r
3965       case BlackASideCastleFR:\r
3966       /* End of code added by Tord */\r
3967       case IllegalMove:         /* bug or odd chess variant */\r
3968         *fromX = currentMoveString[0] - AAA;\r
3969         *fromY = currentMoveString[1] - ONE;\r
3970         *toX = currentMoveString[2] - AAA;\r
3971         *toY = currentMoveString[3] - ONE;\r
3972         *promoChar = currentMoveString[4];\r
3973         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
3974             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
3975     if (appData.debugMode) {\r
3976         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
3977     }\r
3978             *fromX = *fromY = *toX = *toY = 0;\r
3979             return FALSE;\r
3980         }\r
3981         if (appData.testLegality) {\r
3982           return (*moveType != IllegalMove);\r
3983         } else {\r
3984           return !(fromX == fromY && toX == toY);\r
3985         }\r
3986 \r
3987       case WhiteDrop:\r
3988       case BlackDrop:\r
3989         *fromX = *moveType == WhiteDrop ?\r
3990           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
3991           (int) CharToPiece(ToLower(currentMoveString[0]));\r
3992         *fromY = DROP_RANK;\r
3993         *toX = currentMoveString[2] - AAA;\r
3994         *toY = currentMoveString[3] - ONE;\r
3995         *promoChar = NULLCHAR;\r
3996         return TRUE;\r
3997 \r
3998       case AmbiguousMove:\r
3999       case ImpossibleMove:\r
4000       case (ChessMove) 0:       /* end of file */\r
4001       case ElapsedTime:\r
4002       case Comment:\r
4003       case PGNTag:\r
4004       case NAG:\r
4005       case WhiteWins:\r
4006       case BlackWins:\r
4007       case GameIsDrawn:\r
4008       default:\r
4009     if (appData.debugMode) {\r
4010         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
4011     }\r
4012         /* bug? */\r
4013         *fromX = *fromY = *toX = *toY = 0;\r
4014         *promoChar = NULLCHAR;\r
4015         return FALSE;\r
4016     }\r
4017 }\r
4018 \r
4019 /* [AS] FRC game initialization */\r
4020 static int FindEmptySquare( Board board, int n )\r
4021 {\r
4022     int i = 0;\r
4023 \r
4024     while( 1 ) {\r
4025         while( board[0][i] != EmptySquare ) i++;\r
4026         if( n == 0 )\r
4027             break;\r
4028         n--;\r
4029         i++;\r
4030     }\r
4031 \r
4032     return i;\r
4033 }\r
4034 \r
4035 #if 0\r
4036 static void ShuffleFRC( Board board )\r
4037 {\r
4038     int i;\r
4039 \r
4040     srand( time(0) );\r
4041     \r
4042     for( i=0; i<8; i++ ) {\r
4043         board[0][i] = EmptySquare;\r
4044     }\r
4045 \r
4046     board[0][(rand() % 4)*2  ] = WhiteBishop; /* On dark square */\r
4047     board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */\r
4048     board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;\r
4049     board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;\r
4050     board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;\r
4051     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4052     initialRights[1]  = initialRights[4]  =\r
4053     castlingRights[0][1] = castlingRights[0][4] = i;\r
4054     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4055     initialRights[2]  = initialRights[5]  =\r
4056     castlingRights[0][2] = castlingRights[0][5] = i;\r
4057     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4058     initialRights[0]  = initialRights[3]  =\r
4059     castlingRights[0][0] = castlingRights[0][3] = i;\r
4060 \r
4061     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4062         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4063     }\r
4064 }\r
4065 \r
4066 static unsigned char FRC_KnightTable[10] = {\r
4067     0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33\r
4068 };\r
4069 \r
4070 static void SetupFRC( Board board, int pos_index )\r
4071 {\r
4072     int i;\r
4073     unsigned char knights;\r
4074 \r
4075     /* Bring the position index into a safe range (just in case...) */\r
4076     if( pos_index < 0 ) pos_index = 0;\r
4077 \r
4078     pos_index %= 960;\r
4079 \r
4080     /* Clear the board */\r
4081     for( i=0; i<8; i++ ) {\r
4082         board[0][i] = EmptySquare;\r
4083     }\r
4084 \r
4085     /* Place bishops and queen */\r
4086     board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */\r
4087     pos_index /= 4;\r
4088     \r
4089     board[0][ (pos_index % 4)*2     ] = WhiteBishop; /* On dark square */\r
4090     pos_index /= 4;\r
4091 \r
4092     board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;\r
4093     pos_index /= 6;\r
4094 \r
4095     /* Place knigths */\r
4096     knights = FRC_KnightTable[ pos_index ];\r
4097 \r
4098     board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;\r
4099     board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;\r
4100 \r
4101     /* Place rooks and king */\r
4102     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4103     initialRights[1]  = initialRights[4]  =\r
4104     castlingRights[0][1] = castlingRights[0][4] = i;\r
4105     board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;\r
4106     initialRights[2]  = initialRights[5]  =\r
4107     castlingRights[0][2] = castlingRights[0][5] = i;\r
4108     board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;\r
4109     initialRights[0]  = initialRights[3]  =\r
4110     castlingRights[0][0] = castlingRights[0][3] = i;\r
4111 \r
4112     /* Mirror piece placement for black */\r
4113     for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {\r
4114         board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;\r
4115     }\r
4116 }\r
4117 #else\r
4118 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.\r
4119 // All positions will have equal probability, but the current method will not provide a unique\r
4120 // numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
4121 #define DARK 1\r
4122 #define LITE 2\r
4123 #define ANY 3\r
4124 \r
4125 int squaresLeft[4];\r
4126 int piecesLeft[(int)BlackPawn];\r
4127 long long int seed, nrOfShuffles;\r
4128 \r
4129 int put(Board board, int pieceType, int rank, int n, int shade)\r
4130 // put the piece on the (n-1)-th empty squares of the given shade\r
4131 {\r
4132         int i;\r
4133 \r
4134         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
4135                 if( ((i-BOARD_LEFT)&1)+1 & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
4136                         board[rank][i] = (ChessSquare) pieceType;\r
4137                         squaresLeft[(i-BOARD_LEFT&1) + 1]--;\r
4138                         squaresLeft[ANY]--;\r
4139                         piecesLeft[pieceType]--; \r
4140                         return i;\r
4141                 }\r
4142         }\r
4143         return -1;\r
4144 }\r
4145 \r
4146 \r
4147 void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
4148 // calculate where the next piece goes, (any empty square), and put it there\r
4149 {\r
4150         int i;\r
4151 \r
4152         i = seed % squaresLeft[shade];\r
4153         nrOfShuffles *= squaresLeft[shade];\r
4154         seed /= squaresLeft[shade];\r
4155         put(board, pieceType, rank, i, shade);\r
4156 }\r
4157 \r
4158 void AddTwoPieces(Board board, int pieceType, int rank)\r
4159 // calculate where the next 2 identical pieces go, (any empty square), and put it there\r
4160 {\r
4161         int i, n=squaresLeft[ANY], j=n-1, k;\r
4162 \r
4163         k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
4164         i = seed % k;  // pick one\r
4165         nrOfShuffles *= k;\r
4166         seed /= k;\r
4167         while(i >= j) i -= j--;\r
4168         j = n - 1 - j; i += j;\r
4169         put(board, pieceType, rank, j, ANY);\r
4170         put(board, pieceType, rank, i, ANY);\r
4171 }\r
4172 \r
4173 void SetUpShuffle(Board board, int number)\r
4174 {\r
4175         int i, p, first=1;\r
4176 \r
4177         seed = number, nrOfShuffles = 1;\r
4178         if(number < 0) { \r
4179                 srand(time(0)); \r
4180                 for(i=0; i<50; i++) seed += rand();\r
4181                 seed = rand() ^ rand()<<16; \r
4182         }\r
4183 \r
4184         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
4185         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;\r
4186         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
4187 \r
4188         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
4189 \r
4190         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
4191             p = (int) board[0][i];\r
4192             if(p < (int) BlackPawn) piecesLeft[p] ++;\r
4193             board[0][i] = EmptySquare;\r
4194         }\r
4195 \r
4196         if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
4197             // shuffles restricted to allow normal castling put KRR first\r
4198             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
4199                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4200             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
4201                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
4202             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
4203                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
4204             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
4205                 put(board, WhiteRook, 0, 0, ANY);\r
4206             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
4207         }\r
4208 \r
4209         if((BOARD_RGHT-BOARD_LEFT & 1) == 0)\r
4210             // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
4211             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
4212                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
4213                 while(piecesLeft[p] >= 2) {\r
4214                     AddOnePiece(board, p, 0, LITE);\r
4215                     AddOnePiece(board, p, 0, DARK);\r
4216                 }\r
4217                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
4218             }\r
4219 \r
4220         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
4221             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
4222             // but we leave King and Rooks for last, to possibly obey FRC restriction\r
4223             if(p == (int)WhiteRook) continue;\r
4224             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
4225             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece\r
4226         }\r
4227 \r
4228         // now everything is placed, except perhaps King (Unicorn) and Rooks\r
4229 \r
4230         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
4231             // Last King gets castling rights\r
4232             while(piecesLeft[(int)WhiteUnicorn]) {\r
4233                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4234                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4235             }\r
4236 \r
4237             while(piecesLeft[(int)WhiteKing]) {\r
4238                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
4239                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;\r
4240             }\r
4241 \r
4242 \r
4243         } else {\r
4244             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);\r
4245             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
4246         }\r
4247 \r
4248         // Only Rooks can be left; simply place them all\r
4249         while(piecesLeft[(int)WhiteRook]) {\r
4250                 i = put(board, WhiteRook, 0, 0, ANY);\r
4251                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
4252                         if(first) {\r
4253                                 first=0;\r
4254                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;\r
4255                         }\r
4256                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;\r
4257                 }\r
4258         }\r
4259         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
4260             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
4261         }\r
4262 \r
4263         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
4264 }\r
4265 \r
4266 #endif\r
4267 \r
4268 BOOL SetCharTable( char *table, const char * map )\r
4269 /* [HGM] moved here from winboard.c because of its general usefulness */\r
4270 /*       Basically a safe strcpy that uses the last character as King */\r
4271 {\r
4272     BOOL result = FALSE; int NrPieces;\r
4273 \r
4274     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
4275                     && NrPieces >= 12 && !(NrPieces&1)) {\r
4276         int i; /* [HGM] Accept even length from 12 to 34 */\r
4277 \r
4278         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
4279         for( i=0; i<NrPieces/2-1; i++ ) {\r
4280             table[i] = map[i];\r
4281             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
4282         }\r
4283         table[(int) WhiteKing]  = map[NrPieces/2-1];\r
4284         table[(int) BlackKing]  = map[NrPieces-1];\r
4285 \r
4286         result = TRUE;\r
4287     }\r
4288 \r
4289     return result;\r
4290 }\r
4291 \r
4292 void\r
4293 InitPosition(redraw)\r
4294      int redraw;\r
4295 {\r
4296     ChessSquare (* pieces)[BOARD_SIZE];\r
4297     int i, j, pawnRow, overrule,\r
4298     oldx = gameInfo.boardWidth,\r
4299     oldy = gameInfo.boardHeight,\r
4300     oldh = gameInfo.holdingsWidth,\r
4301     oldv = gameInfo.variant;\r
4302 \r
4303     currentMove = forwardMostMove = backwardMostMove = 0;\r
4304     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
4305 \r
4306     /* [AS] Initialize pv info list [HGM] and game status */\r
4307     {\r
4308         for( i=0; i<MAX_MOVES; i++ ) {\r
4309             pvInfoList[i].depth = 0;\r
4310             epStatus[i]=EP_NONE;\r
4311             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
4312         }\r
4313 \r
4314         initialRulePlies = 0; /* 50-move counter start */\r
4315 \r
4316         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
4317         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
4318     }\r
4319 \r
4320     \r
4321     /* [HGM] logic here is completely changed. In stead of full positions */\r
4322     /* the initialized data only consist of the two backranks. The switch */\r
4323     /* selects which one we will use, which is than copied to the Board   */\r
4324     /* initialPosition, which for the rest is initialized by Pawns and    */\r
4325     /* empty squares. This initial position is then copied to boards[0],  */\r
4326     /* possibly after shuffling, so that it remains available.            */\r
4327 \r
4328     gameInfo.holdingsWidth = 0; /* default board sizes */\r
4329     gameInfo.boardWidth    = 8;\r
4330     gameInfo.boardHeight   = 8;\r
4331     gameInfo.holdingsSize  = 0;\r
4332     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
4333     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
4334     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
4335 \r
4336     switch (gameInfo.variant) {\r
4337     case VariantFischeRandom:\r
4338       shuffleOpenings = TRUE;\r
4339     default:\r
4340       pieces = FIDEArray;\r
4341       break;\r
4342     case VariantShatranj:\r
4343       pieces = ShatranjArray;\r
4344       nrCastlingRights = 0;\r
4345       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
4346       break;\r
4347     case VariantTwoKings:\r
4348       pieces = twoKingsArray;\r
4349       nrCastlingRights = 8;                 /* add rights for second King */\r
4350       castlingRights[0][6] = initialRights[2] = 5;\r
4351       castlingRights[0][7] = initialRights[5] = 5;\r
4352       castlingRank[6] = 0;\r
4353       castlingRank[7] = BOARD_HEIGHT-1;\r
4354       break;\r
4355     case VariantCapaRandom:\r
4356       shuffleOpenings = TRUE;\r
4357     case VariantCapablanca:\r
4358       pieces = CapablancaArray;\r
4359       gameInfo.boardWidth = 10;\r
4360       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4361       break;\r
4362     case VariantGothic:\r
4363       pieces = GothicArray;\r
4364       gameInfo.boardWidth = 10;\r
4365       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
4366       break;\r
4367     case VariantJanus:\r
4368       pieces = JanusArray;\r
4369       gameInfo.boardWidth = 10;\r
4370       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
4371       nrCastlingRights = 6;\r
4372         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4373         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4374         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH-1>>1;\r
4375         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4376         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4377         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH-1>>1;\r
4378       break;\r
4379     case VariantFalcon:\r
4380       pieces = FalconArray;\r
4381       gameInfo.boardWidth = 10;\r
4382       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
4383       break;\r
4384     case VariantXiangqi:\r
4385       pieces = XiangqiArray;\r
4386       gameInfo.boardWidth  = 9;\r
4387       gameInfo.boardHeight = 10;\r
4388       nrCastlingRights = 0;\r
4389       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
4390       break;\r
4391     case VariantShogi:\r
4392       pieces = ShogiArray;\r
4393       gameInfo.boardWidth  = 9;\r
4394       gameInfo.boardHeight = 9;\r
4395       gameInfo.holdingsSize = 7;\r
4396       nrCastlingRights = 0;\r
4397       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
4398       break;\r
4399     case VariantCourier:\r
4400       pieces = CourierArray;\r
4401       gameInfo.boardWidth  = 12;\r
4402       nrCastlingRights = 0;\r
4403       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
4404       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4405       break;\r
4406     case VariantKnightmate:\r
4407       pieces = KnightmateArray;\r
4408       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
4409       break;\r
4410     case VariantFairy:\r
4411       pieces = fairyArray;\r
4412       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
4413       break;\r
4414     case VariantCrazyhouse:\r
4415     case VariantBughouse:\r
4416       pieces = FIDEArray;\r
4417       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
4418       gameInfo.holdingsSize = 5;\r
4419       break;\r
4420     case VariantWildCastle:\r
4421       pieces = FIDEArray;\r
4422       /* !!?shuffle with kings guaranteed to be on d or e file */\r
4423       shuffleOpenings = 1;\r
4424       break;\r
4425     case VariantNoCastle:\r
4426       pieces = FIDEArray;\r
4427       nrCastlingRights = 0;\r
4428       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
4429       /* !!?unconstrained back-rank shuffle */\r
4430       shuffleOpenings = 1;\r
4431       break;\r
4432     }\r
4433 \r
4434     overrule = 0;\r
4435     if(appData.NrFiles >= 0) {\r
4436         if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
4437         gameInfo.boardWidth = appData.NrFiles;\r
4438     }\r
4439     if(appData.NrRanks >= 0) {\r
4440         gameInfo.boardHeight = appData.NrRanks;\r
4441     }\r
4442     if(appData.holdingsSize >= 0) {\r
4443         i = appData.holdingsSize;\r
4444         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
4445         gameInfo.holdingsSize = i;\r
4446     }\r
4447     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
4448     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
4449         DisplayFatalError("Recompile to support this BOARD_SIZE!", 0, 2);\r
4450 \r
4451     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
4452     if(pawnRow < 1) pawnRow = 1;\r
4453 \r
4454     /* User pieceToChar list overrules defaults */\r
4455     if(appData.pieceToCharTable != NULL)\r
4456         SetCharTable(pieceToChar, appData.pieceToCharTable);\r
4457 \r
4458     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
4459 \r
4460         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
4461             s = (ChessSquare) 0; /* account holding counts in guard band */\r
4462         for( i=0; i<BOARD_HEIGHT; i++ )\r
4463             initialPosition[i][j] = s;\r
4464 \r
4465         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
4466         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
4467         initialPosition[pawnRow][j] = WhitePawn;\r
4468         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
4469         if(gameInfo.variant == VariantXiangqi) {\r
4470             if(j&1) {\r
4471                 initialPosition[pawnRow][j] = \r
4472                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
4473                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
4474                    initialPosition[2][j] = WhiteCannon;\r
4475                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
4476                 }\r
4477             }\r
4478         }\r
4479         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];\r
4480     }\r
4481     if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
4482 \r
4483             j=BOARD_LEFT+1;\r
4484             initialPosition[1][j] = WhiteBishop;\r
4485             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
4486             j=BOARD_RGHT-2;\r
4487             initialPosition[1][j] = WhiteRook;\r
4488             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
4489     }\r
4490 \r
4491     if( nrCastlingRights == -1) {\r
4492         /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
4493         /*       This sets default castling rights from none to normal corners   */\r
4494         /* Variants with other castling rights must set them themselves above    */\r
4495         nrCastlingRights = 6;\r
4496        \r
4497         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
4498         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
4499         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
4500         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
4501         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
4502         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
4503      }\r
4504 \r
4505 #if 0\r
4506     if(gameInfo.variant == VariantFischeRandom) {\r
4507       if( appData.defaultFrcPosition < 0 ) {\r
4508         ShuffleFRC( initialPosition );\r
4509       }\r
4510       else {\r
4511         SetupFRC( initialPosition, appData.defaultFrcPosition );\r
4512       }\r
4513       startedFromSetupPosition = TRUE;\r
4514     } else \r
4515 #else\r
4516   if (appData.debugMode) {\r
4517     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
4518   }\r
4519     if(shuffleOpenings) {\r
4520         SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
4521         startedFromSetupPosition = TRUE;\r
4522     }\r
4523 #endif\r
4524     if(startedFromPositionFile) {\r
4525       /* [HGM] loadPos: use PositionFile for every new game */\r
4526       CopyBoard(initialPosition, filePosition);\r
4527       for(i=0; i<nrCastlingRights; i++)\r
4528           castlingRights[0][i] = initialRights[i] = fileRights[i];\r
4529       startedFromSetupPosition = TRUE;\r
4530     }\r
4531 \r
4532     CopyBoard(boards[0], initialPosition);\r
4533 \r
4534     if(oldx != gameInfo.boardWidth ||\r
4535        oldy != gameInfo.boardHeight ||\r
4536        oldh != gameInfo.holdingsWidth\r
4537 #ifdef GOTHIC\r
4538        || oldv == VariantGothic ||        // For licensing popups\r
4539        gameInfo.variant == VariantGothic\r
4540 #endif\r
4541 #ifdef FALCON\r
4542        || oldv == VariantFalcon ||\r
4543        gameInfo.variant == VariantFalcon\r
4544 #endif\r
4545                                          )\r
4546             InitDrawingSizes(-2 ,0);\r
4547 \r
4548     if (redraw)\r
4549       DrawPosition(TRUE, boards[currentMove]);\r
4550 }\r
4551 \r
4552 void\r
4553 SendBoard(cps, moveNum)\r
4554      ChessProgramState *cps;\r
4555      int moveNum;\r
4556 {\r
4557     char message[MSG_SIZ];\r
4558     \r
4559     if (cps->useSetboard) {\r
4560       char* fen = PositionToFEN(moveNum, cps->useFEN960);\r
4561       sprintf(message, "setboard %s\n", fen);\r
4562       SendToProgram(message, cps);\r
4563       free(fen);\r
4564 \r
4565     } else {\r
4566       ChessSquare *bp;\r
4567       int i, j;\r
4568       /* Kludge to set black to move, avoiding the troublesome and now\r
4569        * deprecated "black" command.\r
4570        */\r
4571       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
4572 \r
4573       SendToProgram("edit\n", cps);\r
4574       SendToProgram("#\n", cps);\r
4575       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4576         bp = &boards[moveNum][i][BOARD_LEFT];\r
4577         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4578           if ((int) *bp < (int) BlackPawn) {\r
4579             sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
4580                     AAA + j, ONE + i);\r
4581             if(message[0] == '+' || message[0] == '~') {\r
4582                 sprintf(message, "%c%c%c+\n",\r
4583                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4584                         AAA + j, ONE + i);\r
4585             }\r
4586             if(appData.alphaRank) {\r
4587                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4588                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4589             }\r
4590             SendToProgram(message, cps);\r
4591           }\r
4592         }\r
4593       }\r
4594     \r
4595       SendToProgram("c\n", cps);\r
4596       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
4597         bp = &boards[moveNum][i][BOARD_LEFT];\r
4598         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
4599           if (((int) *bp != (int) EmptySquare)\r
4600               && ((int) *bp >= (int) BlackPawn)) {\r
4601             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
4602                     AAA + j, ONE + i);\r
4603             if(message[0] == '+' || message[0] == '~') {\r
4604                 sprintf(message, "%c%c%c+\n",\r
4605                         PieceToChar((ChessSquare)(DEMOTED *bp)),\r
4606                         AAA + j, ONE + i);\r
4607             }\r
4608             if(appData.alphaRank) {\r
4609                 message[1] = BOARD_RGHT   - 1 - j + '1';\r
4610                 message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
4611             }\r
4612             SendToProgram(message, cps);\r
4613           }\r
4614         }\r
4615       }\r
4616     \r
4617       SendToProgram(".\n", cps);\r
4618     }\r
4619     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
4620 }\r
4621 \r
4622 int\r
4623 IsPromotion(fromX, fromY, toX, toY)\r
4624      int fromX, fromY, toX, toY;\r
4625 {\r
4626     /* [HGM] add Shogi promotions */\r
4627     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
4628     ChessSquare piece;\r
4629 \r
4630     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
4631       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
4632    /* [HGM] Note to self: line above also weeds out drops */\r
4633     piece = boards[currentMove][fromY][fromX];\r
4634     if(gameInfo.variant == VariantShogi) {\r
4635         promotionZoneSize = 3;\r
4636         highestPromotingPiece = (int)WhiteKing;\r
4637         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
4638            and if in normal chess we then allow promotion to King, why not\r
4639            allow promotion of other piece in Shogi?                         */\r
4640     }\r
4641     if((int)piece >= BlackPawn) {\r
4642         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
4643              return FALSE;\r
4644         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
4645     } else {\r
4646         if(  toY < BOARD_HEIGHT - promotionZoneSize &&\r
4647            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
4648     }\r
4649     return ( (int)piece <= highestPromotingPiece );\r
4650 }\r
4651 \r
4652 int\r
4653 InPalace(row, column)\r
4654      int row, column;\r
4655 {   /* [HGM] for Xiangqi */\r
4656     if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
4657          column < (BOARD_WIDTH + 4)/2 &&\r
4658          column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
4659     return FALSE;\r
4660 }\r
4661 \r
4662 int\r
4663 PieceForSquare (x, y)\r
4664      int x;\r
4665      int y;\r
4666 {\r
4667   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
4668      return -1;\r
4669   else\r
4670      return boards[currentMove][y][x];\r
4671 }\r
4672 \r
4673 int\r
4674 OKToStartUserMove(x, y)\r
4675      int x, y;\r
4676 {\r
4677     ChessSquare from_piece;\r
4678     int white_piece;\r
4679 \r
4680     if (matchMode) return FALSE;\r
4681     if (gameMode == EditPosition) return TRUE;\r
4682 \r
4683     if (x >= 0 && y >= 0)\r
4684       from_piece = boards[currentMove][y][x];\r
4685     else\r
4686       from_piece = EmptySquare;\r
4687 \r
4688     if (from_piece == EmptySquare) return FALSE;\r
4689 \r
4690     white_piece = (int)from_piece >= (int)WhitePawn &&\r
4691       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
4692 \r
4693     switch (gameMode) {\r
4694       case PlayFromGameFile:\r
4695       case AnalyzeFile:\r
4696       case TwoMachinesPlay:\r
4697       case EndOfGame:\r
4698         return FALSE;\r
4699 \r
4700       case IcsObserving:\r
4701       case IcsIdle:\r
4702         return FALSE;\r
4703 \r
4704       case MachinePlaysWhite:\r
4705       case IcsPlayingBlack:\r
4706         if (appData.zippyPlay) return FALSE;\r
4707         if (white_piece) {\r
4708             DisplayMoveError("You are playing Black");\r
4709             return FALSE;\r
4710         }\r
4711         break;\r
4712 \r
4713       case MachinePlaysBlack:\r
4714       case IcsPlayingWhite:\r
4715         if (appData.zippyPlay) return FALSE;\r
4716         if (!white_piece) {\r
4717             DisplayMoveError("You are playing White");\r
4718             return FALSE;\r
4719         }\r
4720         break;\r
4721 \r
4722       case EditGame:\r
4723         if (!white_piece && WhiteOnMove(currentMove)) {\r
4724             DisplayMoveError("It is White's turn");\r
4725             return FALSE;\r
4726         }           \r
4727         if (white_piece && !WhiteOnMove(currentMove)) {\r
4728             DisplayMoveError("It is Black's turn");\r
4729             return FALSE;\r
4730         }           \r
4731         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
4732             /* Editing correspondence game history */\r
4733             /* Could disallow this or prompt for confirmation */\r
4734             cmailOldMove = -1;\r
4735         }\r
4736         if (currentMove < forwardMostMove) {\r
4737             /* Discarding moves */\r
4738             /* Could prompt for confirmation here,\r
4739                but I don't think that's such a good idea */\r
4740             forwardMostMove = currentMove;\r
4741         }\r
4742         break;\r
4743 \r
4744       case BeginningOfGame:\r
4745         if (appData.icsActive) return FALSE;\r
4746         if (!appData.noChessProgram) {\r
4747             if (!white_piece) {\r
4748                 DisplayMoveError("You are playing White");\r
4749                 return FALSE;\r
4750             }\r
4751         }\r
4752         break;\r
4753         \r
4754       case Training:\r
4755         if (!white_piece && WhiteOnMove(currentMove)) {\r
4756             DisplayMoveError("It is White's turn");\r
4757             return FALSE;\r
4758         }           \r
4759         if (white_piece && !WhiteOnMove(currentMove)) {\r
4760             DisplayMoveError("It is Black's turn");\r
4761             return FALSE;\r
4762         }           \r
4763         break;\r
4764 \r
4765       default:\r
4766       case IcsExamining:\r
4767         break;\r
4768     }\r
4769     if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
4770         && gameMode != AnalyzeFile && gameMode != Training) {\r
4771         DisplayMoveError("Displayed position is not current");\r
4772         return FALSE;\r
4773     }\r
4774     return TRUE;\r
4775 }\r
4776 \r
4777 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
4778 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
4779 int lastLoadGameUseList = FALSE;\r
4780 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
4781 ChessMove lastLoadGameStart = (ChessMove) 0;\r
4782 \r
4783 \r
4784 ChessMove\r
4785 UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
4786      int fromX, fromY, toX, toY;\r
4787      int promoChar;\r
4788 {\r
4789     ChessMove moveType;\r
4790     ChessSquare pdown, pup;\r
4791 \r
4792     if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
4793     if ((fromX == toX) && (fromY == toY)) {\r
4794         return ImpossibleMove;\r
4795     }\r
4796 \r
4797     /* [HGM] suppress all moves into holdings area and guard band */\r
4798     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
4799             return ImpossibleMove;\r
4800 \r
4801     /* [HGM] <sameColor> moved to here from winboard.c */\r
4802     /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
4803     pdown = boards[currentMove][fromY][fromX];\r
4804     pup = boards[currentMove][toY][toX];\r
4805     if (    gameMode != EditPosition &&\r
4806             (WhitePawn <= pdown && pdown < BlackPawn &&\r
4807              WhitePawn <= pup && pup < BlackPawn  ||\r
4808              BlackPawn <= pdown && pdown < EmptySquare &&\r
4809              BlackPawn <= pup && pup < EmptySquare \r
4810             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
4811                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
4812                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) \r
4813         )           )\r
4814          return ImpossibleMove;\r
4815 \r
4816     /* Check if the user is playing in turn.  This is complicated because we\r
4817        let the user "pick up" a piece before it is his turn.  So the piece he\r
4818        tried to pick up may have been captured by the time he puts it down!\r
4819        Therefore we use the color the user is supposed to be playing in this\r
4820        test, not the color of the piece that is currently on the starting\r
4821        square---except in EditGame mode, where the user is playing both\r
4822        sides; fortunately there the capture race can't happen.  (It can\r
4823        now happen in IcsExamining mode, but that's just too bad.  The user\r
4824        will get a somewhat confusing message in that case.)\r
4825        */\r
4826 \r
4827     switch (gameMode) {\r
4828       case PlayFromGameFile:\r
4829       case AnalyzeFile:\r
4830       case TwoMachinesPlay:\r
4831       case EndOfGame:\r
4832       case IcsObserving:\r
4833       case IcsIdle:\r
4834         /* We switched into a game mode where moves are not accepted,\r
4835            perhaps while the mouse button was down. */\r
4836         return ImpossibleMove;\r
4837 \r
4838       case MachinePlaysWhite:\r
4839         /* User is moving for Black */\r
4840         if (WhiteOnMove(currentMove)) {\r
4841             DisplayMoveError("It is White's turn");\r
4842             return ImpossibleMove;\r
4843         }\r
4844         break;\r
4845 \r
4846       case MachinePlaysBlack:\r
4847         /* User is moving for White */\r
4848         if (!WhiteOnMove(currentMove)) {\r
4849             DisplayMoveError("It is Black's turn");\r
4850             return ImpossibleMove;\r
4851         }\r
4852         break;\r
4853 \r
4854       case EditGame:\r
4855       case IcsExamining:\r
4856       case BeginningOfGame:\r
4857       case AnalyzeMode:\r
4858       case Training:\r
4859         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
4860             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
4861             /* User is moving for Black */\r
4862             if (WhiteOnMove(currentMove)) {\r
4863                 DisplayMoveError("It is White's turn");\r
4864                 return ImpossibleMove;\r
4865             }\r
4866         } else {\r
4867             /* User is moving for White */\r
4868             if (!WhiteOnMove(currentMove)) {\r
4869                 DisplayMoveError("It is Black's turn");\r
4870                 return ImpossibleMove;\r
4871             }\r
4872         }\r
4873         break;\r
4874 \r
4875       case IcsPlayingBlack:\r
4876         /* User is moving for Black */\r
4877         if (WhiteOnMove(currentMove)) {\r
4878             if (!appData.premove) {\r
4879                 DisplayMoveError("It is White's turn");\r
4880             } else if (toX >= 0 && toY >= 0) {\r
4881                 premoveToX = toX;\r
4882                 premoveToY = toY;\r
4883                 premoveFromX = fromX;\r
4884                 premoveFromY = fromY;\r
4885                 premovePromoChar = promoChar;\r
4886                 gotPremove = 1;\r
4887                 if (appData.debugMode) \r
4888                     fprintf(debugFP, "Got premove: fromX %d,"\r
4889                             "fromY %d, toX %d, toY %d\n",\r
4890                             fromX, fromY, toX, toY);\r
4891             }\r
4892             return ImpossibleMove;\r
4893         }\r
4894         break;\r
4895 \r
4896       case IcsPlayingWhite:\r
4897         /* User is moving for White */\r
4898         if (!WhiteOnMove(currentMove)) {\r
4899             if (!appData.premove) {\r
4900                 DisplayMoveError("It is Black's turn");\r
4901             } else if (toX >= 0 && toY >= 0) {\r
4902                 premoveToX = toX;\r
4903                 premoveToY = toY;\r
4904                 premoveFromX = fromX;\r
4905                 premoveFromY = fromY;\r
4906                 premovePromoChar = promoChar;\r
4907                 gotPremove = 1;\r
4908                 if (appData.debugMode) \r
4909                     fprintf(debugFP, "Got premove: fromX %d,"\r
4910                             "fromY %d, toX %d, toY %d\n",\r
4911                             fromX, fromY, toX, toY);\r
4912             }\r
4913             return ImpossibleMove;\r
4914         }\r
4915         break;\r
4916 \r
4917       default:\r
4918         break;\r
4919 \r
4920       case EditPosition:\r
4921         /* EditPosition, empty square, or different color piece;\r
4922            click-click move is possible */\r
4923         if (toX == -2 || toY == -2) {\r
4924             boards[0][fromY][fromX] = EmptySquare;\r
4925             DrawPosition(FALSE, boards[currentMove]);\r
4926         } else if (toX >= 0 && toY >= 0) {\r
4927             boards[0][toY][toX] = boards[0][fromY][fromX];\r
4928             boards[0][fromY][fromX] = EmptySquare;\r
4929             DrawPosition(FALSE, boards[currentMove]);\r
4930         }\r
4931         return ImpossibleMove;\r
4932     }\r
4933 \r
4934     /* [HGM] If move started in holdings, it means a drop */\r
4935     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
4936          if( pup != EmptySquare ) return ImpossibleMove;\r
4937          if(appData.testLegality) {\r
4938              /* it would be more logical if LegalityTest() also figured out\r
4939               * which drops are legal. For now we forbid pawns on back rank.\r
4940               * Shogi is on its own here...\r
4941               */\r
4942              if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
4943                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
4944                  return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
4945          }\r
4946          return WhiteDrop; /* Not needed to specify white or black yet */\r
4947     }\r
4948 \r
4949     userOfferedDraw = FALSE;\r
4950         \r
4951     /* [HGM] always test for legality, to get promotion info */\r
4952     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
4953                           epStatus[currentMove], castlingRights[currentMove],\r
4954                                          fromY, fromX, toY, toX, promoChar);\r
4955 \r
4956     /* [HGM] but possibly ignore an IllegalMove result */\r
4957     if (appData.testLegality) {\r
4958         if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
4959             DisplayMoveError("Illegal move");\r
4960             return ImpossibleMove;\r
4961         }\r
4962     }\r
4963 \r
4964     return moveType;\r
4965     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
4966        function is made into one that returns an OK move type if FinishMove\r
4967        should be called. This to give the calling driver routine the\r
4968        opportunity to finish the userMove input with a promotion popup,\r
4969        without bothering the user with this for invalid or illegal moves */\r
4970 \r
4971 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
4972 }\r
4973 \r
4974 /* Common tail of UserMoveEvent and DropMenuEvent */\r
4975 void\r
4976 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
4977      ChessMove moveType;\r
4978      int fromX, fromY, toX, toY;\r
4979      /*char*/int promoChar;\r
4980 {\r
4981     /* [HGM] <popupFix> kludge to avoid having know the exact promotion\r
4982        move type in caller when we know the move is a legal promotion */\r
4983     if(moveType == NormalMove)\r
4984         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
4985 \r
4986     /* [HGM] convert drag-and-drop piece drops to standard form */\r
4987     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
4988          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
4989          fromX = boards[currentMove][fromY][fromX];\r
4990          fromY = DROP_RANK;\r
4991     }\r
4992 \r
4993     /* [HGM] <popupFix> The following if has been moved here from\r
4994        UserMoveEvent(). Because it seemed to belon here (why not allow\r
4995        piece drops in training games?), and because it can only be\r
4996        performed after it is known to what we promote. */\r
4997     if (gameMode == Training) {\r
4998       /* compare the move played on the board to the next move in the\r
4999        * game. If they match, display the move and the opponent's response. \r
5000        * If they don't match, display an error message.\r
5001        */\r
5002       int saveAnimate;\r
5003       Board testBoard;\r
5004       CopyBoard(testBoard, boards[currentMove]);\r
5005       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);\r
5006 \r
5007       if (CompareBoards(testBoard, boards[currentMove+1])) {\r
5008         ForwardInner(currentMove+1);\r
5009 \r
5010         /* Autoplay the opponent's response.\r
5011          * if appData.animate was TRUE when Training mode was entered,\r
5012          * the response will be animated.\r
5013          */\r
5014         saveAnimate = appData.animate;\r
5015         appData.animate = animateTraining;\r
5016         ForwardInner(currentMove+1);\r
5017         appData.animate = saveAnimate;\r
5018 \r
5019         /* check for the end of the game */\r
5020         if (currentMove >= forwardMostMove) {\r
5021           gameMode = PlayFromGameFile;\r
5022           ModeHighlight();\r
5023           SetTrainingModeOff();\r
5024           DisplayInformation("End of game");\r
5025         }\r
5026       } else {\r
5027         DisplayError("Incorrect move", 0);\r
5028       }\r
5029       return;\r
5030     }\r
5031 \r
5032   /* Ok, now we know that the move is good, so we can kill\r
5033      the previous line in Analysis Mode */\r
5034   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
5035     forwardMostMove = currentMove;\r
5036   }\r
5037 \r
5038   /* If we need the chess program but it's dead, restart it */\r
5039   ResurrectChessProgram();\r
5040 \r
5041   /* A user move restarts a paused game*/\r
5042   if (pausing)\r
5043     PauseEvent();\r
5044 \r
5045   thinkOutput[0] = NULLCHAR;\r
5046 \r
5047   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
5048 \r
5049   if (gameMode == BeginningOfGame) {\r
5050     if (appData.noChessProgram) {\r
5051       gameMode = EditGame;\r
5052       SetGameInfo();\r
5053     } else {\r
5054       char buf[MSG_SIZ];\r
5055       gameMode = MachinePlaysBlack;\r
5056       SetGameInfo();\r
5057       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
5058       DisplayTitle(buf);\r
5059       if (first.sendName) {\r
5060         sprintf(buf, "name %s\n", gameInfo.white);\r
5061         SendToProgram(buf, &first);\r
5062       }\r
5063       StartClocks();\r
5064     }\r
5065     ModeHighlight();\r
5066   }\r
5067 \r
5068   /* Relay move to ICS or chess engine */\r
5069   if (appData.icsActive) {\r
5070     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
5071         gameMode == IcsExamining) {\r
5072       SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5073       ics_user_moved = 1;\r
5074     }\r
5075   } else {\r
5076     if (first.sendTime && (gameMode == BeginningOfGame ||\r
5077                            gameMode == MachinePlaysWhite ||\r
5078                            gameMode == MachinePlaysBlack)) {\r
5079       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
5080     }\r
5081     SendMoveToProgram(forwardMostMove-1, &first);\r
5082     if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
5083       first.maybeThinking = TRUE;\r
5084     }\r
5085     if (currentMove == cmailOldMove + 1) {\r
5086       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
5087     }\r
5088   }\r
5089 \r
5090   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5091 \r
5092   switch (gameMode) {\r
5093   case EditGame:\r
5094     switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
5095                      EP_UNKNOWN, castlingRights[currentMove]) ) {\r
5096     case MT_NONE:\r
5097     case MT_CHECK:\r
5098       break;\r
5099     case MT_CHECKMATE:\r
5100       if (WhiteOnMove(currentMove)) {\r
5101         GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
5102       } else {\r
5103         GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
5104       }\r
5105       break;\r
5106     case MT_STALEMATE:\r
5107       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
5108       break;\r
5109     }\r
5110     break;\r
5111     \r
5112   case MachinePlaysBlack:\r
5113   case MachinePlaysWhite:\r
5114     /* disable certain menu options while machine is thinking */\r
5115     SetMachineThinkingEnables();\r
5116     break;\r
5117 \r
5118   default:\r
5119     break;\r
5120   }\r
5121 }\r
5122 \r
5123 void\r
5124 UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
5125      int fromX, fromY, toX, toY;\r
5126      int promoChar;\r
5127 {\r
5128     /* [HGM] This routine was added to allow calling of its two logical\r
5129        parts from other modules in the old way. Before, UserMoveEvent()\r
5130        automatically called FinishMove() if the move was OK, and returned\r
5131        otherwise. I separated the two, in order to make it possible to\r
5132        slip a promotion popup in between. But that it always needs two\r
5133        calls, to the first part, (now called UserMoveTest() ), and to\r
5134        FinishMove if the first part succeeded. Calls that do not need\r
5135        to do anything in between, can call this routine the old way. \r
5136     */\r
5137     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
5138 \r
5139     if(moveType != ImpossibleMove)\r
5140         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
5141 }\r
5142 \r
5143 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
5144 {\r
5145     char * hint = lastHint;\r
5146     FrontEndProgramStats stats;\r
5147 \r
5148     stats.which = cps == &first ? 0 : 1;\r
5149     stats.depth = cpstats->depth;\r
5150     stats.nodes = cpstats->nodes;\r
5151     stats.score = cpstats->score;\r
5152     stats.time = cpstats->time;\r
5153     stats.pv = cpstats->movelist;\r
5154     stats.hint = lastHint;\r
5155     stats.an_move_index = 0;\r
5156     stats.an_move_count = 0;\r
5157 \r
5158     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
5159         stats.hint = cpstats->move_name;\r
5160         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
5161         stats.an_move_count = cpstats->nr_moves;\r
5162     }\r
5163 \r
5164     SetProgramStats( &stats );\r
5165 }\r
5166 \r
5167 void\r
5168 HandleMachineMove(message, cps)\r
5169      char *message;\r
5170      ChessProgramState *cps;\r
5171 {\r
5172     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
5173     char realname[MSG_SIZ];\r
5174     int fromX, fromY, toX, toY;\r
5175     ChessMove moveType;\r
5176     char promoChar;\r
5177     char *p;\r
5178     int machineWhite;\r
5179 \r
5180     /*\r
5181      * Kludge to ignore BEL characters\r
5182      */\r
5183     while (*message == '\007') message++;\r
5184 \r
5185     /*\r
5186      * [HGM] engine debug message: ignore lines starting with '#' character\r
5187      */\r
5188     if(cps->debug && *message == '#') return;\r
5189 \r
5190     /*\r
5191      * Look for book output\r
5192      */\r
5193     if (cps == &first && bookRequested) {\r
5194         if (message[0] == '\t' || message[0] == ' ') {\r
5195             /* Part of the book output is here; append it */\r
5196             strcat(bookOutput, message);\r
5197             strcat(bookOutput, "  \n");\r
5198             return;\r
5199         } else if (bookOutput[0] != NULLCHAR) {\r
5200             /* All of book output has arrived; display it */\r
5201             char *p = bookOutput;\r
5202             while (*p != NULLCHAR) {\r
5203                 if (*p == '\t') *p = ' ';\r
5204                 p++;\r
5205             }\r
5206             DisplayInformation(bookOutput);\r
5207             bookRequested = FALSE;\r
5208             /* Fall through to parse the current output */\r
5209         }\r
5210     }\r
5211 \r
5212     /*\r
5213      * Look for machine move.\r
5214      */\r
5215     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
5216         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
5217     {\r
5218         /* This method is only useful on engines that support ping */\r
5219         if (cps->lastPing != cps->lastPong) {\r
5220           if (gameMode == BeginningOfGame) {\r
5221             /* Extra move from before last new; ignore */\r
5222             if (appData.debugMode) {\r
5223                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5224             }\r
5225           } else {\r
5226             if (appData.debugMode) {\r
5227                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5228                         cps->which, gameMode);\r
5229             }\r
5230 \r
5231             SendToProgram("undo\n", cps);\r
5232           }\r
5233           return;\r
5234         }\r
5235 \r
5236         switch (gameMode) {\r
5237           case BeginningOfGame:\r
5238             /* Extra move from before last reset; ignore */\r
5239             if (appData.debugMode) {\r
5240                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
5241             }\r
5242             return;\r
5243 \r
5244           case EndOfGame:\r
5245           case IcsIdle:\r
5246           default:\r
5247             /* Extra move after we tried to stop.  The mode test is\r
5248                not a reliable way of detecting this problem, but it's\r
5249                the best we can do on engines that don't support ping.\r
5250             */\r
5251             if (appData.debugMode) {\r
5252                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
5253                         cps->which, gameMode);\r
5254             }\r
5255             SendToProgram("undo\n", cps);\r
5256             return;\r
5257 \r
5258           case MachinePlaysWhite:\r
5259           case IcsPlayingWhite:\r
5260             machineWhite = TRUE;\r
5261             break;\r
5262 \r
5263           case MachinePlaysBlack:\r
5264           case IcsPlayingBlack:\r
5265             machineWhite = FALSE;\r
5266             break;\r
5267 \r
5268           case TwoMachinesPlay:\r
5269             machineWhite = (cps->twoMachinesColor[0] == 'w');\r
5270             break;\r
5271         }\r
5272         if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
5273             if (appData.debugMode) {\r
5274                 fprintf(debugFP,\r
5275                         "Ignoring move out of turn by %s, gameMode %d"\r
5276                         ", forwardMost %d\n",\r
5277                         cps->which, gameMode, forwardMostMove);\r
5278             }\r
5279             return;\r
5280         }\r
5281 \r
5282     if (appData.debugMode) { int f = forwardMostMove;\r
5283         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
5284                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
5285     }\r
5286         AlphaRank(machineMove, 4);\r
5287         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
5288                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5289             /* Machine move could not be parsed; ignore it. */\r
5290             sprintf(buf1, "Illegal move \"%s\" from %s machine",\r
5291                     machineMove, cps->which);\r
5292             DisplayError(buf1, 0);\r
5293             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",\r
5294                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5295             if (gameMode == TwoMachinesPlay) {\r
5296               GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5297                        buf1, GE_XBOARD);\r
5298             }\r
5299             return;\r
5300         }\r
5301 \r
5302         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
5303         /* So we have to redo legality test with true e.p. status here,  */\r
5304         /* to make sure an illegal e.p. capture does not slip through,   */\r
5305         /* to cause a forfeit on a justified illegal-move complaint      */\r
5306         /* of the opponent.                                              */\r
5307         if( gameMode==TwoMachinesPlay && appData.testLegality\r
5308             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
5309                                                               ) {\r
5310            ChessMove moveType;\r
5311            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
5312                         epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
5313                              fromY, fromX, toY, toX, promoChar);\r
5314             if (appData.debugMode) {\r
5315                 int i;\r
5316                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
5317                     castlingRights[forwardMostMove][i], castlingRank[i]);\r
5318                 fprintf(debugFP, "castling rights\n");\r
5319             }\r
5320             if(moveType == IllegalMove) {\r
5321                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
5322                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
5323                 GameEnds(machineWhite ? BlackWins : WhiteWins,\r
5324                            buf1, GE_XBOARD);\r
5325            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
5326            /* [HGM] Kludge to handle engines that send FRC-style castling\r
5327               when they shouldn't (like TSCP-Gothic) */\r
5328            switch(moveType) {\r
5329              case WhiteASideCastleFR:\r
5330              case BlackASideCastleFR:\r
5331                toX+=2;\r
5332                currentMoveString[2]++;\r
5333                break;\r
5334              case WhiteHSideCastleFR:\r
5335              case BlackHSideCastleFR:\r
5336                toX--;\r
5337                currentMoveString[2]--;\r
5338                break;\r
5339            }\r
5340         }\r
5341         hintRequested = FALSE;\r
5342         lastHint[0] = NULLCHAR;\r
5343         bookRequested = FALSE;\r
5344         /* Program may be pondering now */\r
5345         cps->maybeThinking = TRUE;\r
5346         if (cps->sendTime == 2) cps->sendTime = 1;\r
5347         if (cps->offeredDraw) cps->offeredDraw--;\r
5348 \r
5349 #if ZIPPY\r
5350         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
5351             first.initDone) {\r
5352           SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
5353           ics_user_moved = 1;\r
5354           if(appData.autoKibitz) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
5355                 char buf[3*MSG_SIZ];\r
5356 \r
5357                 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",\r
5358                         programStats.depth,\r
5359                         programStats.score / 100.,\r
5360                         programStats.time / 100.,\r
5361                         (double) programStats.nodes,\r
5362                         programStats.nodes / (10*abs(programStats.time) + 1.),\r
5363                         programStats.movelist);\r
5364                 SendToICS(buf);\r
5365           }\r
5366         }\r
5367 #endif\r
5368         /* currentMoveString is set as a side-effect of ParseOneMove */\r
5369         strcpy(machineMove, currentMoveString);\r
5370         strcat(machineMove, "\n");\r
5371         strcpy(moveList[forwardMostMove], machineMove);\r
5372 \r
5373         /* [AS] Save move info and clear stats for next move */\r
5374         pvInfoList[ forwardMostMove ].score = programStats.score;\r
5375         pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
5376         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats\r
5377         ClearProgramStats();\r
5378         thinkOutput[0] = NULLCHAR;\r
5379         hiddenThinkOutputState = 0;\r
5380 \r
5381         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
5382 \r
5383         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
5384         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
5385             int count = 0;\r
5386 \r
5387             while( count < adjudicateLossPlies ) {\r
5388                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
5389 \r
5390                 if( count & 1 ) {\r
5391                     score = -score; /* Flip score for winning side */\r
5392                 }\r
5393 \r
5394                 if( score > adjudicateLossThreshold ) {\r
5395                     break;\r
5396                 }\r
5397 \r
5398                 count++;\r
5399             }\r
5400 \r
5401             if( count >= adjudicateLossPlies ) {\r
5402                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5403 \r
5404                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
5405                     "Xboard adjudication", \r
5406                     GE_XBOARD );\r
5407 \r
5408                 return;\r
5409             }\r
5410         }\r
5411 \r
5412 #ifdef ADJUDICATE // [HGM] some adjudications useful with buggy engines\r
5413 \r
5414         if( gameMode == TwoMachinesPlay && gameInfo.holdingsSize == 0) {\r
5415             int count = 0, epFile = epStatus[forwardMostMove];\r
5416 \r
5417             if(appData.testLegality && appData.checkMates) \r
5418             // don't wait for engine to announce game end if we can judge ourselves\r
5419             switch (MateTest(boards[forwardMostMove],\r
5420                                  PosFlags(forwardMostMove), epFile,\r
5421                                        castlingRights[forwardMostMove]) ) {\r
5422               case MT_NONE:\r
5423               case MT_CHECK:\r
5424               default:\r
5425                 break;\r
5426               case MT_STALEMATE:\r
5427                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5428                 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
5429                     GE_XBOARD );\r
5430                 break;\r
5431               case MT_CHECKMATE:\r
5432                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5433                 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
5434                     "Xboard adjudication: Checkmate", \r
5435                     GE_XBOARD );\r
5436                 break;\r
5437             }\r
5438 \r
5439             if( appData.testLegality )\r
5440             {   /* [HGM] Some more adjudications for obstinate engines */\r
5441                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
5442                     NrWQ=0, NrBQ=0, bishopsColor = 0,\r
5443                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;\r
5444                 static int moveCount;\r
5445 \r
5446                 /* First absolutely insufficient mating material. Count what is on board. */\r
5447                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
5448                 {   ChessSquare p = boards[forwardMostMove][i][j];\r
5449                     int m=i;\r
5450 \r
5451                     switch((int) p)\r
5452                     {   /* count B,N,R and other of each side */\r
5453                         case WhiteKnight:\r
5454                              NrWN++; break;\r
5455                         case WhiteBishop:\r
5456                              bishopsColor |= 1 << ((i^j)&1);\r
5457                              NrWB++; break;\r
5458                         case BlackKnight:\r
5459                              NrBN++; break;\r
5460                         case BlackBishop:\r
5461                              bishopsColor |= 1 << ((i^j)&1);\r
5462                              NrBB++; break;\r
5463                         case WhiteRook:\r
5464                              NrWR++; break;\r
5465                         case BlackRook:\r
5466                              NrBR++; break;\r
5467                         case WhiteQueen:\r
5468                              NrWQ++; break;\r
5469                         case BlackQueen:\r
5470                              NrBQ++; break;\r
5471                         case EmptySquare: \r
5472                              break;\r
5473                         case BlackPawn:\r
5474                              m = 7-i;\r
5475                         case WhitePawn:\r
5476                              PawnAdvance += m; NrPawns++;\r
5477                     }\r
5478                     NrPieces += (p != EmptySquare);\r
5479                 }\r
5480 \r
5481                 if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2\r
5482                  || NrPieces == 4 && NrBB+NrWB==2 && bishopsColor != 3)\r
5483                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
5484 \r
5485                      /* always flag draws, for judging claims */\r
5486                      epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
5487 \r
5488                      if(appData.materialDraws) {\r
5489                          /* but only adjudicate them if adjudication enabled */\r
5490                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5491                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
5492                          return;\r
5493                      }\r
5494                 }\r
5495 \r
5496                 /* Shatranj baring rule */\r
5497                 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )\r
5498                 {    /* bare King */\r
5499 \r
5500                      if(--bare < 0 && appData.checkMates) {\r
5501                          /* but only adjudicate them if adjudication enabled */\r
5502                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5503                          GameEnds( NrW > 1 ? WhiteWins : NrPiece - NrW > 1 ? BlackWins : GameIsDrawn, \r
5504                                                         "Xboard adjudication: Bare king", GE_XBOARD );\r
5505                          return;\r
5506                      }\r
5507                 } else bare = 1;\r
5508 \r
5509                 /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
5510                 if(NrPieces == 4 && \r
5511                    (   NrWR == 1 && NrBR == 1 /* KRKR */\r
5512                    || NrWQ==1 && NrBQ==1     /* KQKQ */\r
5513                    || NrWN==2 || NrBN==2     /* KNNK */\r
5514                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
5515                   ) ) {\r
5516                      if(--moveCount < 0 && appData.trivialDraws)\r
5517                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
5518                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5519                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
5520                           return;\r
5521                      }\r
5522                 } else moveCount = 6;\r
5523 #if 0\r
5524     if (appData.debugMode) { int i;\r
5525       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
5526               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
5527               appData.drawRepeats);\r
5528       for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
5529            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
5530 \r
5531     }\r
5532 #endif\r
5533                 /* Check for rep-draws */\r
5534                 count = 0;\r
5535                 for(k = forwardMostMove-2;\r
5536                     k>=backwardMostMove && k>=forwardMostMove-100 &&\r
5537                         epStatus[k] < EP_UNKNOWN &&\r
5538                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
5539                     k-=2)\r
5540                 {   int rights=0;\r
5541 #if 0\r
5542     if (appData.debugMode) {\r
5543       fprintf(debugFP, " loop\n");\r
5544     }\r
5545 #endif\r
5546                     if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
5547 #if 0\r
5548     if (appData.debugMode) {\r
5549       fprintf(debugFP, "match\n");\r
5550     }\r
5551 #endif\r
5552                         /* compare castling rights */\r
5553                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
5554                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
5555                                 rights++; /* King lost rights, while rook still had them */\r
5556                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
5557                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
5558                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
5559                                    rights++; /* but at least one rook lost them */\r
5560                         }\r
5561                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
5562                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
5563                                 rights++; \r
5564                         if( castlingRights[forwardMostMove][5] >= 0 ) {\r
5565                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
5566                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
5567                                    rights++;\r
5568                         }\r
5569 #if 0\r
5570     if (appData.debugMode) {\r
5571       for(i=0; i<nrCastlingRights; i++)\r
5572       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
5573     }\r
5574 \r
5575     if (appData.debugMode) {\r
5576       fprintf(debugFP, " %d %d\n", rights, k);\r
5577     }\r
5578 #endif\r
5579                         if( rights == 0 && ++count > appData.drawRepeats-2\r
5580                             && appData.drawRepeats > 1) {\r
5581                              /* adjudicate after user-specified nr of repeats */\r
5582                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5583                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
5584                              return;\r
5585                         }\r
5586                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
5587                              epStatus[forwardMostMove] = EP_REP_DRAW;\r
5588                     }\r
5589                 }\r
5590 \r
5591                 /* Now we test for 50-move draws. Determine ply count */\r
5592                 count = forwardMostMove;\r
5593                 /* look for last irreversble move */\r
5594                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
5595                     count--;\r
5596                 /* if we hit starting position, add initial plies */\r
5597                 if( count == backwardMostMove )\r
5598                     count -= initialRulePlies;\r
5599                 count = forwardMostMove - count; \r
5600                 if( count >= 100)\r
5601                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
5602                          /* this is used to judge if draw claims are legal */\r
5603                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
5604                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5605                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
5606                          return;\r
5607                 }\r
5608 \r
5609                 /* if draw offer is pending, treat it as a draw claim\r
5610                  * when draw condition present, to allow engines a way to\r
5611                  * claim draws before making their move to avoid a race\r
5612                  * condition occurring after their move\r
5613                  */\r
5614                 if( cps->other->offeredDraw || cps->offeredDraw ) {\r
5615                          char *p = NULL;\r
5616                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
5617                              p = "Draw claim: 50-move rule";\r
5618                          if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
5619                              p = "Draw claim: 3-fold repetition";\r
5620                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
5621                              p = "Draw claim: insufficient mating material";\r
5622                          if( p != NULL ) {\r
5623                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
5624                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5625                              return;\r
5626                          }\r
5627                 }\r
5628 \r
5629             }\r
5630 \r
5631 \r
5632         }\r
5633 #endif\r
5634         if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
5635             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5636 \r
5637             GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
5638 \r
5639             return;\r
5640         }\r
5641 \r
5642         if (gameMode == TwoMachinesPlay) {\r
5643             /* [HGM] relaying draw offers moved to after reception of move */\r
5644             /* and interpreting offer as claim if it brings draw condition */\r
5645             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
5646                 SendToProgram("draw\n", cps->other);\r
5647             }\r
5648             if (cps->other->sendTime) {\r
5649                 SendTimeRemaining(cps->other,\r
5650                                   cps->other->twoMachinesColor[0] == 'w');\r
5651             }\r
5652             SendMoveToProgram(forwardMostMove-1, cps->other);\r
5653             if (firstMove) {\r
5654                 firstMove = FALSE;\r
5655                 if (cps->other->useColors) {\r
5656                   SendToProgram(cps->other->twoMachinesColor, cps->other);\r
5657                 }\r
5658                 SendToProgram("go\n", cps->other);\r
5659             }\r
5660             cps->other->maybeThinking = TRUE;\r
5661         }\r
5662 \r
5663         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
5664         \r
5665         if (!pausing && appData.ringBellAfterMoves) {\r
5666             RingBell();\r
5667         }\r
5668 \r
5669         /* \r
5670          * Reenable menu items that were disabled while\r
5671          * machine was thinking\r
5672          */\r
5673         if (gameMode != TwoMachinesPlay)\r
5674             SetUserThinkingEnables();\r
5675 \r
5676         return;\r
5677     }\r
5678 \r
5679     /* Set special modes for chess engines.  Later something general\r
5680      *  could be added here; for now there is just one kludge feature,\r
5681      *  needed because Crafty 15.10 and earlier don't ignore SIGINT\r
5682      *  when "xboard" is given as an interactive command.\r
5683      */\r
5684     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
5685         cps->useSigint = FALSE;\r
5686         cps->useSigterm = FALSE;\r
5687     }\r
5688 \r
5689     /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
5690      * want this, I was asked to put it in, and obliged.\r
5691      */\r
5692     if (!strncmp(message, "setboard ", 9)) {\r
5693         Board initial_position; int i;\r
5694 \r
5695         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
5696 \r
5697         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
5698             DisplayError("Bad FEN received from engine", 0);\r
5699             return ;\r
5700         } else {\r
5701            Reset(FALSE, FALSE);\r
5702            CopyBoard(boards[0], initial_position);\r
5703            initialRulePlies = FENrulePlies;\r
5704            epStatus[0] = FENepStatus;\r
5705            for( i=0; i<nrCastlingRights; i++ )\r
5706                 castlingRights[0][i] = FENcastlingRights[i];\r
5707            if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
5708            else gameMode = MachinePlaysBlack;                 \r
5709            DrawPosition(FALSE, boards[currentMove]);\r
5710         }\r
5711         return;\r
5712     }\r
5713 \r
5714     /*\r
5715      * Look for communication commands\r
5716      */\r
5717     if (!strncmp(message, "telluser ", 9)) {\r
5718         DisplayNote(message + 9);\r
5719         return;\r
5720     }\r
5721     if (!strncmp(message, "tellusererror ", 14)) {\r
5722         DisplayError(message + 14, 0);\r
5723         return;\r
5724     }\r
5725     if (!strncmp(message, "tellopponent ", 13)) {\r
5726       if (appData.icsActive) {\r
5727         if (loggedOn) {\r
5728           sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);\r
5729           SendToICS(buf1);\r
5730         }\r
5731       } else {\r
5732         DisplayNote(message + 13);\r
5733       }\r
5734       return;\r
5735     }\r
5736     if (!strncmp(message, "tellothers ", 11)) {\r
5737       if (appData.icsActive) {\r
5738         if (loggedOn) {\r
5739           sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);\r
5740           SendToICS(buf1);\r
5741         }\r
5742       }\r
5743       return;\r
5744     }\r
5745     if (!strncmp(message, "tellall ", 8)) {\r
5746       if (appData.icsActive) {\r
5747         if (loggedOn) {\r
5748           sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);\r
5749           SendToICS(buf1);\r
5750         }\r
5751       } else {\r
5752         DisplayNote(message + 8);\r
5753       }\r
5754       return;\r
5755     }\r
5756     if (strncmp(message, "warning", 7) == 0) {\r
5757         /* Undocumented feature, use tellusererror in new code */\r
5758         DisplayError(message, 0);\r
5759         return;\r
5760     }\r
5761     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
5762         strcpy(realname, cps->tidy);\r
5763         strcat(realname, " query");\r
5764         AskQuestion(realname, buf2, buf1, cps->pr);\r
5765         return;\r
5766     }\r
5767     /* Commands from the engine directly to ICS.  We don't allow these to be \r
5768      *  sent until we are logged on. Crafty kibitzes have been known to \r
5769      *  interfere with the login process.\r
5770      */\r
5771     if (loggedOn) {\r
5772         if (!strncmp(message, "tellics ", 8)) {\r
5773             SendToICS(message + 8);\r
5774             SendToICS("\n");\r
5775             return;\r
5776         }\r
5777         if (!strncmp(message, "tellicsnoalias ", 15)) {\r
5778             SendToICS(ics_prefix);\r
5779             SendToICS(message + 15);\r
5780             SendToICS("\n");\r
5781             return;\r
5782         }\r
5783         /* The following are for backward compatibility only */\r
5784         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
5785             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
5786             SendToICS(ics_prefix);\r
5787             SendToICS(message);\r
5788             SendToICS("\n");\r
5789             return;\r
5790         }\r
5791     }\r
5792     if (strncmp(message, "feature ", 8) == 0) {\r
5793       ParseFeatures(message+8, cps);\r
5794     }\r
5795     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
5796         return;\r
5797     }\r
5798     /*\r
5799      * If the move is illegal, cancel it and redraw the board.\r
5800      * Also deal with other error cases.  Matching is rather loose\r
5801      * here to accommodate engines written before the spec.\r
5802      */\r
5803     if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
5804         strncmp(message, "Error", 5) == 0) {\r
5805         if (StrStr(message, "name") || \r
5806             StrStr(message, "rating") || StrStr(message, "?") ||\r
5807             StrStr(message, "result") || StrStr(message, "board") ||\r
5808             StrStr(message, "bk") || StrStr(message, "computer") ||\r
5809             StrStr(message, "variant") || StrStr(message, "hint") ||\r
5810             StrStr(message, "random") || StrStr(message, "depth") ||\r
5811             StrStr(message, "accepted")) {\r
5812             return;\r
5813         }\r
5814         if (StrStr(message, "protover")) {\r
5815           /* Program is responding to input, so it's apparently done\r
5816              initializing, and this error message indicates it is\r
5817              protocol version 1.  So we don't need to wait any longer\r
5818              for it to initialize and send feature commands. */\r
5819           FeatureDone(cps, 1);\r
5820           cps->protocolVersion = 1;\r
5821           return;\r
5822         }\r
5823         cps->maybeThinking = FALSE;\r
5824 \r
5825         if (StrStr(message, "draw")) {\r
5826             /* Program doesn't have "draw" command */\r
5827             cps->sendDrawOffers = 0;\r
5828             return;\r
5829         }\r
5830         if (cps->sendTime != 1 &&\r
5831             (StrStr(message, "time") || StrStr(message, "otim"))) {\r
5832           /* Program apparently doesn't have "time" or "otim" command */\r
5833           cps->sendTime = 0;\r
5834           return;\r
5835         }\r
5836         if (StrStr(message, "analyze")) {\r
5837             cps->analysisSupport = FALSE;\r
5838             cps->analyzing = FALSE;\r
5839             Reset(FALSE, TRUE);\r
5840             sprintf(buf2, "%s does not support analysis", cps->tidy);\r
5841             DisplayError(buf2, 0);\r
5842             return;\r
5843         }\r
5844         if (StrStr(message, "(no matching move)st")) {\r
5845           /* Special kludge for GNU Chess 4 only */\r
5846           cps->stKludge = TRUE;\r
5847           SendTimeControl(cps, movesPerSession, timeControl,\r
5848                           timeIncrement, appData.searchDepth,\r
5849                           searchTime);\r
5850           return;\r
5851         }\r
5852         if (StrStr(message, "(no matching move)sd")) {\r
5853           /* Special kludge for GNU Chess 4 only */\r
5854           cps->sdKludge = TRUE;\r
5855           SendTimeControl(cps, movesPerSession, timeControl,\r
5856                           timeIncrement, appData.searchDepth,\r
5857                           searchTime);\r
5858           return;\r
5859         }\r
5860         if (!StrStr(message, "llegal")) {\r
5861             return;\r
5862         }\r
5863         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
5864             gameMode == IcsIdle) return;\r
5865         if (forwardMostMove <= backwardMostMove) return;\r
5866 #if 0\r
5867         /* Following removed: it caused a bug where a real illegal move\r
5868            message in analyze mored would be ignored. */\r
5869         if (cps == &first && programStats.ok_to_send == 0) {\r
5870             /* Bogus message from Crafty responding to "."  This filtering\r
5871                can miss some of the bad messages, but fortunately the bug \r
5872                is fixed in current Crafty versions, so it doesn't matter. */\r
5873             return;\r
5874         }\r
5875 #endif\r
5876         if (pausing) PauseEvent();\r
5877         if (gameMode == PlayFromGameFile) {\r
5878             /* Stop reading this game file */\r
5879             gameMode = EditGame;\r
5880             ModeHighlight();\r
5881         }\r
5882         currentMove = --forwardMostMove;\r
5883         DisplayMove(currentMove-1); /* before DisplayMoveError */\r
5884         SwitchClocks();\r
5885         DisplayBothClocks();\r
5886         sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)",\r
5887                 parseList[currentMove], cps->which);\r
5888         DisplayMoveError(buf1);\r
5889         DrawPosition(FALSE, boards[currentMove]);\r
5890 \r
5891         /* [HGM] illegal-move claim should forfeit game when Xboard */\r
5892         /* only passes fully legal moves                            */\r
5893         if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
5894             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
5895                                 "False illegal-move claim", GE_XBOARD );\r
5896         }\r
5897         return;\r
5898     }\r
5899     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
5900         /* Program has a broken "time" command that\r
5901            outputs a string not ending in newline.\r
5902            Don't use it. */\r
5903         cps->sendTime = 0;\r
5904     }\r
5905     \r
5906     /*\r
5907      * If chess program startup fails, exit with an error message.\r
5908      * Attempts to recover here are futile.\r
5909      */\r
5910     if ((StrStr(message, "unknown host") != NULL)\r
5911         || (StrStr(message, "No remote directory") != NULL)\r
5912         || (StrStr(message, "not found") != NULL)\r
5913         || (StrStr(message, "No such file") != NULL)\r
5914         || (StrStr(message, "can't alloc") != NULL)\r
5915         || (StrStr(message, "Permission denied") != NULL)) {\r
5916 \r
5917         cps->maybeThinking = FALSE;\r
5918         sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n",\r
5919                 cps->which, cps->program, cps->host, message);\r
5920         RemoveInputSource(cps->isr);\r
5921         DisplayFatalError(buf1, 0, 1);\r
5922         return;\r
5923     }\r
5924     \r
5925     /* \r
5926      * Look for hint output\r
5927      */\r
5928     if (sscanf(message, "Hint: %s", buf1) == 1) {\r
5929         if (cps == &first && hintRequested) {\r
5930             hintRequested = FALSE;\r
5931             if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
5932                                  &fromX, &fromY, &toX, &toY, &promoChar)) {\r
5933                 (void) CoordsToAlgebraic(boards[forwardMostMove],\r
5934                                     PosFlags(forwardMostMove), EP_UNKNOWN,\r
5935                                     fromY, fromX, toY, toX, promoChar, buf1);\r
5936                 sprintf(buf2, "Hint: %s", buf1);\r
5937                 DisplayInformation(buf2);\r
5938             } else {\r
5939                 /* Hint move could not be parsed!? */\r
5940                 sprintf(buf2,\r
5941                         "Illegal hint move \"%s\"\nfrom %s chess program",\r
5942                         buf1, cps->which);\r
5943                 DisplayError(buf2, 0);\r
5944             }\r
5945         } else {\r
5946             strcpy(lastHint, buf1);\r
5947         }\r
5948         return;\r
5949     }\r
5950 \r
5951     /*\r
5952      * Ignore other messages if game is not in progress\r
5953      */\r
5954     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
5955         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
5956 \r
5957     /*\r
5958      * look for win, lose, draw, or draw offer\r
5959      */\r
5960     if (strncmp(message, "1-0", 3) == 0) {\r
5961         char *p, *q, *r = "";\r
5962         p = strchr(message, '{');\r
5963         if (p) {\r
5964             q = strchr(p, '}');\r
5965             if (q) {\r
5966                 *q = NULLCHAR;\r
5967                 r = p + 1;\r
5968             }\r
5969         }\r
5970         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
5971         return;\r
5972     } else if (strncmp(message, "0-1", 3) == 0) {\r
5973         char *p, *q, *r = "";\r
5974         p = strchr(message, '{');\r
5975         if (p) {\r
5976             q = strchr(p, '}');\r
5977             if (q) {\r
5978                 *q = NULLCHAR;\r
5979                 r = p + 1;\r
5980             }\r
5981         }\r
5982         /* Kludge for Arasan 4.1 bug */\r
5983         if (strcmp(r, "Black resigns") == 0) {\r
5984             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
5985             return;\r
5986         }\r
5987         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
5988         return;\r
5989     } else if (strncmp(message, "1/2", 3) == 0) {\r
5990         char *p, *q, *r = "";\r
5991         p = strchr(message, '{');\r
5992         if (p) {\r
5993             q = strchr(p, '}');\r
5994             if (q) {\r
5995                 *q = NULLCHAR;\r
5996                 r = p + 1;\r
5997             }\r
5998         }\r
5999             \r
6000         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
6001         return;\r
6002 \r
6003     } else if (strncmp(message, "White resign", 12) == 0) {\r
6004         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6005         return;\r
6006     } else if (strncmp(message, "Black resign", 12) == 0) {\r
6007         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6008         return;\r
6009     } else if (strncmp(message, "White matches", 13) == 0 ||\r
6010                strncmp(message, "Black matches", 13) == 0   ) {\r
6011         /* [HGM] ignore GNUShogi noises */\r
6012         return;\r
6013     } else if (strncmp(message, "White", 5) == 0 &&\r
6014                message[5] != '(' &&\r
6015                StrStr(message, "Black") == NULL) {\r
6016         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6017         return;\r
6018     } else if (strncmp(message, "Black", 5) == 0 &&\r
6019                message[5] != '(') {\r
6020         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6021         return;\r
6022     } else if (strcmp(message, "resign") == 0 ||\r
6023                strcmp(message, "computer resigns") == 0) {\r
6024         switch (gameMode) {\r
6025           case MachinePlaysBlack:\r
6026           case IcsPlayingBlack:\r
6027             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
6028             break;\r
6029           case MachinePlaysWhite:\r
6030           case IcsPlayingWhite:\r
6031             GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
6032             break;\r
6033           case TwoMachinesPlay:\r
6034             if (cps->twoMachinesColor[0] == 'w')\r
6035               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
6036             else\r
6037               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
6038             break;\r
6039           default:\r
6040             /* can't happen */\r
6041             break;\r
6042         }\r
6043         return;\r
6044     } else if (strncmp(message, "opponent mates", 14) == 0) {\r
6045         switch (gameMode) {\r
6046           case MachinePlaysBlack:\r
6047           case IcsPlayingBlack:\r
6048             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6049             break;\r
6050           case MachinePlaysWhite:\r
6051           case IcsPlayingWhite:\r
6052             GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
6053             break;\r
6054           case TwoMachinesPlay:\r
6055             if (cps->twoMachinesColor[0] == 'w')\r
6056               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6057             else\r
6058               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6059             break;\r
6060           default:\r
6061             /* can't happen */\r
6062             break;\r
6063         }\r
6064         return;\r
6065     } else if (strncmp(message, "computer mates", 14) == 0) {\r
6066         switch (gameMode) {\r
6067           case MachinePlaysBlack:\r
6068           case IcsPlayingBlack:\r
6069             GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
6070             break;\r
6071           case MachinePlaysWhite:\r
6072           case IcsPlayingWhite:\r
6073             GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
6074             break;\r
6075           case TwoMachinesPlay:\r
6076             if (cps->twoMachinesColor[0] == 'w')\r
6077               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6078             else\r
6079               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6080             break;\r
6081           default:\r
6082             /* can't happen */\r
6083             break;\r
6084         }\r
6085         return;\r
6086     } else if (strncmp(message, "checkmate", 9) == 0) {\r
6087         if (WhiteOnMove(forwardMostMove)) {\r
6088             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
6089         } else {\r
6090             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
6091         }\r
6092         return;\r
6093     } else if (strstr(message, "Draw") != NULL ||\r
6094                strstr(message, "game is a draw") != NULL) {\r
6095         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
6096         return;\r
6097     } else if (strstr(message, "offer") != NULL &&\r
6098                strstr(message, "draw") != NULL) {\r
6099 #if ZIPPY\r
6100         if (appData.zippyPlay && first.initDone) {\r
6101             /* Relay offer to ICS */\r
6102             SendToICS(ics_prefix);\r
6103             SendToICS("draw\n");\r
6104         }\r
6105 #endif\r
6106         cps->offeredDraw = 2; /* valid until this engine moves twice */\r
6107         if (gameMode == TwoMachinesPlay) {\r
6108             if (cps->other->offeredDraw) {\r
6109                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6110             /* [HGM] in two-machine mode we delay relaying draw offer      */\r
6111             /* until after we also have move, to see if it is really claim */\r
6112             }\r
6113 #if 0\r
6114               else {\r
6115                 if (cps->other->sendDrawOffers) {\r
6116                     SendToProgram("draw\n", cps->other);\r
6117                 }\r
6118             }\r
6119 #endif\r
6120         } else if (gameMode == MachinePlaysWhite ||\r
6121                    gameMode == MachinePlaysBlack) {\r
6122           if (userOfferedDraw) {\r
6123             DisplayInformation("Machine accepts your draw offer");\r
6124             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
6125           } else {\r
6126             DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree");\r
6127           }\r
6128         }\r
6129     }\r
6130 \r
6131     \r
6132     /*\r
6133      * Look for thinking output\r
6134      */\r
6135     if ( appData.showThinking) {\r
6136         int plylev, mvleft, mvtot, curscore, time;\r
6137         char mvname[MOVE_LEN];\r
6138         unsigned long nodes;\r
6139         char plyext;\r
6140         int ignore = FALSE;\r
6141         int prefixHint = FALSE;\r
6142         mvname[0] = NULLCHAR;\r
6143 \r
6144         switch (gameMode) {\r
6145           case MachinePlaysBlack:\r
6146           case IcsPlayingBlack:\r
6147             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6148             break;\r
6149           case MachinePlaysWhite:\r
6150           case IcsPlayingWhite:\r
6151             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
6152             break;\r
6153           case AnalyzeMode:\r
6154           case AnalyzeFile:\r
6155             break;\r
6156           case TwoMachinesPlay:\r
6157             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
6158                 ignore = TRUE;\r
6159             }\r
6160             break;\r
6161           default:\r
6162             ignore = TRUE;\r
6163             break;\r
6164         }\r
6165 \r
6166         if (!ignore) {\r
6167             buf1[0] = NULLCHAR;\r
6168             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
6169                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
6170 \r
6171                 if (plyext != ' ' && plyext != '\t') {\r
6172                     time *= 100;\r
6173                 }\r
6174 \r
6175                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6176                 if( cps->scoreIsAbsolute && \r
6177                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
6178                 {\r
6179                     curscore = -curscore;\r
6180                 }\r
6181 \r
6182 \r
6183                 programStats.depth = plylev;\r
6184                 programStats.nodes = nodes;\r
6185                 programStats.time = time;\r
6186                 programStats.score = curscore;\r
6187                 programStats.got_only_move = 0;\r
6188 \r
6189                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
6190                         int ticklen;\r
6191 \r
6192                         if(cps->nps == 0) ticklen = 10*time;       // use engine reported time\r
6193                         else ticklen = (1000. * nodes) / cps->nps; // convert node count to time\r
6194                         if(WhiteOnMove(forwardMostMove)) \r
6195                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
6196                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
6197                 }\r
6198 \r
6199                 /* Buffer overflow protection */\r
6200                 if (buf1[0] != NULLCHAR) {\r
6201                     if (strlen(buf1) >= sizeof(programStats.movelist)\r
6202                         && appData.debugMode) {\r
6203                         fprintf(debugFP,\r
6204                                 "PV is too long; using the first %d bytes.\n",\r
6205                                 sizeof(programStats.movelist) - 1);\r
6206                     }\r
6207 \r
6208                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
6209                 } else {\r
6210                     sprintf(programStats.movelist, " no PV\n");\r
6211                 }\r
6212 \r
6213                 if (programStats.seen_stat) {\r
6214                     programStats.ok_to_send = 1;\r
6215                 }\r
6216 \r
6217                 if (strchr(programStats.movelist, '(') != NULL) {\r
6218                     programStats.line_is_book = 1;\r
6219                     programStats.nr_moves = 0;\r
6220                     programStats.moves_left = 0;\r
6221                 } else {\r
6222                     programStats.line_is_book = 0;\r
6223                 }\r
6224 \r
6225                 SendProgramStatsToFrontend( cps, &programStats );\r
6226 \r
6227                 /* \r
6228                     [AS] Protect the thinkOutput buffer from overflow... this\r
6229                     is only useful if buf1 hasn't overflowed first!\r
6230                 */\r
6231                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
6232                         plylev, \r
6233                         (gameMode == TwoMachinesPlay ?\r
6234                          ToUpper(cps->twoMachinesColor[0]) : ' '),\r
6235                         ((double) curscore) / 100.0,\r
6236                         prefixHint ? lastHint : "",\r
6237                         prefixHint ? " " : "" );\r
6238 \r
6239                 if( buf1[0] != NULLCHAR ) {\r
6240                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
6241 \r
6242                     if( strlen(buf1) > max_len ) {\r
6243                         if( appData.debugMode) {\r
6244                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
6245                         }\r
6246                         buf1[max_len+1] = '\0';\r
6247                     }\r
6248 \r
6249                     strcat( thinkOutput, buf1 );\r
6250                 }\r
6251 \r
6252                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
6253                     DisplayMove(currentMove - 1);\r
6254                     DisplayAnalysis();\r
6255                 }\r
6256                 return;\r
6257 \r
6258             } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
6259                 /* crafty (9.25+) says "(only move) <move>"\r
6260                  * if there is only 1 legal move\r
6261                  */\r
6262                 sscanf(p, "(only move) %s", buf1);\r
6263                 sprintf(thinkOutput, "%s (only move)", buf1);\r
6264                 sprintf(programStats.movelist, "%s (only move)", buf1);\r
6265                 programStats.depth = 1;\r
6266                 programStats.nr_moves = 1;\r
6267                 programStats.moves_left = 1;\r
6268                 programStats.nodes = 1;\r
6269                 programStats.time = 1;\r
6270                 programStats.got_only_move = 1;\r
6271 \r
6272                 /* Not really, but we also use this member to\r
6273                    mean "line isn't going to change" (Crafty\r
6274                    isn't searching, so stats won't change) */\r
6275                 programStats.line_is_book = 1;\r
6276 \r
6277                 SendProgramStatsToFrontend( cps, &programStats );\r
6278                 \r
6279                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
6280                     DisplayMove(currentMove - 1);\r
6281                     DisplayAnalysis();\r
6282                 }\r
6283                 return;\r
6284             } else if (sscanf(message,"stat01: %d %lu %d %d %d %s",\r
6285                               &time, &nodes, &plylev, &mvleft,\r
6286                               &mvtot, mvname) >= 5) {\r
6287                 /* The stat01: line is from Crafty (9.29+) in response\r
6288                    to the "." command */\r
6289                 programStats.seen_stat = 1;\r
6290                 cps->maybeThinking = TRUE;\r
6291 \r
6292                 if (programStats.got_only_move || !appData.periodicUpdates)\r
6293                   return;\r
6294 \r
6295                 programStats.depth = plylev;\r
6296                 programStats.time = time;\r
6297                 programStats.nodes = nodes;\r
6298                 programStats.moves_left = mvleft;\r
6299                 programStats.nr_moves = mvtot;\r
6300                 strcpy(programStats.move_name, mvname);\r
6301                 programStats.ok_to_send = 1;\r
6302                 programStats.movelist[0] = '\0';\r
6303 \r
6304                 SendProgramStatsToFrontend( cps, &programStats );\r
6305 \r
6306                 DisplayAnalysis();\r
6307                 return;\r
6308 \r
6309             } else if (strncmp(message,"++",2) == 0) {\r
6310                 /* Crafty 9.29+ outputs this */\r
6311                 programStats.got_fail = 2;\r
6312                 return;\r
6313 \r
6314             } else if (strncmp(message,"--",2) == 0) {\r
6315                 /* Crafty 9.29+ outputs this */\r
6316                 programStats.got_fail = 1;\r
6317                 return;\r
6318 \r
6319             } else if (thinkOutput[0] != NULLCHAR &&\r
6320                        strncmp(message, "    ", 4) == 0) {\r
6321                 unsigned message_len;\r
6322 \r
6323                 p = message;\r
6324                 while (*p && *p == ' ') p++;\r
6325 \r
6326                 message_len = strlen( p );\r
6327 \r
6328                 /* [AS] Avoid buffer overflow */\r
6329                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
6330                     strcat(thinkOutput, " ");\r
6331                     strcat(thinkOutput, p);\r
6332                 }\r
6333 \r
6334                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
6335                     strcat(programStats.movelist, " ");\r
6336                     strcat(programStats.movelist, p);\r
6337                 }\r
6338 \r
6339                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) {\r
6340                     DisplayMove(currentMove - 1);\r
6341                     DisplayAnalysis();\r
6342                 }\r
6343                 return;\r
6344             }\r
6345         }\r
6346         else {\r
6347             buf1[0] = NULLCHAR;\r
6348 \r
6349             if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",\r
6350                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
6351             {\r
6352                 ChessProgramStats cpstats;\r
6353 \r
6354                 if (plyext != ' ' && plyext != '\t') {\r
6355                     time *= 100;\r
6356                 }\r
6357 \r
6358                 /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
6359                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
6360                     curscore = -curscore;\r
6361                 }\r
6362 \r
6363                 cpstats.depth = plylev;\r
6364                 cpstats.nodes = nodes;\r
6365                 cpstats.time = time;\r
6366                 cpstats.score = curscore;\r
6367                 cpstats.got_only_move = 0;\r
6368                 cpstats.movelist[0] = '\0';\r
6369 \r
6370                 if (buf1[0] != NULLCHAR) {\r
6371                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
6372                 }\r
6373 \r
6374                 cpstats.ok_to_send = 0;\r
6375                 cpstats.line_is_book = 0;\r
6376                 cpstats.nr_moves = 0;\r
6377                 cpstats.moves_left = 0;\r
6378 \r
6379                 SendProgramStatsToFrontend( cps, &cpstats );\r
6380             }\r
6381         }\r
6382     }\r
6383 }\r
6384 \r
6385 \r
6386 /* Parse a game score from the character string "game", and\r
6387    record it as the history of the current game.  The game\r
6388    score is NOT assumed to start from the standard position. \r
6389    The display is not updated in any way.\r
6390    */\r
6391 void\r
6392 ParseGameHistory(game)\r
6393      char *game;\r
6394 {\r
6395     ChessMove moveType;\r
6396     int fromX, fromY, toX, toY, boardIndex;\r
6397     char promoChar;\r
6398     char *p, *q;\r
6399     char buf[MSG_SIZ];\r
6400 \r
6401     if (appData.debugMode)\r
6402       fprintf(debugFP, "Parsing game history: %s\n", game);\r
6403 \r
6404     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
6405     gameInfo.site = StrSave(appData.icsHost);\r
6406     gameInfo.date = PGNDate();\r
6407     gameInfo.round = StrSave("-");\r
6408 \r
6409     /* Parse out names of players */\r
6410     while (*game == ' ') game++;\r
6411     p = buf;\r
6412     while (*game != ' ') *p++ = *game++;\r
6413     *p = NULLCHAR;\r
6414     gameInfo.white = StrSave(buf);\r
6415     while (*game == ' ') game++;\r
6416     p = buf;\r
6417     while (*game != ' ' && *game != '\n') *p++ = *game++;\r
6418     *p = NULLCHAR;\r
6419     gameInfo.black = StrSave(buf);\r
6420 \r
6421     /* Parse moves */\r
6422     boardIndex = blackPlaysFirst ? 1 : 0;\r
6423     yynewstr(game);\r
6424     for (;;) {\r
6425         yyboardindex = boardIndex;\r
6426         moveType = (ChessMove) yylex();\r
6427         switch (moveType) {\r
6428           case IllegalMove:             /* maybe suicide chess, etc. */\r
6429   if (appData.debugMode) {\r
6430     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
6431     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6432     setbuf(debugFP, NULL);\r
6433   }\r
6434           case WhitePromotionChancellor:\r
6435           case BlackPromotionChancellor:\r
6436           case WhitePromotionArchbishop:\r
6437           case BlackPromotionArchbishop:\r
6438           case WhitePromotionQueen:\r
6439           case BlackPromotionQueen:\r
6440           case WhitePromotionRook:\r
6441           case BlackPromotionRook:\r
6442           case WhitePromotionBishop:\r
6443           case BlackPromotionBishop:\r
6444           case WhitePromotionKnight:\r
6445           case BlackPromotionKnight:\r
6446           case WhitePromotionKing:\r
6447           case BlackPromotionKing:\r
6448           case NormalMove:\r
6449           case WhiteCapturesEnPassant:\r
6450           case BlackCapturesEnPassant:\r
6451           case WhiteKingSideCastle:\r
6452           case WhiteQueenSideCastle:\r
6453           case BlackKingSideCastle:\r
6454           case BlackQueenSideCastle:\r
6455           case WhiteKingSideCastleWild:\r
6456           case WhiteQueenSideCastleWild:\r
6457           case BlackKingSideCastleWild:\r
6458           case BlackQueenSideCastleWild:\r
6459           /* PUSH Fabien */\r
6460           case WhiteHSideCastleFR:\r
6461           case WhiteASideCastleFR:\r
6462           case BlackHSideCastleFR:\r
6463           case BlackASideCastleFR:\r
6464           /* POP Fabien */\r
6465             fromX = currentMoveString[0] - AAA;\r
6466             fromY = currentMoveString[1] - ONE;\r
6467             toX = currentMoveString[2] - AAA;\r
6468             toY = currentMoveString[3] - ONE;\r
6469             promoChar = currentMoveString[4];\r
6470             break;\r
6471           case WhiteDrop:\r
6472           case BlackDrop:\r
6473             fromX = moveType == WhiteDrop ?\r
6474               (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
6475             (int) CharToPiece(ToLower(currentMoveString[0]));\r
6476             fromY = DROP_RANK;\r
6477             toX = currentMoveString[2] - AAA;\r
6478             toY = currentMoveString[3] - ONE;\r
6479             promoChar = NULLCHAR;\r
6480             break;\r
6481           case AmbiguousMove:\r
6482             /* bug? */\r
6483             sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text);\r
6484   if (appData.debugMode) {\r
6485     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
6486     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6487     setbuf(debugFP, NULL);\r
6488   }\r
6489             DisplayError(buf, 0);\r
6490             return;\r
6491           case ImpossibleMove:\r
6492             /* bug? */\r
6493             sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text);\r
6494   if (appData.debugMode) {\r
6495     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
6496     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
6497     setbuf(debugFP, NULL);\r
6498   }\r
6499             DisplayError(buf, 0);\r
6500             return;\r
6501           case (ChessMove) 0:   /* end of file */\r
6502             if (boardIndex < backwardMostMove) {\r
6503                 /* Oops, gap.  How did that happen? */\r
6504                 DisplayError("Gap in move list", 0);\r
6505                 return;\r
6506             }\r
6507             backwardMostMove =  blackPlaysFirst ? 1 : 0;\r
6508             if (boardIndex > forwardMostMove) {\r
6509                 forwardMostMove = boardIndex;\r
6510             }\r
6511             return;\r
6512           case ElapsedTime:\r
6513             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
6514                 strcat(parseList[boardIndex-1], " ");\r
6515                 strcat(parseList[boardIndex-1], yy_text);\r
6516             }\r
6517             continue;\r
6518           case Comment:\r
6519           case PGNTag:\r
6520           case NAG:\r
6521           default:\r
6522             /* ignore */\r
6523             continue;\r
6524           case WhiteWins:\r
6525           case BlackWins:\r
6526           case GameIsDrawn:\r
6527           case GameUnfinished:\r
6528             if (gameMode == IcsExamining) {\r
6529                 if (boardIndex < backwardMostMove) {\r
6530                     /* Oops, gap.  How did that happen? */\r
6531                     return;\r
6532                 }\r
6533                 backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6534                 return;\r
6535             }\r
6536             gameInfo.result = moveType;\r
6537             p = strchr(yy_text, '{');\r
6538             if (p == NULL) p = strchr(yy_text, '(');\r
6539             if (p == NULL) {\r
6540                 p = yy_text;\r
6541                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
6542             } else {\r
6543                 q = strchr(p, *p == '{' ? '}' : ')');\r
6544                 if (q != NULL) *q = NULLCHAR;\r
6545                 p++;\r
6546             }\r
6547             gameInfo.resultDetails = StrSave(p);\r
6548             continue;\r
6549         }\r
6550         if (boardIndex >= forwardMostMove &&\r
6551             !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
6552             backwardMostMove = blackPlaysFirst ? 1 : 0;\r
6553             return;\r
6554         }\r
6555         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
6556                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
6557                                  parseList[boardIndex]);\r
6558         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
6559         /* currentMoveString is set as a side-effect of yylex */\r
6560         strcpy(moveList[boardIndex], currentMoveString);\r
6561         strcat(moveList[boardIndex], "\n");\r
6562         boardIndex++;\r
6563         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);\r
6564         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
6565                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
6566           case MT_NONE:\r
6567           case MT_STALEMATE:\r
6568           default:\r
6569             break;\r
6570           case MT_CHECK:\r
6571             if(gameInfo.variant != VariantShogi)\r
6572                 strcat(parseList[boardIndex - 1], "+");\r
6573             break;\r
6574           case MT_CHECKMATE:\r
6575             strcat(parseList[boardIndex - 1], "#");\r
6576             break;\r
6577         }\r
6578     }\r
6579 }\r
6580 \r
6581 \r
6582 /* Apply a move to the given board  */\r
6583 void\r
6584 ApplyMove(fromX, fromY, toX, toY, promoChar, board)\r
6585      int fromX, fromY, toX, toY;\r
6586      int promoChar;\r
6587      Board board;\r
6588 {\r
6589   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
6590 \r
6591     /* [HGM] compute & store e.p. status and castling rights for new position */\r
6592     /* if we are updating a board for which those exist (i.e. in boards[])    */\r
6593     if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)\r
6594     { int i, j;\r
6595 \r
6596       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
6597       oldEP = epStatus[p-1];\r
6598       epStatus[p] = EP_NONE;\r
6599 \r
6600       if( board[toY][toX] != EmptySquare ) \r
6601            epStatus[p] = EP_CAPTURE;  \r
6602 \r
6603       if( board[fromY][fromX] == WhitePawn ) {\r
6604            epStatus[p] = EP_PAWN_MOVE; \r
6605            if( toY-fromY==2)\r
6606                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
6607                         gameInfo.variant != VariantBerolina || toX < fromX)\r
6608                       epStatus[p] = toX | berolina;\r
6609                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
6610                         gameInfo.variant != VariantBerolina || toX > fromX) \r
6611                       epStatus[p] = toX;\r
6612       } else \r
6613       if( board[fromY][fromX] == BlackPawn ) {\r
6614            epStatus[p] = EP_PAWN_MOVE; \r
6615            if( toY-fromY== -2)\r
6616                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
6617                         gameInfo.variant != VariantBerolina || toX < fromX)\r
6618                       epStatus[p] = toX | berolina;\r
6619                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
6620                         gameInfo.variant != VariantBerolina || toX > fromX) \r
6621                       epStatus[p] = toX;\r
6622        }\r
6623 \r
6624        for(i=0; i<nrCastlingRights; i++) {\r
6625            castlingRights[p][i] = castlingRights[p-1][i];\r
6626            if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||\r
6627               castlingRights[p][i] == toX   && castlingRank[i] == toY   \r
6628              ) castlingRights[p][i] = -1; // revoke for moved or captured piece\r
6629        }\r
6630 \r
6631     }\r
6632 \r
6633   /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
6634   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
6635        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
6636          \r
6637   if (fromX == toX && fromY == toY) return;\r
6638 \r
6639   if (fromY == DROP_RANK) {\r
6640         /* must be first */\r
6641         piece = board[toY][toX] = (ChessSquare) fromX;\r
6642   } else {\r
6643      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
6644      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
6645      if(gameInfo.variant == VariantKnightmate)\r
6646          king += (int) WhiteUnicorn - (int) WhiteKing;\r
6647 \r
6648     /* Code added by Tord: */\r
6649     /* FRC castling assumed when king captures friendly rook. */\r
6650     if (board[fromY][fromX] == WhiteKing &&\r
6651              board[toY][toX] == WhiteRook) {\r
6652       board[fromY][fromX] = EmptySquare;\r
6653       board[toY][toX] = EmptySquare;\r
6654       if(toX > fromX) {\r
6655         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
6656       } else {\r
6657         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
6658       }\r
6659     } else if (board[fromY][fromX] == BlackKing &&\r
6660                board[toY][toX] == BlackRook) {\r
6661       board[fromY][fromX] = EmptySquare;\r
6662       board[toY][toX] = EmptySquare;\r
6663       if(toX > fromX) {\r
6664         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
6665       } else {\r
6666         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
6667       }\r
6668     /* End of code added by Tord */\r
6669 \r
6670     } else if (board[fromY][fromX] == king\r
6671         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
6672         && toY == fromY && toX > fromX+1) {\r
6673         board[fromY][fromX] = EmptySquare;\r
6674         board[toY][toX] = king;\r
6675         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
6676         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
6677     } else if (board[fromY][fromX] == king\r
6678         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
6679                && toY == fromY && toX < fromX-1) {\r
6680         board[fromY][fromX] = EmptySquare;\r
6681         board[toY][toX] = king;\r
6682         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
6683         board[fromY][BOARD_LEFT] = EmptySquare;\r
6684     } else if (board[fromY][fromX] == WhitePawn\r
6685                && toY == BOARD_HEIGHT-1\r
6686                && gameInfo.variant != VariantXiangqi\r
6687                ) {\r
6688         /* white pawn promotion */\r
6689         board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
6690         if (board[toY][toX] == EmptySquare) {\r
6691             board[toY][toX] = WhiteQueen;\r
6692         }\r
6693         if(gameInfo.variant==VariantBughouse ||\r
6694            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
6695             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
6696         board[fromY][fromX] = EmptySquare;\r
6697     } else if ((fromY == BOARD_HEIGHT-4)\r
6698                && (toX != fromX)\r
6699                && gameInfo.variant != VariantXiangqi\r
6700                && gameInfo.variant != VariantBerolina\r
6701                && (board[fromY][fromX] == WhitePawn)\r
6702                && (board[toY][toX] == EmptySquare)) {\r
6703         board[fromY][fromX] = EmptySquare;\r
6704         board[toY][toX] = WhitePawn;\r
6705         captured = board[toY - 1][toX];\r
6706         board[toY - 1][toX] = EmptySquare;\r
6707     } else if ((fromY == BOARD_HEIGHT-4)\r
6708                && (toX == fromX)\r
6709                && gameInfo.variant == VariantBerolina\r
6710                && (board[fromY][fromX] == WhitePawn)\r
6711                && (board[toY][toX] == EmptySquare)) {\r
6712         board[fromY][fromX] = EmptySquare;\r
6713         board[toY][toX] = WhitePawn;\r
6714         if(oldEP & EP_BEROLIN_A) {\r
6715                 captured = board[fromY][fromX-1];\r
6716                 board[fromY][fromX-1] = EmptySquare;\r
6717         }else{  captured = board[fromY][fromX+1];\r
6718                 board[fromY][fromX+1] = EmptySquare;\r
6719         }\r
6720     } else if (board[fromY][fromX] == king\r
6721         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
6722                && toY == fromY && toX > fromX+1) {\r
6723         board[fromY][fromX] = EmptySquare;\r
6724         board[toY][toX] = king;\r
6725         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
6726         board[fromY][BOARD_RGHT-1] = EmptySquare;\r
6727     } else if (board[fromY][fromX] == king\r
6728         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
6729                && toY == fromY && toX < fromX-1) {\r
6730         board[fromY][fromX] = EmptySquare;\r
6731         board[toY][toX] = king;\r
6732         board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
6733         board[fromY][BOARD_LEFT] = EmptySquare;\r
6734     } else if (fromY == 7 && fromX == 3\r
6735                && board[fromY][fromX] == BlackKing\r
6736                && toY == 7 && toX == 5) {\r
6737         board[fromY][fromX] = EmptySquare;\r
6738         board[toY][toX] = BlackKing;\r
6739         board[fromY][7] = EmptySquare;\r
6740         board[toY][4] = BlackRook;\r
6741     } else if (fromY == 7 && fromX == 3\r
6742                && board[fromY][fromX] == BlackKing\r
6743                && toY == 7 && toX == 1) {\r
6744         board[fromY][fromX] = EmptySquare;\r
6745         board[toY][toX] = BlackKing;\r
6746         board[fromY][0] = EmptySquare;\r
6747         board[toY][2] = BlackRook;\r
6748     } else if (board[fromY][fromX] == BlackPawn\r
6749                && toY == 0\r
6750                && gameInfo.variant != VariantXiangqi\r
6751                ) {\r
6752         /* black pawn promotion */\r
6753         board[0][toX] = CharToPiece(ToLower(promoChar));\r
6754         if (board[0][toX] == EmptySquare) {\r
6755             board[0][toX] = BlackQueen;\r
6756         }\r
6757         if(gameInfo.variant==VariantBughouse ||\r
6758            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
6759             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
6760         board[fromY][fromX] = EmptySquare;\r
6761     } else if ((fromY == 3)\r
6762                && (toX != fromX)\r
6763                && gameInfo.variant != VariantXiangqi\r
6764                && gameInfo.variant != VariantBerolina\r
6765                && (board[fromY][fromX] == BlackPawn)\r
6766                && (board[toY][toX] == EmptySquare)) {\r
6767         board[fromY][fromX] = EmptySquare;\r
6768         board[toY][toX] = BlackPawn;\r
6769         captured = board[toY + 1][toX];\r
6770         board[toY + 1][toX] = EmptySquare;\r
6771     } else if ((fromY == 3)\r
6772                && (toX == fromX)\r
6773                && gameInfo.variant == VariantBerolina\r
6774                && (board[fromY][fromX] == BlackPawn)\r
6775                && (board[toY][toX] == EmptySquare)) {\r
6776         board[fromY][fromX] = EmptySquare;\r
6777         board[toY][toX] = BlackPawn;\r
6778         if(oldEP & EP_BEROLIN_A) {\r
6779                 captured = board[fromY][fromX-1];\r
6780                 board[fromY][fromX-1] = EmptySquare;\r
6781         }else{  captured = board[fromY][fromX+1];\r
6782                 board[fromY][fromX+1] = EmptySquare;\r
6783         }\r
6784     } else {\r
6785         board[toY][toX] = board[fromY][fromX];\r
6786         board[fromY][fromX] = EmptySquare;\r
6787     }\r
6788 \r
6789     /* [HGM] now we promote for Shogi, if needed */\r
6790     if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
6791         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
6792   }\r
6793 \r
6794     if (gameInfo.holdingsWidth != 0) {\r
6795 \r
6796       /* !!A lot more code needs to be written to support holdings  */\r
6797       /* [HGM] OK, so I have written it. Holdings are stored in the */\r
6798       /* penultimate board files, so they are automaticlly stored   */\r
6799       /* in the game history.                                       */\r
6800       if (fromY == DROP_RANK) {\r
6801         /* Delete from holdings, by decreasing count */\r
6802         /* and erasing image if necessary            */\r
6803         p = (int) fromX;\r
6804         if(p < (int) BlackPawn) { /* white drop */\r
6805              p -= (int)WhitePawn;\r
6806              if(p >= gameInfo.holdingsSize) p = 0;\r
6807              if(--board[p][BOARD_WIDTH-2] == 0)\r
6808                   board[p][BOARD_WIDTH-1] = EmptySquare;\r
6809         } else {                  /* black drop */\r
6810              p -= (int)BlackPawn;\r
6811              if(p >= gameInfo.holdingsSize) p = 0;\r
6812              if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
6813                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
6814         }\r
6815       }\r
6816       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
6817           && gameInfo.variant != VariantBughouse        ) {\r
6818         /* Add to holdings, if holdings exist */\r
6819         p = (int) captured;\r
6820         if (p >= (int) BlackPawn) {\r
6821           p -= (int)BlackPawn;\r
6822           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
6823                   /* in Shogi restore piece to its original  first */\r
6824                   captured = (ChessSquare) (DEMOTED captured);\r
6825                   p = DEMOTED p;\r
6826           }\r
6827           p = PieceToNumber((ChessSquare)p);\r
6828           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
6829           board[p][BOARD_WIDTH-2]++;\r
6830           board[p][BOARD_WIDTH-1] =\r
6831                                    BLACK_TO_WHITE captured;\r
6832         } else {\r
6833           p -= (int)WhitePawn;\r
6834           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
6835                   captured = (ChessSquare) (DEMOTED captured);\r
6836                   p = DEMOTED p;\r
6837           }\r
6838           p = PieceToNumber((ChessSquare)p);\r
6839           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
6840           board[BOARD_HEIGHT-1-p][1]++;\r
6841           board[BOARD_HEIGHT-1-p][0] =\r
6842                                   WHITE_TO_BLACK captured;\r
6843         }\r
6844       }\r
6845 \r
6846     } else if (gameInfo.variant == VariantAtomic) {\r
6847       if (captured != EmptySquare) {\r
6848         int y, x;\r
6849         for (y = toY-1; y <= toY+1; y++) {\r
6850           for (x = toX-1; x <= toX+1; x++) {\r
6851             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
6852                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
6853               board[y][x] = EmptySquare;\r
6854             }\r
6855           }\r
6856         }\r
6857         board[toY][toX] = EmptySquare;\r
6858       }\r
6859     }\r
6860     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
6861         /* [HGM] Shogi promotions */\r
6862         board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
6863     }\r
6864 \r
6865 }\r
6866 \r
6867 /* Updates forwardMostMove */\r
6868 void\r
6869 MakeMove(fromX, fromY, toX, toY, promoChar)\r
6870      int fromX, fromY, toX, toY;\r
6871      int promoChar;\r
6872 {\r
6873     forwardMostMove++;\r
6874 \r
6875     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting */\r
6876         int timeLeft; static int lastLoadFlag=0; int king, piece;\r
6877         piece = boards[forwardMostMove-1][fromY][fromX];\r
6878         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
6879         if(gameInfo.variant == VariantKnightmate)\r
6880             king += (int) WhiteUnicorn - (int) WhiteKing;\r
6881         if(forwardMostMove == 1) {\r
6882             if(blackPlaysFirst) \r
6883                 fprintf(serverMoves, "%s;", second.tidy);\r
6884             fprintf(serverMoves, "%s;", first.tidy);\r
6885             if(!blackPlaysFirst) \r
6886                 fprintf(serverMoves, "%s;", second.tidy);\r
6887         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
6888         lastLoadFlag = loadFlag;\r
6889         // print base move\r
6890         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
6891         // print castling suffix\r
6892         if( toY == fromY && piece == king ) {\r
6893             if(toX-fromX > 1)\r
6894                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
6895             if(fromX-toX >1)\r
6896                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
6897         }\r
6898         // e.p. suffix\r
6899         if( (boards[forwardMostMove-1][fromY][fromX] == WhitePawn ||\r
6900              boards[forwardMostMove-1][fromY][fromX] == BlackPawn   ) &&\r
6901              boards[forwardMostMove-1][toY][toX] == EmptySquare\r
6902              && fromX != toX )\r
6903                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
6904         // promotion suffix\r
6905         if(promoChar != NULLCHAR)\r
6906                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
6907         if(!loadFlag) {\r
6908             fprintf(serverMoves, "/%d/%d",\r
6909                pvInfoList[forwardMostMove-1].depth, pvInfoList[forwardMostMove-1].score);\r
6910             if(forwardMostMove & 1) timeLeft = whiteTimeRemaining/1000;\r
6911             else                    timeLeft = blackTimeRemaining/1000;\r
6912             fprintf(serverMoves, "/%d", timeLeft);\r
6913         }\r
6914         fflush(serverMoves);\r
6915     }\r
6916 \r
6917     if (forwardMostMove >= MAX_MOVES) {\r
6918       DisplayFatalError("Game too long; increase MAX_MOVES and recompile",\r
6919                         0, 1);\r
6920       return;\r
6921     }\r
6922     SwitchClocks();\r
6923     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
6924     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
6925     if (commentList[forwardMostMove] != NULL) {\r
6926         free(commentList[forwardMostMove]);\r
6927         commentList[forwardMostMove] = NULL;\r
6928     }\r
6929     CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);\r
6930     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);\r
6931     gameInfo.result = GameUnfinished;\r
6932     if (gameInfo.resultDetails != NULL) {\r
6933         free(gameInfo.resultDetails);\r
6934         gameInfo.resultDetails = NULL;\r
6935     }\r
6936     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
6937                               moveList[forwardMostMove - 1]);\r
6938     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
6939                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
6940                              fromY, fromX, toY, toX, promoChar,\r
6941                              parseList[forwardMostMove - 1]);\r
6942     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
6943                        epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
6944                             castlingRights[forwardMostMove]) ) {\r
6945       case MT_NONE:\r
6946       case MT_STALEMATE:\r
6947       default:\r
6948         break;\r
6949       case MT_CHECK:\r
6950         if(gameInfo.variant != VariantShogi)\r
6951             strcat(parseList[forwardMostMove - 1], "+");\r
6952         break;\r
6953       case MT_CHECKMATE:\r
6954         strcat(parseList[forwardMostMove - 1], "#");\r
6955         break;\r
6956     }\r
6957     if (appData.debugMode) {\r
6958         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
6959     }\r
6960 \r
6961 }\r
6962 \r
6963 /* Updates currentMove if not pausing */\r
6964 void\r
6965 ShowMove(fromX, fromY, toX, toY)\r
6966 {\r
6967     int instant = (gameMode == PlayFromGameFile) ?\r
6968         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
6969     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
6970         if (!instant) {\r
6971             if (forwardMostMove == currentMove + 1) {\r
6972                 AnimateMove(boards[forwardMostMove - 1],\r
6973                             fromX, fromY, toX, toY);\r
6974             }\r
6975             if (appData.highlightLastMove) {\r
6976                 SetHighlights(fromX, fromY, toX, toY);\r
6977             }\r
6978         }\r
6979         currentMove = forwardMostMove;\r
6980     }\r
6981 \r
6982     if (instant) return;\r
6983 \r
6984     DisplayMove(currentMove - 1);\r
6985     DrawPosition(FALSE, boards[currentMove]);\r
6986     DisplayBothClocks();\r
6987     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
6988 }\r
6989 \r
6990 \r
6991 void\r
6992 InitChessProgram(cps, setup)\r
6993      ChessProgramState *cps;\r
6994      int setup; /* [HGM] needed to setup FRC opening position */\r
6995 {\r
6996     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
6997     if (appData.noChessProgram) return;\r
6998     hintRequested = FALSE;\r
6999     bookRequested = FALSE;\r
7000     SendToProgram(cps->initString, cps);\r
7001     if (gameInfo.variant != VariantNormal &&\r
7002         gameInfo.variant != VariantLoadable\r
7003         /* [HGM] also send variant if board size non-standard */\r
7004         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
7005                                             ) {\r
7006       char *v = VariantName(gameInfo.variant);\r
7007       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
7008         /* [HGM] in protocol 1 we have to assume all variants valid */\r
7009         sprintf(buf, "Variant %s not supported by %s", v, cps->tidy);\r
7010         DisplayFatalError(buf, 0, 1);\r
7011         return;\r
7012       }\r
7013 \r
7014       /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
7015       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7016       if( gameInfo.variant == VariantXiangqi )\r
7017            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
7018       if( gameInfo.variant == VariantShogi )\r
7019            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
7020       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
7021            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
7022       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
7023                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )\r
7024            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7025       if( gameInfo.variant == VariantCourier )\r
7026            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
7027 \r
7028       if(overruled) {\r
7029            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
7030                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
7031            /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
7032            if(StrStr(cps->variants, b) == NULL) { \r
7033                // specific sized variant not known, check if general sizing allowed\r
7034                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
7035                    if(StrStr(cps->variants, "boardsize") == NULL) {\r
7036                        sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
7037                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
7038                        DisplayFatalError(buf, 0, 1);\r
7039                        return;\r
7040                    }\r
7041                    /* [HGM] here we really should compare with the maximum supported board size */\r
7042                }\r
7043            }\r
7044       } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
7045       sprintf(buf, "variant %s\n", b);\r
7046       SendToProgram(buf, cps);\r
7047     }\r
7048     currentlyInitializedVariant = gameInfo.variant;\r
7049 \r
7050     /* [HGM] send opening position in FRC to first engine */\r
7051     if(setup) {\r
7052           SendToProgram("force\n", cps);\r
7053           SendBoard(cps, 0);\r
7054           /* engine is now in force mode! Set flag to wake it up after first move. */\r
7055           setboardSpoiledMachineBlack = 1;\r
7056     }\r
7057 \r
7058     if (cps->sendICS) {\r
7059       sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
7060       SendToProgram(buf, cps);\r
7061     }\r
7062     cps->maybeThinking = FALSE;\r
7063     cps->offeredDraw = 0;\r
7064     if (!appData.icsActive) {\r
7065         SendTimeControl(cps, movesPerSession, timeControl,\r
7066                         timeIncrement, appData.searchDepth,\r
7067                         searchTime);\r
7068     }\r
7069     if (appData.showThinking) {\r
7070         SendToProgram("post\n", cps);\r
7071     }\r
7072     SendToProgram("hard\n", cps);\r
7073     if (!appData.ponderNextMove) {\r
7074         /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
7075            it without being sure what state we are in first.  "hard"\r
7076            is not a toggle, so that one is OK.\r
7077          */\r
7078         SendToProgram("easy\n", cps);\r
7079     }\r
7080     if (cps->usePing) {\r
7081       sprintf(buf, "ping %d\n", ++cps->lastPing);\r
7082       SendToProgram(buf, cps);\r
7083     }\r
7084     cps->initDone = TRUE;\r
7085 }   \r
7086 \r
7087 \r
7088 void\r
7089 StartChessProgram(cps)\r
7090      ChessProgramState *cps;\r
7091 {\r
7092     char buf[MSG_SIZ];\r
7093     int err;\r
7094 \r
7095     if (appData.noChessProgram) return;\r
7096     cps->initDone = FALSE;\r
7097 \r
7098     if (strcmp(cps->host, "localhost") == 0) {\r
7099         err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
7100     } else if (*appData.remoteShell == NULLCHAR) {\r
7101         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
7102     } else {\r
7103         if (*appData.remoteUser == NULLCHAR) {\r
7104             sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,\r
7105                     cps->program);\r
7106         } else {\r
7107             sprintf(buf, "%s %s -l %s %s", appData.remoteShell,\r
7108                     cps->host, appData.remoteUser, cps->program);\r
7109         }\r
7110         err = StartChildProcess(buf, "", &cps->pr);\r
7111     }\r
7112     \r
7113     if (err != 0) {\r
7114         sprintf(buf, "Startup failure on '%s'", cps->program);\r
7115         DisplayFatalError(buf, err, 1);\r
7116         cps->pr = NoProc;\r
7117         cps->isr = NULL;\r
7118         return;\r
7119     }\r
7120     \r
7121     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
7122     if (cps->protocolVersion > 1) {\r
7123       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
7124       SendToProgram(buf, cps);\r
7125     } else {\r
7126       SendToProgram("xboard\n", cps);\r
7127     }\r
7128 }\r
7129 \r
7130 \r
7131 void\r
7132 TwoMachinesEventIfReady P((void))\r
7133 {\r
7134   if (first.lastPing != first.lastPong) {\r
7135     DisplayMessage("", "Waiting for first chess program");\r
7136     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
7137     return;\r
7138   }\r
7139   if (second.lastPing != second.lastPong) {\r
7140     DisplayMessage("", "Waiting for second chess program");\r
7141     ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000);\r
7142     return;\r
7143   }\r
7144   ThawUI();\r
7145   TwoMachinesEvent();\r
7146 }\r
7147 \r
7148 void\r
7149 NextMatchGame P((void))\r
7150 {\r
7151     Reset(FALSE, TRUE);\r
7152     if (*appData.loadGameFile != NULLCHAR) {\r
7153         LoadGameFromFile(appData.loadGameFile,\r
7154                          appData.loadGameIndex,\r
7155                          appData.loadGameFile, FALSE);\r
7156     } else if (*appData.loadPositionFile != NULLCHAR) {\r
7157         LoadPositionFromFile(appData.loadPositionFile,\r
7158                              appData.loadPositionIndex,\r
7159                              appData.loadPositionFile);\r
7160     }\r
7161     TwoMachinesEventIfReady();\r
7162 }\r
7163 \r
7164 void UserAdjudicationEvent( int result )\r
7165 {\r
7166     ChessMove gameResult = GameIsDrawn;\r
7167 \r
7168     if( result > 0 ) {\r
7169         gameResult = WhiteWins;\r
7170     }\r
7171     else if( result < 0 ) {\r
7172         gameResult = BlackWins;\r
7173     }\r
7174 \r
7175     if( gameMode == TwoMachinesPlay ) {\r
7176         GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
7177     }\r
7178 }\r
7179 \r
7180 \r
7181 void\r
7182 GameEnds(result, resultDetails, whosays)\r
7183      ChessMove result;\r
7184      char *resultDetails;\r
7185      int whosays;\r
7186 {\r
7187     GameMode nextGameMode;\r
7188     int isIcsGame;\r
7189     char buf[MSG_SIZ];\r
7190 \r
7191     if(endingGame) return; /* [HGM] crash: forbid recursion */\r
7192     endingGame = 1;\r
7193 \r
7194     if (appData.debugMode) {\r
7195       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
7196               result, resultDetails ? resultDetails : "(null)", whosays);\r
7197     }\r
7198 \r
7199     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
7200         /* If we are playing on ICS, the server decides when the\r
7201            game is over, but the engine can offer to draw, claim \r
7202            a draw, or resign. \r
7203          */\r
7204 #if ZIPPY\r
7205         if (appData.zippyPlay && first.initDone) {\r
7206             if (result == GameIsDrawn) {\r
7207                 /* In case draw still needs to be claimed */\r
7208                 SendToICS(ics_prefix);\r
7209                 SendToICS("draw\n");\r
7210             } else if (StrCaseStr(resultDetails, "resign")) {\r
7211                 SendToICS(ics_prefix);\r
7212                 SendToICS("resign\n");\r
7213             }\r
7214         }\r
7215 #endif\r
7216         endingGame = 0; /* [HGM] crash */\r
7217         return;\r
7218     }\r
7219 \r
7220     /* If we're loading the game from a file, stop */\r
7221     if (whosays == GE_FILE) {\r
7222       (void) StopLoadGameTimer();\r
7223       gameFileFP = NULL;\r
7224     }\r
7225 \r
7226     /* Cancel draw offers */\r
7227     first.offeredDraw = second.offeredDraw = 0;\r
7228 \r
7229     /* If this is an ICS game, only ICS can really say it's done;\r
7230        if not, anyone can. */\r
7231     isIcsGame = (gameMode == IcsPlayingWhite || \r
7232                  gameMode == IcsPlayingBlack || \r
7233                  gameMode == IcsObserving    || \r
7234                  gameMode == IcsExamining);\r
7235 \r
7236     if (!isIcsGame || whosays == GE_ICS) {\r
7237         /* OK -- not an ICS game, or ICS said it was done */\r
7238         StopClocks();\r
7239     if (appData.debugMode) {\r
7240       fprintf(debugFP, "GameEnds(%d, %s, %d) clock stopped\n",\r
7241               result, resultDetails ? resultDetails : "(null)", whosays);\r
7242     }\r
7243         if (!isIcsGame && !appData.noChessProgram) \r
7244           SetUserThinkingEnables();\r
7245     \r
7246         /* [HGM] if a machine claims the game end we verify this claim */\r
7247         if( appData.testLegality && gameMode == TwoMachinesPlay &&\r
7248             appData.testClaims && whosays >= GE_ENGINE1 ) {\r
7249                 char claimer;\r
7250 \r
7251     if (appData.debugMode) {\r
7252       fprintf(debugFP, "GameEnds(%d, %s, %d) test claims\n",\r
7253               result, resultDetails ? resultDetails : "(null)", whosays);\r
7254     }\r
7255                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
7256                                             first.twoMachinesColor[0] :\r
7257                                             second.twoMachinesColor[0] ;\r
7258                 if( gameInfo.holdingsWidth == 0 &&\r
7259                     (result == WhiteWins && claimer == 'w' ||\r
7260                      result == BlackWins && claimer == 'b'   ) ) {\r
7261                       /* Xboard immediately adjudicates all mates, so win claims must be false */\r
7262                       sprintf(buf, "False win claim: '%s'", resultDetails);\r
7263                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7264                       resultDetails = buf;\r
7265                 } else\r
7266                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
7267                     && (forwardMostMove <= backwardMostMove ||\r
7268                         epStatus[forwardMostMove-1] > EP_DRAWS ||\r
7269                         (claimer=='b')==(forwardMostMove&1))\r
7270                                                                                   ) {\r
7271                       /* Draw that was not flagged by Xboard is false */\r
7272                       sprintf(buf, "False draw claim: '%s'", resultDetails);\r
7273                       result = claimer == 'w' ? BlackWins : WhiteWins;\r
7274                       resultDetails = buf;\r
7275                 }\r
7276                 /* (Claiming a loss is accepted no questions asked!) */\r
7277         }\r
7278 \r
7279         if(serverMoves != NULL && !loadFlag) { char c = '=';\r
7280             if(result==WhiteWins) c = '+';\r
7281             if(result==BlackWins) c = '-';\r
7282             if(resultDetails != NULL)\r
7283                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
7284         }\r
7285     if (appData.debugMode) {\r
7286       fprintf(debugFP, "GameEnds(%d, %s, %d) after test\n",\r
7287               result, resultDetails ? resultDetails : "(null)", whosays);\r
7288     }\r
7289         if (resultDetails != NULL) {\r
7290             gameInfo.result = result;\r
7291             gameInfo.resultDetails = StrSave(resultDetails);\r
7292 \r
7293             /* display last move only if game was not loaded from file */\r
7294             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
7295                 DisplayMove(currentMove - 1);\r
7296     \r
7297             if (forwardMostMove != 0) {\r
7298                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
7299                     if (*appData.saveGameFile != NULLCHAR) {\r
7300                         SaveGameToFile(appData.saveGameFile, TRUE);\r
7301                     } else if (appData.autoSaveGames) {\r
7302                         AutoSaveGame();\r
7303                     }\r
7304                     if (*appData.savePositionFile != NULLCHAR) {\r
7305                         SavePositionToFile(appData.savePositionFile);\r
7306                     }\r
7307                 }\r
7308             }\r
7309 \r
7310             /* Tell program how game ended in case it is learning */\r
7311             /* [HGM] Moved this to after saving the PGN, just in case */\r
7312             /* engine died and we got here through time loss. In that */\r
7313             /* case we will get a fatal error writing the pipe, which */\r
7314             /* would otherwise lose us the PGN.                       */\r
7315             /* [HGM] crash: not needed anymore, but doesn't hurt;     */\r
7316             /* output during GameEnds should never be fatal anymore   */\r
7317             if (gameMode == MachinePlaysWhite ||\r
7318                 gameMode == MachinePlaysBlack ||\r
7319                 gameMode == TwoMachinesPlay ||\r
7320                 gameMode == IcsPlayingWhite ||\r
7321                 gameMode == IcsPlayingBlack ||\r
7322                 gameMode == BeginningOfGame) {\r
7323                 char buf[MSG_SIZ];\r
7324                 sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
7325                         resultDetails);\r
7326                 if (first.pr != NoProc) {\r
7327                     SendToProgram(buf, &first);\r
7328                 }\r
7329                 if (second.pr != NoProc &&\r
7330                     gameMode == TwoMachinesPlay) {\r
7331                     SendToProgram(buf, &second);\r
7332                 }\r
7333             }\r
7334         }\r
7335 \r
7336         if (appData.icsActive) {\r
7337             if (appData.quietPlay &&\r
7338                 (gameMode == IcsPlayingWhite ||\r
7339                  gameMode == IcsPlayingBlack)) {\r
7340                 SendToICS(ics_prefix);\r
7341                 SendToICS("set shout 1\n");\r
7342             }\r
7343             nextGameMode = IcsIdle;\r
7344             ics_user_moved = FALSE;\r
7345             /* clean up premove.  It's ugly when the game has ended and the\r
7346              * premove highlights are still on the board.\r
7347              */\r
7348             if (gotPremove) {\r
7349               gotPremove = FALSE;\r
7350               ClearPremoveHighlights();\r
7351               DrawPosition(FALSE, boards[currentMove]);\r
7352             }\r
7353             if (whosays == GE_ICS) {\r
7354                 switch (result) {\r
7355                 case WhiteWins:\r
7356                     if (gameMode == IcsPlayingWhite)\r
7357                         PlayIcsWinSound();\r
7358                     else if(gameMode == IcsPlayingBlack)\r
7359                         PlayIcsLossSound();\r
7360                     break;\r
7361                 case BlackWins:\r
7362                     if (gameMode == IcsPlayingBlack)\r
7363                         PlayIcsWinSound();\r
7364                     else if(gameMode == IcsPlayingWhite)\r
7365                         PlayIcsLossSound();\r
7366                     break;\r
7367                 case GameIsDrawn:\r
7368                     PlayIcsDrawSound();\r
7369                     break;\r
7370                 default:\r
7371                     PlayIcsUnfinishedSound();\r
7372                 }\r
7373             }\r
7374         } else if (gameMode == EditGame ||\r
7375                    gameMode == PlayFromGameFile || \r
7376                    gameMode == AnalyzeMode || \r
7377                    gameMode == AnalyzeFile) {\r
7378             nextGameMode = gameMode;\r
7379         } else {\r
7380             nextGameMode = EndOfGame;\r
7381         }\r
7382         pausing = FALSE;\r
7383         ModeHighlight();\r
7384     } else {\r
7385         nextGameMode = gameMode;\r
7386     }\r
7387 \r
7388     if (appData.noChessProgram) {\r
7389         gameMode = nextGameMode;\r
7390         ModeHighlight();\r
7391         endingGame = 0; /* [HGM] crash */\r
7392         return;\r
7393     }\r
7394 \r
7395     if (first.reuse) {\r
7396         /* Put first chess program into idle state */\r
7397         if (first.pr != NoProc &&\r
7398             (gameMode == MachinePlaysWhite ||\r
7399              gameMode == MachinePlaysBlack ||\r
7400              gameMode == TwoMachinesPlay ||\r
7401              gameMode == IcsPlayingWhite ||\r
7402              gameMode == IcsPlayingBlack ||\r
7403              gameMode == BeginningOfGame)) {\r
7404             SendToProgram("force\n", &first);\r
7405             if (first.usePing) {\r
7406               char buf[MSG_SIZ];\r
7407               sprintf(buf, "ping %d\n", ++first.lastPing);\r
7408               SendToProgram(buf, &first);\r
7409             }\r
7410         }\r
7411     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7412         /* Kill off first chess program */\r
7413         if (first.isr != NULL)\r
7414           RemoveInputSource(first.isr);\r
7415         first.isr = NULL;\r
7416     \r
7417         if (first.pr != NoProc) {\r
7418             ExitAnalyzeMode();\r
7419             DoSleep( appData.delayBeforeQuit );\r
7420             SendToProgram("quit\n", &first);\r
7421             DoSleep( appData.delayAfterQuit );\r
7422             DestroyChildProcess(first.pr, first.useSigterm);\r
7423         }\r
7424         first.pr = NoProc;\r
7425     }\r
7426     if (second.reuse) {\r
7427         /* Put second chess program into idle state */\r
7428         if (second.pr != NoProc &&\r
7429             gameMode == TwoMachinesPlay) {\r
7430             SendToProgram("force\n", &second);\r
7431             if (second.usePing) {\r
7432               char buf[MSG_SIZ];\r
7433               sprintf(buf, "ping %d\n", ++second.lastPing);\r
7434               SendToProgram(buf, &second);\r
7435             }\r
7436         }\r
7437     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
7438         /* Kill off second chess program */\r
7439         if (second.isr != NULL)\r
7440           RemoveInputSource(second.isr);\r
7441         second.isr = NULL;\r
7442     \r
7443         if (second.pr != NoProc) {\r
7444             DoSleep( appData.delayBeforeQuit );\r
7445             SendToProgram("quit\n", &second);\r
7446             DoSleep( appData.delayAfterQuit );\r
7447             DestroyChildProcess(second.pr, second.useSigterm);\r
7448         }\r
7449         second.pr = NoProc;\r
7450     }\r
7451 \r
7452     if (matchMode && gameMode == TwoMachinesPlay) {\r
7453         switch (result) {\r
7454         case WhiteWins:\r
7455           if (first.twoMachinesColor[0] == 'w') {\r
7456             first.matchWins++;\r
7457           } else {\r
7458             second.matchWins++;\r
7459           }\r
7460           break;\r
7461         case BlackWins:\r
7462           if (first.twoMachinesColor[0] == 'b') {\r
7463             first.matchWins++;\r
7464           } else {\r
7465             second.matchWins++;\r
7466           }\r
7467           break;\r
7468         default:\r
7469           break;\r
7470         }\r
7471         if (matchGame < appData.matchGames) {\r
7472             char *tmp;\r
7473             tmp = first.twoMachinesColor;\r
7474             first.twoMachinesColor = second.twoMachinesColor;\r
7475             second.twoMachinesColor = tmp;\r
7476             gameMode = nextGameMode;\r
7477             matchGame++;\r
7478             if(appData.matchPause>10000 || appData.matchPause<10)\r
7479                 appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
7480             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
7481             endingGame = 0; /* [HGM] crash */\r
7482             return;\r
7483         } else {\r
7484             char buf[MSG_SIZ];\r
7485             gameMode = nextGameMode;\r
7486             sprintf(buf, "Match %s vs. %s: final score %d-%d-%d",\r
7487                     first.tidy, second.tidy,\r
7488                     first.matchWins, second.matchWins,\r
7489                     appData.matchGames - (first.matchWins + second.matchWins));\r
7490             DisplayFatalError(buf, 0, 0);\r
7491         }\r
7492     }\r
7493     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
7494         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
7495       ExitAnalyzeMode();\r
7496     gameMode = nextGameMode;\r
7497     ModeHighlight();\r
7498     endingGame = 0;  /* [HGM] crash */\r
7499 }\r
7500 \r
7501 /* Assumes program was just initialized (initString sent).\r
7502    Leaves program in force mode. */\r
7503 void\r
7504 FeedMovesToProgram(cps, upto) \r
7505      ChessProgramState *cps;\r
7506      int upto;\r
7507 {\r
7508     int i;\r
7509     \r
7510     if (appData.debugMode)\r
7511       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
7512               startedFromSetupPosition ? "position and " : "",\r
7513               backwardMostMove, upto, cps->which);\r
7514     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
7515         // [HGM] variantswitch: make engine aware of new variant\r
7516         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
7517                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
7518         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
7519         SendToProgram(buf, cps);\r
7520         currentlyInitializedVariant = gameInfo.variant;\r
7521     }\r
7522     SendToProgram("force\n", cps);\r
7523     if (startedFromSetupPosition) {\r
7524         SendBoard(cps, backwardMostMove);\r
7525     if (appData.debugMode) {\r
7526         fprintf(debugFP, "feedMoves\n");\r
7527     }\r
7528     }\r
7529     for (i = backwardMostMove; i < upto; i++) {\r
7530         SendMoveToProgram(i, cps);\r
7531     }\r
7532 }\r
7533 \r
7534 \r
7535 void\r
7536 ResurrectChessProgram()\r
7537 {\r
7538      /* The chess program may have exited.\r
7539         If so, restart it and feed it all the moves made so far. */\r
7540 \r
7541     if (appData.noChessProgram || first.pr != NoProc) return;\r
7542     \r
7543     StartChessProgram(&first);\r
7544     InitChessProgram(&first, FALSE);\r
7545     FeedMovesToProgram(&first, currentMove);\r
7546 \r
7547     if (!first.sendTime) {\r
7548         /* can't tell gnuchess what its clock should read,\r
7549            so we bow to its notion. */\r
7550         ResetClocks();\r
7551         timeRemaining[0][currentMove] = whiteTimeRemaining;\r
7552         timeRemaining[1][currentMove] = blackTimeRemaining;\r
7553     }\r
7554 \r
7555     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
7556         first.analysisSupport) {\r
7557       SendToProgram("analyze\n", &first);\r
7558       first.analyzing = TRUE;\r
7559     }\r
7560 }\r
7561 \r
7562 /*\r
7563  * Button procedures\r
7564  */\r
7565 void\r
7566 Reset(redraw, init)\r
7567      int redraw, init;\r
7568 {\r
7569     int i;\r
7570 \r
7571     if (appData.debugMode) {\r
7572         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
7573                 redraw, init, gameMode);\r
7574     }\r
7575     pausing = pauseExamInvalid = FALSE;\r
7576     startedFromSetupPosition = blackPlaysFirst = FALSE;\r
7577     firstMove = TRUE;\r
7578     whiteFlag = blackFlag = FALSE;\r
7579     userOfferedDraw = FALSE;\r
7580     hintRequested = bookRequested = FALSE;\r
7581     first.maybeThinking = FALSE;\r
7582     second.maybeThinking = FALSE;\r
7583     thinkOutput[0] = NULLCHAR;\r
7584     lastHint[0] = NULLCHAR;\r
7585     ClearGameInfo(&gameInfo);\r
7586     gameInfo.variant = StringToVariant(appData.variant);\r
7587     ics_user_moved = ics_clock_paused = FALSE;\r
7588     ics_getting_history = H_FALSE;\r
7589     ics_gamenum = -1;\r
7590     white_holding[0] = black_holding[0] = NULLCHAR;\r
7591     ClearProgramStats();\r
7592     \r
7593     ResetFrontEnd();\r
7594     ClearHighlights();\r
7595     flipView = appData.flipView;\r
7596     ClearPremoveHighlights();\r
7597     gotPremove = FALSE;\r
7598     alarmSounded = FALSE;\r
7599 \r
7600     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
7601     if(appData.serverMovesName != NULL) {\r
7602         /* [HGM] prepare to make moves file for broadcasting */\r
7603         clock_t t = clock();\r
7604         if(serverMoves != NULL) fclose(serverMoves);\r
7605         serverMoves = fopen(appData.serverMovesName, "r");\r
7606         if(serverMoves != NULL) {\r
7607             fclose(serverMoves);\r
7608             /* delay 15 sec before overwriting, so all clients can see end */\r
7609             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
7610         }\r
7611         serverMoves = fopen(appData.serverMovesName, "w");\r
7612     }\r
7613 \r
7614     ExitAnalyzeMode();\r
7615     gameMode = BeginningOfGame;\r
7616     ModeHighlight();\r
7617     if(appData.icsActive) gameInfo.variant = VariantNormal;\r
7618     InitPosition(redraw);\r
7619     for (i = 0; i < MAX_MOVES; i++) {\r
7620         if (commentList[i] != NULL) {\r
7621             free(commentList[i]);\r
7622             commentList[i] = NULL;\r
7623         }\r
7624     }\r
7625     ResetClocks();\r
7626     timeRemaining[0][0] = whiteTimeRemaining;\r
7627     timeRemaining[1][0] = blackTimeRemaining;\r
7628     if (first.pr == NULL) {\r
7629         StartChessProgram(&first);\r
7630     }\r
7631     if (init) {\r
7632             InitChessProgram(&first, startedFromSetupPosition);\r
7633     }\r
7634     DisplayTitle("");\r
7635     DisplayMessage("", "");\r
7636     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
7637 }\r
7638 \r
7639 void\r
7640 AutoPlayGameLoop()\r
7641 {\r
7642     for (;;) {\r
7643         if (!AutoPlayOneMove())\r
7644           return;\r
7645         if (matchMode || appData.timeDelay == 0)\r
7646           continue;\r
7647         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
7648           return;\r
7649         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
7650         break;\r
7651     }\r
7652 }\r
7653 \r
7654 \r
7655 int\r
7656 AutoPlayOneMove()\r
7657 {\r
7658     int fromX, fromY, toX, toY;\r
7659 \r
7660     if (appData.debugMode) {\r
7661       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
7662     }\r
7663 \r
7664     if (gameMode != PlayFromGameFile)\r
7665       return FALSE;\r
7666 \r
7667     if (currentMove >= forwardMostMove) {\r
7668       gameMode = EditGame;\r
7669       ModeHighlight();\r
7670 \r
7671       /* [AS] Clear current move marker at the end of a game */\r
7672       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
7673 \r
7674       return FALSE;\r
7675     }\r
7676     \r
7677     toX = moveList[currentMove][2] - AAA;\r
7678     toY = moveList[currentMove][3] - ONE;\r
7679 \r
7680     if (moveList[currentMove][1] == '@') {\r
7681         if (appData.highlightLastMove) {\r
7682             SetHighlights(-1, -1, toX, toY);\r
7683         }\r
7684     } else {\r
7685         fromX = moveList[currentMove][0] - AAA;\r
7686         fromY = moveList[currentMove][1] - ONE;\r
7687 \r
7688         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
7689 \r
7690         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
7691 \r
7692         if (appData.highlightLastMove) {\r
7693             SetHighlights(fromX, fromY, toX, toY);\r
7694         }\r
7695     }\r
7696     DisplayMove(currentMove);\r
7697     SendMoveToProgram(currentMove++, &first);\r
7698     DisplayBothClocks();\r
7699     DrawPosition(FALSE, boards[currentMove]);\r
7700     // [HGM] PV info: always display, routine tests if empty\r
7701     DisplayComment(currentMove - 1, commentList[currentMove]);\r
7702     return TRUE;\r
7703 }\r
7704 \r
7705 \r
7706 int\r
7707 LoadGameOneMove(readAhead)\r
7708      ChessMove readAhead;\r
7709 {\r
7710     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
7711     char promoChar = NULLCHAR;\r
7712     ChessMove moveType;\r
7713     char move[MSG_SIZ];\r
7714     char *p, *q;\r
7715     \r
7716     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
7717         gameMode != AnalyzeMode && gameMode != Training) {\r
7718         gameFileFP = NULL;\r
7719         return FALSE;\r
7720     }\r
7721     \r
7722     yyboardindex = forwardMostMove;\r
7723     if (readAhead != (ChessMove)0) {\r
7724       moveType = readAhead;\r
7725     } else {\r
7726       if (gameFileFP == NULL)\r
7727           return FALSE;\r
7728       moveType = (ChessMove) yylex();\r
7729     }\r
7730     \r
7731     done = FALSE;\r
7732     switch (moveType) {\r
7733       case Comment:\r
7734         if (appData.debugMode) \r
7735           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
7736         p = yy_text;\r
7737         if (*p == '{' || *p == '[' || *p == '(') {\r
7738             p[strlen(p) - 1] = NULLCHAR;\r
7739             p++;\r
7740         }\r
7741 \r
7742         /* append the comment but don't display it */\r
7743         while (*p == '\n') p++;\r
7744         AppendComment(currentMove, p);\r
7745         return TRUE;\r
7746 \r
7747       case WhiteCapturesEnPassant:\r
7748       case BlackCapturesEnPassant:\r
7749       case WhitePromotionChancellor:\r
7750       case BlackPromotionChancellor:\r
7751       case WhitePromotionArchbishop:\r
7752       case BlackPromotionArchbishop:\r
7753       case WhitePromotionQueen:\r
7754       case BlackPromotionQueen:\r
7755       case WhitePromotionRook:\r
7756       case BlackPromotionRook:\r
7757       case WhitePromotionBishop:\r
7758       case BlackPromotionBishop:\r
7759       case WhitePromotionKnight:\r
7760       case BlackPromotionKnight:\r
7761       case WhitePromotionKing:\r
7762       case BlackPromotionKing:\r
7763       case NormalMove:\r
7764       case WhiteKingSideCastle:\r
7765       case WhiteQueenSideCastle:\r
7766       case BlackKingSideCastle:\r
7767       case BlackQueenSideCastle:\r
7768       case WhiteKingSideCastleWild:\r
7769       case WhiteQueenSideCastleWild:\r
7770       case BlackKingSideCastleWild:\r
7771       case BlackQueenSideCastleWild:\r
7772       /* PUSH Fabien */\r
7773       case WhiteHSideCastleFR:\r
7774       case WhiteASideCastleFR:\r
7775       case BlackHSideCastleFR:\r
7776       case BlackASideCastleFR:\r
7777       /* POP Fabien */\r
7778         if (appData.debugMode)\r
7779           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
7780         fromX = currentMoveString[0] - AAA;\r
7781         fromY = currentMoveString[1] - ONE;\r
7782         toX = currentMoveString[2] - AAA;\r
7783         toY = currentMoveString[3] - ONE;\r
7784         promoChar = currentMoveString[4];\r
7785         break;\r
7786 \r
7787       case WhiteDrop:\r
7788       case BlackDrop:\r
7789         if (appData.debugMode)\r
7790           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
7791         fromX = moveType == WhiteDrop ?\r
7792           (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
7793         (int) CharToPiece(ToLower(currentMoveString[0]));\r
7794         fromY = DROP_RANK;\r
7795         toX = currentMoveString[2] - AAA;\r
7796         toY = currentMoveString[3] - ONE;\r
7797         break;\r
7798 \r
7799       case WhiteWins:\r
7800       case BlackWins:\r
7801       case GameIsDrawn:\r
7802       case GameUnfinished:\r
7803         if (appData.debugMode)\r
7804           fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
7805         p = strchr(yy_text, '{');\r
7806         if (p == NULL) p = strchr(yy_text, '(');\r
7807         if (p == NULL) {\r
7808             p = yy_text;\r
7809             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
7810         } else {\r
7811             q = strchr(p, *p == '{' ? '}' : ')');\r
7812             if (q != NULL) *q = NULLCHAR;\r
7813             p++;\r
7814         }\r
7815         GameEnds(moveType, p, GE_FILE);\r
7816         done = TRUE;\r
7817         if (cmailMsgLoaded) {\r
7818             ClearHighlights();\r
7819             flipView = WhiteOnMove(currentMove);\r
7820             if (moveType == GameUnfinished) flipView = !flipView;\r
7821             if (appData.debugMode)\r
7822               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
7823         }\r
7824         break;\r
7825 \r
7826       case (ChessMove) 0:       /* end of file */\r
7827         if (appData.debugMode)\r
7828           fprintf(debugFP, "Parser hit end of file\n");\r
7829         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
7830                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
7831           case MT_NONE:\r
7832           case MT_CHECK:\r
7833             break;\r
7834           case MT_CHECKMATE:\r
7835             if (WhiteOnMove(currentMove)) {\r
7836                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
7837             } else {\r
7838                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
7839             }\r
7840             break;\r
7841           case MT_STALEMATE:\r
7842             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
7843             break;\r
7844         }\r
7845         done = TRUE;\r
7846         break;\r
7847 \r
7848       case MoveNumberOne:\r
7849         if (lastLoadGameStart == GNUChessGame) {\r
7850             /* GNUChessGames have numbers, but they aren't move numbers */\r
7851             if (appData.debugMode)\r
7852               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
7853                       yy_text, (int) moveType);\r
7854             return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
7855         }\r
7856         /* else fall thru */\r
7857 \r
7858       case XBoardGame:\r
7859       case GNUChessGame:\r
7860       case PGNTag:\r
7861         /* Reached start of next game in file */\r
7862         if (appData.debugMode)\r
7863           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
7864         switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
7865                          EP_UNKNOWN, castlingRights[currentMove]) ) {\r
7866           case MT_NONE:\r
7867           case MT_CHECK:\r
7868             break;\r
7869           case MT_CHECKMATE:\r
7870             if (WhiteOnMove(currentMove)) {\r
7871                 GameEnds(BlackWins, "Black mates", GE_FILE);\r
7872             } else {\r
7873                 GameEnds(WhiteWins, "White mates", GE_FILE);\r
7874             }\r
7875             break;\r
7876           case MT_STALEMATE:\r
7877             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
7878             break;\r
7879         }\r
7880         done = TRUE;\r
7881         break;\r
7882 \r
7883       case PositionDiagram:     /* should not happen; ignore */\r
7884       case ElapsedTime:         /* ignore */\r
7885       case NAG:                 /* ignore */\r
7886         if (appData.debugMode)\r
7887           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
7888                   yy_text, (int) moveType);\r
7889         return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
7890 \r
7891       case IllegalMove:\r
7892         if (appData.testLegality) {\r
7893             if (appData.debugMode)\r
7894               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
7895             sprintf(move, "Illegal move: %d.%s%s",\r
7896                     (forwardMostMove / 2) + 1,\r
7897                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
7898             DisplayError(move, 0);\r
7899             done = TRUE;\r
7900         } else {\r
7901             if (appData.debugMode)\r
7902               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
7903                       yy_text, currentMoveString);\r
7904             fromX = currentMoveString[0] - AAA;\r
7905             fromY = currentMoveString[1] - ONE;\r
7906             toX = currentMoveString[2] - AAA;\r
7907             toY = currentMoveString[3] - ONE;\r
7908             promoChar = currentMoveString[4];\r
7909         }\r
7910         break;\r
7911 \r
7912       case AmbiguousMove:\r
7913         if (appData.debugMode)\r
7914           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
7915         sprintf(move, "Ambiguous move: %d.%s%s",\r
7916                 (forwardMostMove / 2) + 1,\r
7917                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
7918         DisplayError(move, 0);\r
7919         done = TRUE;\r
7920         break;\r
7921 \r
7922       default:\r
7923       case ImpossibleMove:\r
7924         if (appData.debugMode)\r
7925           fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text);\r
7926         sprintf(move, "Illegal move: %d.%s%s",\r
7927                 (forwardMostMove / 2) + 1,\r
7928                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
7929         DisplayError(move, 0);\r
7930         done = TRUE;\r
7931         break;\r
7932     }\r
7933 \r
7934     if (done) {\r
7935         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
7936             DrawPosition(FALSE, boards[currentMove]);\r
7937             DisplayBothClocks();\r
7938             if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
7939               DisplayComment(currentMove - 1, commentList[currentMove]);\r
7940         }\r
7941         (void) StopLoadGameTimer();\r
7942         gameFileFP = NULL;\r
7943         cmailOldMove = forwardMostMove;\r
7944         return FALSE;\r
7945     } else {\r
7946         /* currentMoveString is set as a side-effect of yylex */\r
7947         strcat(currentMoveString, "\n");\r
7948         strcpy(moveList[forwardMostMove], currentMoveString);\r
7949         \r
7950         thinkOutput[0] = NULLCHAR;\r
7951         MakeMove(fromX, fromY, toX, toY, promoChar);\r
7952         currentMove = forwardMostMove;\r
7953         return TRUE;\r
7954     }\r
7955 }\r
7956 \r
7957 /* Load the nth game from the given file */\r
7958 int\r
7959 LoadGameFromFile(filename, n, title, useList)\r
7960      char *filename;\r
7961      int n;\r
7962      char *title;\r
7963      /*Boolean*/ int useList;\r
7964 {\r
7965     FILE *f;\r
7966     char buf[MSG_SIZ];\r
7967 \r
7968     if (strcmp(filename, "-") == 0) {\r
7969         f = stdin;\r
7970         title = "stdin";\r
7971     } else {\r
7972         f = fopen(filename, "rb");\r
7973         if (f == NULL) {\r
7974             sprintf(buf, "Can't open \"%s\"", filename);\r
7975             DisplayError(buf, errno);\r
7976             return FALSE;\r
7977         }\r
7978     }\r
7979     if (fseek(f, 0, 0) == -1) {\r
7980         /* f is not seekable; probably a pipe */\r
7981         useList = FALSE;\r
7982     }\r
7983     if (useList && n == 0) {\r
7984         int error = GameListBuild(f);\r
7985         if (error) {\r
7986             DisplayError("Cannot build game list", error);\r
7987         } else if (!ListEmpty(&gameList) &&\r
7988                    ((ListGame *) gameList.tailPred)->number > 1) {\r
7989             GameListPopUp(f, title);\r
7990             return TRUE;\r
7991         }\r
7992         GameListDestroy();\r
7993         n = 1;\r
7994     }\r
7995     if (n == 0) n = 1;\r
7996     return LoadGame(f, n, title, FALSE);\r
7997 }\r
7998 \r
7999 \r
8000 void\r
8001 MakeRegisteredMove()\r
8002 {\r
8003     int fromX, fromY, toX, toY;\r
8004     char promoChar;\r
8005     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8006         switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
8007           case CMAIL_MOVE:\r
8008           case CMAIL_DRAW:\r
8009             if (appData.debugMode)\r
8010               fprintf(debugFP, "Restoring %s for game %d\n",\r
8011                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
8012     \r
8013             thinkOutput[0] = NULLCHAR;\r
8014             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
8015             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
8016             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
8017             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
8018             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
8019             promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
8020             MakeMove(fromX, fromY, toX, toY, promoChar);\r
8021             ShowMove(fromX, fromY, toX, toY);\r
8022               \r
8023             switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
8024                              EP_UNKNOWN, castlingRights[currentMove]) ) {\r
8025               case MT_NONE:\r
8026               case MT_CHECK:\r
8027                 break;\r
8028                 \r
8029               case MT_CHECKMATE:\r
8030                 if (WhiteOnMove(currentMove)) {\r
8031                     GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
8032                 } else {\r
8033                     GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
8034                 }\r
8035                 break;\r
8036                 \r
8037               case MT_STALEMATE:\r
8038                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
8039                 break;\r
8040             }\r
8041 \r
8042             break;\r
8043             \r
8044           case CMAIL_RESIGN:\r
8045             if (WhiteOnMove(currentMove)) {\r
8046                 GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
8047             } else {\r
8048                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
8049             }\r
8050             break;\r
8051             \r
8052           case CMAIL_ACCEPT:\r
8053             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
8054             break;\r
8055               \r
8056           default:\r
8057             break;\r
8058         }\r
8059     }\r
8060 \r
8061     return;\r
8062 }\r
8063 \r
8064 /* Wrapper around LoadGame for use when a Cmail message is loaded */\r
8065 int\r
8066 CmailLoadGame(f, gameNumber, title, useList)\r
8067      FILE *f;\r
8068      int gameNumber;\r
8069      char *title;\r
8070      int useList;\r
8071 {\r
8072     int retVal;\r
8073 \r
8074     if (gameNumber > nCmailGames) {\r
8075         DisplayError("No more games in this message", 0);\r
8076         return FALSE;\r
8077     }\r
8078     if (f == lastLoadGameFP) {\r
8079         int offset = gameNumber - lastLoadGameNumber;\r
8080         if (offset == 0) {\r
8081             cmailMsg[0] = NULLCHAR;\r
8082             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
8083                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
8084                 nCmailMovesRegistered--;\r
8085             }\r
8086             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
8087             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
8088                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
8089             }\r
8090         } else {\r
8091             if (! RegisterMove()) return FALSE;\r
8092         }\r
8093     }\r
8094 \r
8095     retVal = LoadGame(f, gameNumber, title, useList);\r
8096 \r
8097     /* Make move registered during previous look at this game, if any */\r
8098     MakeRegisteredMove();\r
8099 \r
8100     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
8101         commentList[currentMove]\r
8102           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
8103         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8104     }\r
8105 \r
8106     return retVal;\r
8107 }\r
8108 \r
8109 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
8110 int\r
8111 ReloadGame(offset)\r
8112      int offset;\r
8113 {\r
8114     int gameNumber = lastLoadGameNumber + offset;\r
8115     if (lastLoadGameFP == NULL) {\r
8116         DisplayError("No game has been loaded yet", 0);\r
8117         return FALSE;\r
8118     }\r
8119     if (gameNumber <= 0) {\r
8120         DisplayError("Can't back up any further", 0);\r
8121         return FALSE;\r
8122     }\r
8123     if (cmailMsgLoaded) {\r
8124         return CmailLoadGame(lastLoadGameFP, gameNumber,\r
8125                              lastLoadGameTitle, lastLoadGameUseList);\r
8126     } else {\r
8127         return LoadGame(lastLoadGameFP, gameNumber,\r
8128                         lastLoadGameTitle, lastLoadGameUseList);\r
8129     }\r
8130 }\r
8131 \r
8132 \r
8133 \r
8134 /* Load the nth game from open file f */\r
8135 int\r
8136 LoadGame(f, gameNumber, title, useList)\r
8137      FILE *f;\r
8138      int gameNumber;\r
8139      char *title;\r
8140      int useList;\r
8141 {\r
8142     ChessMove cm;\r
8143     char buf[MSG_SIZ];\r
8144     int gn = gameNumber;\r
8145     ListGame *lg = NULL;\r
8146     int numPGNTags = 0;\r
8147     int err;\r
8148     GameMode oldGameMode;\r
8149     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
8150 \r
8151     if (appData.debugMode) \r
8152         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
8153 \r
8154     if (gameMode == Training )\r
8155         SetTrainingModeOff();\r
8156 \r
8157     oldGameMode = gameMode;\r
8158     if (gameMode != BeginningOfGame) {\r
8159       Reset(FALSE, TRUE);\r
8160     }\r
8161 \r
8162     gameFileFP = f;\r
8163     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
8164         fclose(lastLoadGameFP);\r
8165     }\r
8166 \r
8167     if (useList) {\r
8168         lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
8169         \r
8170         if (lg) {\r
8171             fseek(f, lg->offset, 0);\r
8172             GameListHighlight(gameNumber);\r
8173             gn = 1;\r
8174         }\r
8175         else {\r
8176             DisplayError("Game number out of range", 0);\r
8177             return FALSE;\r
8178         }\r
8179     } else {\r
8180         GameListDestroy();\r
8181         if (fseek(f, 0, 0) == -1) {\r
8182             if (f == lastLoadGameFP ?\r
8183                 gameNumber == lastLoadGameNumber + 1 :\r
8184                 gameNumber == 1) {\r
8185                 gn = 1;\r
8186             } else {\r
8187                 DisplayError("Can't seek on game file", 0);\r
8188                 return FALSE;\r
8189             }\r
8190         }\r
8191     }\r
8192     lastLoadGameFP = f;\r
8193     lastLoadGameNumber = gameNumber;\r
8194     strcpy(lastLoadGameTitle, title);\r
8195     lastLoadGameUseList = useList;\r
8196 \r
8197     yynewfile(f);\r
8198 \r
8199     if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
8200         sprintf(buf, "%s vs. %s", lg->gameInfo.white,\r
8201                 lg->gameInfo.black);\r
8202             DisplayTitle(buf);\r
8203     } else if (*title != NULLCHAR) {\r
8204         if (gameNumber > 1) {\r
8205             sprintf(buf, "%s %d", title, gameNumber);\r
8206             DisplayTitle(buf);\r
8207         } else {\r
8208             DisplayTitle(title);\r
8209         }\r
8210     }\r
8211 \r
8212     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
8213         gameMode = PlayFromGameFile;\r
8214         ModeHighlight();\r
8215     }\r
8216 \r
8217     currentMove = forwardMostMove = backwardMostMove = 0;\r
8218     CopyBoard(boards[0], initialPosition);\r
8219     StopClocks();\r
8220 \r
8221     /*\r
8222      * Skip the first gn-1 games in the file.\r
8223      * Also skip over anything that precedes an identifiable \r
8224      * start of game marker, to avoid being confused by \r
8225      * garbage at the start of the file.  Currently \r
8226      * recognized start of game markers are the move number "1",\r
8227      * the pattern "gnuchess .* game", the pattern\r
8228      * "^[#;%] [^ ]* game file", and a PGN tag block.  \r
8229      * A game that starts with one of the latter two patterns\r
8230      * will also have a move number 1, possibly\r
8231      * following a position diagram.\r
8232      * 5-4-02: Let's try being more lenient and allowing a game to\r
8233      * start with an unnumbered move.  Does that break anything?\r
8234      */\r
8235     cm = lastLoadGameStart = (ChessMove) 0;\r
8236     while (gn > 0) {\r
8237         yyboardindex = forwardMostMove;\r
8238         cm = (ChessMove) yylex();\r
8239         switch (cm) {\r
8240           case (ChessMove) 0:\r
8241             if (cmailMsgLoaded) {\r
8242                 nCmailGames = CMAIL_MAX_GAMES - gn;\r
8243             } else {\r
8244                 Reset(TRUE, TRUE);\r
8245                 DisplayError("Game not found in file", 0);\r
8246             }\r
8247             return FALSE;\r
8248 \r
8249           case GNUChessGame:\r
8250           case XBoardGame:\r
8251             gn--;\r
8252             lastLoadGameStart = cm;\r
8253             break;\r
8254             \r
8255           case MoveNumberOne:\r
8256             switch (lastLoadGameStart) {\r
8257               case GNUChessGame:\r
8258               case XBoardGame:\r
8259               case PGNTag:\r
8260                 break;\r
8261               case MoveNumberOne:\r
8262               case (ChessMove) 0:\r
8263                 gn--;           /* count this game */\r
8264                 lastLoadGameStart = cm;\r
8265                 break;\r
8266               default:\r
8267                 /* impossible */\r
8268                 break;\r
8269             }\r
8270             break;\r
8271 \r
8272           case PGNTag:\r
8273             switch (lastLoadGameStart) {\r
8274               case GNUChessGame:\r
8275               case PGNTag:\r
8276               case MoveNumberOne:\r
8277               case (ChessMove) 0:\r
8278                 gn--;           /* count this game */\r
8279                 lastLoadGameStart = cm;\r
8280                 break;\r
8281               case XBoardGame:\r
8282                 lastLoadGameStart = cm; /* game counted already */\r
8283                 break;\r
8284               default:\r
8285                 /* impossible */\r
8286                 break;\r
8287             }\r
8288             if (gn > 0) {\r
8289                 do {\r
8290                     yyboardindex = forwardMostMove;\r
8291                     cm = (ChessMove) yylex();\r
8292                 } while (cm == PGNTag || cm == Comment);\r
8293             }\r
8294             break;\r
8295 \r
8296           case WhiteWins:\r
8297           case BlackWins:\r
8298           case GameIsDrawn:\r
8299             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
8300                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
8301                     != CMAIL_OLD_RESULT) {\r
8302                     nCmailResults ++ ;\r
8303                     cmailResult[  CMAIL_MAX_GAMES\r
8304                                 - gn - 1] = CMAIL_OLD_RESULT;\r
8305                 }\r
8306             }\r
8307             break;\r
8308 \r
8309           case NormalMove:\r
8310             /* Only a NormalMove can be at the start of a game\r
8311              * without a position diagram. */\r
8312             if (lastLoadGameStart == (ChessMove) 0) {\r
8313               gn--;\r
8314               lastLoadGameStart = MoveNumberOne;\r
8315             }\r
8316             break;\r
8317 \r
8318           default:\r
8319             break;\r
8320         }\r
8321     }\r
8322     \r
8323     if (appData.debugMode)\r
8324       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
8325 \r
8326     if (cm == XBoardGame) {\r
8327         /* Skip any header junk before position diagram and/or move 1 */\r
8328         for (;;) {\r
8329             yyboardindex = forwardMostMove;\r
8330             cm = (ChessMove) yylex();\r
8331 \r
8332             if (cm == (ChessMove) 0 ||\r
8333                 cm == GNUChessGame || cm == XBoardGame) {\r
8334                 /* Empty game; pretend end-of-file and handle later */\r
8335                 cm = (ChessMove) 0;\r
8336                 break;\r
8337             }\r
8338 \r
8339             if (cm == MoveNumberOne || cm == PositionDiagram ||\r
8340                 cm == PGNTag || cm == Comment)\r
8341               break;\r
8342         }\r
8343     } else if (cm == GNUChessGame) {\r
8344         if (gameInfo.event != NULL) {\r
8345             free(gameInfo.event);\r
8346         }\r
8347         gameInfo.event = StrSave(yy_text);\r
8348     }   \r
8349 \r
8350     startedFromSetupPosition = FALSE;\r
8351     while (cm == PGNTag) {\r
8352         if (appData.debugMode) \r
8353           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
8354         err = ParsePGNTag(yy_text, &gameInfo);\r
8355         if (!err) numPGNTags++;\r
8356 \r
8357         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
8358         if(gameInfo.variant != oldVariant) {\r
8359             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
8360             InitPosition(TRUE);\r
8361             oldVariant = gameInfo.variant;\r
8362             if (appData.debugMode) \r
8363               fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
8364         }\r
8365 \r
8366 \r
8367         if (gameInfo.fen != NULL) {\r
8368           Board initial_position;\r
8369           startedFromSetupPosition = TRUE;\r
8370           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
8371             Reset(TRUE, TRUE);\r
8372             DisplayError("Bad FEN position in file", 0);\r
8373             return FALSE;\r
8374           }\r
8375           CopyBoard(boards[0], initial_position);\r
8376           /* [HGM] copy FEN attributes as well */\r
8377           {   int i;\r
8378               initialRulePlies = FENrulePlies;\r
8379               epStatus[0] = FENepStatus;\r
8380               for( i=0; i< nrCastlingRights; i++ )\r
8381                   initialRights[i] = castlingRights[0][i] = FENcastlingRights[i];\r
8382           }\r
8383           if (blackPlaysFirst) {\r
8384             currentMove = forwardMostMove = backwardMostMove = 1;\r
8385             CopyBoard(boards[1], initial_position);\r
8386             strcpy(moveList[0], "");\r
8387             strcpy(parseList[0], "");\r
8388             timeRemaining[0][1] = whiteTimeRemaining;\r
8389             timeRemaining[1][1] = blackTimeRemaining;\r
8390             if (commentList[0] != NULL) {\r
8391               commentList[1] = commentList[0];\r
8392               commentList[0] = NULL;\r
8393             }\r
8394           } else {\r
8395             currentMove = forwardMostMove = backwardMostMove = 0;\r
8396           }\r
8397           yyboardindex = forwardMostMove;\r
8398           free(gameInfo.fen);\r
8399           gameInfo.fen = NULL;\r
8400         }\r
8401 \r
8402         yyboardindex = forwardMostMove;\r
8403         cm = (ChessMove) yylex();\r
8404 \r
8405         /* Handle comments interspersed among the tags */\r
8406         while (cm == Comment) {\r
8407             char *p;\r
8408             if (appData.debugMode) \r
8409               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8410             p = yy_text;\r
8411             if (*p == '{' || *p == '[' || *p == '(') {\r
8412                 p[strlen(p) - 1] = NULLCHAR;\r
8413                 p++;\r
8414             }\r
8415             while (*p == '\n') p++;\r
8416             AppendComment(currentMove, p);\r
8417             yyboardindex = forwardMostMove;\r
8418             cm = (ChessMove) yylex();\r
8419         }\r
8420     }\r
8421 \r
8422     /* don't rely on existence of Event tag since if game was\r
8423      * pasted from clipboard the Event tag may not exist\r
8424      */\r
8425     if (numPGNTags > 0){\r
8426         char *tags;\r
8427         if (gameInfo.variant == VariantNormal) {\r
8428           gameInfo.variant = StringToVariant(gameInfo.event);\r
8429         }\r
8430         if (!matchMode) {\r
8431           if( appData.autoDisplayTags ) {\r
8432             tags = PGNTags(&gameInfo);\r
8433             TagsPopUp(tags, CmailMsg());\r
8434             free(tags);\r
8435           }\r
8436         }\r
8437     } else {\r
8438         /* Make something up, but don't display it now */\r
8439         SetGameInfo();\r
8440         TagsPopDown();\r
8441     }\r
8442 \r
8443     if (cm == PositionDiagram) {\r
8444         int i, j;\r
8445         char *p;\r
8446         Board initial_position;\r
8447 \r
8448         if (appData.debugMode)\r
8449           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
8450 \r
8451         if (!startedFromSetupPosition) {\r
8452             p = yy_text;\r
8453             for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
8454               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
8455                 switch (*p) {\r
8456                   case '[':\r
8457                   case '-':\r
8458                   case ' ':\r
8459                   case '\t':\r
8460                   case '\n':\r
8461                   case '\r':\r
8462                     break;\r
8463                   default:\r
8464                     initial_position[i][j++] = CharToPiece(*p);\r
8465                     break;\r
8466                 }\r
8467             while (*p == ' ' || *p == '\t' ||\r
8468                    *p == '\n' || *p == '\r') p++;\r
8469         \r
8470             if (strncmp(p, "black", strlen("black"))==0)\r
8471               blackPlaysFirst = TRUE;\r
8472             else\r
8473               blackPlaysFirst = FALSE;\r
8474             startedFromSetupPosition = TRUE;\r
8475         \r
8476             CopyBoard(boards[0], initial_position);\r
8477             if (blackPlaysFirst) {\r
8478                 currentMove = forwardMostMove = backwardMostMove = 1;\r
8479                 CopyBoard(boards[1], initial_position);\r
8480                 strcpy(moveList[0], "");\r
8481                 strcpy(parseList[0], "");\r
8482                 timeRemaining[0][1] = whiteTimeRemaining;\r
8483                 timeRemaining[1][1] = blackTimeRemaining;\r
8484                 if (commentList[0] != NULL) {\r
8485                     commentList[1] = commentList[0];\r
8486                     commentList[0] = NULL;\r
8487                 }\r
8488             } else {\r
8489                 currentMove = forwardMostMove = backwardMostMove = 0;\r
8490             }\r
8491         }\r
8492         yyboardindex = forwardMostMove;\r
8493         cm = (ChessMove) yylex();\r
8494     }\r
8495 \r
8496     if (first.pr == NoProc) {\r
8497         StartChessProgram(&first);\r
8498     }\r
8499     InitChessProgram(&first, FALSE);\r
8500     SendToProgram("force\n", &first);\r
8501     if (startedFromSetupPosition) {\r
8502         SendBoard(&first, forwardMostMove);\r
8503     if (appData.debugMode) {\r
8504         fprintf(debugFP, "Load Game\n");\r
8505     }\r
8506         DisplayBothClocks();\r
8507     }      \r
8508 \r
8509     /* [HGM] server: flag to write setup moves in broadcast file as one */\r
8510     loadFlag = appData.suppressLoadMoves;\r
8511 \r
8512     while (cm == Comment) {\r
8513         char *p;\r
8514         if (appData.debugMode) \r
8515           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
8516         p = yy_text;\r
8517         if (*p == '{' || *p == '[' || *p == '(') {\r
8518             p[strlen(p) - 1] = NULLCHAR;\r
8519             p++;\r
8520         }\r
8521         while (*p == '\n') p++;\r
8522         AppendComment(currentMove, p);\r
8523         yyboardindex = forwardMostMove;\r
8524         cm = (ChessMove) yylex();\r
8525     }\r
8526 \r
8527     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
8528         cm == WhiteWins || cm == BlackWins ||\r
8529         cm == GameIsDrawn || cm == GameUnfinished) {\r
8530         DisplayMessage("", "No moves in game");\r
8531         if (cmailMsgLoaded) {\r
8532             if (appData.debugMode)\r
8533               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
8534             ClearHighlights();\r
8535             flipView = FALSE;\r
8536         }\r
8537         DrawPosition(FALSE, boards[currentMove]);\r
8538         DisplayBothClocks();\r
8539         gameMode = EditGame;\r
8540         ModeHighlight();\r
8541         gameFileFP = NULL;\r
8542         cmailOldMove = 0;\r
8543         return TRUE;\r
8544     }\r
8545 \r
8546     // [HGM] PV info: routine tests if comment empty\r
8547     if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
8548         DisplayComment(currentMove - 1, commentList[currentMove]);\r
8549     }\r
8550     if (!matchMode && appData.timeDelay != 0) \r
8551       DrawPosition(FALSE, boards[currentMove]);\r
8552 \r
8553     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
8554       programStats.ok_to_send = 1;\r
8555     }\r
8556 \r
8557     /* if the first token after the PGN tags is a move\r
8558      * and not move number 1, retrieve it from the parser \r
8559      */\r
8560     if (cm != MoveNumberOne)\r
8561         LoadGameOneMove(cm);\r
8562 \r
8563     /* load the remaining moves from the file */\r
8564     while (LoadGameOneMove((ChessMove)0)) {\r
8565       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
8566       timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
8567     }\r
8568 \r
8569     /* rewind to the start of the game */\r
8570     currentMove = backwardMostMove;\r
8571 \r
8572     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
8573 \r
8574     if (oldGameMode == AnalyzeFile ||\r
8575         oldGameMode == AnalyzeMode) {\r
8576       AnalyzeFileEvent();\r
8577     }\r
8578 \r
8579     if (matchMode || appData.timeDelay == 0) {\r
8580       ToEndEvent();\r
8581       gameMode = EditGame;\r
8582       ModeHighlight();\r
8583     } else if (appData.timeDelay > 0) {\r
8584       AutoPlayGameLoop();\r
8585     }\r
8586 \r
8587     if (appData.debugMode) \r
8588         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
8589 \r
8590     loadFlag = 0; /* [HGM] true game starts */\r
8591     return TRUE;\r
8592 }\r
8593 \r
8594 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
8595 int\r
8596 ReloadPosition(offset)\r
8597      int offset;\r
8598 {\r
8599     int positionNumber = lastLoadPositionNumber + offset;\r
8600     if (lastLoadPositionFP == NULL) {\r
8601         DisplayError("No position has been loaded yet", 0);\r
8602         return FALSE;\r
8603     }\r
8604     if (positionNumber <= 0) {\r
8605         DisplayError("Can't back up any further", 0);\r
8606         return FALSE;\r
8607     }\r
8608     return LoadPosition(lastLoadPositionFP, positionNumber,\r
8609                         lastLoadPositionTitle);\r
8610 }\r
8611 \r
8612 /* Load the nth position from the given file */\r
8613 int\r
8614 LoadPositionFromFile(filename, n, title)\r
8615      char *filename;\r
8616      int n;\r
8617      char *title;\r
8618 {\r
8619     FILE *f;\r
8620     char buf[MSG_SIZ];\r
8621 \r
8622     if (strcmp(filename, "-") == 0) {\r
8623         return LoadPosition(stdin, n, "stdin");\r
8624     } else {\r
8625         f = fopen(filename, "rb");\r
8626         if (f == NULL) {\r
8627             sprintf(buf, "Can't open \"%s\"", filename);\r
8628             DisplayError(buf, errno);\r
8629             return FALSE;\r
8630         } else {\r
8631             return LoadPosition(f, n, title);\r
8632         }\r
8633     }\r
8634 }\r
8635 \r
8636 /* Load the nth position from the given open file, and close it */\r
8637 int\r
8638 LoadPosition(f, positionNumber, title)\r
8639      FILE *f;\r
8640      int positionNumber;\r
8641      char *title;\r
8642 {\r
8643     char *p, line[MSG_SIZ];\r
8644     Board initial_position;\r
8645     int i, j, fenMode, pn;\r
8646     \r
8647     if (gameMode == Training )\r
8648         SetTrainingModeOff();\r
8649 \r
8650     if (gameMode != BeginningOfGame) {\r
8651         Reset(FALSE, TRUE);\r
8652     }\r
8653     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
8654         fclose(lastLoadPositionFP);\r
8655     }\r
8656     if (positionNumber == 0) positionNumber = 1;\r
8657     lastLoadPositionFP = f;\r
8658     lastLoadPositionNumber = positionNumber;\r
8659     strcpy(lastLoadPositionTitle, title);\r
8660     if (first.pr == NoProc) {\r
8661       StartChessProgram(&first);\r
8662       InitChessProgram(&first, FALSE);\r
8663     }    \r
8664     pn = positionNumber;\r
8665     if (positionNumber < 0) {\r
8666         /* Negative position number means to seek to that byte offset */\r
8667         if (fseek(f, -positionNumber, 0) == -1) {\r
8668             DisplayError("Can't seek on position file", 0);\r
8669             return FALSE;\r
8670         };\r
8671         pn = 1;\r
8672     } else {\r
8673         if (fseek(f, 0, 0) == -1) {\r
8674             if (f == lastLoadPositionFP ?\r
8675                 positionNumber == lastLoadPositionNumber + 1 :\r
8676                 positionNumber == 1) {\r
8677                 pn = 1;\r
8678             } else {\r
8679                 DisplayError("Can't seek on position file", 0);\r
8680                 return FALSE;\r
8681             }\r
8682         }\r
8683     }\r
8684     /* See if this file is FEN or old-style xboard */\r
8685     if (fgets(line, MSG_SIZ, f) == NULL) {\r
8686         DisplayError("Position not found in file", 0);\r
8687         return FALSE;\r
8688     }\r
8689 #if 0\r
8690     switch (line[0]) {\r
8691       case '#':  case 'x':\r
8692       default:\r
8693         fenMode = FALSE;\r
8694         break;\r
8695       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':\r
8696       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':\r
8697       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':\r
8698       case '7':  case '8':  case '9':\r
8699       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':\r
8700       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':\r
8701       case 'C':  case 'W':             case 'c':  case 'w': \r
8702         fenMode = TRUE;\r
8703         break;\r
8704     }\r
8705 #else\r
8706     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
8707     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
8708 #endif\r
8709 \r
8710     if (pn >= 2) {\r
8711         if (fenMode || line[0] == '#') pn--;\r
8712         while (pn > 0) {\r
8713             /* skip postions before number pn */\r
8714             if (fgets(line, MSG_SIZ, f) == NULL) {\r
8715                 Reset(TRUE, TRUE);\r
8716                 DisplayError("Position not found in file", 0);\r
8717                 return FALSE;\r
8718             }\r
8719             if (fenMode || line[0] == '#') pn--;\r
8720         }\r
8721     }\r
8722 \r
8723     if (fenMode) {\r
8724         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
8725             DisplayError("Bad FEN position in file", 0);\r
8726             return FALSE;\r
8727         }\r
8728     } else {\r
8729         (void) fgets(line, MSG_SIZ, f);\r
8730         (void) fgets(line, MSG_SIZ, f);\r
8731     \r
8732         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
8733             (void) fgets(line, MSG_SIZ, f);\r
8734             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
8735                 if (*p == ' ')\r
8736                   continue;\r
8737                 initial_position[i][j++] = CharToPiece(*p);\r
8738             }\r
8739         }\r
8740     \r
8741         blackPlaysFirst = FALSE;\r
8742         if (!feof(f)) {\r
8743             (void) fgets(line, MSG_SIZ, f);\r
8744             if (strncmp(line, "black", strlen("black"))==0)\r
8745               blackPlaysFirst = TRUE;\r
8746         }\r
8747     }\r
8748     startedFromSetupPosition = TRUE;\r
8749     \r
8750     SendToProgram("force\n", &first);\r
8751     CopyBoard(boards[0], initial_position);\r
8752           /* [HGM] copy FEN attributes as well */\r
8753           {   int i;\r
8754               initialRulePlies = FENrulePlies;\r
8755               epStatus[0] = FENepStatus;\r
8756               for( i=0; i< nrCastlingRights; i++ )\r
8757                   castlingRights[0][i] = FENcastlingRights[i];\r
8758           }\r
8759     if (blackPlaysFirst) {\r
8760         currentMove = forwardMostMove = backwardMostMove = 1;\r
8761         strcpy(moveList[0], "");\r
8762         strcpy(parseList[0], "");\r
8763         CopyBoard(boards[1], initial_position);\r
8764         DisplayMessage("", "Black to play");\r
8765     } else {\r
8766         currentMove = forwardMostMove = backwardMostMove = 0;\r
8767         DisplayMessage("", "White to play");\r
8768     }\r
8769     SendBoard(&first, forwardMostMove);\r
8770     if (appData.debugMode) {\r
8771         fprintf(debugFP, "Load Position\n");\r
8772     }\r
8773 \r
8774     if (positionNumber > 1) {\r
8775         sprintf(line, "%s %d", title, positionNumber);\r
8776         DisplayTitle(line);\r
8777     } else {\r
8778         DisplayTitle(title);\r
8779     }\r
8780     gameMode = EditGame;\r
8781     ModeHighlight();\r
8782     ResetClocks();\r
8783     timeRemaining[0][1] = whiteTimeRemaining;\r
8784     timeRemaining[1][1] = blackTimeRemaining;\r
8785     DrawPosition(FALSE, boards[currentMove]);\r
8786    \r
8787     return TRUE;\r
8788 }\r
8789 \r
8790 \r
8791 void\r
8792 CopyPlayerNameIntoFileName(dest, src)\r
8793      char **dest, *src;\r
8794 {\r
8795     while (*src != NULLCHAR && *src != ',') {\r
8796         if (*src == ' ') {\r
8797             *(*dest)++ = '_';\r
8798             src++;\r
8799         } else {\r
8800             *(*dest)++ = *src++;\r
8801         }\r
8802     }\r
8803 }\r
8804 \r
8805 char *DefaultFileName(ext)\r
8806      char *ext;\r
8807 {\r
8808     static char def[MSG_SIZ];\r
8809     char *p;\r
8810 \r
8811     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
8812         p = def;\r
8813         CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
8814         *p++ = '-';\r
8815         CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
8816         *p++ = '.';\r
8817         strcpy(p, ext);\r
8818     } else {\r
8819         def[0] = NULLCHAR;\r
8820     }\r
8821     return def;\r
8822 }\r
8823 \r
8824 /* Save the current game to the given file */\r
8825 int\r
8826 SaveGameToFile(filename, append)\r
8827      char *filename;\r
8828      int append;\r
8829 {\r
8830     FILE *f;\r
8831     char buf[MSG_SIZ];\r
8832 \r
8833     if (strcmp(filename, "-") == 0) {\r
8834         return SaveGame(stdout, 0, NULL);\r
8835     } else {\r
8836         f = fopen(filename, append ? "a" : "w");\r
8837         if (f == NULL) {\r
8838             sprintf(buf, "Can't open \"%s\"", filename);\r
8839             DisplayError(buf, errno);\r
8840             return FALSE;\r
8841         } else {\r
8842             return SaveGame(f, 0, NULL);\r
8843         }\r
8844     }\r
8845 }\r
8846 \r
8847 char *\r
8848 SavePart(str)\r
8849      char *str;\r
8850 {\r
8851     static char buf[MSG_SIZ];\r
8852     char *p;\r
8853     \r
8854     p = strchr(str, ' ');\r
8855     if (p == NULL) return str;\r
8856     strncpy(buf, str, p - str);\r
8857     buf[p - str] = NULLCHAR;\r
8858     return buf;\r
8859 }\r
8860 \r
8861 #define PGN_MAX_LINE 75\r
8862 \r
8863 #define PGN_SIDE_WHITE  0\r
8864 #define PGN_SIDE_BLACK  1\r
8865 \r
8866 /* [AS] */\r
8867 static int FindFirstMoveOutOfBook( int side )\r
8868 {\r
8869     int result = -1;\r
8870 \r
8871     if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
8872         int index = backwardMostMove;\r
8873         int has_book_hit = 0;\r
8874 \r
8875         if( (index % 2) != side ) {\r
8876             index++;\r
8877         }\r
8878 \r
8879         while( index < forwardMostMove ) {\r
8880             /* Check to see if engine is in book */\r
8881             int depth = pvInfoList[index].depth;\r
8882             int score = pvInfoList[index].score;\r
8883             int in_book = 0;\r
8884 \r
8885             if( depth <= 2 ) {\r
8886                 in_book = 1;\r
8887             }\r
8888             else if( score == 0 && depth == 63 ) {\r
8889                 in_book = 1; /* Zappa */\r
8890             }\r
8891             else if( score == 2 && depth == 99 ) {\r
8892                 in_book = 1; /* Abrok */\r
8893             }\r
8894 \r
8895             has_book_hit += in_book;\r
8896 \r
8897             if( ! in_book ) {\r
8898                 result = index;\r
8899 \r
8900                 break;\r
8901             }\r
8902 \r
8903             index += 2;\r
8904         }\r
8905     }\r
8906 \r
8907     return result;\r
8908 }\r
8909 \r
8910 /* [AS] */\r
8911 void GetOutOfBookInfo( char * buf )\r
8912 {\r
8913     int oob[2];\r
8914     int i;\r
8915     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
8916 \r
8917     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
8918     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
8919 \r
8920     *buf = '\0';\r
8921 \r
8922     if( oob[0] >= 0 || oob[1] >= 0 ) {\r
8923         for( i=0; i<2; i++ ) {\r
8924             int idx = oob[i];\r
8925 \r
8926             if( idx >= 0 ) {\r
8927                 if( i > 0 && oob[0] >= 0 ) {\r
8928                     strcat( buf, "   " );\r
8929                 }\r
8930 \r
8931                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
8932                 sprintf( buf+strlen(buf), "%s%.2f", \r
8933                     pvInfoList[idx].score >= 0 ? "+" : "",\r
8934                     pvInfoList[idx].score / 100.0 );\r
8935             }\r
8936         }\r
8937     }\r
8938 }\r
8939 \r
8940 /* Save game in PGN style and close the file */\r
8941 int\r
8942 SaveGamePGN(f)\r
8943      FILE *f;\r
8944 {\r
8945     int i, offset, linelen, newblock;\r
8946     time_t tm;\r
8947     char *movetext;\r
8948     char numtext[32];\r
8949     int movelen, numlen, blank;\r
8950     char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
8951 \r
8952     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
8953     \r
8954     tm = time((time_t *) NULL);\r
8955     \r
8956     PrintPGNTags(f, &gameInfo);\r
8957     \r
8958     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
8959         char *fen = PositionToFEN(backwardMostMove, 1);\r
8960         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
8961         fprintf(f, "\n{--------------\n");\r
8962         PrintPosition(f, backwardMostMove);\r
8963         fprintf(f, "--------------}\n");\r
8964         free(fen);\r
8965     }\r
8966     else {\r
8967         /* [AS] Out of book annotation */\r
8968         if( appData.saveOutOfBookInfo ) {\r
8969             char buf[64];\r
8970 \r
8971             GetOutOfBookInfo( buf );\r
8972 \r
8973             if( buf[0] != '\0' ) {\r
8974                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
8975             }\r
8976         }\r
8977 \r
8978         fprintf(f, "\n");\r
8979     }\r
8980 \r
8981     i = backwardMostMove;\r
8982     linelen = 0;\r
8983     newblock = TRUE;\r
8984 \r
8985     while (i < forwardMostMove) {\r
8986         /* Print comments preceding this move */\r
8987         if (commentList[i] != NULL) {\r
8988             if (linelen > 0) fprintf(f, "\n");\r
8989             fprintf(f, "{\n%s}\n", commentList[i]);\r
8990             linelen = 0;\r
8991             newblock = TRUE;\r
8992         }\r
8993 \r
8994         /* Format move number */\r
8995         if ((i % 2) == 0) {\r
8996             sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
8997         } else {\r
8998             if (newblock) {\r
8999                 sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
9000             } else {\r
9001                 numtext[0] = NULLCHAR;\r
9002             }\r
9003         }\r
9004         numlen = strlen(numtext);\r
9005         newblock = FALSE;\r
9006 \r
9007         /* Print move number */\r
9008         blank = linelen > 0 && numlen > 0;\r
9009         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
9010             fprintf(f, "\n");\r
9011             linelen = 0;\r
9012             blank = 0;\r
9013         }\r
9014         if (blank) {\r
9015             fprintf(f, " ");\r
9016             linelen++;\r
9017         }\r
9018         fprintf(f, numtext);\r
9019         linelen += numlen;\r
9020 \r
9021         /* Get move */\r
9022         movetext = SavePart(parseList[i]);\r
9023 \r
9024         /* [AS] Add PV info if present */\r
9025         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
9026             /* [HGM] add time */\r
9027             char buf[MSG_SIZ]; int seconds = 0;\r
9028 \r
9029 #if 0\r
9030             if(i >= backwardMostMove) {\r
9031                 /* take the time that changed */\r
9032                 seconds = timeRemaining[0][i] - timeRemaining[0][i+1];\r
9033               if(seconds <= 0)\r
9034                     seconds = timeRemaining[1][i] - timeRemaining[1][i+1];\r
9035             }\r
9036             seconds /= 1000;\r
9037 #endif\r
9038             seconds = pvInfoList[i].time/100; // [HGM] PVtime: use engine time\r
9039     if (appData.debugMode) {\r
9040         fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",\r
9041                 timeRemaining[0][i+1], timeRemaining[0][i],\r
9042                      timeRemaining[1][i+1], timeRemaining[1][i], seconds\r
9043         );\r
9044     }\r
9045 \r
9046             if( seconds < 0 ) buf[0] = 0; else\r
9047             if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0);\r
9048             else    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
9049 \r
9050             sprintf( move_buffer, "%s {%s%.2f/%d%s}", \r
9051                 movetext, \r
9052                 pvInfoList[i].score >= 0 ? "+" : "",\r
9053                 pvInfoList[i].score / 100.0,\r
9054                 pvInfoList[i].depth,\r
9055                 buf );\r
9056             movetext = move_buffer;\r
9057         }\r
9058 \r
9059         movelen = strlen(movetext);\r
9060 \r
9061         /* Print move */\r
9062         blank = linelen > 0 && movelen > 0;\r
9063         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
9064             fprintf(f, "\n");\r
9065             linelen = 0;\r
9066             blank = 0;\r
9067         }\r
9068         if (blank) {\r
9069             fprintf(f, " ");\r
9070             linelen++;\r
9071         }\r
9072         fprintf(f, movetext);\r
9073         linelen += movelen;\r
9074 \r
9075         i++;\r
9076     }\r
9077     \r
9078     /* Start a new line */\r
9079     if (linelen > 0) fprintf(f, "\n");\r
9080 \r
9081     /* Print comments after last move */\r
9082     if (commentList[i] != NULL) {\r
9083         fprintf(f, "{\n%s}\n", commentList[i]);\r
9084     }\r
9085 \r
9086     /* Print result */\r
9087     if (gameInfo.resultDetails != NULL &&\r
9088         gameInfo.resultDetails[0] != NULLCHAR) {\r
9089         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
9090                 PGNResult(gameInfo.result));\r
9091     } else {\r
9092         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9093     }\r
9094 \r
9095     fclose(f);\r
9096     return TRUE;\r
9097 }\r
9098 \r
9099 /* Save game in old style and close the file */\r
9100 int\r
9101 SaveGameOldStyle(f)\r
9102      FILE *f;\r
9103 {\r
9104     int i, offset;\r
9105     time_t tm;\r
9106     \r
9107     tm = time((time_t *) NULL);\r
9108     \r
9109     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
9110     PrintOpponents(f);\r
9111     \r
9112     if (backwardMostMove > 0 || startedFromSetupPosition) {\r
9113         fprintf(f, "\n[--------------\n");\r
9114         PrintPosition(f, backwardMostMove);\r
9115         fprintf(f, "--------------]\n");\r
9116     } else {\r
9117         fprintf(f, "\n");\r
9118     }\r
9119 \r
9120     i = backwardMostMove;\r
9121     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
9122 \r
9123     while (i < forwardMostMove) {\r
9124         if (commentList[i] != NULL) {\r
9125             fprintf(f, "[%s]\n", commentList[i]);\r
9126         }\r
9127 \r
9128         if ((i % 2) == 1) {\r
9129             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);\r
9130             i++;\r
9131         } else {\r
9132             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);\r
9133             i++;\r
9134             if (commentList[i] != NULL) {\r
9135                 fprintf(f, "\n");\r
9136                 continue;\r
9137             }\r
9138             if (i >= forwardMostMove) {\r
9139                 fprintf(f, "\n");\r
9140                 break;\r
9141             }\r
9142             fprintf(f, "%s\n", parseList[i]);\r
9143             i++;\r
9144         }\r
9145     }\r
9146     \r
9147     if (commentList[i] != NULL) {\r
9148         fprintf(f, "[%s]\n", commentList[i]);\r
9149     }\r
9150 \r
9151     /* This isn't really the old style, but it's close enough */\r
9152     if (gameInfo.resultDetails != NULL &&\r
9153         gameInfo.resultDetails[0] != NULLCHAR) {\r
9154         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
9155                 gameInfo.resultDetails);\r
9156     } else {\r
9157         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
9158     }\r
9159 \r
9160     fclose(f);\r
9161     return TRUE;\r
9162 }\r
9163 \r
9164 /* Save the current game to open file f and close the file */\r
9165 int\r
9166 SaveGame(f, dummy, dummy2)\r
9167      FILE *f;\r
9168      int dummy;\r
9169      char *dummy2;\r
9170 {\r
9171     if (gameMode == EditPosition) EditPositionDone();\r
9172     if (appData.oldSaveStyle)\r
9173       return SaveGameOldStyle(f);\r
9174     else\r
9175       return SaveGamePGN(f);\r
9176 }\r
9177 \r
9178 /* Save the current position to the given file */\r
9179 int\r
9180 SavePositionToFile(filename)\r
9181      char *filename;\r
9182 {\r
9183     FILE *f;\r
9184     char buf[MSG_SIZ];\r
9185 \r
9186     if (strcmp(filename, "-") == 0) {\r
9187         return SavePosition(stdout, 0, NULL);\r
9188     } else {\r
9189         f = fopen(filename, "a");\r
9190         if (f == NULL) {\r
9191             sprintf(buf, "Can't open \"%s\"", filename);\r
9192             DisplayError(buf, errno);\r
9193             return FALSE;\r
9194         } else {\r
9195             SavePosition(f, 0, NULL);\r
9196             return TRUE;\r
9197         }\r
9198     }\r
9199 }\r
9200 \r
9201 /* Save the current position to the given open file and close the file */\r
9202 int\r
9203 SavePosition(f, dummy, dummy2)\r
9204      FILE *f;\r
9205      int dummy;\r
9206      char *dummy2;\r
9207 {\r
9208     time_t tm;\r
9209     char *fen;\r
9210     \r
9211     if (appData.oldSaveStyle) {\r
9212         tm = time((time_t *) NULL);\r
9213     \r
9214         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
9215         PrintOpponents(f);\r
9216         fprintf(f, "[--------------\n");\r
9217         PrintPosition(f, currentMove);\r
9218         fprintf(f, "--------------]\n");\r
9219     } else {\r
9220         fen = PositionToFEN(currentMove, 1);\r
9221         fprintf(f, "%s\n", fen);\r
9222         free(fen);\r
9223     }\r
9224     fclose(f);\r
9225     return TRUE;\r
9226 }\r
9227 \r
9228 void\r
9229 ReloadCmailMsgEvent(unregister)\r
9230      int unregister;\r
9231 {\r
9232 #if !WIN32\r
9233     static char *inFilename = NULL;\r
9234     static char *outFilename;\r
9235     int i;\r
9236     struct stat inbuf, outbuf;\r
9237     int status;\r
9238     \r
9239     /* Any registered moves are unregistered if unregister is set, */\r
9240     /* i.e. invoked by the signal handler */\r
9241     if (unregister) {\r
9242         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9243             cmailMoveRegistered[i] = FALSE;\r
9244             if (cmailCommentList[i] != NULL) {\r
9245                 free(cmailCommentList[i]);\r
9246                 cmailCommentList[i] = NULL;\r
9247             }\r
9248         }\r
9249         nCmailMovesRegistered = 0;\r
9250     }\r
9251 \r
9252     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
9253         cmailResult[i] = CMAIL_NOT_RESULT;\r
9254     }\r
9255     nCmailResults = 0;\r
9256 \r
9257     if (inFilename == NULL) {\r
9258         /* Because the filenames are static they only get malloced once  */\r
9259         /* and they never get freed                                      */\r
9260         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
9261         sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
9262 \r
9263         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
9264         sprintf(outFilename, "%s.out", appData.cmailGameName);\r
9265     }\r
9266     \r
9267     status = stat(outFilename, &outbuf);\r
9268     if (status < 0) {\r
9269         cmailMailedMove = FALSE;\r
9270     } else {\r
9271         status = stat(inFilename, &inbuf);\r
9272         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
9273     }\r
9274     \r
9275     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
9276        counts the games, notes how each one terminated, etc.\r
9277        \r
9278        It would be nice to remove this kludge and instead gather all\r
9279        the information while building the game list.  (And to keep it\r
9280        in the game list nodes instead of having a bunch of fixed-size\r
9281        parallel arrays.)  Note this will require getting each game's\r
9282        termination from the PGN tags, as the game list builder does\r
9283        not process the game moves.  --mann\r
9284        */\r
9285     cmailMsgLoaded = TRUE;\r
9286     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
9287     \r
9288     /* Load first game in the file or popup game menu */\r
9289     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
9290 \r
9291 #endif /* !WIN32 */\r
9292     return;\r
9293 }\r
9294 \r
9295 int\r
9296 RegisterMove()\r
9297 {\r
9298     FILE *f;\r
9299     char string[MSG_SIZ];\r
9300 \r
9301     if (   cmailMailedMove\r
9302         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
9303         return TRUE;            /* Allow free viewing  */\r
9304     }\r
9305 \r
9306     /* Unregister move to ensure that we don't leave RegisterMove        */\r
9307     /* with the move registered when the conditions for registering no   */\r
9308     /* longer hold                                                       */\r
9309     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
9310         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
9311         nCmailMovesRegistered --;\r
9312 \r
9313         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
9314           {\r
9315               free(cmailCommentList[lastLoadGameNumber - 1]);\r
9316               cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
9317           }\r
9318     }\r
9319 \r
9320     if (cmailOldMove == -1) {\r
9321         DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0);\r
9322         return FALSE;\r
9323     }\r
9324 \r
9325     if (currentMove > cmailOldMove + 1) {\r
9326         DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0);\r
9327         return FALSE;\r
9328     }\r
9329 \r
9330     if (currentMove < cmailOldMove) {\r
9331         DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0);\r
9332         return FALSE;\r
9333     }\r
9334 \r
9335     if (forwardMostMove > currentMove) {\r
9336         /* Silently truncate extra moves */\r
9337         TruncateGame();\r
9338     }\r
9339 \r
9340     if (   (currentMove == cmailOldMove + 1)\r
9341         || (   (currentMove == cmailOldMove)\r
9342             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
9343                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
9344         if (gameInfo.result != GameUnfinished) {\r
9345             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
9346         }\r
9347 \r
9348         if (commentList[currentMove] != NULL) {\r
9349             cmailCommentList[lastLoadGameNumber - 1]\r
9350               = StrSave(commentList[currentMove]);\r
9351         }\r
9352         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
9353 \r
9354         if (appData.debugMode)\r
9355           fprintf(debugFP, "Saving %s for game %d\n",\r
9356                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
9357 \r
9358         sprintf(string,\r
9359                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
9360         \r
9361         f = fopen(string, "w");\r
9362         if (appData.oldSaveStyle) {\r
9363             SaveGameOldStyle(f); /* also closes the file */\r
9364             \r
9365             sprintf(string, "%s.pos.out", appData.cmailGameName);\r
9366             f = fopen(string, "w");\r
9367             SavePosition(f, 0, NULL); /* also closes the file */\r
9368         } else {\r
9369             fprintf(f, "{--------------\n");\r
9370             PrintPosition(f, currentMove);\r
9371             fprintf(f, "--------------}\n\n");\r
9372             \r
9373             SaveGame(f, 0, NULL); /* also closes the file*/\r
9374         }\r
9375         \r
9376         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
9377         nCmailMovesRegistered ++;\r
9378     } else if (nCmailGames == 1) {\r
9379         DisplayError("You have not made a move yet", 0);\r
9380         return FALSE;\r
9381     }\r
9382 \r
9383     return TRUE;\r
9384 }\r
9385 \r
9386 void\r
9387 MailMoveEvent()\r
9388 {\r
9389 #if !WIN32\r
9390     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
9391     FILE *commandOutput;\r
9392     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
9393     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */\r
9394     int nBuffers;\r
9395     int i;\r
9396     int archived;\r
9397     char *arcDir;\r
9398 \r
9399     if (! cmailMsgLoaded) {\r
9400         DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0);\r
9401         return;\r
9402     }\r
9403 \r
9404     if (nCmailGames == nCmailResults) {\r
9405         DisplayError("No unfinished games", 0);\r
9406         return;\r
9407     }\r
9408 \r
9409 #if CMAIL_PROHIBIT_REMAIL\r
9410     if (cmailMailedMove) {\r
9411         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
9412         DisplayError(msg, 0);\r
9413         return;\r
9414     }\r
9415 #endif\r
9416 \r
9417     if (! (cmailMailedMove || RegisterMove())) return;\r
9418     \r
9419     if (   cmailMailedMove\r
9420         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
9421         sprintf(string, partCommandString,\r
9422                 appData.debugMode ? " -v" : "", appData.cmailGameName);\r
9423         commandOutput = popen(string, "rb");\r
9424 \r
9425         if (commandOutput == NULL) {\r
9426             DisplayError("Failed to invoke cmail", 0);\r
9427         } else {\r
9428             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
9429                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
9430             }\r
9431             if (nBuffers > 1) {\r
9432                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
9433                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
9434                 nBytes = MSG_SIZ - 1;\r
9435             } else {\r
9436                 (void) memcpy(msg, buffer, nBytes);\r
9437             }\r
9438             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
9439 \r
9440             if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
9441                 cmailMailedMove = TRUE; /* Prevent >1 moves    */\r
9442 \r
9443                 archived = TRUE;\r
9444                 for (i = 0; i < nCmailGames; i ++) {\r
9445                     if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
9446                         archived = FALSE;\r
9447                     }\r
9448                 }\r
9449                 if (   archived\r
9450                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
9451                         != NULL)) {\r
9452                     sprintf(buffer, "%s/%s.%s.archive",\r
9453                             arcDir,\r
9454                             appData.cmailGameName,\r
9455                             gameInfo.date);\r
9456                     LoadGameFromFile(buffer, 1, buffer, FALSE);\r
9457                     cmailMsgLoaded = FALSE;\r
9458                 }\r
9459             }\r
9460 \r
9461             DisplayInformation(msg);\r
9462             pclose(commandOutput);\r
9463         }\r
9464     } else {\r
9465         if ((*cmailMsg) != '\0') {\r
9466             DisplayInformation(cmailMsg);\r
9467         }\r
9468     }\r
9469 \r
9470     return;\r
9471 #endif /* !WIN32 */\r
9472 }\r
9473 \r
9474 char *\r
9475 CmailMsg()\r
9476 {\r
9477 #if WIN32\r
9478     return NULL;\r
9479 #else\r
9480     int  prependComma = 0;\r
9481     char number[5];\r
9482     char string[MSG_SIZ];       /* Space for game-list */\r
9483     int  i;\r
9484     \r
9485     if (!cmailMsgLoaded) return "";\r
9486 \r
9487     if (cmailMailedMove) {\r
9488         sprintf(cmailMsg, "Waiting for reply from opponent\n");\r
9489     } else {\r
9490         /* Create a list of games left */\r
9491         sprintf(string, "[");\r
9492         for (i = 0; i < nCmailGames; i ++) {\r
9493             if (! (   cmailMoveRegistered[i]\r
9494                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
9495                 if (prependComma) {\r
9496                     sprintf(number, ",%d", i + 1);\r
9497                 } else {\r
9498                     sprintf(number, "%d", i + 1);\r
9499                     prependComma = 1;\r
9500                 }\r
9501                 \r
9502                 strcat(string, number);\r
9503             }\r
9504         }\r
9505         strcat(string, "]");\r
9506 \r
9507         if (nCmailMovesRegistered + nCmailResults == 0) {\r
9508             switch (nCmailGames) {\r
9509               case 1:\r
9510                 sprintf(cmailMsg,\r
9511                         "Still need to make move for game\n");\r
9512                 break;\r
9513                 \r
9514               case 2:\r
9515                 sprintf(cmailMsg,\r
9516                         "Still need to make moves for both games\n");\r
9517                 break;\r
9518                 \r
9519               default:\r
9520                 sprintf(cmailMsg,\r
9521                         "Still need to make moves for all %d games\n",\r
9522                         nCmailGames);\r
9523                 break;\r
9524             }\r
9525         } else {\r
9526             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
9527               case 1:\r
9528                 sprintf(cmailMsg,\r
9529                         "Still need to make a move for game %s\n",\r
9530                         string);\r
9531                 break;\r
9532                 \r
9533               case 0:\r
9534                 if (nCmailResults == nCmailGames) {\r
9535                     sprintf(cmailMsg, "No unfinished games\n");\r
9536                 } else {\r
9537                     sprintf(cmailMsg, "Ready to send mail\n");\r
9538                 }\r
9539                 break;\r
9540                 \r
9541               default:\r
9542                 sprintf(cmailMsg,\r
9543                         "Still need to make moves for games %s\n",\r
9544                         string);\r
9545             }\r
9546         }\r
9547     }\r
9548     return cmailMsg;\r
9549 #endif /* WIN32 */\r
9550 }\r
9551 \r
9552 void\r
9553 ResetGameEvent()\r
9554 {\r
9555     if (gameMode == Training)\r
9556       SetTrainingModeOff();\r
9557 \r
9558     Reset(TRUE, TRUE);\r
9559     cmailMsgLoaded = FALSE;\r
9560     if (appData.icsActive) {\r
9561       SendToICS(ics_prefix);\r
9562       SendToICS("refresh\n");\r
9563     }\r
9564 }\r
9565 \r
9566 void\r
9567 ExitEvent(status)\r
9568      int status;\r
9569 {\r
9570     exiting++;\r
9571     if (exiting > 2) {\r
9572       /* Give up on clean exit */\r
9573       exit(status);\r
9574     }\r
9575     if (exiting > 1) {\r
9576       /* Keep trying for clean exit */\r
9577       return;\r
9578     }\r
9579 \r
9580     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
9581 \r
9582     if (telnetISR != NULL) {\r
9583       RemoveInputSource(telnetISR);\r
9584     }\r
9585     if (icsPR != NoProc) {\r
9586       DestroyChildProcess(icsPR, TRUE);\r
9587     }\r
9588 #if 0\r
9589     /* Save game if resource set and not already saved by GameEnds() */\r
9590     if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
9591                              && forwardMostMove > 0) {\r
9592       if (*appData.saveGameFile != NULLCHAR) {\r
9593         SaveGameToFile(appData.saveGameFile, TRUE);\r
9594       } else if (appData.autoSaveGames) {\r
9595         AutoSaveGame();\r
9596       }\r
9597       if (*appData.savePositionFile != NULLCHAR) {\r
9598         SavePositionToFile(appData.savePositionFile);\r
9599       }\r
9600     }\r
9601     GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
9602 #else\r
9603     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
9604     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "aborted" : gameInfo.resultDetails, GE_PLAYER);\r
9605 #endif\r
9606     /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
9607     /* make sure this other one finishes before killing it!                  */\r
9608     if(endingGame) { int count = 0;\r
9609         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
9610         while(endingGame && count++ < 10) DoSleep(1);\r
9611         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
9612     }\r
9613 \r
9614     /* Kill off chess programs */\r
9615     if (first.pr != NoProc) {\r
9616         ExitAnalyzeMode();\r
9617         \r
9618         DoSleep( appData.delayBeforeQuit );\r
9619         SendToProgram("quit\n", &first);\r
9620         DoSleep( appData.delayAfterQuit );\r
9621         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
9622     }\r
9623     if (second.pr != NoProc) {\r
9624         DoSleep( appData.delayBeforeQuit );\r
9625         SendToProgram("quit\n", &second);\r
9626         DoSleep( appData.delayAfterQuit );\r
9627         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
9628     }\r
9629     if (first.isr != NULL) {\r
9630         RemoveInputSource(first.isr);\r
9631     }\r
9632     if (second.isr != NULL) {\r
9633         RemoveInputSource(second.isr);\r
9634     }\r
9635 \r
9636     ShutDownFrontEnd();\r
9637     exit(status);\r
9638 }\r
9639 \r
9640 void\r
9641 PauseEvent()\r
9642 {\r
9643     if (appData.debugMode)\r
9644         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
9645     if (pausing) {\r
9646         pausing = FALSE;\r
9647         ModeHighlight();\r
9648         if (gameMode == MachinePlaysWhite ||\r
9649             gameMode == MachinePlaysBlack) {\r
9650             StartClocks();\r
9651         } else {\r
9652             DisplayBothClocks();\r
9653         }\r
9654         if (gameMode == PlayFromGameFile) {\r
9655             if (appData.timeDelay >= 0) \r
9656                 AutoPlayGameLoop();\r
9657         } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
9658             Reset(FALSE, TRUE);\r
9659             SendToICS(ics_prefix);\r
9660             SendToICS("refresh\n");\r
9661         } else if (currentMove < forwardMostMove) {\r
9662             ForwardInner(forwardMostMove);\r
9663         }\r
9664         pauseExamInvalid = FALSE;\r
9665     } else {\r
9666         switch (gameMode) {\r
9667           default:\r
9668             return;\r
9669           case IcsExamining:\r
9670             pauseExamForwardMostMove = forwardMostMove;\r
9671             pauseExamInvalid = FALSE;\r
9672             /* fall through */\r
9673           case IcsObserving:\r
9674           case IcsPlayingWhite:\r
9675           case IcsPlayingBlack:\r
9676             pausing = TRUE;\r
9677             ModeHighlight();\r
9678             return;\r
9679           case PlayFromGameFile:\r
9680             (void) StopLoadGameTimer();\r
9681             pausing = TRUE;\r
9682             ModeHighlight();\r
9683             break;\r
9684           case BeginningOfGame:\r
9685             if (appData.icsActive) return;\r
9686             /* else fall through */\r
9687           case MachinePlaysWhite:\r
9688           case MachinePlaysBlack:\r
9689           case TwoMachinesPlay:\r
9690             if (forwardMostMove == 0)\r
9691               return;           /* don't pause if no one has moved */\r
9692             if ((gameMode == MachinePlaysWhite &&\r
9693                  !WhiteOnMove(forwardMostMove)) ||\r
9694                 (gameMode == MachinePlaysBlack &&\r
9695                  WhiteOnMove(forwardMostMove))) {\r
9696                 StopClocks();\r
9697             }\r
9698             pausing = TRUE;\r
9699             ModeHighlight();\r
9700             break;\r
9701         }\r
9702     }\r
9703 }\r
9704 \r
9705 void\r
9706 EditCommentEvent()\r
9707 {\r
9708     char title[MSG_SIZ];\r
9709 \r
9710     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
9711         strcpy(title, "Edit comment");\r
9712     } else {\r
9713         sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1,\r
9714                 WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
9715                 parseList[currentMove - 1]);\r
9716     }\r
9717 \r
9718     EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
9719 }\r
9720 \r
9721 \r
9722 void\r
9723 EditTagsEvent()\r
9724 {\r
9725     char *tags = PGNTags(&gameInfo);\r
9726     EditTagsPopUp(tags);\r
9727     free(tags);\r
9728 }\r
9729 \r
9730 void\r
9731 AnalyzeModeEvent()\r
9732 {\r
9733     if (appData.noChessProgram || gameMode == AnalyzeMode)\r
9734       return;\r
9735 \r
9736     if (gameMode != AnalyzeFile) {\r
9737         EditGameEvent();\r
9738         if (gameMode != EditGame) return;\r
9739         ResurrectChessProgram();\r
9740         SendToProgram("analyze\n", &first);\r
9741         first.analyzing = TRUE;\r
9742         /*first.maybeThinking = TRUE;*/\r
9743         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
9744         AnalysisPopUp("Analysis",\r
9745                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
9746     }\r
9747     gameMode = AnalyzeMode;\r
9748     pausing = FALSE;\r
9749     ModeHighlight();\r
9750     SetGameInfo();\r
9751 \r
9752     StartAnalysisClock();\r
9753     GetTimeMark(&lastNodeCountTime);\r
9754     lastNodeCount = 0;\r
9755 }\r
9756 \r
9757 void\r
9758 AnalyzeFileEvent()\r
9759 {\r
9760     if (appData.noChessProgram || gameMode == AnalyzeFile)\r
9761       return;\r
9762 \r
9763     if (gameMode != AnalyzeMode) {\r
9764         EditGameEvent();\r
9765         if (gameMode != EditGame) return;\r
9766         ResurrectChessProgram();\r
9767         SendToProgram("analyze\n", &first);\r
9768         first.analyzing = TRUE;\r
9769         /*first.maybeThinking = TRUE;*/\r
9770         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
9771         AnalysisPopUp("Analysis",\r
9772                       "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.");\r
9773     }\r
9774     gameMode = AnalyzeFile;\r
9775     pausing = FALSE;\r
9776     ModeHighlight();\r
9777     SetGameInfo();\r
9778 \r
9779     StartAnalysisClock();\r
9780     GetTimeMark(&lastNodeCountTime);\r
9781     lastNodeCount = 0;\r
9782 }\r
9783 \r
9784 void\r
9785 MachineWhiteEvent()\r
9786 {\r
9787     char buf[MSG_SIZ];\r
9788 \r
9789     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
9790       return;\r
9791 \r
9792 \r
9793     if (gameMode == PlayFromGameFile || \r
9794         gameMode == TwoMachinesPlay  || \r
9795         gameMode == Training         || \r
9796         gameMode == AnalyzeMode      || \r
9797         gameMode == EndOfGame)\r
9798         EditGameEvent();\r
9799 \r
9800     if (gameMode == EditPosition) \r
9801         EditPositionDone();\r
9802 \r
9803     if (!WhiteOnMove(currentMove)) {\r
9804         DisplayError("It is not White's turn", 0);\r
9805         return;\r
9806     }\r
9807   \r
9808     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
9809       ExitAnalyzeMode();\r
9810 \r
9811     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
9812         gameMode == AnalyzeFile)\r
9813         TruncateGame();\r
9814 \r
9815     ResurrectChessProgram();    /* in case it isn't running */\r
9816     gameMode = MachinePlaysWhite;\r
9817     pausing = FALSE;\r
9818     ModeHighlight();\r
9819     SetGameInfo();\r
9820     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
9821     DisplayTitle(buf);\r
9822     if (first.sendName) {\r
9823       sprintf(buf, "name %s\n", gameInfo.black);\r
9824       SendToProgram(buf, &first);\r
9825     }\r
9826     if (first.sendTime) {\r
9827       if (first.useColors) {\r
9828         SendToProgram("black\n", &first); /*gnu kludge*/\r
9829       }\r
9830       SendTimeRemaining(&first, TRUE);\r
9831     }\r
9832     if (first.useColors) {\r
9833       SendToProgram("white\ngo\n", &first);\r
9834     } else {\r
9835       SendToProgram("go\n", &first);\r
9836     }\r
9837     SetMachineThinkingEnables();\r
9838     first.maybeThinking = TRUE;\r
9839     StartClocks();\r
9840 \r
9841     if (appData.autoFlipView && !flipView) {\r
9842       flipView = !flipView;\r
9843       DrawPosition(FALSE, NULL);\r
9844     }\r
9845 }\r
9846 \r
9847 void\r
9848 MachineBlackEvent()\r
9849 {\r
9850     char buf[MSG_SIZ];\r
9851 \r
9852     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
9853         return;\r
9854 \r
9855 \r
9856     if (gameMode == PlayFromGameFile || \r
9857         gameMode == TwoMachinesPlay  || \r
9858         gameMode == Training         || \r
9859         gameMode == AnalyzeMode      || \r
9860         gameMode == EndOfGame)\r
9861         EditGameEvent();\r
9862 \r
9863     if (gameMode == EditPosition) \r
9864         EditPositionDone();\r
9865 \r
9866     if (WhiteOnMove(currentMove)) {\r
9867         DisplayError("It is not Black's turn", 0);\r
9868         return;\r
9869     }\r
9870     \r
9871     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
9872       ExitAnalyzeMode();\r
9873 \r
9874     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
9875         gameMode == AnalyzeFile)\r
9876         TruncateGame();\r
9877 \r
9878     ResurrectChessProgram();    /* in case it isn't running */\r
9879     gameMode = MachinePlaysBlack;\r
9880     pausing = FALSE;\r
9881     ModeHighlight();\r
9882     SetGameInfo();\r
9883     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
9884     DisplayTitle(buf);\r
9885     if (first.sendName) {\r
9886       sprintf(buf, "name %s\n", gameInfo.white);\r
9887       SendToProgram(buf, &first);\r
9888     }\r
9889     if (first.sendTime) {\r
9890       if (first.useColors) {\r
9891         SendToProgram("white\n", &first); /*gnu kludge*/\r
9892       }\r
9893       SendTimeRemaining(&first, FALSE);\r
9894     }\r
9895     if (first.useColors) {\r
9896       SendToProgram("black\ngo\n", &first);\r
9897     } else {\r
9898       SendToProgram("go\n", &first);\r
9899     }\r
9900     SetMachineThinkingEnables();\r
9901     first.maybeThinking = TRUE;\r
9902     StartClocks();\r
9903 \r
9904     if (appData.autoFlipView && flipView) {\r
9905       flipView = !flipView;\r
9906       DrawPosition(FALSE, NULL);\r
9907     }\r
9908 }\r
9909 \r
9910 \r
9911 void\r
9912 DisplayTwoMachinesTitle()\r
9913 {\r
9914     char buf[MSG_SIZ];\r
9915     if (appData.matchGames > 0) {\r
9916         if (first.twoMachinesColor[0] == 'w') {\r
9917             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
9918                     gameInfo.white, gameInfo.black,\r
9919                     first.matchWins, second.matchWins,\r
9920                     matchGame - 1 - (first.matchWins + second.matchWins));\r
9921         } else {\r
9922             sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
9923                     gameInfo.white, gameInfo.black,\r
9924                     second.matchWins, first.matchWins,\r
9925                     matchGame - 1 - (first.matchWins + second.matchWins));\r
9926         }\r
9927     } else {\r
9928         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
9929     }\r
9930     DisplayTitle(buf);\r
9931 }\r
9932 \r
9933 void\r
9934 TwoMachinesEvent P((void))\r
9935 {\r
9936     int i;\r
9937     char buf[MSG_SIZ];\r
9938     ChessProgramState *onmove;\r
9939     \r
9940     if (appData.noChessProgram) return;\r
9941 \r
9942     switch (gameMode) {\r
9943       case TwoMachinesPlay:\r
9944         return;\r
9945       case MachinePlaysWhite:\r
9946       case MachinePlaysBlack:\r
9947         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
9948             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
9949             return;\r
9950         }\r
9951         /* fall through */\r
9952       case BeginningOfGame:\r
9953       case PlayFromGameFile:\r
9954       case EndOfGame:\r
9955         EditGameEvent();\r
9956         if (gameMode != EditGame) return;\r
9957         break;\r
9958       case EditPosition:\r
9959         EditPositionDone();\r
9960         break;\r
9961       case AnalyzeMode:\r
9962       case AnalyzeFile:\r
9963         ExitAnalyzeMode();\r
9964         break;\r
9965       case EditGame:\r
9966       default:\r
9967         break;\r
9968     }\r
9969 \r
9970     forwardMostMove = currentMove;\r
9971     ResurrectChessProgram();    /* in case first program isn't running */\r
9972 \r
9973     if (second.pr == NULL) {\r
9974         StartChessProgram(&second);\r
9975         if (second.protocolVersion == 1) {\r
9976           TwoMachinesEventIfReady();\r
9977         } else {\r
9978           /* kludge: allow timeout for initial "feature" command */\r
9979           FreezeUI();\r
9980           DisplayMessage("", "Starting second chess program");\r
9981           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
9982         }\r
9983         return;\r
9984     }\r
9985     DisplayMessage("", "");\r
9986     InitChessProgram(&second, FALSE);\r
9987     SendToProgram("force\n", &second);\r
9988     if (startedFromSetupPosition) {\r
9989         SendBoard(&second, backwardMostMove);\r
9990     if (appData.debugMode) {\r
9991         fprintf(debugFP, "Two Machines\n");\r
9992     }\r
9993     }\r
9994     for (i = backwardMostMove; i < forwardMostMove; i++) {\r
9995         SendMoveToProgram(i, &second);\r
9996     }\r
9997 \r
9998     gameMode = TwoMachinesPlay;\r
9999     pausing = FALSE;\r
10000     ModeHighlight();\r
10001     SetGameInfo();\r
10002     DisplayTwoMachinesTitle();\r
10003     firstMove = TRUE;\r
10004     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
10005         onmove = &first;\r
10006     } else {\r
10007         onmove = &second;\r
10008     }\r
10009 \r
10010     SendToProgram(first.computerString, &first);\r
10011     if (first.sendName) {\r
10012       sprintf(buf, "name %s\n", second.tidy);\r
10013       SendToProgram(buf, &first);\r
10014     }\r
10015     SendToProgram(second.computerString, &second);\r
10016     if (second.sendName) {\r
10017       sprintf(buf, "name %s\n", first.tidy);\r
10018       SendToProgram(buf, &second);\r
10019     }\r
10020 \r
10021     if (!first.sendTime || !second.sendTime) {\r
10022         ResetClocks();\r
10023         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10024         timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10025     }\r
10026     if (onmove->sendTime) {\r
10027       if (onmove->useColors) {\r
10028         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
10029       }\r
10030       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
10031     }\r
10032     if (onmove->useColors) {\r
10033       SendToProgram(onmove->twoMachinesColor, onmove);\r
10034     }\r
10035     SendToProgram("go\n", onmove);\r
10036     onmove->maybeThinking = TRUE;\r
10037     SetMachineThinkingEnables();\r
10038 \r
10039     StartClocks();\r
10040 }\r
10041 \r
10042 void\r
10043 TrainingEvent()\r
10044 {\r
10045     if (gameMode == Training) {\r
10046       SetTrainingModeOff();\r
10047       gameMode = PlayFromGameFile;\r
10048       DisplayMessage("", "Training mode off");\r
10049     } else {\r
10050       gameMode = Training;\r
10051       animateTraining = appData.animate;\r
10052 \r
10053       /* make sure we are not already at the end of the game */\r
10054       if (currentMove < forwardMostMove) {\r
10055         SetTrainingModeOn();\r
10056         DisplayMessage("", "Training mode on");\r
10057       } else {\r
10058         gameMode = PlayFromGameFile;\r
10059         DisplayError("Already at end of game", 0);\r
10060       }\r
10061     }\r
10062     ModeHighlight();\r
10063 }\r
10064 \r
10065 void\r
10066 IcsClientEvent()\r
10067 {\r
10068     if (!appData.icsActive) return;\r
10069     switch (gameMode) {\r
10070       case IcsPlayingWhite:\r
10071       case IcsPlayingBlack:\r
10072       case IcsObserving:\r
10073       case IcsIdle:\r
10074       case BeginningOfGame:\r
10075       case IcsExamining:\r
10076         return;\r
10077 \r
10078       case EditGame:\r
10079         break;\r
10080 \r
10081       case EditPosition:\r
10082         EditPositionDone();\r
10083         break;\r
10084 \r
10085       case AnalyzeMode:\r
10086       case AnalyzeFile:\r
10087         ExitAnalyzeMode();\r
10088         break;\r
10089         \r
10090       default:\r
10091         EditGameEvent();\r
10092         break;\r
10093     }\r
10094 \r
10095     gameMode = IcsIdle;\r
10096     ModeHighlight();\r
10097     return;\r
10098 }\r
10099 \r
10100 \r
10101 void\r
10102 EditGameEvent()\r
10103 {\r
10104     int i;\r
10105 \r
10106     switch (gameMode) {\r
10107       case Training:\r
10108         SetTrainingModeOff();\r
10109         break;\r
10110       case MachinePlaysWhite:\r
10111       case MachinePlaysBlack:\r
10112       case BeginningOfGame:\r
10113         SendToProgram("force\n", &first);\r
10114         SetUserThinkingEnables();\r
10115         break;\r
10116       case PlayFromGameFile:\r
10117         (void) StopLoadGameTimer();\r
10118         if (gameFileFP != NULL) {\r
10119             gameFileFP = NULL;\r
10120         }\r
10121         break;\r
10122       case EditPosition:\r
10123         EditPositionDone();\r
10124         break;\r
10125       case AnalyzeMode:\r
10126       case AnalyzeFile:\r
10127         ExitAnalyzeMode();\r
10128         SendToProgram("force\n", &first);\r
10129         break;\r
10130       case TwoMachinesPlay:\r
10131         GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
10132         ResurrectChessProgram();\r
10133         SetUserThinkingEnables();\r
10134         break;\r
10135       case EndOfGame:\r
10136         ResurrectChessProgram();\r
10137         break;\r
10138       case IcsPlayingBlack:\r
10139       case IcsPlayingWhite:\r
10140         DisplayError("Warning: You are still playing a game", 0);\r
10141         break;\r
10142       case IcsObserving:\r
10143         DisplayError("Warning: You are still observing a game", 0);\r
10144         break;\r
10145       case IcsExamining:\r
10146         DisplayError("Warning: You are still examining a game", 0);\r
10147         break;\r
10148       case IcsIdle:\r
10149         break;\r
10150       case EditGame:\r
10151       default:\r
10152         return;\r
10153     }\r
10154     \r
10155     pausing = FALSE;\r
10156     StopClocks();\r
10157     first.offeredDraw = second.offeredDraw = 0;\r
10158 \r
10159     if (gameMode == PlayFromGameFile) {\r
10160         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10161         blackTimeRemaining = timeRemaining[1][currentMove];\r
10162         DisplayTitle("");\r
10163     }\r
10164 \r
10165     if (gameMode == MachinePlaysWhite ||\r
10166         gameMode == MachinePlaysBlack ||\r
10167         gameMode == TwoMachinesPlay ||\r
10168         gameMode == EndOfGame) {\r
10169         i = forwardMostMove;\r
10170         while (i > currentMove) {\r
10171             SendToProgram("undo\n", &first);\r
10172             i--;\r
10173         }\r
10174         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10175         blackTimeRemaining = timeRemaining[1][currentMove];\r
10176         DisplayBothClocks();\r
10177         if (whiteFlag || blackFlag) {\r
10178             whiteFlag = blackFlag = 0;\r
10179         }\r
10180         DisplayTitle("");\r
10181     }           \r
10182     \r
10183     gameMode = EditGame;\r
10184     ModeHighlight();\r
10185     SetGameInfo();\r
10186 }\r
10187 \r
10188 \r
10189 void\r
10190 EditPositionEvent()\r
10191 {\r
10192     if (gameMode == EditPosition) {\r
10193         EditGameEvent();\r
10194         return;\r
10195     }\r
10196     \r
10197     EditGameEvent();\r
10198     if (gameMode != EditGame) return;\r
10199     \r
10200     gameMode = EditPosition;\r
10201     ModeHighlight();\r
10202     SetGameInfo();\r
10203     if (currentMove > 0)\r
10204       CopyBoard(boards[0], boards[currentMove]);\r
10205     \r
10206     blackPlaysFirst = !WhiteOnMove(currentMove);\r
10207     ResetClocks();\r
10208     currentMove = forwardMostMove = backwardMostMove = 0;\r
10209     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10210     DisplayMove(-1);\r
10211 }\r
10212 \r
10213 void\r
10214 ExitAnalyzeMode()\r
10215 {\r
10216     if (first.analysisSupport && first.analyzing) {\r
10217       SendToProgram("exit\n", &first);\r
10218       first.analyzing = FALSE;\r
10219     }\r
10220     AnalysisPopDown();\r
10221     thinkOutput[0] = NULLCHAR;\r
10222 }\r
10223 \r
10224 void\r
10225 EditPositionDone()\r
10226 {\r
10227     startedFromSetupPosition = TRUE;\r
10228     InitChessProgram(&first, FALSE);\r
10229     SendToProgram("force\n", &first);\r
10230     if (blackPlaysFirst) {\r
10231         strcpy(moveList[0], "");\r
10232         strcpy(parseList[0], "");\r
10233         currentMove = forwardMostMove = backwardMostMove = 1;\r
10234         CopyBoard(boards[1], boards[0]);\r
10235     } else {\r
10236         currentMove = forwardMostMove = backwardMostMove = 0;\r
10237     }\r
10238     SendBoard(&first, forwardMostMove);\r
10239     if (appData.debugMode) {\r
10240         fprintf(debugFP, "EditPosDone\n");\r
10241     }\r
10242     DisplayTitle("");\r
10243     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
10244     timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
10245     gameMode = EditGame;\r
10246     ModeHighlight();\r
10247     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
10248     ClearHighlights(); /* [AS] */\r
10249 }\r
10250 \r
10251 /* Pause for `ms' milliseconds */\r
10252 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10253 void\r
10254 TimeDelay(ms)\r
10255      long ms;\r
10256 {\r
10257     TimeMark m1, m2;\r
10258 \r
10259     GetTimeMark(&m1);\r
10260     do {\r
10261         GetTimeMark(&m2);\r
10262     } while (SubtractTimeMarks(&m2, &m1) < ms);\r
10263 }\r
10264 \r
10265 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
10266 void\r
10267 SendMultiLineToICS(buf)\r
10268      char *buf;\r
10269 {\r
10270     char temp[MSG_SIZ+1], *p;\r
10271     int len;\r
10272 \r
10273     len = strlen(buf);\r
10274     if (len > MSG_SIZ)\r
10275       len = MSG_SIZ;\r
10276   \r
10277     strncpy(temp, buf, len);\r
10278     temp[len] = 0;\r
10279 \r
10280     p = temp;\r
10281     while (*p) {\r
10282         if (*p == '\n' || *p == '\r')\r
10283           *p = ' ';\r
10284         ++p;\r
10285     }\r
10286 \r
10287     strcat(temp, "\n");\r
10288     SendToICS(temp);\r
10289     SendToPlayer(temp, strlen(temp));\r
10290 }\r
10291 \r
10292 void\r
10293 SetWhiteToPlayEvent()\r
10294 {\r
10295     if (gameMode == EditPosition) {\r
10296         blackPlaysFirst = FALSE;\r
10297         DisplayBothClocks();    /* works because currentMove is 0 */\r
10298     } else if (gameMode == IcsExamining) {\r
10299         SendToICS(ics_prefix);\r
10300         SendToICS("tomove white\n");\r
10301     }\r
10302 }\r
10303 \r
10304 void\r
10305 SetBlackToPlayEvent()\r
10306 {\r
10307     if (gameMode == EditPosition) {\r
10308         blackPlaysFirst = TRUE;\r
10309         currentMove = 1;        /* kludge */\r
10310         DisplayBothClocks();\r
10311         currentMove = 0;\r
10312     } else if (gameMode == IcsExamining) {\r
10313         SendToICS(ics_prefix);\r
10314         SendToICS("tomove black\n");\r
10315     }\r
10316 }\r
10317 \r
10318 void\r
10319 EditPositionMenuEvent(selection, x, y)\r
10320      ChessSquare selection;\r
10321      int x, y;\r
10322 {\r
10323     char buf[MSG_SIZ];\r
10324     ChessSquare piece = boards[0][y][x];\r
10325 \r
10326     if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
10327 \r
10328     switch (selection) {\r
10329       case ClearBoard:\r
10330         if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
10331             SendToICS(ics_prefix);\r
10332             SendToICS("bsetup clear\n");\r
10333         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
10334             SendToICS(ics_prefix);\r
10335             SendToICS("clearboard\n");\r
10336         } else {\r
10337             for (x = 0; x < BOARD_WIDTH; x++) {\r
10338                 for (y = 0; y < BOARD_HEIGHT; y++) {\r
10339                     if (gameMode == IcsExamining) {\r
10340                         if (boards[currentMove][y][x] != EmptySquare) {\r
10341                             sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
10342                                     AAA + x, ONE + y);\r
10343                             SendToICS(buf);\r
10344                         }\r
10345                     } else {\r
10346                         boards[0][y][x] = EmptySquare;\r
10347                     }\r
10348                 }\r
10349             }\r
10350         }\r
10351         if (gameMode == EditPosition) {\r
10352             DrawPosition(FALSE, boards[0]);\r
10353         }\r
10354         break;\r
10355 \r
10356       case WhitePlay:\r
10357         SetWhiteToPlayEvent();\r
10358         break;\r
10359 \r
10360       case BlackPlay:\r
10361         SetBlackToPlayEvent();\r
10362         break;\r
10363 \r
10364       case EmptySquare:\r
10365         if (gameMode == IcsExamining) {\r
10366             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
10367             SendToICS(buf);\r
10368         } else {\r
10369             boards[0][y][x] = EmptySquare;\r
10370             DrawPosition(FALSE, boards[0]);\r
10371         }\r
10372         break;\r
10373 \r
10374       case PromotePiece:\r
10375         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
10376            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {\r
10377             selection = (ChessSquare) (PROMOTED piece);\r
10378         } else if(piece == EmptySquare) selection = WhiteSilver;\r
10379         else selection = (ChessSquare)((int)piece - 1);\r
10380         goto defaultlabel;\r
10381 \r
10382       case DemotePiece:\r
10383         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
10384            piece > (int)BlackMan && piece <= (int)BlackKing   ) {\r
10385             selection = (ChessSquare) (DEMOTED piece);\r
10386         } else if(piece == EmptySquare) selection = BlackSilver;\r
10387         else selection = (ChessSquare)((int)piece + 1);       \r
10388         goto defaultlabel;\r
10389 \r
10390       case WhiteQueen:\r
10391       case BlackQueen:\r
10392         if(gameInfo.variant == VariantShatranj ||\r
10393            gameInfo.variant == VariantXiangqi  ||\r
10394            gameInfo.variant == VariantCourier    )\r
10395             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
10396         goto defaultlabel;\r
10397 \r
10398       case WhiteKing:\r
10399       case BlackKing:\r
10400         if(gameInfo.variant == VariantXiangqi)\r
10401             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
10402         if(gameInfo.variant == VariantKnightmate)\r
10403             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
10404       default:\r
10405         defaultlabel:\r
10406         if (gameMode == IcsExamining) {\r
10407             sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
10408                     PieceToChar(selection), AAA + x, ONE + y);\r
10409             SendToICS(buf);\r
10410         } else {\r
10411             boards[0][y][x] = selection;\r
10412             DrawPosition(FALSE, boards[0]);\r
10413         }\r
10414         break;\r
10415     }\r
10416 }\r
10417 \r
10418 \r
10419 void\r
10420 DropMenuEvent(selection, x, y)\r
10421      ChessSquare selection;\r
10422      int x, y;\r
10423 {\r
10424     ChessMove moveType;\r
10425 \r
10426     switch (gameMode) {\r
10427       case IcsPlayingWhite:\r
10428       case MachinePlaysBlack:\r
10429         if (!WhiteOnMove(currentMove)) {\r
10430             DisplayMoveError("It is Black's turn");\r
10431             return;\r
10432         }\r
10433         moveType = WhiteDrop;\r
10434         break;\r
10435       case IcsPlayingBlack:\r
10436       case MachinePlaysWhite:\r
10437         if (WhiteOnMove(currentMove)) {\r
10438             DisplayMoveError("It is White's turn");\r
10439             return;\r
10440         }\r
10441         moveType = BlackDrop;\r
10442         break;\r
10443       case EditGame:\r
10444         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
10445         break;\r
10446       default:\r
10447         return;\r
10448     }\r
10449 \r
10450     if (moveType == BlackDrop && selection < BlackPawn) {\r
10451       selection = (ChessSquare) ((int) selection\r
10452                                  + (int) BlackPawn - (int) WhitePawn);\r
10453     }\r
10454     if (boards[currentMove][y][x] != EmptySquare) {\r
10455         DisplayMoveError("That square is occupied");\r
10456         return;\r
10457     }\r
10458 \r
10459     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
10460 }\r
10461 \r
10462 void\r
10463 AcceptEvent()\r
10464 {\r
10465     /* Accept a pending offer of any kind from opponent */\r
10466     \r
10467     if (appData.icsActive) {\r
10468         SendToICS(ics_prefix);\r
10469         SendToICS("accept\n");\r
10470     } else if (cmailMsgLoaded) {\r
10471         if (currentMove == cmailOldMove &&\r
10472             commentList[cmailOldMove] != NULL &&\r
10473             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
10474                    "Black offers a draw" : "White offers a draw")) {\r
10475             TruncateGame();\r
10476             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
10477             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
10478         } else {\r
10479             DisplayError("There is no pending offer on this move", 0);\r
10480             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
10481         }\r
10482     } else {\r
10483         /* Not used for offers from chess program */\r
10484     }\r
10485 }\r
10486 \r
10487 void\r
10488 DeclineEvent()\r
10489 {\r
10490     /* Decline a pending offer of any kind from opponent */\r
10491     \r
10492     if (appData.icsActive) {\r
10493         SendToICS(ics_prefix);\r
10494         SendToICS("decline\n");\r
10495     } else if (cmailMsgLoaded) {\r
10496         if (currentMove == cmailOldMove &&\r
10497             commentList[cmailOldMove] != NULL &&\r
10498             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
10499                    "Black offers a draw" : "White offers a draw")) {\r
10500 #ifdef NOTDEF\r
10501             AppendComment(cmailOldMove, "Draw declined");\r
10502             DisplayComment(cmailOldMove - 1, "Draw declined");\r
10503 #endif /*NOTDEF*/\r
10504         } else {\r
10505             DisplayError("There is no pending offer on this move", 0);\r
10506         }\r
10507     } else {\r
10508         /* Not used for offers from chess program */\r
10509     }\r
10510 }\r
10511 \r
10512 void\r
10513 RematchEvent()\r
10514 {\r
10515     /* Issue ICS rematch command */\r
10516     if (appData.icsActive) {\r
10517         SendToICS(ics_prefix);\r
10518         SendToICS("rematch\n");\r
10519     }\r
10520 }\r
10521 \r
10522 void\r
10523 CallFlagEvent()\r
10524 {\r
10525     /* Call your opponent's flag (claim a win on time) */\r
10526     if (appData.icsActive) {\r
10527         SendToICS(ics_prefix);\r
10528         SendToICS("flag\n");\r
10529     } else {\r
10530         switch (gameMode) {\r
10531           default:\r
10532             return;\r
10533           case MachinePlaysWhite:\r
10534             if (whiteFlag) {\r
10535                 if (blackFlag)\r
10536                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
10537                            GE_PLAYER);\r
10538                 else\r
10539                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
10540             } else {\r
10541                 DisplayError("Your opponent is not out of time", 0);\r
10542             }\r
10543             break;\r
10544           case MachinePlaysBlack:\r
10545             if (blackFlag) {\r
10546                 if (whiteFlag)\r
10547                   GameEnds(GameIsDrawn, "Both players ran out of time",\r
10548                            GE_PLAYER);\r
10549                 else\r
10550                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
10551             } else {\r
10552                 DisplayError("Your opponent is not out of time", 0);\r
10553             }\r
10554             break;\r
10555         }\r
10556     }\r
10557 }\r
10558 \r
10559 void\r
10560 DrawEvent()\r
10561 {\r
10562     /* Offer draw or accept pending draw offer from opponent */\r
10563     \r
10564     if (appData.icsActive) {\r
10565         /* Note: tournament rules require draw offers to be\r
10566            made after you make your move but before you punch\r
10567            your clock.  Currently ICS doesn't let you do that;\r
10568            instead, you immediately punch your clock after making\r
10569            a move, but you can offer a draw at any time. */\r
10570         \r
10571         SendToICS(ics_prefix);\r
10572         SendToICS("draw\n");\r
10573     } else if (cmailMsgLoaded) {\r
10574         if (currentMove == cmailOldMove &&\r
10575             commentList[cmailOldMove] != NULL &&\r
10576             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
10577                    "Black offers a draw" : "White offers a draw")) {\r
10578             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
10579             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
10580         } else if (currentMove == cmailOldMove + 1) {\r
10581             char *offer = WhiteOnMove(cmailOldMove) ?\r
10582               "White offers a draw" : "Black offers a draw";\r
10583             AppendComment(currentMove, offer);\r
10584             DisplayComment(currentMove - 1, offer);\r
10585             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
10586         } else {\r
10587             DisplayError("You must make your move before offering a draw", 0);\r
10588             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
10589         }\r
10590     } else if (first.offeredDraw) {\r
10591         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
10592     } else {\r
10593         if (first.sendDrawOffers) {\r
10594             SendToProgram("draw\n", &first);\r
10595             userOfferedDraw = TRUE;\r
10596         }\r
10597     }\r
10598 }\r
10599 \r
10600 void\r
10601 AdjournEvent()\r
10602 {\r
10603     /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
10604     \r
10605     if (appData.icsActive) {\r
10606         SendToICS(ics_prefix);\r
10607         SendToICS("adjourn\n");\r
10608     } else {\r
10609         /* Currently GNU Chess doesn't offer or accept Adjourns */\r
10610     }\r
10611 }\r
10612 \r
10613 \r
10614 void\r
10615 AbortEvent()\r
10616 {\r
10617     /* Offer Abort or accept pending Abort offer from opponent */\r
10618     \r
10619     if (appData.icsActive) {\r
10620         SendToICS(ics_prefix);\r
10621         SendToICS("abort\n");\r
10622     } else {\r
10623         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
10624     }\r
10625 }\r
10626 \r
10627 void\r
10628 ResignEvent()\r
10629 {\r
10630     /* Resign.  You can do this even if it's not your turn. */\r
10631     \r
10632     if (appData.icsActive) {\r
10633         SendToICS(ics_prefix);\r
10634         SendToICS("resign\n");\r
10635     } else {\r
10636         switch (gameMode) {\r
10637           case MachinePlaysWhite:\r
10638             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
10639             break;\r
10640           case MachinePlaysBlack:\r
10641             GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
10642             break;\r
10643           case EditGame:\r
10644             if (cmailMsgLoaded) {\r
10645                 TruncateGame();\r
10646                 if (WhiteOnMove(cmailOldMove)) {\r
10647                     GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
10648                 } else {\r
10649                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
10650                 }\r
10651                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
10652             }\r
10653             break;\r
10654           default:\r
10655             break;\r
10656         }\r
10657     }\r
10658 }\r
10659 \r
10660 \r
10661 void\r
10662 StopObservingEvent()\r
10663 {\r
10664     /* Stop observing current games */\r
10665     SendToICS(ics_prefix);\r
10666     SendToICS("unobserve\n");\r
10667 }\r
10668 \r
10669 void\r
10670 StopExaminingEvent()\r
10671 {\r
10672     /* Stop observing current game */\r
10673     SendToICS(ics_prefix);\r
10674     SendToICS("unexamine\n");\r
10675 }\r
10676 \r
10677 void\r
10678 ForwardInner(target)\r
10679      int target;\r
10680 {\r
10681     int limit;\r
10682 \r
10683     if (appData.debugMode)\r
10684         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
10685                 target, currentMove, forwardMostMove);\r
10686 \r
10687     if (gameMode == EditPosition)\r
10688       return;\r
10689 \r
10690     if (gameMode == PlayFromGameFile && !pausing)\r
10691       PauseEvent();\r
10692     \r
10693     if (gameMode == IcsExamining && pausing)\r
10694       limit = pauseExamForwardMostMove;\r
10695     else\r
10696       limit = forwardMostMove;\r
10697     \r
10698     if (target > limit) target = limit;\r
10699 \r
10700     if (target > 0 && moveList[target - 1][0]) {\r
10701         int fromX, fromY, toX, toY;\r
10702         toX = moveList[target - 1][2] - AAA;\r
10703         toY = moveList[target - 1][3] - ONE;\r
10704         if (moveList[target - 1][1] == '@') {\r
10705             if (appData.highlightLastMove) {\r
10706                 SetHighlights(-1, -1, toX, toY);\r
10707             }\r
10708         } else {\r
10709             fromX = moveList[target - 1][0] - AAA;\r
10710             fromY = moveList[target - 1][1] - ONE;\r
10711             if (target == currentMove + 1) {\r
10712                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
10713             }\r
10714             if (appData.highlightLastMove) {\r
10715                 SetHighlights(fromX, fromY, toX, toY);\r
10716             }\r
10717         }\r
10718     }\r
10719     if (gameMode == EditGame || gameMode == AnalyzeMode || \r
10720         gameMode == Training || gameMode == PlayFromGameFile || \r
10721         gameMode == AnalyzeFile) {\r
10722         while (currentMove < target) {\r
10723             SendMoveToProgram(currentMove++, &first);\r
10724         }\r
10725     } else {\r
10726         currentMove = target;\r
10727     }\r
10728     \r
10729     if (gameMode == EditGame || gameMode == EndOfGame) {\r
10730         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10731         blackTimeRemaining = timeRemaining[1][currentMove];\r
10732     }\r
10733     DisplayBothClocks();\r
10734     DisplayMove(currentMove - 1);\r
10735     DrawPosition(FALSE, boards[currentMove]);\r
10736     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
10737     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
10738         DisplayComment(currentMove - 1, commentList[currentMove]);\r
10739     }\r
10740 }\r
10741 \r
10742 \r
10743 void\r
10744 ForwardEvent()\r
10745 {\r
10746     if (gameMode == IcsExamining && !pausing) {\r
10747         SendToICS(ics_prefix);\r
10748         SendToICS("forward\n");\r
10749     } else {\r
10750         ForwardInner(currentMove + 1);\r
10751     }\r
10752 }\r
10753 \r
10754 void\r
10755 ToEndEvent()\r
10756 {\r
10757     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10758         /* to optimze, we temporarily turn off analysis mode while we feed\r
10759          * the remaining moves to the engine. Otherwise we get analysis output\r
10760          * after each move.\r
10761          */ \r
10762         if (first.analysisSupport) {\r
10763           SendToProgram("exit\nforce\n", &first);\r
10764           first.analyzing = FALSE;\r
10765         }\r
10766     }\r
10767         \r
10768     if (gameMode == IcsExamining && !pausing) {\r
10769         SendToICS(ics_prefix);\r
10770         SendToICS("forward 999999\n");\r
10771     } else {\r
10772         ForwardInner(forwardMostMove);\r
10773     }\r
10774 \r
10775     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10776         /* we have fed all the moves, so reactivate analysis mode */\r
10777         SendToProgram("analyze\n", &first);\r
10778         first.analyzing = TRUE;\r
10779         /*first.maybeThinking = TRUE;*/\r
10780         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10781     }\r
10782 }\r
10783 \r
10784 void\r
10785 BackwardInner(target)\r
10786      int target;\r
10787 {\r
10788     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
10789 \r
10790     if (appData.debugMode)\r
10791         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
10792                 target, currentMove, forwardMostMove);\r
10793 \r
10794     if (gameMode == EditPosition) return;\r
10795     if (currentMove <= backwardMostMove) {\r
10796         ClearHighlights();\r
10797         DrawPosition(full_redraw, boards[currentMove]);\r
10798         return;\r
10799     }\r
10800     if (gameMode == PlayFromGameFile && !pausing)\r
10801       PauseEvent();\r
10802     \r
10803     if (moveList[target][0]) {\r
10804         int fromX, fromY, toX, toY;\r
10805         toX = moveList[target][2] - AAA;\r
10806         toY = moveList[target][3] - ONE;\r
10807         if (moveList[target][1] == '@') {\r
10808             if (appData.highlightLastMove) {\r
10809                 SetHighlights(-1, -1, toX, toY);\r
10810             }\r
10811         } else {\r
10812             fromX = moveList[target][0] - AAA;\r
10813             fromY = moveList[target][1] - ONE;\r
10814             if (target == currentMove - 1) {\r
10815                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
10816             }\r
10817             if (appData.highlightLastMove) {\r
10818                 SetHighlights(fromX, fromY, toX, toY);\r
10819             }\r
10820         }\r
10821     }\r
10822     if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
10823         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
10824         while (currentMove > target) {\r
10825             SendToProgram("undo\n", &first);\r
10826             currentMove--;\r
10827         }\r
10828     } else {\r
10829         currentMove = target;\r
10830     }\r
10831     \r
10832     if (gameMode == EditGame || gameMode == EndOfGame) {\r
10833         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10834         blackTimeRemaining = timeRemaining[1][currentMove];\r
10835     }\r
10836     DisplayBothClocks();\r
10837     DisplayMove(currentMove - 1);\r
10838     DrawPosition(full_redraw, boards[currentMove]);\r
10839     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
10840     // [HGM] PV info: routine tests if comment empty\r
10841     DisplayComment(currentMove - 1, commentList[currentMove]);\r
10842 }\r
10843 \r
10844 void\r
10845 BackwardEvent()\r
10846 {\r
10847     if (gameMode == IcsExamining && !pausing) {\r
10848         SendToICS(ics_prefix);\r
10849         SendToICS("backward\n");\r
10850     } else {\r
10851         BackwardInner(currentMove - 1);\r
10852     }\r
10853 }\r
10854 \r
10855 void\r
10856 ToStartEvent()\r
10857 {\r
10858     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10859         /* to optimze, we temporarily turn off analysis mode while we undo\r
10860          * all the moves. Otherwise we get analysis output after each undo.\r
10861          */ \r
10862         if (first.analysisSupport) {\r
10863           SendToProgram("exit\nforce\n", &first);\r
10864           first.analyzing = FALSE;\r
10865         }\r
10866     }\r
10867 \r
10868     if (gameMode == IcsExamining && !pausing) {\r
10869         SendToICS(ics_prefix);\r
10870         SendToICS("backward 999999\n");\r
10871     } else {\r
10872         BackwardInner(backwardMostMove);\r
10873     }\r
10874 \r
10875     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
10876         /* we have fed all the moves, so reactivate analysis mode */\r
10877         SendToProgram("analyze\n", &first);\r
10878         first.analyzing = TRUE;\r
10879         /*first.maybeThinking = TRUE;*/\r
10880         first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
10881     }\r
10882 }\r
10883 \r
10884 void\r
10885 ToNrEvent(int to)\r
10886 {\r
10887   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
10888   if (to >= forwardMostMove) to = forwardMostMove;\r
10889   if (to <= backwardMostMove) to = backwardMostMove;\r
10890   if (to < currentMove) {\r
10891     BackwardInner(to);\r
10892   } else {\r
10893     ForwardInner(to);\r
10894   }\r
10895 }\r
10896 \r
10897 void\r
10898 RevertEvent()\r
10899 {\r
10900     if (gameMode != IcsExamining) {\r
10901         DisplayError("You are not examining a game", 0);\r
10902         return;\r
10903     }\r
10904     if (pausing) {\r
10905         DisplayError("You can't revert while pausing", 0);\r
10906         return;\r
10907     }\r
10908     SendToICS(ics_prefix);\r
10909     SendToICS("revert\n");\r
10910 }\r
10911 \r
10912 void\r
10913 RetractMoveEvent()\r
10914 {\r
10915     switch (gameMode) {\r
10916       case MachinePlaysWhite:\r
10917       case MachinePlaysBlack:\r
10918         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
10919             DisplayError("Wait until your turn,\nor select Move Now", 0);\r
10920             return;\r
10921         }\r
10922         if (forwardMostMove < 2) return;\r
10923         currentMove = forwardMostMove = forwardMostMove - 2;\r
10924         whiteTimeRemaining = timeRemaining[0][currentMove];\r
10925         blackTimeRemaining = timeRemaining[1][currentMove];\r
10926         DisplayBothClocks();\r
10927         DisplayMove(currentMove - 1);\r
10928         ClearHighlights();/*!! could figure this out*/\r
10929         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
10930         SendToProgram("remove\n", &first);\r
10931         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
10932         break;\r
10933 \r
10934       case BeginningOfGame:\r
10935       default:\r
10936         break;\r
10937 \r
10938       case IcsPlayingWhite:\r
10939       case IcsPlayingBlack:\r
10940         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
10941             SendToICS(ics_prefix);\r
10942             SendToICS("takeback 2\n");\r
10943         } else {\r
10944             SendToICS(ics_prefix);\r
10945             SendToICS("takeback 1\n");\r
10946         }\r
10947         break;\r
10948     }\r
10949 }\r
10950 \r
10951 void\r
10952 MoveNowEvent()\r
10953 {\r
10954     ChessProgramState *cps;\r
10955 \r
10956     switch (gameMode) {\r
10957       case MachinePlaysWhite:\r
10958         if (!WhiteOnMove(forwardMostMove)) {\r
10959             DisplayError("It is your turn", 0);\r
10960             return;\r
10961         }\r
10962         cps = &first;\r
10963         break;\r
10964       case MachinePlaysBlack:\r
10965         if (WhiteOnMove(forwardMostMove)) {\r
10966             DisplayError("It is your turn", 0);\r
10967             return;\r
10968         }\r
10969         cps = &first;\r
10970         break;\r
10971       case TwoMachinesPlay:\r
10972         if (WhiteOnMove(forwardMostMove) ==\r
10973             (first.twoMachinesColor[0] == 'w')) {\r
10974             cps = &first;\r
10975         } else {\r
10976             cps = &second;\r
10977         }\r
10978         break;\r
10979       case BeginningOfGame:\r
10980       default:\r
10981         return;\r
10982     }\r
10983     SendToProgram("?\n", cps);\r
10984 }\r
10985 \r
10986 void\r
10987 TruncateGameEvent()\r
10988 {\r
10989     EditGameEvent();\r
10990     if (gameMode != EditGame) return;\r
10991     TruncateGame();\r
10992 }\r
10993 \r
10994 void\r
10995 TruncateGame()\r
10996 {\r
10997     if (forwardMostMove > currentMove) {\r
10998         if (gameInfo.resultDetails != NULL) {\r
10999             free(gameInfo.resultDetails);\r
11000             gameInfo.resultDetails = NULL;\r
11001             gameInfo.result = GameUnfinished;\r
11002         }\r
11003         forwardMostMove = currentMove;\r
11004         HistorySet(parseList, backwardMostMove, forwardMostMove,\r
11005                    currentMove-1);\r
11006     }\r
11007 }\r
11008 \r
11009 void\r
11010 HintEvent()\r
11011 {\r
11012     if (appData.noChessProgram) return;\r
11013     switch (gameMode) {\r
11014       case MachinePlaysWhite:\r
11015         if (WhiteOnMove(forwardMostMove)) {\r
11016             DisplayError("Wait until your turn", 0);\r
11017             return;\r
11018         }\r
11019         break;\r
11020       case BeginningOfGame:\r
11021       case MachinePlaysBlack:\r
11022         if (!WhiteOnMove(forwardMostMove)) {\r
11023             DisplayError("Wait until your turn", 0);\r
11024             return;\r
11025         }\r
11026         break;\r
11027       default:\r
11028         DisplayError("No hint available", 0);\r
11029         return;\r
11030     }\r
11031     SendToProgram("hint\n", &first);\r
11032     hintRequested = TRUE;\r
11033 }\r
11034 \r
11035 void\r
11036 BookEvent()\r
11037 {\r
11038     if (appData.noChessProgram) return;\r
11039     switch (gameMode) {\r
11040       case MachinePlaysWhite:\r
11041         if (WhiteOnMove(forwardMostMove)) {\r
11042             DisplayError("Wait until your turn", 0);\r
11043             return;\r
11044         }\r
11045         break;\r
11046       case BeginningOfGame:\r
11047       case MachinePlaysBlack:\r
11048         if (!WhiteOnMove(forwardMostMove)) {\r
11049             DisplayError("Wait until your turn", 0);\r
11050             return;\r
11051         }\r
11052         break;\r
11053       case EditPosition:\r
11054         EditPositionDone();\r
11055         break;\r
11056       case TwoMachinesPlay:\r
11057         return;\r
11058       default:\r
11059         break;\r
11060     }\r
11061     SendToProgram("bk\n", &first);\r
11062     bookOutput[0] = NULLCHAR;\r
11063     bookRequested = TRUE;\r
11064 }\r
11065 \r
11066 void\r
11067 AboutGameEvent()\r
11068 {\r
11069     char *tags = PGNTags(&gameInfo);\r
11070     TagsPopUp(tags, CmailMsg());\r
11071     free(tags);\r
11072 }\r
11073 \r
11074 /* end button procedures */\r
11075 \r
11076 void\r
11077 PrintPosition(fp, move)\r
11078      FILE *fp;\r
11079      int move;\r
11080 {\r
11081     int i, j;\r
11082     \r
11083     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
11084         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
11085             char c = PieceToChar(boards[move][i][j]);\r
11086             fputc(c == 'x' ? '.' : c, fp);\r
11087             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
11088         }\r
11089     }\r
11090     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
11091       fprintf(fp, "white to play\n");\r
11092     else\r
11093       fprintf(fp, "black to play\n");\r
11094 }\r
11095 \r
11096 void\r
11097 PrintOpponents(fp)\r
11098      FILE *fp;\r
11099 {\r
11100     if (gameInfo.white != NULL) {\r
11101         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
11102     } else {\r
11103         fprintf(fp, "\n");\r
11104     }\r
11105 }\r
11106 \r
11107 /* Find last component of program's own name, using some heuristics */\r
11108 void\r
11109 TidyProgramName(prog, host, buf)\r
11110      char *prog, *host, buf[MSG_SIZ];\r
11111 {\r
11112     char *p, *q;\r
11113     int local = (strcmp(host, "localhost") == 0);\r
11114     while (!local && (p = strchr(prog, ';')) != NULL) {\r
11115         p++;\r
11116         while (*p == ' ') p++;\r
11117         prog = p;\r
11118     }\r
11119     if (*prog == '"' || *prog == '\'') {\r
11120         q = strchr(prog + 1, *prog);\r
11121     } else {\r
11122         q = strchr(prog, ' ');\r
11123     }\r
11124     if (q == NULL) q = prog + strlen(prog);\r
11125     p = q;\r
11126     while (p >= prog && *p != '/' && *p != '\\') p--;\r
11127     p++;\r
11128     if(p == prog && *p == '"') p++;\r
11129     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
11130     memcpy(buf, p, q - p);\r
11131     buf[q - p] = NULLCHAR;\r
11132     if (!local) {\r
11133         strcat(buf, "@");\r
11134         strcat(buf, host);\r
11135     }\r
11136 }\r
11137 \r
11138 char *\r
11139 TimeControlTagValue()\r
11140 {\r
11141     char buf[MSG_SIZ];\r
11142     if (!appData.clockMode) {\r
11143         strcpy(buf, "-");\r
11144     } else if (movesPerSession > 0) {\r
11145         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
11146     } else if (timeIncrement == 0) {\r
11147         sprintf(buf, "%ld", timeControl/1000);\r
11148     } else {\r
11149         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
11150     }\r
11151     return StrSave(buf);\r
11152 }\r
11153 \r
11154 void\r
11155 SetGameInfo()\r
11156 {\r
11157     /* This routine is used only for certain modes */\r
11158     VariantClass v = gameInfo.variant;\r
11159     ClearGameInfo(&gameInfo);\r
11160     gameInfo.variant = v;\r
11161 \r
11162     switch (gameMode) {\r
11163       case MachinePlaysWhite:\r
11164         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11165         gameInfo.site = StrSave(HostName());\r
11166         gameInfo.date = PGNDate();\r
11167         gameInfo.round = StrSave("-");\r
11168         gameInfo.white = StrSave(first.tidy);\r
11169         gameInfo.black = StrSave(UserName());\r
11170         gameInfo.timeControl = TimeControlTagValue();\r
11171         break;\r
11172 \r
11173       case MachinePlaysBlack:\r
11174         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11175         gameInfo.site = StrSave(HostName());\r
11176         gameInfo.date = PGNDate();\r
11177         gameInfo.round = StrSave("-");\r
11178         gameInfo.white = StrSave(UserName());\r
11179         gameInfo.black = StrSave(first.tidy);\r
11180         gameInfo.timeControl = TimeControlTagValue();\r
11181         break;\r
11182 \r
11183       case TwoMachinesPlay:\r
11184         gameInfo.event = StrSave( appData.pgnEventHeader );\r
11185         gameInfo.site = StrSave(HostName());\r
11186         gameInfo.date = PGNDate();\r
11187         if (matchGame > 0) {\r
11188             char buf[MSG_SIZ];\r
11189             sprintf(buf, "%d", matchGame);\r
11190             gameInfo.round = StrSave(buf);\r
11191         } else {\r
11192             gameInfo.round = StrSave("-");\r
11193         }\r
11194         if (first.twoMachinesColor[0] == 'w') {\r
11195             gameInfo.white = StrSave(first.tidy);\r
11196             gameInfo.black = StrSave(second.tidy);\r
11197         } else {\r
11198             gameInfo.white = StrSave(second.tidy);\r
11199             gameInfo.black = StrSave(first.tidy);\r
11200         }\r
11201         gameInfo.timeControl = TimeControlTagValue();\r
11202         break;\r
11203 \r
11204       case EditGame:\r
11205         gameInfo.event = StrSave("Edited game");\r
11206         gameInfo.site = StrSave(HostName());\r
11207         gameInfo.date = PGNDate();\r
11208         gameInfo.round = StrSave("-");\r
11209         gameInfo.white = StrSave("-");\r
11210         gameInfo.black = StrSave("-");\r
11211         break;\r
11212 \r
11213       case EditPosition:\r
11214         gameInfo.event = StrSave("Edited position");\r
11215         gameInfo.site = StrSave(HostName());\r
11216         gameInfo.date = PGNDate();\r
11217         gameInfo.round = StrSave("-");\r
11218         gameInfo.white = StrSave("-");\r
11219         gameInfo.black = StrSave("-");\r
11220         break;\r
11221 \r
11222       case IcsPlayingWhite:\r
11223       case IcsPlayingBlack:\r
11224       case IcsObserving:\r
11225       case IcsExamining:\r
11226         break;\r
11227 \r
11228       case PlayFromGameFile:\r
11229         gameInfo.event = StrSave("Game from non-PGN file");\r
11230         gameInfo.site = StrSave(HostName());\r
11231         gameInfo.date = PGNDate();\r
11232         gameInfo.round = StrSave("-");\r
11233         gameInfo.white = StrSave("?");\r
11234         gameInfo.black = StrSave("?");\r
11235         break;\r
11236 \r
11237       default:\r
11238         break;\r
11239     }\r
11240 }\r
11241 \r
11242 void\r
11243 ReplaceComment(index, text)\r
11244      int index;\r
11245      char *text;\r
11246 {\r
11247     int len;\r
11248 \r
11249     while (*text == '\n') text++;\r
11250     len = strlen(text);\r
11251     while (len > 0 && text[len - 1] == '\n') len--;\r
11252 \r
11253     if (commentList[index] != NULL)\r
11254       free(commentList[index]);\r
11255 \r
11256     if (len == 0) {\r
11257         commentList[index] = NULL;\r
11258         return;\r
11259     }\r
11260     commentList[index] = (char *) malloc(len + 2);\r
11261     strncpy(commentList[index], text, len);\r
11262     commentList[index][len] = '\n';\r
11263     commentList[index][len + 1] = NULLCHAR;\r
11264 }\r
11265 \r
11266 void\r
11267 CrushCRs(text)\r
11268      char *text;\r
11269 {\r
11270   char *p = text;\r
11271   char *q = text;\r
11272   char ch;\r
11273 \r
11274   do {\r
11275     ch = *p++;\r
11276     if (ch == '\r') continue;\r
11277     *q++ = ch;\r
11278   } while (ch != '\0');\r
11279 }\r
11280 \r
11281 void\r
11282 AppendComment(index, text)\r
11283      int index;\r
11284      char *text;\r
11285 {\r
11286     int oldlen, len;\r
11287     char *old;\r
11288 \r
11289     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
11290 \r
11291     CrushCRs(text);\r
11292     while (*text == '\n') text++;\r
11293     len = strlen(text);\r
11294     while (len > 0 && text[len - 1] == '\n') len--;\r
11295 \r
11296     if (len == 0) return;\r
11297 \r
11298     if (commentList[index] != NULL) {\r
11299         old = commentList[index];\r
11300         oldlen = strlen(old);\r
11301         commentList[index] = (char *) malloc(oldlen + len + 2);\r
11302         strcpy(commentList[index], old);\r
11303         free(old);\r
11304         strncpy(&commentList[index][oldlen], text, len);\r
11305         commentList[index][oldlen + len] = '\n';\r
11306         commentList[index][oldlen + len + 1] = NULLCHAR;\r
11307     } else {\r
11308         commentList[index] = (char *) malloc(len + 2);\r
11309         strncpy(commentList[index], text, len);\r
11310         commentList[index][len] = '\n';\r
11311         commentList[index][len + 1] = NULLCHAR;\r
11312     }\r
11313 }\r
11314 \r
11315 static char * FindStr( char * text, char * sub_text )\r
11316 {\r
11317     char * result = strstr( text, sub_text );\r
11318 \r
11319     if( result != NULL ) {\r
11320         result += strlen( sub_text );\r
11321     }\r
11322 \r
11323     return result;\r
11324 }\r
11325 \r
11326 /* [AS] Try to extract PV info from PGN comment */\r
11327 /* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
11328 char *GetInfoFromComment( int index, char * text )\r
11329 {\r
11330     char * sep = text;\r
11331 \r
11332     if( text != NULL && index > 0 ) {\r
11333         int score = 0;\r
11334         int depth = 0;\r
11335         int time = -1, sec = 0;\r
11336         char * s_eval = FindStr( text, "[%eval " );\r
11337         char * s_emt = FindStr( text, "[%emt " );\r
11338 \r
11339         if( s_eval != NULL || s_emt != NULL ) {\r
11340             /* New style */\r
11341             char delim;\r
11342 \r
11343             if( s_eval != NULL ) {\r
11344                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
11345                     return text;\r
11346                 }\r
11347 \r
11348                 if( delim != ']' ) {\r
11349                     return text;\r
11350                 }\r
11351             }\r
11352 \r
11353             if( s_emt != NULL ) {\r
11354             }\r
11355         }\r
11356         else {\r
11357             /* We expect something like: [+|-]nnn.nn/dd */\r
11358             int score_lo = 0;\r
11359 \r
11360             sep = strchr( text, '/' );\r
11361             if( sep == NULL || sep < (text+4) ) {\r
11362                 return text;\r
11363             }\r
11364 \r
11365             time = -1; sec = -1;\r
11366             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
11367                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
11368                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {\r
11369                 return text;\r
11370             }\r
11371 \r
11372             if( score_lo < 0 || score_lo >= 100 ) {\r
11373                 return text;\r
11374             }\r
11375 \r
11376             if(sec >= 0) time = 60*time + sec;\r
11377             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
11378 \r
11379             /* [HGM] PV time: now locate end of PV info */\r
11380             while( *++sep >= '0' && *sep <= '9'); // strip depth\r
11381             if(time >= 0)\r
11382             while( *++sep >= '0' && *sep <= '9'); // strip time\r
11383             if(sec >= 0)\r
11384             while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
11385             while(*sep == ' ') sep++;\r
11386         }\r
11387 \r
11388         if( depth <= 0 ) {\r
11389             return text;\r
11390         }\r
11391 \r
11392         if( time < 0 ) {\r
11393             time = -1;\r
11394         }\r
11395 \r
11396         pvInfoList[index-1].depth = depth;\r
11397         pvInfoList[index-1].score = score;\r
11398         pvInfoList[index-1].time  = time;\r
11399     }\r
11400     return sep;\r
11401 }\r
11402 \r
11403 void\r
11404 SendToProgram(message, cps)\r
11405      char *message;\r
11406      ChessProgramState *cps;\r
11407 {\r
11408     int count, outCount, error;\r
11409     char buf[MSG_SIZ];\r
11410 \r
11411     if (cps->pr == NULL) return;\r
11412     Attention(cps);\r
11413     \r
11414     if (appData.debugMode) {\r
11415         TimeMark now;\r
11416         GetTimeMark(&now);\r
11417         fprintf(debugFP, "%ld >%-6s: %s", \r
11418                 SubtractTimeMarks(&now, &programStartTime),\r
11419                 cps->which, message);\r
11420     }\r
11421     \r
11422     count = strlen(message);\r
11423     outCount = OutputToProcess(cps->pr, message, count, &error);\r
11424     if (outCount < count && !exiting \r
11425                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
11426         sprintf(buf, "Error writing to %s chess program", cps->which);\r
11427         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
11428             if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
11429                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
11430                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
11431             } else {\r
11432                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
11433             }\r
11434             gameInfo.resultDetails = buf;\r
11435         }\r
11436         DisplayFatalError(buf, error, 1);\r
11437     }\r
11438 }\r
11439 \r
11440 void\r
11441 ReceiveFromProgram(isr, closure, message, count, error)\r
11442      InputSourceRef isr;\r
11443      VOIDSTAR closure;\r
11444      char *message;\r
11445      int count;\r
11446      int error;\r
11447 {\r
11448     char *end_str;\r
11449     char buf[MSG_SIZ];\r
11450     ChessProgramState *cps = (ChessProgramState *)closure;\r
11451 \r
11452     if (isr != cps->isr) return; /* Killed intentionally */\r
11453     if (count <= 0) {\r
11454         if (count == 0) {\r
11455             sprintf(buf,\r
11456                     "Error: %s chess program (%s) exited unexpectedly",\r
11457                     cps->which, cps->program);\r
11458         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
11459                 if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
11460                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
11461                     sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
11462                 } else {\r
11463                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
11464                 }\r
11465                 gameInfo.resultDetails = buf;\r
11466             }\r
11467             RemoveInputSource(cps->isr);\r
11468             DisplayFatalError(buf, 0, 1);\r
11469         } else {\r
11470             sprintf(buf,\r
11471                     "Error reading from %s chess program (%s)",\r
11472                     cps->which, cps->program);\r
11473             RemoveInputSource(cps->isr);\r
11474 \r
11475             /* [AS] Program is misbehaving badly... kill it */\r
11476             if( count == -2 ) {\r
11477                 DestroyChildProcess( cps->pr, 9 );\r
11478                 cps->pr = NoProc;\r
11479             }\r
11480 \r
11481             DisplayFatalError(buf, error, 1);\r
11482         }\r
11483         return;\r
11484     }\r
11485     \r
11486     if ((end_str = strchr(message, '\r')) != NULL)\r
11487       *end_str = NULLCHAR;\r
11488     if ((end_str = strchr(message, '\n')) != NULL)\r
11489       *end_str = NULLCHAR;\r
11490     \r
11491     if (appData.debugMode) {\r
11492         TimeMark now; int print = 1;\r
11493         char *quote = ""; char c; int i;\r
11494 \r
11495         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
11496                 char start = message[0];\r
11497                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
11498                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
11499                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&\r
11500                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
11501                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
11502                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&\r
11503                    sscanf(message, "askuser%c", &c)!=1 && sscanf(message, "1-0 %c", &c)!=1 && \r
11504                    sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
11505                         { quote = "# "; print = (appData.engineComments == 2); }\r
11506                 message[0] = start; // restore original message\r
11507         }\r
11508         if(print) {\r
11509                 GetTimeMark(&now);\r
11510                 fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
11511                         SubtractTimeMarks(&now, &programStartTime), cps->which, \r
11512                         quote,\r
11513                         message);\r
11514         }\r
11515     }\r
11516     HandleMachineMove(message, cps);\r
11517 }\r
11518 \r
11519 \r
11520 void\r
11521 SendTimeControl(cps, mps, tc, inc, sd, st)\r
11522      ChessProgramState *cps;\r
11523      int mps, inc, sd, st;\r
11524      long tc;\r
11525 {\r
11526     char buf[MSG_SIZ];\r
11527     int seconds;\r
11528 \r
11529     if( timeControl_2 > 0 ) {\r
11530         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
11531             tc = timeControl_2;\r
11532         }\r
11533     }\r
11534     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
11535     inc /= cps->timeOdds;\r
11536     st  /= cps->timeOdds;\r
11537 \r
11538     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
11539 \r
11540     if (st > 0) {\r
11541       /* Set exact time per move, normally using st command */\r
11542       if (cps->stKludge) {\r
11543         /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
11544         seconds = st % 60;\r
11545         if (seconds == 0) {\r
11546           sprintf(buf, "level 1 %d\n", st/60);\r
11547         } else {\r
11548           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
11549         }\r
11550       } else {\r
11551         sprintf(buf, "st %d\n", st);\r
11552       }\r
11553     } else {\r
11554       /* Set conventional or incremental time control, using level command */\r
11555       if (seconds == 0) {\r
11556         /* Note old gnuchess bug -- minutes:seconds used to not work.\r
11557            Fixed in later versions, but still avoid :seconds\r
11558            when seconds is 0. */\r
11559         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
11560       } else {\r
11561         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
11562                 seconds, inc/1000);\r
11563       }\r
11564     }\r
11565     SendToProgram(buf, cps);\r
11566 \r
11567     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
11568     /* Orthogonally, limit search to given depth */\r
11569     if (sd > 0) {\r
11570       if (cps->sdKludge) {\r
11571         sprintf(buf, "depth\n%d\n", sd);\r
11572       } else {\r
11573         sprintf(buf, "sd %d\n", sd);\r
11574       }\r
11575       SendToProgram(buf, cps);\r
11576     }\r
11577 \r
11578     if(cps->nps > 0) { /* [HGM] nps */\r
11579         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
11580         else {\r
11581                 sprintf(buf, "nps %d\n", cps->nps);\r
11582               SendToProgram(buf, cps);\r
11583         }\r
11584     }\r
11585 }\r
11586 \r
11587 ChessProgramState *WhitePlayer()\r
11588 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
11589 {\r
11590     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')\r
11591         return &second;\r
11592     return &first;\r
11593 }\r
11594 \r
11595 void\r
11596 SendTimeRemaining(cps, machineWhite)\r
11597      ChessProgramState *cps;\r
11598      int /*boolean*/ machineWhite;\r
11599 {\r
11600     char message[MSG_SIZ];\r
11601     long time, otime;\r
11602 \r
11603     /* Note: this routine must be called when the clocks are stopped\r
11604        or when they have *just* been set or switched; otherwise\r
11605        it will be off by the time since the current tick started.\r
11606     */\r
11607     if (machineWhite) {\r
11608         time = whiteTimeRemaining / 10;\r
11609         otime = blackTimeRemaining / 10;\r
11610     } else {\r
11611         time = blackTimeRemaining / 10;\r
11612         otime = whiteTimeRemaining / 10;\r
11613     }\r
11614     /* [HGM] translate opponent's time by time-odds factor */\r
11615     otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
11616     if (appData.debugMode) {\r
11617         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
11618     }\r
11619 \r
11620     if (time <= 0) time = 1;\r
11621     if (otime <= 0) otime = 1;\r
11622     \r
11623     sprintf(message, "time %ld\n", time);\r
11624     SendToProgram(message, cps);\r
11625 \r
11626     sprintf(message, "otim %ld\n", otime);\r
11627     SendToProgram(message, cps);\r
11628 }\r
11629 \r
11630 int\r
11631 BoolFeature(p, name, loc, cps)\r
11632      char **p;\r
11633      char *name;\r
11634      int *loc;\r
11635      ChessProgramState *cps;\r
11636 {\r
11637   char buf[MSG_SIZ];\r
11638   int len = strlen(name);\r
11639   int val;\r
11640   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
11641     (*p) += len + 1;\r
11642     sscanf(*p, "%d", &val);\r
11643     *loc = (val != 0);\r
11644     while (**p && **p != ' ') (*p)++;\r
11645     sprintf(buf, "accepted %s\n", name);\r
11646     SendToProgram(buf, cps);\r
11647     return TRUE;\r
11648   }\r
11649   return FALSE;\r
11650 }\r
11651 \r
11652 int\r
11653 IntFeature(p, name, loc, cps)\r
11654      char **p;\r
11655      char *name;\r
11656      int *loc;\r
11657      ChessProgramState *cps;\r
11658 {\r
11659   char buf[MSG_SIZ];\r
11660   int len = strlen(name);\r
11661   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
11662     (*p) += len + 1;\r
11663     sscanf(*p, "%d", loc);\r
11664     while (**p && **p != ' ') (*p)++;\r
11665     sprintf(buf, "accepted %s\n", name);\r
11666     SendToProgram(buf, cps);\r
11667     return TRUE;\r
11668   }\r
11669   return FALSE;\r
11670 }\r
11671 \r
11672 int\r
11673 StringFeature(p, name, loc, cps)\r
11674      char **p;\r
11675      char *name;\r
11676      char loc[];\r
11677      ChessProgramState *cps;\r
11678 {\r
11679   char buf[MSG_SIZ];\r
11680   int len = strlen(name);\r
11681   if (strncmp((*p), name, len) == 0\r
11682       && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
11683     (*p) += len + 2;\r
11684     sscanf(*p, "%[^\"]", loc);\r
11685     while (**p && **p != '\"') (*p)++;\r
11686     if (**p == '\"') (*p)++;\r
11687     sprintf(buf, "accepted %s\n", name);\r
11688     SendToProgram(buf, cps);\r
11689     return TRUE;\r
11690   }\r
11691   return FALSE;\r
11692 }\r
11693 \r
11694 void\r
11695 FeatureDone(cps, val)\r
11696      ChessProgramState* cps;\r
11697      int val;\r
11698 {\r
11699   DelayedEventCallback cb = GetDelayedEvent();\r
11700   if ((cb == InitBackEnd3 && cps == &first) ||\r
11701       (cb == TwoMachinesEventIfReady && cps == &second)) {\r
11702     CancelDelayedEvent();\r
11703     ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
11704   }\r
11705   cps->initDone = val;\r
11706 }\r
11707 \r
11708 /* Parse feature command from engine */\r
11709 void\r
11710 ParseFeatures(args, cps)\r
11711      char* args;\r
11712      ChessProgramState *cps;  \r
11713 {\r
11714   char *p = args;\r
11715   char *q;\r
11716   int val;\r
11717   char buf[MSG_SIZ];\r
11718 \r
11719   for (;;) {\r
11720     while (*p == ' ') p++;\r
11721     if (*p == NULLCHAR) return;\r
11722 \r
11723     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
11724     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    \r
11725     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    \r
11726     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    \r
11727     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    \r
11728     if (BoolFeature(&p, "reuse", &val, cps)) {\r
11729       /* Engine can disable reuse, but can't enable it if user said no */\r
11730       if (!val) cps->reuse = FALSE;\r
11731       continue;\r
11732     }\r
11733     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
11734     if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
11735       if (gameMode == TwoMachinesPlay) {\r
11736         DisplayTwoMachinesTitle();\r
11737       } else {\r
11738         DisplayTitle("");\r
11739       }\r
11740       continue;\r
11741     }\r
11742     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
11743     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
11744     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
11745     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
11746     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
11747     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
11748     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
11749     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
11750     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
11751     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
11752     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
11753     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
11754     if (IntFeature(&p, "done", &val, cps)) {\r
11755       FeatureDone(cps, val);\r
11756       continue;\r
11757     }\r
11758     /* Added by Tord: */\r
11759     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
11760     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
11761     /* End of additions by Tord */\r
11762 \r
11763     /* unknown feature: complain and skip */\r
11764     q = p;\r
11765     while (*q && *q != '=') q++;\r
11766     sprintf(buf, "rejected %.*s\n", q-p, p);\r
11767     SendToProgram(buf, cps);\r
11768     p = q;\r
11769     if (*p == '=') {\r
11770       p++;\r
11771       if (*p == '\"') {\r
11772         p++;\r
11773         while (*p && *p != '\"') p++;\r
11774         if (*p == '\"') p++;\r
11775       } else {\r
11776         while (*p && *p != ' ') p++;\r
11777       }\r
11778     }\r
11779   }\r
11780 \r
11781 }\r
11782 \r
11783 void\r
11784 PeriodicUpdatesEvent(newState)\r
11785      int newState;\r
11786 {\r
11787     if (newState == appData.periodicUpdates)\r
11788       return;\r
11789 \r
11790     appData.periodicUpdates=newState;\r
11791 \r
11792     /* Display type changes, so update it now */\r
11793     DisplayAnalysis();\r
11794 \r
11795     /* Get the ball rolling again... */\r
11796     if (newState) {\r
11797         AnalysisPeriodicEvent(1);\r
11798         StartAnalysisClock();\r
11799     }\r
11800 }\r
11801 \r
11802 void\r
11803 PonderNextMoveEvent(newState)\r
11804      int newState;\r
11805 {\r
11806     if (newState == appData.ponderNextMove) return;\r
11807     if (gameMode == EditPosition) EditPositionDone();\r
11808     if (newState) {\r
11809         SendToProgram("hard\n", &first);\r
11810         if (gameMode == TwoMachinesPlay) {\r
11811             SendToProgram("hard\n", &second);\r
11812         }\r
11813     } else {\r
11814         SendToProgram("easy\n", &first);\r
11815         thinkOutput[0] = NULLCHAR;\r
11816         if (gameMode == TwoMachinesPlay) {\r
11817             SendToProgram("easy\n", &second);\r
11818         }\r
11819     }\r
11820     appData.ponderNextMove = newState;\r
11821 }\r
11822 \r
11823 void\r
11824 ShowThinkingEvent(newState)\r
11825      int newState;\r
11826 {\r
11827     if (newState == appData.showThinking) return;\r
11828     if (gameMode == EditPosition) EditPositionDone();\r
11829     if (newState) {\r
11830         SendToProgram("post\n", &first);\r
11831         if (gameMode == TwoMachinesPlay) {\r
11832             SendToProgram("post\n", &second);\r
11833         }\r
11834     } else {\r
11835         SendToProgram("nopost\n", &first);\r
11836         thinkOutput[0] = NULLCHAR;\r
11837         if (gameMode == TwoMachinesPlay) {\r
11838             SendToProgram("nopost\n", &second);\r
11839         }\r
11840     }\r
11841     appData.showThinking = newState;\r
11842 }\r
11843 \r
11844 void\r
11845 AskQuestionEvent(title, question, replyPrefix, which)\r
11846      char *title; char *question; char *replyPrefix; char *which;\r
11847 {\r
11848   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
11849   if (pr == NoProc) return;\r
11850   AskQuestion(title, question, replyPrefix, pr);\r
11851 }\r
11852 \r
11853 void\r
11854 DisplayMove(moveNumber)\r
11855      int moveNumber;\r
11856 {\r
11857     char message[MSG_SIZ];\r
11858     char res[MSG_SIZ];\r
11859     char cpThinkOutput[MSG_SIZ];\r
11860 \r
11861     if (moveNumber == forwardMostMove - 1 || \r
11862         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11863 \r
11864         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
11865 \r
11866         if (strchr(cpThinkOutput, '\n')) {\r
11867             *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
11868         }\r
11869     } else {\r
11870         *cpThinkOutput = NULLCHAR;\r
11871     }\r
11872 \r
11873     /* [AS] Hide thinking from human user */\r
11874     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
11875         *cpThinkOutput = NULLCHAR;\r
11876         if( thinkOutput[0] != NULLCHAR ) {\r
11877             int i;\r
11878 \r
11879             for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
11880                 cpThinkOutput[i] = '.';\r
11881             }\r
11882             cpThinkOutput[i] = NULLCHAR;\r
11883             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
11884         }\r
11885     }\r
11886 \r
11887     if (moveNumber == forwardMostMove - 1 &&\r
11888         gameInfo.resultDetails != NULL) {\r
11889         if (gameInfo.resultDetails[0] == NULLCHAR) {\r
11890             sprintf(res, " %s", PGNResult(gameInfo.result));\r
11891         } else {\r
11892             sprintf(res, " {%s} %s",\r
11893                     gameInfo.resultDetails, PGNResult(gameInfo.result));\r
11894         }\r
11895     } else {\r
11896         res[0] = NULLCHAR;\r
11897     }\r
11898     \r
11899     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
11900         DisplayMessage(res, cpThinkOutput);\r
11901     } else {\r
11902         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
11903                 WhiteOnMove(moveNumber) ? " " : ".. ",\r
11904                 parseList[moveNumber], res);\r
11905         DisplayMessage(message, cpThinkOutput);\r
11906     }\r
11907 }\r
11908 \r
11909 void\r
11910 DisplayAnalysisText(text)\r
11911      char *text;\r
11912 {\r
11913     char buf[MSG_SIZ];\r
11914 \r
11915     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
11916         sprintf(buf, "Analysis (%s)", first.tidy);\r
11917         AnalysisPopUp(buf, text);\r
11918     }\r
11919 }\r
11920 \r
11921 static int\r
11922 only_one_move(str)\r
11923      char *str;\r
11924 {\r
11925     while (*str && isspace(*str)) ++str;\r
11926     while (*str && !isspace(*str)) ++str;\r
11927     if (!*str) return 1;\r
11928     while (*str && isspace(*str)) ++str;\r
11929     if (!*str) return 1;\r
11930     return 0;\r
11931 }\r
11932 \r
11933 void\r
11934 DisplayAnalysis()\r
11935 {\r
11936     char buf[MSG_SIZ];\r
11937     char lst[MSG_SIZ / 2];\r
11938     double nps;\r
11939     static char *xtra[] = { "", " (--)", " (++)" };\r
11940     int h, m, s, cs;\r
11941   \r
11942     if (programStats.time == 0) {\r
11943         programStats.time = 1;\r
11944     }\r
11945   \r
11946     if (programStats.got_only_move) {\r
11947         safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
11948     } else {\r
11949         safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
11950 \r
11951         nps = (((double)programStats.nodes) /\r
11952                (((double)programStats.time)/100.0));\r
11953 \r
11954         cs = programStats.time % 100;\r
11955         s = programStats.time / 100;\r
11956         h = (s / (60*60));\r
11957         s = s - h*60*60;\r
11958         m = (s/60);\r
11959         s = s - m*60;\r
11960 \r
11961         if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
11962           if (programStats.move_name[0] != NULLCHAR) {\r
11963             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
11964                     programStats.depth,\r
11965                     programStats.nr_moves-programStats.moves_left,\r
11966                     programStats.nr_moves, programStats.move_name,\r
11967                     ((float)programStats.score)/100.0, lst,\r
11968                     only_one_move(lst)?\r
11969                     xtra[programStats.got_fail] : "",\r
11970                     programStats.nodes, (int)nps, h, m, s, cs);\r
11971           } else {\r
11972             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
11973                     programStats.depth,\r
11974                     programStats.nr_moves-programStats.moves_left,\r
11975                     programStats.nr_moves, ((float)programStats.score)/100.0,\r
11976                     lst,\r
11977                     only_one_move(lst)?\r
11978                     xtra[programStats.got_fail] : "",\r
11979                     programStats.nodes, (int)nps, h, m, s, cs);\r
11980           }\r
11981         } else {\r
11982             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
11983                     programStats.depth,\r
11984                     ((float)programStats.score)/100.0,\r
11985                     lst,\r
11986                     only_one_move(lst)?\r
11987                     xtra[programStats.got_fail] : "",\r
11988                     programStats.nodes, (int)nps, h, m, s, cs);\r
11989         }\r
11990     }\r
11991     DisplayAnalysisText(buf);\r
11992 }\r
11993 \r
11994 void\r
11995 DisplayComment(moveNumber, text)\r
11996      int moveNumber;\r
11997      char *text;\r
11998 {\r
11999     char title[MSG_SIZ];\r
12000     char buf[8000]; // comment can be long!\r
12001     int score, depth;\r
12002 \r
12003     if( appData.autoDisplayComment ) {\r
12004         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
12005             strcpy(title, "Comment");\r
12006         } else {\r
12007             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
12008                     WhiteOnMove(moveNumber) ? " " : ".. ",\r
12009                     parseList[moveNumber]);\r
12010         }\r
12011     } else title[0] = 0;\r
12012 \r
12013     // [HGM] PV info: display PV info together with (or as) comment\r
12014     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
12015         if(text == NULL) text = "";                                           \r
12016         score = pvInfoList[moveNumber].score;\r
12017         sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
12018                               depth, pvInfoList[moveNumber].time, text);\r
12019         CommentPopUp(title, buf);\r
12020     } else\r
12021     if (text != NULL)\r
12022         CommentPopUp(title, text);\r
12023 }\r
12024 \r
12025 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
12026  * might be busy thinking or pondering.  It can be omitted if your\r
12027  * gnuchess is configured to stop thinking immediately on any user\r
12028  * input.  However, that gnuchess feature depends on the FIONREAD\r
12029  * ioctl, which does not work properly on some flavors of Unix.\r
12030  */\r
12031 void\r
12032 Attention(cps)\r
12033      ChessProgramState *cps;\r
12034 {\r
12035 #if ATTENTION\r
12036     if (!cps->useSigint) return;\r
12037     if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
12038     switch (gameMode) {\r
12039       case MachinePlaysWhite:\r
12040       case MachinePlaysBlack:\r
12041       case TwoMachinesPlay:\r
12042       case IcsPlayingWhite:\r
12043       case IcsPlayingBlack:\r
12044       case AnalyzeMode:\r
12045       case AnalyzeFile:\r
12046         /* Skip if we know it isn't thinking */\r
12047         if (!cps->maybeThinking) return;\r
12048         if (appData.debugMode)\r
12049           fprintf(debugFP, "Interrupting %s\n", cps->which);\r
12050         InterruptChildProcess(cps->pr);\r
12051         cps->maybeThinking = FALSE;\r
12052         break;\r
12053       default:\r
12054         break;\r
12055     }\r
12056 #endif /*ATTENTION*/\r
12057 }\r
12058 \r
12059 int\r
12060 CheckFlags()\r
12061 {\r
12062     if (whiteTimeRemaining <= 0) {\r
12063         if (!whiteFlag) {\r
12064             whiteFlag = TRUE;\r
12065             if (appData.icsActive) {\r
12066                 if (appData.autoCallFlag &&\r
12067                     gameMode == IcsPlayingBlack && !blackFlag) {\r
12068                   SendToICS(ics_prefix);\r
12069                   SendToICS("flag\n");\r
12070                 }\r
12071             } else {\r
12072                 if (blackFlag) {\r
12073                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
12074                 } else {\r
12075                     if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell");\r
12076                     if (appData.autoCallFlag) {\r
12077                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
12078                         return TRUE;\r
12079                     }\r
12080                 }\r
12081             }\r
12082         }\r
12083     }\r
12084     if (blackTimeRemaining <= 0) {\r
12085         if (!blackFlag) {\r
12086             blackFlag = TRUE;\r
12087             if (appData.icsActive) {\r
12088                 if (appData.autoCallFlag &&\r
12089                     gameMode == IcsPlayingWhite && !whiteFlag) {\r
12090                   SendToICS(ics_prefix);\r
12091                   SendToICS("flag\n");\r
12092                 }\r
12093             } else {\r
12094                 if (whiteFlag) {\r
12095                     if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell");\r
12096                 } else {\r
12097                     if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell");\r
12098                     if (appData.autoCallFlag) {\r
12099                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
12100                         return TRUE;\r
12101                     }\r
12102                 }\r
12103             }\r
12104         }\r
12105     }\r
12106     return FALSE;\r
12107 }\r
12108 \r
12109 void\r
12110 CheckTimeControl()\r
12111 {\r
12112     if (!appData.clockMode || appData.icsActive ||\r
12113         gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
12114 \r
12115     /*\r
12116      * add time to clocks when time control is achieved ([HGM] now also used fot increment)\r
12117      */\r
12118     if ( !WhiteOnMove(forwardMostMove) )\r
12119         /* White made time control */\r
12120         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12121         /* [HGM] time odds: correct new time quota for time odds! */\r
12122                                             / WhitePlayer()->timeOdds;\r
12123       else\r
12124         /* Black made time control */\r
12125         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
12126                                             / WhitePlayer()->other->timeOdds;\r
12127 }\r
12128 \r
12129 void\r
12130 DisplayBothClocks()\r
12131 {\r
12132     int wom = gameMode == EditPosition ?\r
12133       !blackPlaysFirst : WhiteOnMove(currentMove);\r
12134     DisplayWhiteClock(whiteTimeRemaining, wom);\r
12135     DisplayBlackClock(blackTimeRemaining, !wom);\r
12136 }\r
12137 \r
12138 \r
12139 /* Timekeeping seems to be a portability nightmare.  I think everyone\r
12140    has ftime(), but I'm really not sure, so I'm including some ifdefs\r
12141    to use other calls if you don't.  Clocks will be less accurate if\r
12142    you have neither ftime nor gettimeofday.\r
12143 */\r
12144 \r
12145 /* Get the current time as a TimeMark */\r
12146 void\r
12147 GetTimeMark(tm)\r
12148      TimeMark *tm;\r
12149 {\r
12150 #if HAVE_GETTIMEOFDAY\r
12151 \r
12152     struct timeval timeVal;\r
12153     struct timezone timeZone;\r
12154 \r
12155     gettimeofday(&timeVal, &timeZone);\r
12156     tm->sec = (long) timeVal.tv_sec; \r
12157     tm->ms = (int) (timeVal.tv_usec / 1000L);\r
12158 \r
12159 #else /*!HAVE_GETTIMEOFDAY*/\r
12160 #if HAVE_FTIME\r
12161 \r
12162 #include <sys/timeb.h>\r
12163     struct timeb timeB;\r
12164 \r
12165     ftime(&timeB);\r
12166     tm->sec = (long) timeB.time;\r
12167     tm->ms = (int) timeB.millitm;\r
12168 \r
12169 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
12170     tm->sec = (long) time(NULL);\r
12171     tm->ms = 0;\r
12172 #endif\r
12173 #endif\r
12174 }\r
12175 \r
12176 /* Return the difference in milliseconds between two\r
12177    time marks.  We assume the difference will fit in a long!\r
12178 */\r
12179 long\r
12180 SubtractTimeMarks(tm2, tm1)\r
12181      TimeMark *tm2, *tm1;\r
12182 {\r
12183     return 1000L*(tm2->sec - tm1->sec) +\r
12184            (long) (tm2->ms - tm1->ms);\r
12185 }\r
12186 \r
12187 \r
12188 /*\r
12189  * Code to manage the game clocks.\r
12190  *\r
12191  * In tournament play, black starts the clock and then white makes a move.\r
12192  * We give the human user a slight advantage if he is playing white---the\r
12193  * clocks don't run until he makes his first move, so it takes zero time.\r
12194  * Also, we don't account for network lag, so we could get out of sync\r
12195  * with GNU Chess's clock -- but then, referees are always right.  \r
12196  */\r
12197 \r
12198 static TimeMark tickStartTM;\r
12199 static long intendedTickLength;\r
12200 \r
12201 long\r
12202 NextTickLength(timeRemaining)\r
12203      long timeRemaining;\r
12204 {\r
12205     long nominalTickLength, nextTickLength;\r
12206 \r
12207     if (timeRemaining > 0L && timeRemaining <= 10000L)\r
12208       nominalTickLength = 100L;\r
12209     else\r
12210       nominalTickLength = 1000L;\r
12211     nextTickLength = timeRemaining % nominalTickLength;\r
12212     if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
12213 \r
12214     return nextTickLength;\r
12215 }\r
12216 \r
12217 /* Adjust clock one minute up or down */\r
12218 void\r
12219 AdjustClock(Boolean which, int dir)\r
12220 {\r
12221     if(which) blackTimeRemaining += 60000*dir;\r
12222     else      whiteTimeRemaining += 60000*dir;\r
12223     DisplayBothClocks();\r
12224 }\r
12225 \r
12226 /* Stop clocks and reset to a fresh time control */\r
12227 void\r
12228 ResetClocks() \r
12229 {\r
12230     (void) StopClockTimer();\r
12231     if (appData.icsActive) {\r
12232         whiteTimeRemaining = blackTimeRemaining = 0;\r
12233     } else { /* [HGM] correct new time quote for time odds */\r
12234         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
12235         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
12236     }\r
12237     if (whiteFlag || blackFlag) {\r
12238         DisplayTitle("");\r
12239         whiteFlag = blackFlag = FALSE;\r
12240     }\r
12241     DisplayBothClocks();\r
12242 }\r
12243 \r
12244 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
12245 \r
12246 /* Decrement running clock by amount of time that has passed */\r
12247 void\r
12248 DecrementClocks()\r
12249 {\r
12250     long timeRemaining;\r
12251     long lastTickLength, fudge;\r
12252     TimeMark now;\r
12253 \r
12254     if (!appData.clockMode) return;\r
12255     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
12256         \r
12257     GetTimeMark(&now);\r
12258 \r
12259     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
12260 \r
12261     /* Fudge if we woke up a little too soon */\r
12262     fudge = intendedTickLength - lastTickLength;\r
12263     if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
12264 \r
12265     if (WhiteOnMove(forwardMostMove)) {\r
12266         if(whiteNPS >= 0) lastTickLength = 0;\r
12267         timeRemaining = whiteTimeRemaining -= lastTickLength;\r
12268         DisplayWhiteClock(whiteTimeRemaining - fudge,\r
12269                           WhiteOnMove(currentMove));\r
12270     } else {\r
12271         if(blackNPS >= 0) lastTickLength = 0;\r
12272         timeRemaining = blackTimeRemaining -= lastTickLength;\r
12273         DisplayBlackClock(blackTimeRemaining - fudge,\r
12274                           !WhiteOnMove(currentMove));\r
12275     }\r
12276 \r
12277     if (CheckFlags()) return;\r
12278         \r
12279     tickStartTM = now;\r
12280     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
12281     StartClockTimer(intendedTickLength);\r
12282 \r
12283     /* if the time remaining has fallen below the alarm threshold, sound the\r
12284      * alarm. if the alarm has sounded and (due to a takeback or time control\r
12285      * with increment) the time remaining has increased to a level above the\r
12286      * threshold, reset the alarm so it can sound again. \r
12287      */\r
12288     \r
12289     if (appData.icsActive && appData.icsAlarm) {\r
12290 \r
12291         /* make sure we are dealing with the user's clock */\r
12292         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
12293                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
12294            )) return;\r
12295 \r
12296         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
12297             alarmSounded = FALSE;\r
12298         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
12299             PlayAlarmSound();\r
12300             alarmSounded = TRUE;\r
12301         }\r
12302     }\r
12303 }\r
12304 \r
12305 \r
12306 /* A player has just moved, so stop the previously running\r
12307    clock and (if in clock mode) start the other one.\r
12308    We redisplay both clocks in case we're in ICS mode, because\r
12309    ICS gives us an update to both clocks after every move.\r
12310    Note that this routine is called *after* forwardMostMove\r
12311    is updated, so the last fractional tick must be subtracted\r
12312    from the color that is *not* on move now.\r
12313 */\r
12314 void\r
12315 SwitchClocks()\r
12316 {\r
12317     long lastTickLength;\r
12318     TimeMark now;\r
12319     int flagged = FALSE;\r
12320 \r
12321     GetTimeMark(&now);\r
12322 \r
12323     if (StopClockTimer() && appData.clockMode) {\r
12324         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
12325         if (WhiteOnMove(forwardMostMove)) {\r
12326             if(blackNPS >= 0) lastTickLength = 0;\r
12327             blackTimeRemaining -= lastTickLength;\r
12328            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
12329             if(pvInfoList[forwardMostMove-1].time == -1)\r
12330                  pvInfoList[forwardMostMove-1].time = \r
12331                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
12332         } else {\r
12333            if(whiteNPS >= 0) lastTickLength = 0;\r
12334            whiteTimeRemaining -= lastTickLength;\r
12335            /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
12336             if(pvInfoList[forwardMostMove-1].time == -1)\r
12337                  pvInfoList[forwardMostMove-1].time = \r
12338                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
12339         }\r
12340         flagged = CheckFlags();\r
12341     }\r
12342     CheckTimeControl();\r
12343 \r
12344     if (flagged || !appData.clockMode) return;\r
12345 \r
12346     switch (gameMode) {\r
12347       case MachinePlaysBlack:\r
12348       case MachinePlaysWhite:\r
12349       case BeginningOfGame:\r
12350         if (pausing) return;\r
12351         break;\r
12352 \r
12353       case EditGame:\r
12354       case PlayFromGameFile:\r
12355       case IcsExamining:\r
12356         return;\r
12357 \r
12358       default:\r
12359         break;\r
12360     }\r
12361 \r
12362     tickStartTM = now;\r
12363     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
12364       whiteTimeRemaining : blackTimeRemaining);\r
12365     StartClockTimer(intendedTickLength);\r
12366 }\r
12367         \r
12368 \r
12369 /* Stop both clocks */\r
12370 void\r
12371 StopClocks()\r
12372 {       \r
12373     long lastTickLength;\r
12374     TimeMark now;\r
12375 \r
12376     if (!StopClockTimer()) return;\r
12377     if (!appData.clockMode) return;\r
12378 \r
12379     GetTimeMark(&now);\r
12380 \r
12381     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
12382     if (WhiteOnMove(forwardMostMove)) {\r
12383         if(whiteNPS >= 0) lastTickLength = 0;\r
12384         whiteTimeRemaining -= lastTickLength;\r
12385         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
12386     } else {\r
12387         if(blackNPS >= 0) lastTickLength = 0;\r
12388         blackTimeRemaining -= lastTickLength;\r
12389         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
12390     }\r
12391     CheckFlags();\r
12392 }\r
12393         \r
12394 /* Start clock of player on move.  Time may have been reset, so\r
12395    if clock is already running, stop and restart it. */\r
12396 void\r
12397 StartClocks()\r
12398 {\r
12399     (void) StopClockTimer(); /* in case it was running already */\r
12400     DisplayBothClocks();\r
12401     if (CheckFlags()) return;\r
12402 \r
12403     if (!appData.clockMode) return;\r
12404     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
12405 \r
12406     GetTimeMark(&tickStartTM);\r
12407     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
12408       whiteTimeRemaining : blackTimeRemaining);\r
12409 \r
12410    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
12411     whiteNPS = blackNPS = -1; \r
12412     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
12413        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
12414         whiteNPS = first.nps;\r
12415     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
12416        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
12417         blackNPS = first.nps;\r
12418     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
12419         whiteNPS = second.nps;\r
12420     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
12421         blackNPS = second.nps;\r
12422     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
12423 \r
12424     StartClockTimer(intendedTickLength);\r
12425 }\r
12426 \r
12427 char *\r
12428 TimeString(ms)\r
12429      long ms;\r
12430 {\r
12431     long second, minute, hour, day;\r
12432     char *sign = "";\r
12433     static char buf[32];\r
12434     \r
12435     if (ms > 0 && ms <= 9900) {\r
12436       /* convert milliseconds to tenths, rounding up */\r
12437       double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
12438 \r
12439       sprintf(buf, " %03.1f ", tenths/10.0);\r
12440       return buf;\r
12441     }\r
12442 \r
12443     /* convert milliseconds to seconds, rounding up */\r
12444     /* use floating point to avoid strangeness of integer division\r
12445        with negative dividends on many machines */\r
12446     second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
12447 \r
12448     if (second < 0) {\r
12449         sign = "-";\r
12450         second = -second;\r
12451     }\r
12452     \r
12453     day = second / (60 * 60 * 24);\r
12454     second = second % (60 * 60 * 24);\r
12455     hour = second / (60 * 60);\r
12456     second = second % (60 * 60);\r
12457     minute = second / 60;\r
12458     second = second % 60;\r
12459     \r
12460     if (day > 0)\r
12461       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
12462               sign, day, hour, minute, second);\r
12463     else if (hour > 0)\r
12464       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
12465     else\r
12466       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
12467     \r
12468     return buf;\r
12469 }\r
12470 \r
12471 \r
12472 /*\r
12473  * This is necessary because some C libraries aren't ANSI C compliant yet.\r
12474  */\r
12475 char *\r
12476 StrStr(string, match)\r
12477      char *string, *match;\r
12478 {\r
12479     int i, length;\r
12480     \r
12481     length = strlen(match);\r
12482     \r
12483     for (i = strlen(string) - length; i >= 0; i--, string++)\r
12484       if (!strncmp(match, string, length))\r
12485         return string;\r
12486     \r
12487     return NULL;\r
12488 }\r
12489 \r
12490 char *\r
12491 StrCaseStr(string, match)\r
12492      char *string, *match;\r
12493 {\r
12494     int i, j, length;\r
12495     \r
12496     length = strlen(match);\r
12497     \r
12498     for (i = strlen(string) - length; i >= 0; i--, string++) {\r
12499         for (j = 0; j < length; j++) {\r
12500             if (ToLower(match[j]) != ToLower(string[j]))\r
12501               break;\r
12502         }\r
12503         if (j == length) return string;\r
12504     }\r
12505 \r
12506     return NULL;\r
12507 }\r
12508 \r
12509 #ifndef _amigados\r
12510 int\r
12511 StrCaseCmp(s1, s2)\r
12512      char *s1, *s2;\r
12513 {\r
12514     char c1, c2;\r
12515     \r
12516     for (;;) {\r
12517         c1 = ToLower(*s1++);\r
12518         c2 = ToLower(*s2++);\r
12519         if (c1 > c2) return 1;\r
12520         if (c1 < c2) return -1;\r
12521         if (c1 == NULLCHAR) return 0;\r
12522     }\r
12523 }\r
12524 \r
12525 \r
12526 int\r
12527 ToLower(c)\r
12528      int c;\r
12529 {\r
12530     return isupper(c) ? tolower(c) : c;\r
12531 }\r
12532 \r
12533 \r
12534 int\r
12535 ToUpper(c)\r
12536      int c;\r
12537 {\r
12538     return islower(c) ? toupper(c) : c;\r
12539 }\r
12540 #endif /* !_amigados    */\r
12541 \r
12542 char *\r
12543 StrSave(s)\r
12544      char *s;\r
12545 {\r
12546     char *ret;\r
12547 \r
12548     if ((ret = (char *) malloc(strlen(s) + 1))) {\r
12549         strcpy(ret, s);\r
12550     }\r
12551     return ret;\r
12552 }\r
12553 \r
12554 char *\r
12555 StrSavePtr(s, savePtr)\r
12556      char *s, **savePtr;\r
12557 {\r
12558     if (*savePtr) {\r
12559         free(*savePtr);\r
12560     }\r
12561     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
12562         strcpy(*savePtr, s);\r
12563     }\r
12564     return(*savePtr);\r
12565 }\r
12566 \r
12567 char *\r
12568 PGNDate()\r
12569 {\r
12570     time_t clock;\r
12571     struct tm *tm;\r
12572     char buf[MSG_SIZ];\r
12573 \r
12574     clock = time((time_t *)NULL);\r
12575     tm = localtime(&clock);\r
12576     sprintf(buf, "%04d.%02d.%02d",\r
12577             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
12578     return StrSave(buf);\r
12579 }\r
12580 \r
12581 \r
12582 char *\r
12583 PositionToFEN(move, useFEN960)\r
12584      int move;\r
12585      int useFEN960;\r
12586 {\r
12587     int i, j, fromX, fromY, toX, toY;\r
12588     int whiteToPlay;\r
12589     char buf[128];\r
12590     char *p, *q;\r
12591     int emptycount;\r
12592     ChessSquare piece;\r
12593 \r
12594     whiteToPlay = (gameMode == EditPosition) ?\r
12595       !blackPlaysFirst : (move % 2 == 0);\r
12596     p = buf;\r
12597 \r
12598     /* Piece placement data */\r
12599     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
12600         emptycount = 0;\r
12601         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
12602             if (boards[move][i][j] == EmptySquare) {\r
12603                 emptycount++;\r
12604             } else { ChessSquare piece = boards[move][i][j];\r
12605                 if (emptycount > 0) {\r
12606                     if(emptycount<10) /* [HGM] can be >= 10 */\r
12607                         *p++ = '0' + emptycount;\r
12608                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
12609                     emptycount = 0;\r
12610                 }\r
12611                 if(PieceToChar(piece) == '+') {\r
12612                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
12613                     *p++ = '+';\r
12614                     piece = (ChessSquare)(DEMOTED piece);\r
12615                 } \r
12616                 *p++ = PieceToChar(piece);\r
12617                 if(p[-1] == '~') {\r
12618                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
12619                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
12620                     *p++ = '~';\r
12621                 }\r
12622             }\r
12623         }\r
12624         if (emptycount > 0) {\r
12625             if(emptycount<10) /* [HGM] can be >= 10 */\r
12626                 *p++ = '0' + emptycount;\r
12627             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
12628             emptycount = 0;\r
12629         }\r
12630         *p++ = '/';\r
12631     }\r
12632     *(p - 1) = ' ';\r
12633 \r
12634     /* [HGM] print Crazyhouse or Shogi holdings */\r
12635     if( gameInfo.holdingsWidth ) {\r
12636         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
12637         q = p;\r
12638         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
12639             piece = boards[move][i][BOARD_WIDTH-1];\r
12640             if( piece != EmptySquare )\r
12641               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
12642                   *p++ = PieceToChar(piece);\r
12643         }\r
12644         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
12645             piece = boards[move][BOARD_HEIGHT-i-1][0];\r
12646             if( piece != EmptySquare )\r
12647               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
12648                   *p++ = PieceToChar(piece);\r
12649         }\r
12650 \r
12651         if( q == p ) *p++ = '-';\r
12652         *p++ = ']';\r
12653         *p++ = ' ';\r
12654     }\r
12655 \r
12656     /* Active color */\r
12657     *p++ = whiteToPlay ? 'w' : 'b';\r
12658     *p++ = ' ';\r
12659 \r
12660   if(nrCastlingRights) {\r
12661      q = p;\r
12662      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
12663        /* [HGM] write directly from rights */\r
12664            if(castlingRights[move][2] >= 0 &&\r
12665               castlingRights[move][0] >= 0   )\r
12666                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
12667            if(castlingRights[move][2] >= 0 &&\r
12668               castlingRights[move][1] >= 0   )\r
12669                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
12670            if(castlingRights[move][5] >= 0 &&\r
12671               castlingRights[move][3] >= 0   )\r
12672                 *p++ = castlingRights[move][3] + AAA;\r
12673            if(castlingRights[move][5] >= 0 &&\r
12674               castlingRights[move][4] >= 0   )\r
12675                 *p++ = castlingRights[move][4] + AAA;\r
12676      } else {\r
12677 \r
12678         /* [HGM] write true castling rights */\r
12679         if( nrCastlingRights == 6 ) {\r
12680             if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
12681                castlingRights[move][2] >= 0  ) *p++ = 'K';\r
12682             if(castlingRights[move][1] == BOARD_LEFT &&\r
12683                castlingRights[move][2] >= 0  ) *p++ = 'Q';\r
12684             if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
12685                castlingRights[move][5] >= 0  ) *p++ = 'k';\r
12686             if(castlingRights[move][4] == BOARD_LEFT &&\r
12687                castlingRights[move][5] >= 0  ) *p++ = 'q';\r
12688         }\r
12689      }\r
12690      if (q == p) *p++ = '-'; /* No castling rights */\r
12691      *p++ = ' ';\r
12692   }\r
12693 \r
12694   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
12695      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
12696     /* En passant target square */\r
12697     if (move > backwardMostMove) {\r
12698         fromX = moveList[move - 1][0] - AAA;\r
12699         fromY = moveList[move - 1][1] - ONE;\r
12700         toX = moveList[move - 1][2] - AAA;\r
12701         toY = moveList[move - 1][3] - ONE;\r
12702         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
12703             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
12704             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
12705             fromX == toX) {\r
12706             /* 2-square pawn move just happened */\r
12707             *p++ = toX + AAA;\r
12708             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
12709         } else {\r
12710             *p++ = '-';\r
12711         }\r
12712     } else {\r
12713         *p++ = '-';\r
12714     }\r
12715     *p++ = ' ';\r
12716   }\r
12717 \r
12718     /* [HGM] find reversible plies */\r
12719     {   int i = 0, j=move;\r
12720 \r
12721         if (appData.debugMode) { int k;\r
12722             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
12723             for(k=backwardMostMove; k<=forwardMostMove; k++)\r
12724                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
12725 \r
12726         }\r
12727 \r
12728         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
12729         if( j == backwardMostMove ) i += initialRulePlies;\r
12730         sprintf(p, "%d ", i);\r
12731         p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
12732     }\r
12733     /* Fullmove number */\r
12734     sprintf(p, "%d", (move / 2) + 1);\r
12735     \r
12736     return StrSave(buf);\r
12737 }\r
12738 \r
12739 Boolean\r
12740 ParseFEN(board, blackPlaysFirst, fen)\r
12741     Board board;\r
12742      int *blackPlaysFirst;\r
12743      char *fen;\r
12744 {\r
12745     int i, j;\r
12746     char *p;\r
12747     int emptycount;\r
12748     ChessSquare piece;\r
12749 \r
12750     p = fen;\r
12751 \r
12752     /* [HGM] by default clear Crazyhouse holdings, if present */\r
12753     if(gameInfo.holdingsWidth) {\r
12754        for(i=0; i<BOARD_HEIGHT; i++) {\r
12755            board[i][0]             = EmptySquare; /* black holdings */\r
12756            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
12757            board[i][1]             = (ChessSquare) 0; /* black counts */\r
12758            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
12759        }\r
12760     }\r
12761 \r
12762     /* Piece placement data */\r
12763     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
12764         j = 0;\r
12765         for (;;) {\r
12766             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
12767                 if (*p == '/') p++;\r
12768                 emptycount = gameInfo.boardWidth - j;\r
12769                 while (emptycount--)\r
12770                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
12771                 break;\r
12772 #if(BOARD_SIZE >= 10)\r
12773             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
12774                 p++; emptycount=10;\r
12775                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
12776                 while (emptycount--)\r
12777                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
12778 #endif\r
12779             } else if (isdigit(*p)) {\r
12780                 emptycount = *p++ - '0';\r
12781                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
12782                 if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
12783                 while (emptycount--)\r
12784                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
12785             } else if (*p == '+' || isalpha(*p)) {\r
12786                 if (j >= gameInfo.boardWidth) return FALSE;\r
12787                 if(*p=='+') {\r
12788                     piece = CharToPiece(*++p);\r
12789                     if(piece == EmptySquare) return FALSE; /* unknown piece */\r
12790                     piece = (ChessSquare) (PROMOTED piece ); p++;\r
12791                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
12792                 } else piece = CharToPiece(*p++);\r
12793 \r
12794                 if(piece==EmptySquare) return FALSE; /* unknown piece */\r
12795                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
12796                     piece = (ChessSquare) (PROMOTED piece);\r
12797                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
12798                     p++;\r
12799                 }\r
12800                 board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
12801             } else {\r
12802                 return FALSE;\r
12803             }\r
12804         }\r
12805     }\r
12806     while (*p == '/' || *p == ' ') p++;\r
12807 \r
12808     /* [HGM] look for Crazyhouse holdings here */\r
12809     while(*p==' ') p++;\r
12810     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
12811         if(*p == '[') p++;\r
12812         if(*p == '-' ) *p++; /* empty holdings */ else {\r
12813             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
12814             /* if we would allow FEN reading to set board size, we would   */\r
12815             /* have to add holdings and shift the board read so far here   */\r
12816             while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
12817                 *p++;\r
12818                 if((int) piece >= (int) BlackPawn ) {\r
12819                     i = (int)piece - (int)BlackPawn;\r
12820                     if( i >= BOARD_HEIGHT ) return FALSE;\r
12821                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
12822                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */\r
12823                 } else {\r
12824                     i = (int)piece - (int)WhitePawn;\r
12825                     if( i >= BOARD_HEIGHT ) return FALSE;\r
12826                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */\r
12827                     board[i][BOARD_WIDTH-2]++;          /* black holdings */\r
12828                 }\r
12829             }\r
12830         }\r
12831         if(*p == ']') *p++;\r
12832     }\r
12833 \r
12834     while(*p == ' ') p++;\r
12835 \r
12836     /* Active color */\r
12837     switch (*p++) {\r
12838       case 'w':\r
12839         *blackPlaysFirst = FALSE;\r
12840         break;\r
12841       case 'b': \r
12842         *blackPlaysFirst = TRUE;\r
12843         break;\r
12844       default:\r
12845         return FALSE;\r
12846     }\r
12847 \r
12848     /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
12849     /* return the extra info in global variiables             */\r
12850 \r
12851     /* set defaults in case FEN is incomplete */\r
12852     FENepStatus = EP_UNKNOWN;\r
12853     for(i=0; i<nrCastlingRights; i++ ) {\r
12854         FENcastlingRights[i] =\r
12855             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
12856     }   /* assume possible unless obviously impossible */\r
12857     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
12858     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
12859     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
12860     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
12861     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
12862     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
12863     FENrulePlies = 0;\r
12864 \r
12865     while(*p==' ') p++;\r
12866     if(nrCastlingRights) {\r
12867       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
12868           /* castling indicator present, so default becomes no castlings */\r
12869           for(i=0; i<nrCastlingRights; i++ ) {\r
12870                  FENcastlingRights[i] = -1;\r
12871           }\r
12872       }\r
12873       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
12874              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
12875              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
12876              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {\r
12877         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
12878 \r
12879         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
12880             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
12881             if(board[0             ][i] == WhiteKing) whiteKingFile = i;\r
12882         }\r
12883         switch(c) {\r
12884           case'K':\r
12885               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
12886               FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
12887               FENcastlingRights[2] = whiteKingFile;\r
12888               break;\r
12889           case'Q':\r
12890               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
12891               FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
12892               FENcastlingRights[2] = whiteKingFile;\r
12893               break;\r
12894           case'k':\r
12895               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
12896               FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
12897               FENcastlingRights[5] = blackKingFile;\r
12898               break;\r
12899           case'q':\r
12900               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
12901               FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
12902               FENcastlingRights[5] = blackKingFile;\r
12903           case '-':\r
12904               break;\r
12905           default: /* FRC castlings */\r
12906               if(c >= 'a') { /* black rights */\r
12907                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
12908                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
12909                   if(i == BOARD_RGHT) break;\r
12910                   FENcastlingRights[5] = i;\r
12911                   c -= AAA;\r
12912                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||\r
12913                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;\r
12914                   if(c > i)\r
12915                       FENcastlingRights[3] = c;\r
12916                   else\r
12917                       FENcastlingRights[4] = c;\r
12918               } else { /* white rights */\r
12919                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
12920                     if(board[0][i] == WhiteKing) break;\r
12921                   if(i == BOARD_RGHT) break;\r
12922                   FENcastlingRights[2] = i;\r
12923                   c -= AAA - 'a' + 'A';\r
12924                   if(board[0][c] >= WhiteKing) break;\r
12925                   if(c > i)\r
12926                       FENcastlingRights[0] = c;\r
12927                   else\r
12928                       FENcastlingRights[1] = c;\r
12929               }\r
12930         }\r
12931       }\r
12932     if (appData.debugMode) {\r
12933         fprintf(debugFP, "FEN castling rights:");\r
12934         for(i=0; i<nrCastlingRights; i++)\r
12935         fprintf(debugFP, " %d", FENcastlingRights[i]);\r
12936         fprintf(debugFP, "\n");\r
12937     }\r
12938 \r
12939       while(*p==' ') p++;\r
12940     }\r
12941 \r
12942     /* read e.p. field in games that know e.p. capture */\r
12943     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&\r
12944        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
12945       if(*p=='-') {\r
12946         p++; FENepStatus = EP_NONE;\r
12947       } else {\r
12948          char c = *p++ - AAA;\r
12949 \r
12950          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
12951          if(*p >= '0' && *p <='9') *p++;\r
12952          FENepStatus = c;\r
12953       }\r
12954     }\r
12955 \r
12956 \r
12957     if(sscanf(p, "%d", &i) == 1) {\r
12958         FENrulePlies = i; /* 50-move ply counter */\r
12959         /* (The move number is still ignored)    */\r
12960     }\r
12961 \r
12962     return TRUE;\r
12963 }\r
12964       \r
12965 void\r
12966 EditPositionPasteFEN(char *fen)\r
12967 {\r
12968   if (fen != NULL) {\r
12969     Board initial_position;\r
12970 \r
12971     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
12972       DisplayError("Bad FEN position in clipboard", 0);\r
12973       return ;\r
12974     } else {\r
12975       int savedBlackPlaysFirst = blackPlaysFirst;\r
12976       EditPositionEvent();\r
12977       blackPlaysFirst = savedBlackPlaysFirst;\r
12978       CopyBoard(boards[0], initial_position);\r
12979           /* [HGM] copy FEN attributes as well */\r
12980           {   int i;\r
12981               initialRulePlies = FENrulePlies;\r
12982               epStatus[0] = FENepStatus;\r
12983               for( i=0; i<nrCastlingRights; i++ )\r
12984                   castlingRights[0][i] = FENcastlingRights[i];\r
12985           }\r
12986       EditPositionDone();\r
12987       DisplayBothClocks();\r
12988       DrawPosition(FALSE, boards[currentMove]);\r
12989     }\r
12990   }\r
12991 }\r