464335e5edc35a32c7dfbfdf703726477ebf26e5
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 #else /* not STDC_HEADERS */
81 # if HAVE_STRING_H
82 #  include <string.h>
83 # else /* not HAVE_STRING_H */
84 #  include <strings.h>
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
87
88 #if HAVE_SYS_FCNTL_H
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
91 # if HAVE_FCNTL_H
92 #  include <fcntl.h>
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
95
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
98 # include <time.h>
99 #else
100 # if HAVE_SYS_TIME_H
101 #  include <sys/time.h>
102 # else
103 #  include <time.h>
104 # endif
105 #endif
106
107 #if defined(_amigados) && !defined(__GNUC__)
108 struct timezone {
109     int tz_minuteswest;
110     int tz_dsttime;
111 };
112 extern int gettimeofday(struct timeval *, struct timezone *);
113 #endif
114
115 #if HAVE_UNISTD_H
116 # include <unistd.h>
117 #endif
118
119 #include "common.h"
120 #include "frontend.h"
121 #include "backend.h"
122 #include "parser.h"
123 #include "moves.h"
124 #if ZIPPY
125 # include "zippy.h"
126 #endif
127 #include "backendz.h"
128 #include "gettext.h"
129
130 #ifdef ENABLE_NLS
131 # define _(s) gettext (s)
132 # define N_(s) gettext_noop (s)
133 #else
134 # define _(s) (s)
135 # define N_(s) s
136 #endif
137
138
139 /* A point in time */
140 typedef struct {
141     long sec;  /* Assuming this is >= 32 bits */
142     int ms;    /* Assuming this is >= 16 bits */
143 } TimeMark;
144
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147                          char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149                       char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
153                       int toX, int toY));
154 void InitPosition P((int redraw));
155 void HandleMachineMove P((char *message, ChessProgramState *cps));
156 int AutoPlayOneMove P((void));
157 int LoadGameOneMove P((ChessMove readAhead));
158 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
159 int LoadPositionFromFile P((char *filename, int n, char *title));
160 int SavePositionToFile P((char *filename));
161 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162                   Board board, char *castle, char *ep));
163 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
164 void ShowMove P((int fromX, int fromY, int toX, int toY));
165 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
166                    /*char*/int promoChar));
167 void BackwardInner P((int target));
168 void ForwardInner P((int target));
169 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
170 void EditPositionDone P((void));
171 void PrintOpponents P((FILE *fp));
172 void PrintPosition P((FILE *fp, int move));
173 void StartChessProgram P((ChessProgramState *cps));
174 void SendToProgram P((char *message, ChessProgramState *cps));
175 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
176 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
177                            char *buf, int count, int error));
178 void SendTimeControl P((ChessProgramState *cps,
179                         int mps, long tc, int inc, int sd, int st));
180 char *TimeControlTagValue P((void));
181 void Attention P((ChessProgramState *cps));
182 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
183 void ResurrectChessProgram P((void));
184 void DisplayComment P((int moveNumber, char *text));
185 void DisplayMove P((int moveNumber));
186 void DisplayAnalysis P((void));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 extern char installDir[MSG_SIZ];
234
235 extern int tinyLayout, smallLayout;
236 ChessProgramStats programStats;
237 static int exiting = 0; /* [HGM] moved to top */
238 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
239 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
240 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
241 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
242 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
243 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
244 int opponentKibitzes;
245
246 /* States for ics_getting_history */
247 #define H_FALSE 0
248 #define H_REQUESTED 1
249 #define H_GOT_REQ_HEADER 2
250 #define H_GOT_UNREQ_HEADER 3
251 #define H_GETTING_MOVES 4
252 #define H_GOT_UNWANTED_HEADER 5
253
254 /* whosays values for GameEnds */
255 #define GE_ICS 0
256 #define GE_ENGINE 1
257 #define GE_PLAYER 2
258 #define GE_FILE 3
259 #define GE_XBOARD 4
260 #define GE_ENGINE1 5
261 #define GE_ENGINE2 6
262
263 /* Maximum number of games in a cmail message */
264 #define CMAIL_MAX_GAMES 20
265
266 /* Different types of move when calling RegisterMove */
267 #define CMAIL_MOVE   0
268 #define CMAIL_RESIGN 1
269 #define CMAIL_DRAW   2
270 #define CMAIL_ACCEPT 3
271
272 /* Different types of result to remember for each game */
273 #define CMAIL_NOT_RESULT 0
274 #define CMAIL_OLD_RESULT 1
275 #define CMAIL_NEW_RESULT 2
276
277 /* Telnet protocol constants */
278 #define TN_WILL 0373
279 #define TN_WONT 0374
280 #define TN_DO   0375
281 #define TN_DONT 0376
282 #define TN_IAC  0377
283 #define TN_ECHO 0001
284 #define TN_SGA  0003
285 #define TN_PORT 23
286
287 /* [AS] */
288 static char * safeStrCpy( char * dst, const char * src, size_t count )
289 {
290     assert( dst != NULL );
291     assert( src != NULL );
292     assert( count > 0 );
293
294     strncpy( dst, src, count );
295     dst[ count-1 ] = '\0';
296     return dst;
297 }
298
299 #if 0
300 //[HGM] for future use? Conditioned out for now to suppress warning.
301 static char * safeStrCat( char * dst, const char * src, size_t count )
302 {
303     size_t  dst_len;
304
305     assert( dst != NULL );
306     assert( src != NULL );
307     assert( count > 0 );
308
309     dst_len = strlen(dst);
310
311     assert( count > dst_len ); /* Buffer size must be greater than current length */
312
313     safeStrCpy( dst + dst_len, src, count - dst_len );
314
315     return dst;
316 }
317 #endif
318
319 /* Some compiler can't cast u64 to double
320  * This function do the job for us:
321
322  * We use the highest bit for cast, this only
323  * works if the highest bit is not
324  * in use (This should not happen)
325  *
326  * We used this for all compiler
327  */
328 double
329 u64ToDouble(u64 value)
330 {
331   double r;
332   u64 tmp = value & u64Const(0x7fffffffffffffff);
333   r = (double)(s64)tmp;
334   if (value & u64Const(0x8000000000000000))
335        r +=  9.2233720368547758080e18; /* 2^63 */
336  return r;
337 }
338
339 /* Fake up flags for now, as we aren't keeping track of castling
340    availability yet. [HGM] Change of logic: the flag now only
341    indicates the type of castlings allowed by the rule of the game.
342    The actual rights themselves are maintained in the array
343    castlingRights, as part of the game history, and are not probed
344    by this function.
345  */
346 int
347 PosFlags(index)
348 {
349   int flags = F_ALL_CASTLE_OK;
350   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
351   switch (gameInfo.variant) {
352   case VariantSuicide:
353     flags &= ~F_ALL_CASTLE_OK;
354   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
355     flags |= F_IGNORE_CHECK;
356   case VariantLosers:
357     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
358     break;
359   case VariantAtomic:
360     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
361     break;
362   case VariantKriegspiel:
363     flags |= F_KRIEGSPIEL_CAPTURE;
364     break;
365   case VariantCapaRandom:
366   case VariantFischeRandom:
367     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
368   case VariantNoCastle:
369   case VariantShatranj:
370   case VariantCourier:
371     flags &= ~F_ALL_CASTLE_OK;
372     break;
373   default:
374     break;
375   }
376   return flags;
377 }
378
379 FILE *gameFileFP, *debugFP;
380
381 /*
382     [AS] Note: sometimes, the sscanf() function is used to parse the input
383     into a fixed-size buffer. Because of this, we must be prepared to
384     receive strings as long as the size of the input buffer, which is currently
385     set to 4K for Windows and 8K for the rest.
386     So, we must either allocate sufficiently large buffers here, or
387     reduce the size of the input buffer in the input reading part.
388 */
389
390 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
391 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
392 char thinkOutput1[MSG_SIZ*10];
393
394 ChessProgramState first, second;
395
396 /* premove variables */
397 int premoveToX = 0;
398 int premoveToY = 0;
399 int premoveFromX = 0;
400 int premoveFromY = 0;
401 int premovePromoChar = 0;
402 int gotPremove = 0;
403 Boolean alarmSounded;
404 /* end premove variables */
405
406 char *ics_prefix = "$";
407 int ics_type = ICS_GENERIC;
408
409 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
410 int pauseExamForwardMostMove = 0;
411 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
412 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
413 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
414 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
415 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
416 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
417 int whiteFlag = FALSE, blackFlag = FALSE;
418 int userOfferedDraw = FALSE;
419 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
420 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
421 int cmailMoveType[CMAIL_MAX_GAMES];
422 long ics_clock_paused = 0;
423 ProcRef icsPR = NoProc, cmailPR = NoProc;
424 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
425 GameMode gameMode = BeginningOfGame;
426 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
427 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
428 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
429 int hiddenThinkOutputState = 0; /* [AS] */
430 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
431 int adjudicateLossPlies = 6;
432 char white_holding[64], black_holding[64];
433 TimeMark lastNodeCountTime;
434 long lastNodeCount=0;
435 int have_sent_ICS_logon = 0;
436 int movesPerSession;
437 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
438 long timeControl_2; /* [AS] Allow separate time controls */
439 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
440 long timeRemaining[2][MAX_MOVES];
441 int matchGame = 0;
442 TimeMark programStartTime;
443 char ics_handle[MSG_SIZ];
444 int have_set_title = 0;
445
446 /* animateTraining preserves the state of appData.animate
447  * when Training mode is activated. This allows the
448  * response to be animated when appData.animate == TRUE and
449  * appData.animateDragging == TRUE.
450  */
451 Boolean animateTraining;
452
453 GameInfo gameInfo;
454
455 AppData appData;
456
457 Board boards[MAX_MOVES];
458 /* [HGM] Following 7 needed for accurate legality tests: */
459 char  epStatus[MAX_MOVES];
460 char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
461 char  castlingRank[BOARD_SIZE]; // and corresponding ranks
462 char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
463 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
464 int   initialRulePlies, FENrulePlies;
465 char  FENepStatus;
466 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
467 int loadFlag = 0;
468 int shuffleOpenings;
469
470 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
472         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
473     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
474         BlackKing, BlackBishop, BlackKnight, BlackRook }
475 };
476
477 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
478     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
479         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
480     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
481         BlackKing, BlackKing, BlackKnight, BlackRook }
482 };
483
484 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
485     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
486         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
487     { BlackRook, BlackMan, BlackBishop, BlackQueen,
488         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
489 };
490
491 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
492     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
493         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
494     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
495         BlackKing, BlackBishop, BlackKnight, BlackRook }
496 };
497
498 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
499     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
500         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
502         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
503 };
504
505
506 #if (BOARD_SIZE>=10)
507 ChessSquare ShogiArray[2][BOARD_SIZE] = {
508     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
509         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
510     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
511         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
512 };
513
514 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
515     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
516         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
518         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
519 };
520
521 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
523         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
525         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
526 };
527
528 ChessSquare GreatArray[2][BOARD_SIZE] = {
529     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
530         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
531     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
532         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
533 };
534
535 ChessSquare JanusArray[2][BOARD_SIZE] = {
536     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
537         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
538     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
539         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
540 };
541
542 #ifdef GOTHIC
543 ChessSquare GothicArray[2][BOARD_SIZE] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
545         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
547         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
548 };
549 #else // !GOTHIC
550 #define GothicArray CapablancaArray
551 #endif // !GOTHIC
552
553 #ifdef FALCON
554 ChessSquare FalconArray[2][BOARD_SIZE] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
556         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
558         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !FALCON
561 #define FalconArray CapablancaArray
562 #endif // !FALCON
563
564 #else // !(BOARD_SIZE>=10)
565 #define XiangqiPosition FIDEArray
566 #define CapablancaArray FIDEArray
567 #define GothicArray FIDEArray
568 #define GreatArray FIDEArray
569 #endif // !(BOARD_SIZE>=10)
570
571 #if (BOARD_SIZE>=12)
572 ChessSquare CourierArray[2][BOARD_SIZE] = {
573     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
574         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
576         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
577 };
578 #else // !(BOARD_SIZE>=12)
579 #define CourierArray CapablancaArray
580 #endif // !(BOARD_SIZE>=12)
581
582
583 Board initialPosition;
584
585
586 /* Convert str to a rating. Checks for special cases of "----",
587
588    "++++", etc. Also strips ()'s */
589 int
590 string_to_rating(str)
591   char *str;
592 {
593   while(*str && !isdigit(*str)) ++str;
594   if (!*str)
595     return 0;   /* One of the special "no rating" cases */
596   else
597     return atoi(str);
598 }
599
600 void
601 ClearProgramStats()
602 {
603     /* Init programStats */
604     programStats.movelist[0] = 0;
605     programStats.depth = 0;
606     programStats.nr_moves = 0;
607     programStats.moves_left = 0;
608     programStats.nodes = 0;
609     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
610     programStats.score = 0;
611     programStats.got_only_move = 0;
612     programStats.got_fail = 0;
613     programStats.line_is_book = 0;
614 }
615
616 void
617 InitBackEnd1()
618 {
619     int matched, min, sec;
620
621     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
622
623     GetTimeMark(&programStartTime);
624     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
625
626     ClearProgramStats();
627     programStats.ok_to_send = 1;
628     programStats.seen_stat = 0;
629
630     /*
631      * Initialize game list
632      */
633     ListNew(&gameList);
634
635
636     /*
637      * Internet chess server status
638      */
639     if (appData.icsActive) {
640         appData.matchMode = FALSE;
641         appData.matchGames = 0;
642 #if ZIPPY
643         appData.noChessProgram = !appData.zippyPlay;
644 #else
645         appData.zippyPlay = FALSE;
646         appData.zippyTalk = FALSE;
647         appData.noChessProgram = TRUE;
648 #endif
649         if (*appData.icsHelper != NULLCHAR) {
650             appData.useTelnet = TRUE;
651             appData.telnetProgram = appData.icsHelper;
652         }
653     } else {
654         appData.zippyTalk = appData.zippyPlay = FALSE;
655     }
656
657     /* [AS] Initialize pv info list [HGM] and game state */
658     {
659         int i, j;
660
661         for( i=0; i<MAX_MOVES; i++ ) {
662             pvInfoList[i].depth = -1;
663             epStatus[i]=EP_NONE;
664             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
665         }
666     }
667
668     /*
669      * Parse timeControl resource
670      */
671     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
672                           appData.movesPerSession)) {
673         char buf[MSG_SIZ];
674         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
675         DisplayFatalError(buf, 0, 2);
676     }
677
678     /*
679      * Parse searchTime resource
680      */
681     if (*appData.searchTime != NULLCHAR) {
682         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
683         if (matched == 1) {
684             searchTime = min * 60;
685         } else if (matched == 2) {
686             searchTime = min * 60 + sec;
687         } else {
688             char buf[MSG_SIZ];
689             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
690             DisplayFatalError(buf, 0, 2);
691         }
692     }
693
694     /* [AS] Adjudication threshold */
695     adjudicateLossThreshold = appData.adjudicateLossThreshold;
696
697     first.which = "first";
698     second.which = "second";
699     first.maybeThinking = second.maybeThinking = FALSE;
700     first.pr = second.pr = NoProc;
701     first.isr = second.isr = NULL;
702     first.sendTime = second.sendTime = 2;
703     first.sendDrawOffers = 1;
704     if (appData.firstPlaysBlack) {
705         first.twoMachinesColor = "black\n";
706         second.twoMachinesColor = "white\n";
707     } else {
708         first.twoMachinesColor = "white\n";
709         second.twoMachinesColor = "black\n";
710     }
711     first.program = appData.firstChessProgram;
712     second.program = appData.secondChessProgram;
713     first.host = appData.firstHost;
714     second.host = appData.secondHost;
715     first.dir = appData.firstDirectory;
716     second.dir = appData.secondDirectory;
717     first.other = &second;
718     second.other = &first;
719     first.initString = appData.initString;
720     second.initString = appData.secondInitString;
721     first.computerString = appData.firstComputerString;
722     second.computerString = appData.secondComputerString;
723     first.useSigint = second.useSigint = TRUE;
724     first.useSigterm = second.useSigterm = TRUE;
725     first.reuse = appData.reuseFirst;
726     second.reuse = appData.reuseSecond;
727     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
728     second.nps = appData.secondNPS;
729     first.useSetboard = second.useSetboard = FALSE;
730     first.useSAN = second.useSAN = FALSE;
731     first.usePing = second.usePing = FALSE;
732     first.lastPing = second.lastPing = 0;
733     first.lastPong = second.lastPong = 0;
734     first.usePlayother = second.usePlayother = FALSE;
735     first.useColors = second.useColors = TRUE;
736     first.useUsermove = second.useUsermove = FALSE;
737     first.sendICS = second.sendICS = FALSE;
738     first.sendName = second.sendName = appData.icsActive;
739     first.sdKludge = second.sdKludge = FALSE;
740     first.stKludge = second.stKludge = FALSE;
741     TidyProgramName(first.program, first.host, first.tidy);
742     TidyProgramName(second.program, second.host, second.tidy);
743     first.matchWins = second.matchWins = 0;
744     strcpy(first.variants, appData.variant);
745     strcpy(second.variants, appData.variant);
746     first.analysisSupport = second.analysisSupport = 2; /* detect */
747     first.analyzing = second.analyzing = FALSE;
748     first.initDone = second.initDone = FALSE;
749
750     /* New features added by Tord: */
751     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
752     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
753     /* End of new features added by Tord. */
754     first.fenOverride  = appData.fenOverride1;
755     second.fenOverride = appData.fenOverride2;
756
757     /* [HGM] time odds: set factor for each machine */
758     first.timeOdds  = appData.firstTimeOdds;
759     second.timeOdds = appData.secondTimeOdds;
760     { int norm = 1;
761         if(appData.timeOddsMode) {
762             norm = first.timeOdds;
763             if(norm > second.timeOdds) norm = second.timeOdds;
764         }
765         first.timeOdds /= norm;
766         second.timeOdds /= norm;
767     }
768
769     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
770     first.accumulateTC = appData.firstAccumulateTC;
771     second.accumulateTC = appData.secondAccumulateTC;
772     first.maxNrOfSessions = second.maxNrOfSessions = 1;
773
774     /* [HGM] debug */
775     first.debug = second.debug = FALSE;
776     first.supportsNPS = second.supportsNPS = UNKNOWN;
777
778     /* [HGM] options */
779     first.optionSettings  = appData.firstOptions;
780     second.optionSettings = appData.secondOptions;
781
782     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
783     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
784     first.isUCI = appData.firstIsUCI; /* [AS] */
785     second.isUCI = appData.secondIsUCI; /* [AS] */
786     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
787     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
788
789     if (appData.firstProtocolVersion > PROTOVER ||
790         appData.firstProtocolVersion < 1) {
791       char buf[MSG_SIZ];
792       sprintf(buf, _("protocol version %d not supported"),
793               appData.firstProtocolVersion);
794       DisplayFatalError(buf, 0, 2);
795     } else {
796       first.protocolVersion = appData.firstProtocolVersion;
797     }
798
799     if (appData.secondProtocolVersion > PROTOVER ||
800         appData.secondProtocolVersion < 1) {
801       char buf[MSG_SIZ];
802       sprintf(buf, _("protocol version %d not supported"),
803               appData.secondProtocolVersion);
804       DisplayFatalError(buf, 0, 2);
805     } else {
806       second.protocolVersion = appData.secondProtocolVersion;
807     }
808
809     if (appData.icsActive) {
810         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
811     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
812         appData.clockMode = FALSE;
813         first.sendTime = second.sendTime = 0;
814     }
815
816 #if ZIPPY
817     /* Override some settings from environment variables, for backward
818        compatibility.  Unfortunately it's not feasible to have the env
819        vars just set defaults, at least in xboard.  Ugh.
820     */
821     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
822       ZippyInit();
823     }
824 #endif
825
826     if (appData.noChessProgram) {
827         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
828         sprintf(programVersion, "%s", PACKAGE_STRING);
829     } else {
830 #if 0
831         char *p, *q;
832         q = first.program;
833         while (*q != ' ' && *q != NULLCHAR) q++;
834         p = q;
835         while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
836         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
837         sprintf(programVersion, "%s + ", PACKAGE_STRING);
838         strncat(programVersion, p, q - p);
839 #else
840         /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
841         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
842         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
843 #endif
844     }
845
846     if (!appData.icsActive) {
847       char buf[MSG_SIZ];
848       /* Check for variants that are supported only in ICS mode,
849          or not at all.  Some that are accepted here nevertheless
850          have bugs; see comments below.
851       */
852       VariantClass variant = StringToVariant(appData.variant);
853       switch (variant) {
854       case VariantBughouse:     /* need four players and two boards */
855       case VariantKriegspiel:   /* need to hide pieces and move details */
856       /* case VariantFischeRandom: (Fabien: moved below) */
857         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
858         DisplayFatalError(buf, 0, 2);
859         return;
860
861       case VariantUnknown:
862       case VariantLoadable:
863       case Variant29:
864       case Variant30:
865       case Variant31:
866       case Variant32:
867       case Variant33:
868       case Variant34:
869       case Variant35:
870       case Variant36:
871       default:
872         sprintf(buf, _("Unknown variant name %s"), appData.variant);
873         DisplayFatalError(buf, 0, 2);
874         return;
875
876       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
877       case VariantFairy:      /* [HGM] TestLegality definitely off! */
878       case VariantGothic:     /* [HGM] should work */
879       case VariantCapablanca: /* [HGM] should work */
880       case VariantCourier:    /* [HGM] initial forced moves not implemented */
881       case VariantShogi:      /* [HGM] drops not tested for legality */
882       case VariantKnightmate: /* [HGM] should work */
883       case VariantCylinder:   /* [HGM] untested */
884       case VariantFalcon:     /* [HGM] untested */
885       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
886                                  offboard interposition not understood */
887       case VariantNormal:     /* definitely works! */
888       case VariantWildCastle: /* pieces not automatically shuffled */
889       case VariantNoCastle:   /* pieces not automatically shuffled */
890       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
891       case VariantLosers:     /* should work except for win condition,
892                                  and doesn't know captures are mandatory */
893       case VariantSuicide:    /* should work except for win condition,
894                                  and doesn't know captures are mandatory */
895       case VariantGiveaway:   /* should work except for win condition,
896                                  and doesn't know captures are mandatory */
897       case VariantTwoKings:   /* should work */
898       case VariantAtomic:     /* should work except for win condition */
899       case Variant3Check:     /* should work except for win condition */
900       case VariantShatranj:   /* should work except for all win conditions */
901       case VariantBerolina:   /* might work if TestLegality is off */
902       case VariantCapaRandom: /* should work */
903       case VariantJanus:      /* should work */
904       case VariantSuper:      /* experimental */
905       case VariantGreat:      /* experimental, requires legality testing to be off */
906         break;
907       }
908     }
909
910     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
911     InitEngineUCI( installDir, &second );
912 }
913
914 int NextIntegerFromString( char ** str, long * value )
915 {
916     int result = -1;
917     char * s = *str;
918
919     while( *s == ' ' || *s == '\t' ) {
920         s++;
921     }
922
923     *value = 0;
924
925     if( *s >= '0' && *s <= '9' ) {
926         while( *s >= '0' && *s <= '9' ) {
927             *value = *value * 10 + (*s - '0');
928             s++;
929         }
930
931         result = 0;
932     }
933
934     *str = s;
935
936     return result;
937 }
938
939 int NextTimeControlFromString( char ** str, long * value )
940 {
941     long temp;
942     int result = NextIntegerFromString( str, &temp );
943
944     if( result == 0 ) {
945         *value = temp * 60; /* Minutes */
946         if( **str == ':' ) {
947             (*str)++;
948             result = NextIntegerFromString( str, &temp );
949             *value += temp; /* Seconds */
950         }
951     }
952
953     return result;
954 }
955
956 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
957 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
958     int result = -1; long temp, temp2;
959
960     if(**str != '+') return -1; // old params remain in force!
961     (*str)++;
962     if( NextTimeControlFromString( str, &temp ) ) return -1;
963
964     if(**str != '/') {
965         /* time only: incremental or sudden-death time control */
966         if(**str == '+') { /* increment follows; read it */
967             (*str)++;
968             if(result = NextIntegerFromString( str, &temp2)) return -1;
969             *inc = temp2 * 1000;
970         } else *inc = 0;
971         *moves = 0; *tc = temp * 1000;
972         return 0;
973     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
974
975     (*str)++; /* classical time control */
976     result = NextTimeControlFromString( str, &temp2);
977     if(result == 0) {
978         *moves = temp/60;
979         *tc    = temp2 * 1000;
980         *inc   = 0;
981     }
982     return result;
983 }
984
985 int GetTimeQuota(int movenr)
986 {   /* [HGM] get time to add from the multi-session time-control string */
987     int moves=1; /* kludge to force reading of first session */
988     long time, increment;
989     char *s = fullTimeControlString;
990
991     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
992     do {
993         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
994         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
995         if(movenr == -1) return time;    /* last move before new session     */
996         if(!moves) return increment;     /* current session is incremental   */
997         if(movenr >= 0) movenr -= moves; /* we already finished this session */
998     } while(movenr >= -1);               /* try again for next session       */
999
1000     return 0; // no new time quota on this move
1001 }
1002
1003 int
1004 ParseTimeControl(tc, ti, mps)
1005      char *tc;
1006      int ti;
1007      int mps;
1008 {
1009 #if 0
1010     int matched, min, sec;
1011
1012     matched = sscanf(tc, "%d:%d", &min, &sec);
1013     if (matched == 1) {
1014         timeControl = min * 60 * 1000;
1015     } else if (matched == 2) {
1016         timeControl = (min * 60 + sec) * 1000;
1017     } else {
1018         return FALSE;
1019     }
1020 #else
1021     long tc1;
1022     long tc2;
1023     char buf[MSG_SIZ];
1024
1025     if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1026     if(ti > 0) {
1027         if(mps)
1028              sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1029         else sprintf(buf, "+%s+%d", tc, ti);
1030     } else {
1031         if(mps)
1032              sprintf(buf, "+%d/%s", mps, tc);
1033         else sprintf(buf, "+%s", tc);
1034     }
1035     fullTimeControlString = StrSave(buf);
1036
1037     if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1038         return FALSE;
1039     }
1040
1041     if( *tc == '/' ) {
1042         /* Parse second time control */
1043         tc++;
1044
1045         if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1046             return FALSE;
1047         }
1048
1049         if( tc2 == 0 ) {
1050             return FALSE;
1051         }
1052
1053         timeControl_2 = tc2 * 1000;
1054     }
1055     else {
1056         timeControl_2 = 0;
1057     }
1058
1059     if( tc1 == 0 ) {
1060         return FALSE;
1061     }
1062
1063     timeControl = tc1 * 1000;
1064 #endif
1065
1066     if (ti >= 0) {
1067         timeIncrement = ti * 1000;  /* convert to ms */
1068         movesPerSession = 0;
1069     } else {
1070         timeIncrement = 0;
1071         movesPerSession = mps;
1072     }
1073     return TRUE;
1074 }
1075
1076 void
1077 InitBackEnd2()
1078 {
1079   if (appData.debugMode) {
1080     fprintf(debugFP, "%s\n", programVersion);
1081   }
1082
1083   if (appData.matchGames > 0) {
1084     appData.matchMode = TRUE;
1085   } else if (appData.matchMode) {
1086     appData.matchGames = 1;
1087   }
1088   if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1089     appData.matchGames = appData.sameColorGames;
1090   if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1091     if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1092     if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1093   }
1094   Reset(TRUE, FALSE);
1095   if (appData.noChessProgram || first.protocolVersion == 1) {
1096     InitBackEnd3();
1097   } else {
1098     /* kludge: allow timeout for initial "feature" commands */
1099     FreezeUI();
1100     DisplayMessage("", _("Starting chess program"));
1101     ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1102   }
1103 }
1104
1105 void
1106 InitBackEnd3 P((void))
1107 {
1108     GameMode initialMode;
1109     char buf[MSG_SIZ];
1110     int err;
1111
1112     InitChessProgram(&first, startedFromSetupPosition);
1113
1114
1115     if (appData.icsActive) {
1116 #ifdef WIN32
1117         /* [DM] Make a console window if needed [HGM] merged ifs */
1118         ConsoleCreate();
1119 #endif
1120         err = establish();
1121         if (err != 0) {
1122             if (*appData.icsCommPort != NULLCHAR) {
1123                 sprintf(buf, _("Could not open comm port %s"),
1124                         appData.icsCommPort);
1125             } else {
1126                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1127                         appData.icsHost, appData.icsPort);
1128             }
1129             DisplayFatalError(buf, err, 1);
1130             return;
1131         }
1132         SetICSMode();
1133         telnetISR =
1134           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1135         fromUserISR =
1136           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1137     } else if (appData.noChessProgram) {
1138         SetNCPMode();
1139     } else {
1140         SetGNUMode();
1141     }
1142
1143     if (*appData.cmailGameName != NULLCHAR) {
1144         SetCmailMode();
1145         OpenLoopback(&cmailPR);
1146         cmailISR =
1147           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1148     }
1149
1150     ThawUI();
1151     DisplayMessage("", "");
1152     if (StrCaseCmp(appData.initialMode, "") == 0) {
1153       initialMode = BeginningOfGame;
1154     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1155       initialMode = TwoMachinesPlay;
1156     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1157       initialMode = AnalyzeFile;
1158     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1159       initialMode = AnalyzeMode;
1160     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1161       initialMode = MachinePlaysWhite;
1162     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1163       initialMode = MachinePlaysBlack;
1164     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1165       initialMode = EditGame;
1166     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1167       initialMode = EditPosition;
1168     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1169       initialMode = Training;
1170     } else {
1171       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1172       DisplayFatalError(buf, 0, 2);
1173       return;
1174     }
1175
1176     if (appData.matchMode) {
1177         /* Set up machine vs. machine match */
1178         if (appData.noChessProgram) {
1179             DisplayFatalError(_("Can't have a match with no chess programs"),
1180                               0, 2);
1181             return;
1182         }
1183         matchMode = TRUE;
1184         matchGame = 1;
1185         if (*appData.loadGameFile != NULLCHAR) {
1186             int index = appData.loadGameIndex; // [HGM] autoinc
1187             if(index<0) lastIndex = index = 1;
1188             if (!LoadGameFromFile(appData.loadGameFile,
1189                                   index,
1190                                   appData.loadGameFile, FALSE)) {
1191                 DisplayFatalError(_("Bad game file"), 0, 1);
1192                 return;
1193             }
1194         } else if (*appData.loadPositionFile != NULLCHAR) {
1195             int index = appData.loadPositionIndex; // [HGM] autoinc
1196             if(index<0) lastIndex = index = 1;
1197             if (!LoadPositionFromFile(appData.loadPositionFile,
1198                                       index,
1199                                       appData.loadPositionFile)) {
1200                 DisplayFatalError(_("Bad position file"), 0, 1);
1201                 return;
1202             }
1203         }
1204         TwoMachinesEvent();
1205     } else if (*appData.cmailGameName != NULLCHAR) {
1206         /* Set up cmail mode */
1207         ReloadCmailMsgEvent(TRUE);
1208     } else {
1209         /* Set up other modes */
1210         if (initialMode == AnalyzeFile) {
1211           if (*appData.loadGameFile == NULLCHAR) {
1212             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1213             return;
1214           }
1215         }
1216         if (*appData.loadGameFile != NULLCHAR) {
1217             (void) LoadGameFromFile(appData.loadGameFile,
1218                                     appData.loadGameIndex,
1219                                     appData.loadGameFile, TRUE);
1220         } else if (*appData.loadPositionFile != NULLCHAR) {
1221             (void) LoadPositionFromFile(appData.loadPositionFile,
1222                                         appData.loadPositionIndex,
1223                                         appData.loadPositionFile);
1224             /* [HGM] try to make self-starting even after FEN load */
1225             /* to allow automatic setup of fairy variants with wtm */
1226             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1227                 gameMode = BeginningOfGame;
1228                 setboardSpoiledMachineBlack = 1;
1229             }
1230             /* [HGM] loadPos: make that every new game uses the setup */
1231             /* from file as long as we do not switch variant          */
1232             if(!blackPlaysFirst) { int i;
1233                 startedFromPositionFile = TRUE;
1234                 CopyBoard(filePosition, boards[0]);
1235                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1236             }
1237         }
1238         if (initialMode == AnalyzeMode) {
1239           if (appData.noChessProgram) {
1240             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1241             return;
1242           }
1243           if (appData.icsActive) {
1244             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1245             return;
1246           }
1247           AnalyzeModeEvent();
1248         } else if (initialMode == AnalyzeFile) {
1249           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1250           ShowThinkingEvent();
1251           AnalyzeFileEvent();
1252           AnalysisPeriodicEvent(1);
1253         } else if (initialMode == MachinePlaysWhite) {
1254           if (appData.noChessProgram) {
1255             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1256                               0, 2);
1257             return;
1258           }
1259           if (appData.icsActive) {
1260             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1261                               0, 2);
1262             return;
1263           }
1264           MachineWhiteEvent();
1265         } else if (initialMode == MachinePlaysBlack) {
1266           if (appData.noChessProgram) {
1267             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1268                               0, 2);
1269             return;
1270           }
1271           if (appData.icsActive) {
1272             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1273                               0, 2);
1274             return;
1275           }
1276           MachineBlackEvent();
1277         } else if (initialMode == TwoMachinesPlay) {
1278           if (appData.noChessProgram) {
1279             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1280                               0, 2);
1281             return;
1282           }
1283           if (appData.icsActive) {
1284             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1285                               0, 2);
1286             return;
1287           }
1288           TwoMachinesEvent();
1289         } else if (initialMode == EditGame) {
1290           EditGameEvent();
1291         } else if (initialMode == EditPosition) {
1292           EditPositionEvent();
1293         } else if (initialMode == Training) {
1294           if (*appData.loadGameFile == NULLCHAR) {
1295             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1296             return;
1297           }
1298           TrainingEvent();
1299         }
1300     }
1301 }
1302
1303 /*
1304  * Establish will establish a contact to a remote host.port.
1305  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1306  *  used to talk to the host.
1307  * Returns 0 if okay, error code if not.
1308  */
1309 int
1310 establish()
1311 {
1312     char buf[MSG_SIZ];
1313
1314     if (*appData.icsCommPort != NULLCHAR) {
1315         /* Talk to the host through a serial comm port */
1316         return OpenCommPort(appData.icsCommPort, &icsPR);
1317
1318     } else if (*appData.gateway != NULLCHAR) {
1319         if (*appData.remoteShell == NULLCHAR) {
1320             /* Use the rcmd protocol to run telnet program on a gateway host */
1321             snprintf(buf, sizeof(buf), "%s %s %s",
1322                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1323             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1324
1325         } else {
1326             /* Use the rsh program to run telnet program on a gateway host */
1327             if (*appData.remoteUser == NULLCHAR) {
1328                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1329                         appData.gateway, appData.telnetProgram,
1330                         appData.icsHost, appData.icsPort);
1331             } else {
1332                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1333                         appData.remoteShell, appData.gateway,
1334                         appData.remoteUser, appData.telnetProgram,
1335                         appData.icsHost, appData.icsPort);
1336             }
1337             return StartChildProcess(buf, "", &icsPR);
1338
1339         }
1340     } else if (appData.useTelnet) {
1341         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1342
1343     } else {
1344         /* TCP socket interface differs somewhat between
1345            Unix and NT; handle details in the front end.
1346            */
1347         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1348     }
1349 }
1350
1351 void
1352 show_bytes(fp, buf, count)
1353      FILE *fp;
1354      char *buf;
1355      int count;
1356 {
1357     while (count--) {
1358         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1359             fprintf(fp, "\\%03o", *buf & 0xff);
1360         } else {
1361             putc(*buf, fp);
1362         }
1363         buf++;
1364     }
1365     fflush(fp);
1366 }
1367
1368 /* Returns an errno value */
1369 int
1370 OutputMaybeTelnet(pr, message, count, outError)
1371      ProcRef pr;
1372      char *message;
1373      int count;
1374      int *outError;
1375 {
1376     char buf[8192], *p, *q, *buflim;
1377     int left, newcount, outcount;
1378
1379     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1380         *appData.gateway != NULLCHAR) {
1381         if (appData.debugMode) {
1382             fprintf(debugFP, ">ICS: ");
1383             show_bytes(debugFP, message, count);
1384             fprintf(debugFP, "\n");
1385         }
1386         return OutputToProcess(pr, message, count, outError);
1387     }
1388
1389     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1390     p = message;
1391     q = buf;
1392     left = count;
1393     newcount = 0;
1394     while (left) {
1395         if (q >= buflim) {
1396             if (appData.debugMode) {
1397                 fprintf(debugFP, ">ICS: ");
1398                 show_bytes(debugFP, buf, newcount);
1399                 fprintf(debugFP, "\n");
1400             }
1401             outcount = OutputToProcess(pr, buf, newcount, outError);
1402             if (outcount < newcount) return -1; /* to be sure */
1403             q = buf;
1404             newcount = 0;
1405         }
1406         if (*p == '\n') {
1407             *q++ = '\r';
1408             newcount++;
1409         } else if (((unsigned char) *p) == TN_IAC) {
1410             *q++ = (char) TN_IAC;
1411             newcount ++;
1412         }
1413         *q++ = *p++;
1414         newcount++;
1415         left--;
1416     }
1417     if (appData.debugMode) {
1418         fprintf(debugFP, ">ICS: ");
1419         show_bytes(debugFP, buf, newcount);
1420         fprintf(debugFP, "\n");
1421     }
1422     outcount = OutputToProcess(pr, buf, newcount, outError);
1423     if (outcount < newcount) return -1; /* to be sure */
1424     return count;
1425 }
1426
1427 void
1428 read_from_player(isr, closure, message, count, error)
1429      InputSourceRef isr;
1430      VOIDSTAR closure;
1431      char *message;
1432      int count;
1433      int error;
1434 {
1435     int outError, outCount;
1436     static int gotEof = 0;
1437
1438     /* Pass data read from player on to ICS */
1439     if (count > 0) {
1440         gotEof = 0;
1441         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1442         if (outCount < count) {
1443             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1444         }
1445     } else if (count < 0) {
1446         RemoveInputSource(isr);
1447         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1448     } else if (gotEof++ > 0) {
1449         RemoveInputSource(isr);
1450         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1451     }
1452 }
1453
1454 void
1455 SendToICS(s)
1456      char *s;
1457 {
1458     int count, outCount, outError;
1459
1460     if (icsPR == NULL) return;
1461
1462     count = strlen(s);
1463     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1464     if (outCount < count) {
1465         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1466     }
1467 }
1468
1469 /* This is used for sending logon scripts to the ICS. Sending
1470    without a delay causes problems when using timestamp on ICC
1471    (at least on my machine). */
1472 void
1473 SendToICSDelayed(s,msdelay)
1474      char *s;
1475      long msdelay;
1476 {
1477     int count, outCount, outError;
1478
1479     if (icsPR == NULL) return;
1480
1481     count = strlen(s);
1482     if (appData.debugMode) {
1483         fprintf(debugFP, ">ICS: ");
1484         show_bytes(debugFP, s, count);
1485         fprintf(debugFP, "\n");
1486     }
1487     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1488                                       msdelay);
1489     if (outCount < count) {
1490         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1491     }
1492 }
1493
1494
1495 /* Remove all highlighting escape sequences in s
1496    Also deletes any suffix starting with '('
1497    */
1498 char *
1499 StripHighlightAndTitle(s)
1500      char *s;
1501 {
1502     static char retbuf[MSG_SIZ];
1503     char *p = retbuf;
1504
1505     while (*s != NULLCHAR) {
1506         while (*s == '\033') {
1507             while (*s != NULLCHAR && !isalpha(*s)) s++;
1508             if (*s != NULLCHAR) s++;
1509         }
1510         while (*s != NULLCHAR && *s != '\033') {
1511             if (*s == '(' || *s == '[') {
1512                 *p = NULLCHAR;
1513                 return retbuf;
1514             }
1515             *p++ = *s++;
1516         }
1517     }
1518     *p = NULLCHAR;
1519     return retbuf;
1520 }
1521
1522 /* Remove all highlighting escape sequences in s */
1523 char *
1524 StripHighlight(s)
1525      char *s;
1526 {
1527     static char retbuf[MSG_SIZ];
1528     char *p = retbuf;
1529
1530     while (*s != NULLCHAR) {
1531         while (*s == '\033') {
1532             while (*s != NULLCHAR && !isalpha(*s)) s++;
1533             if (*s != NULLCHAR) s++;
1534         }
1535         while (*s != NULLCHAR && *s != '\033') {
1536             *p++ = *s++;
1537         }
1538     }
1539     *p = NULLCHAR;
1540     return retbuf;
1541 }
1542
1543 char *variantNames[] = VARIANT_NAMES;
1544 char *
1545 VariantName(v)
1546      VariantClass v;
1547 {
1548     return variantNames[v];
1549 }
1550
1551
1552 /* Identify a variant from the strings the chess servers use or the
1553    PGN Variant tag names we use. */
1554 VariantClass
1555 StringToVariant(e)
1556      char *e;
1557 {
1558     char *p;
1559     int wnum = -1;
1560     VariantClass v = VariantNormal;
1561     int i, found = FALSE;
1562     char buf[MSG_SIZ];
1563
1564     if (!e) return v;
1565
1566     /* [HGM] skip over optional board-size prefixes */
1567     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1568         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1569         while( *e++ != '_');
1570     }
1571
1572     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1573       if (StrCaseStr(e, variantNames[i])) {
1574         v = (VariantClass) i;
1575         found = TRUE;
1576         break;
1577       }
1578     }
1579
1580     if (!found) {
1581       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1582           || StrCaseStr(e, "wild/fr")
1583           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1584         v = VariantFischeRandom;
1585       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1586                  (i = 1, p = StrCaseStr(e, "w"))) {
1587         p += i;
1588         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1589         if (isdigit(*p)) {
1590           wnum = atoi(p);
1591         } else {
1592           wnum = -1;
1593         }
1594         switch (wnum) {
1595         case 0: /* FICS only, actually */
1596         case 1:
1597           /* Castling legal even if K starts on d-file */
1598           v = VariantWildCastle;
1599           break;
1600         case 2:
1601         case 3:
1602         case 4:
1603           /* Castling illegal even if K & R happen to start in
1604              normal positions. */
1605           v = VariantNoCastle;
1606           break;
1607         case 5:
1608         case 7:
1609         case 8:
1610         case 10:
1611         case 11:
1612         case 12:
1613         case 13:
1614         case 14:
1615         case 15:
1616         case 18:
1617         case 19:
1618           /* Castling legal iff K & R start in normal positions */
1619           v = VariantNormal;
1620           break;
1621         case 6:
1622         case 20:
1623         case 21:
1624           /* Special wilds for position setup; unclear what to do here */
1625           v = VariantLoadable;
1626           break;
1627         case 9:
1628           /* Bizarre ICC game */
1629           v = VariantTwoKings;
1630           break;
1631         case 16:
1632           v = VariantKriegspiel;
1633           break;
1634         case 17:
1635           v = VariantLosers;
1636           break;
1637         case 22:
1638           v = VariantFischeRandom;
1639           break;
1640         case 23:
1641           v = VariantCrazyhouse;
1642           break;
1643         case 24:
1644           v = VariantBughouse;
1645           break;
1646         case 25:
1647           v = Variant3Check;
1648           break;
1649         case 26:
1650           /* Not quite the same as FICS suicide! */
1651           v = VariantGiveaway;
1652           break;
1653         case 27:
1654           v = VariantAtomic;
1655           break;
1656         case 28:
1657           v = VariantShatranj;
1658           break;
1659
1660         /* Temporary names for future ICC types.  The name *will* change in
1661            the next xboard/WinBoard release after ICC defines it. */
1662         case 29:
1663           v = Variant29;
1664           break;
1665         case 30:
1666           v = Variant30;
1667           break;
1668         case 31:
1669           v = Variant31;
1670           break;
1671         case 32:
1672           v = Variant32;
1673           break;
1674         case 33:
1675           v = Variant33;
1676           break;
1677         case 34:
1678           v = Variant34;
1679           break;
1680         case 35:
1681           v = Variant35;
1682           break;
1683         case 36:
1684           v = Variant36;
1685           break;
1686         case 37:
1687           v = VariantShogi;
1688           break;
1689         case 38:
1690           v = VariantXiangqi;
1691           break;
1692         case 39:
1693           v = VariantCourier;
1694           break;
1695         case 40:
1696           v = VariantGothic;
1697           break;
1698         case 41:
1699           v = VariantCapablanca;
1700           break;
1701         case 42:
1702           v = VariantKnightmate;
1703           break;
1704         case 43:
1705           v = VariantFairy;
1706           break;
1707         case 44:
1708           v = VariantCylinder;
1709           break;
1710         case 45:
1711           v = VariantFalcon;
1712           break;
1713         case 46:
1714           v = VariantCapaRandom;
1715           break;
1716         case 47:
1717           v = VariantBerolina;
1718           break;
1719         case 48:
1720           v = VariantJanus;
1721           break;
1722         case 49:
1723           v = VariantSuper;
1724           break;
1725         case 50:
1726           v = VariantGreat;
1727           break;
1728         case -1:
1729           /* Found "wild" or "w" in the string but no number;
1730              must assume it's normal chess. */
1731           v = VariantNormal;
1732           break;
1733         default:
1734           sprintf(buf, _("Unknown wild type %d"), wnum);
1735           DisplayError(buf, 0);
1736           v = VariantUnknown;
1737           break;
1738         }
1739       }
1740     }
1741     if (appData.debugMode) {
1742       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1743               e, wnum, VariantName(v));
1744     }
1745     return v;
1746 }
1747
1748 static int leftover_start = 0, leftover_len = 0;
1749 char star_match[STAR_MATCH_N][MSG_SIZ];
1750
1751 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1752    advance *index beyond it, and set leftover_start to the new value of
1753    *index; else return FALSE.  If pattern contains the character '*', it
1754    matches any sequence of characters not containing '\r', '\n', or the
1755    character following the '*' (if any), and the matched sequence(s) are
1756    copied into star_match.
1757    */
1758 int
1759 looking_at(buf, index, pattern)
1760      char *buf;
1761      int *index;
1762      char *pattern;
1763 {
1764     char *bufp = &buf[*index], *patternp = pattern;
1765     int star_count = 0;
1766     char *matchp = star_match[0];
1767
1768     for (;;) {
1769         if (*patternp == NULLCHAR) {
1770             *index = leftover_start = bufp - buf;
1771             *matchp = NULLCHAR;
1772             return TRUE;
1773         }
1774         if (*bufp == NULLCHAR) return FALSE;
1775         if (*patternp == '*') {
1776             if (*bufp == *(patternp + 1)) {
1777                 *matchp = NULLCHAR;
1778                 matchp = star_match[++star_count];
1779                 patternp += 2;
1780                 bufp++;
1781                 continue;
1782             } else if (*bufp == '\n' || *bufp == '\r') {
1783                 patternp++;
1784                 if (*patternp == NULLCHAR)
1785                   continue;
1786                 else
1787                   return FALSE;
1788             } else {
1789                 *matchp++ = *bufp++;
1790                 continue;
1791             }
1792         }
1793         if (*patternp != *bufp) return FALSE;
1794         patternp++;
1795         bufp++;
1796     }
1797 }
1798
1799 void
1800 SendToPlayer(data, length)
1801      char *data;
1802      int length;
1803 {
1804     int error, outCount;
1805     outCount = OutputToProcess(NoProc, data, length, &error);
1806     if (outCount < length) {
1807         DisplayFatalError(_("Error writing to display"), error, 1);
1808     }
1809 }
1810
1811 void
1812 PackHolding(packed, holding)
1813      char packed[];
1814      char *holding;
1815 {
1816     char *p = holding;
1817     char *q = packed;
1818     int runlength = 0;
1819     int curr = 9999;
1820     do {
1821         if (*p == curr) {
1822             runlength++;
1823         } else {
1824             switch (runlength) {
1825               case 0:
1826                 break;
1827               case 1:
1828                 *q++ = curr;
1829                 break;
1830               case 2:
1831                 *q++ = curr;
1832                 *q++ = curr;
1833                 break;
1834               default:
1835                 sprintf(q, "%d", runlength);
1836                 while (*q) q++;
1837                 *q++ = curr;
1838                 break;
1839             }
1840             runlength = 1;
1841             curr = *p;
1842         }
1843     } while (*p++);
1844     *q = NULLCHAR;
1845 }
1846
1847 /* Telnet protocol requests from the front end */
1848 void
1849 TelnetRequest(ddww, option)
1850      unsigned char ddww, option;
1851 {
1852     unsigned char msg[3];
1853     int outCount, outError;
1854
1855     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1856
1857     if (appData.debugMode) {
1858         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1859         switch (ddww) {
1860           case TN_DO:
1861             ddwwStr = "DO";
1862             break;
1863           case TN_DONT:
1864             ddwwStr = "DONT";
1865             break;
1866           case TN_WILL:
1867             ddwwStr = "WILL";
1868             break;
1869           case TN_WONT:
1870             ddwwStr = "WONT";
1871             break;
1872           default:
1873             ddwwStr = buf1;
1874             sprintf(buf1, "%d", ddww);
1875             break;
1876         }
1877         switch (option) {
1878           case TN_ECHO:
1879             optionStr = "ECHO";
1880             break;
1881           default:
1882             optionStr = buf2;
1883             sprintf(buf2, "%d", option);
1884             break;
1885         }
1886         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1887     }
1888     msg[0] = TN_IAC;
1889     msg[1] = ddww;
1890     msg[2] = option;
1891     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1892     if (outCount < 3) {
1893         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1894     }
1895 }
1896
1897 void
1898 DoEcho()
1899 {
1900     if (!appData.icsActive) return;
1901     TelnetRequest(TN_DO, TN_ECHO);
1902 }
1903
1904 void
1905 DontEcho()
1906 {
1907     if (!appData.icsActive) return;
1908     TelnetRequest(TN_DONT, TN_ECHO);
1909 }
1910
1911 void
1912 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1913 {
1914     /* put the holdings sent to us by the server on the board holdings area */
1915     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1916     char p;
1917     ChessSquare piece;
1918
1919     if(gameInfo.holdingsWidth < 2)  return;
1920
1921     if( (int)lowestPiece >= BlackPawn ) {
1922         holdingsColumn = 0;
1923         countsColumn = 1;
1924         holdingsStartRow = BOARD_HEIGHT-1;
1925         direction = -1;
1926     } else {
1927         holdingsColumn = BOARD_WIDTH-1;
1928         countsColumn = BOARD_WIDTH-2;
1929         holdingsStartRow = 0;
1930         direction = 1;
1931     }
1932
1933     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934         board[i][holdingsColumn] = EmptySquare;
1935         board[i][countsColumn]   = (ChessSquare) 0;
1936     }
1937     while( (p=*holdings++) != NULLCHAR ) {
1938         piece = CharToPiece( ToUpper(p) );
1939         if(piece == EmptySquare) continue;
1940         /*j = (int) piece - (int) WhitePawn;*/
1941         j = PieceToNumber(piece);
1942         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943         if(j < 0) continue;               /* should not happen */
1944         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946         board[holdingsStartRow+j*direction][countsColumn]++;
1947     }
1948
1949 }
1950
1951
1952 void
1953 VariantSwitch(Board board, VariantClass newVariant)
1954 {
1955    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1956    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1957 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1958
1959    startedFromPositionFile = FALSE;
1960    if(gameInfo.variant == newVariant) return;
1961
1962    /* [HGM] This routine is called each time an assignment is made to
1963     * gameInfo.variant during a game, to make sure the board sizes
1964     * are set to match the new variant. If that means adding or deleting
1965     * holdings, we shift the playing board accordingly
1966     * This kludge is needed because in ICS observe mode, we get boards
1967     * of an ongoing game without knowing the variant, and learn about the
1968     * latter only later. This can be because of the move list we requested,
1969     * in which case the game history is refilled from the beginning anyway,
1970     * but also when receiving holdings of a crazyhouse game. In the latter
1971     * case we want to add those holdings to the already received position.
1972     */
1973
1974
1975   if (appData.debugMode) {
1976     fprintf(debugFP, "Switch board from %s to %s\n",
1977                VariantName(gameInfo.variant), VariantName(newVariant));
1978     setbuf(debugFP, NULL);
1979   }
1980     shuffleOpenings = 0;       /* [HGM] shuffle */
1981     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1982     switch(newVariant) {
1983             case VariantShogi:
1984               newWidth = 9;  newHeight = 9;
1985               gameInfo.holdingsSize = 7;
1986             case VariantBughouse:
1987             case VariantCrazyhouse:
1988               newHoldingsWidth = 2; break;
1989             default:
1990               newHoldingsWidth = gameInfo.holdingsSize = 0;
1991     }
1992
1993     if(newWidth  != gameInfo.boardWidth  ||
1994        newHeight != gameInfo.boardHeight ||
1995        newHoldingsWidth != gameInfo.holdingsWidth ) {
1996
1997         /* shift position to new playing area, if needed */
1998         if(newHoldingsWidth > gameInfo.holdingsWidth) {
1999            for(i=0; i<BOARD_HEIGHT; i++)
2000                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2001                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2002                                                      board[i][j];
2003            for(i=0; i<newHeight; i++) {
2004                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2005                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2006            }
2007         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2008            for(i=0; i<BOARD_HEIGHT; i++)
2009                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2010                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011                                                  board[i][j];
2012         }
2013
2014         gameInfo.boardWidth  = newWidth;
2015         gameInfo.boardHeight = newHeight;
2016         gameInfo.holdingsWidth = newHoldingsWidth;
2017         gameInfo.variant = newVariant;
2018         InitDrawingSizes(-2, 0);
2019
2020         /* [HGM] The following should definitely be solved in a better way */
2021 #if 0
2022         CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2023         for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2024         saveEP = epStatus[0];
2025 #endif
2026         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2027 #if 0
2028         epStatus[0] = saveEP;
2029         for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2030         CopyBoard(tempBoard, board); /* restore position received from ICS   */
2031 #endif
2032     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2033
2034     forwardMostMove = oldForwardMostMove;
2035     backwardMostMove = oldBackwardMostMove;
2036     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2037 }
2038
2039 static int loggedOn = FALSE;
2040
2041 /*-- Game start info cache: --*/
2042 int gs_gamenum;
2043 char gs_kind[MSG_SIZ];
2044 static char player1Name[128] = "";
2045 static char player2Name[128] = "";
2046 static int player1Rating = -1;
2047 static int player2Rating = -1;
2048 /*----------------------------*/
2049
2050 ColorClass curColor = ColorNormal;
2051 int suppressKibitz = 0;
2052
2053 void
2054 read_from_ics(isr, closure, data, count, error)
2055      InputSourceRef isr;
2056      VOIDSTAR closure;
2057      char *data;
2058      int count;
2059      int error;
2060 {
2061 #define BUF_SIZE 8192
2062 #define STARTED_NONE 0
2063 #define STARTED_MOVES 1
2064 #define STARTED_BOARD 2
2065 #define STARTED_OBSERVE 3
2066 #define STARTED_HOLDINGS 4
2067 #define STARTED_CHATTER 5
2068 #define STARTED_COMMENT 6
2069 #define STARTED_MOVES_NOHIDE 7
2070
2071     static int started = STARTED_NONE;
2072     static char parse[20000];
2073     static int parse_pos = 0;
2074     static char buf[BUF_SIZE + 1];
2075     static int firstTime = TRUE, intfSet = FALSE;
2076     static ColorClass prevColor = ColorNormal;
2077     static int savingComment = FALSE;
2078     char str[500];
2079     int i, oldi;
2080     int buf_len;
2081     int next_out;
2082     int tkind;
2083     int backup;    /* [DM] For zippy color lines */
2084     char *p;
2085
2086     if (appData.debugMode) {
2087       if (!error) {
2088         fprintf(debugFP, "<ICS: ");
2089         show_bytes(debugFP, data, count);
2090         fprintf(debugFP, "\n");
2091       }
2092     }
2093
2094     if (appData.debugMode) { int f = forwardMostMove;
2095         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2096                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2097     }
2098     if (count > 0) {
2099         /* If last read ended with a partial line that we couldn't parse,
2100            prepend it to the new read and try again. */
2101         if (leftover_len > 0) {
2102             for (i=0; i<leftover_len; i++)
2103               buf[i] = buf[leftover_start + i];
2104         }
2105
2106         /* Copy in new characters, removing nulls and \r's */
2107         buf_len = leftover_len;
2108         for (i = 0; i < count; i++) {
2109             if (data[i] != NULLCHAR && data[i] != '\r')
2110               buf[buf_len++] = data[i];
2111             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2112                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ')
2113                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2114         }
2115
2116         buf[buf_len] = NULLCHAR;
2117         next_out = leftover_len;
2118         leftover_start = 0;
2119
2120         i = 0;
2121         while (i < buf_len) {
2122             /* Deal with part of the TELNET option negotiation
2123                protocol.  We refuse to do anything beyond the
2124                defaults, except that we allow the WILL ECHO option,
2125                which ICS uses to turn off password echoing when we are
2126                directly connected to it.  We reject this option
2127                if localLineEditing mode is on (always on in xboard)
2128                and we are talking to port 23, which might be a real
2129                telnet server that will try to keep WILL ECHO on permanently.
2130              */
2131             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2132                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2133                 unsigned char option;
2134                 oldi = i;
2135                 switch ((unsigned char) buf[++i]) {
2136                   case TN_WILL:
2137                     if (appData.debugMode)
2138                       fprintf(debugFP, "\n<WILL ");
2139                     switch (option = (unsigned char) buf[++i]) {
2140                       case TN_ECHO:
2141                         if (appData.debugMode)
2142                           fprintf(debugFP, "ECHO ");
2143                         /* Reply only if this is a change, according
2144                            to the protocol rules. */
2145                         if (remoteEchoOption) break;
2146                         if (appData.localLineEditing &&
2147                             atoi(appData.icsPort) == TN_PORT) {
2148                             TelnetRequest(TN_DONT, TN_ECHO);
2149                         } else {
2150                             EchoOff();
2151                             TelnetRequest(TN_DO, TN_ECHO);
2152                             remoteEchoOption = TRUE;
2153                         }
2154                         break;
2155                       default:
2156                         if (appData.debugMode)
2157                           fprintf(debugFP, "%d ", option);
2158                         /* Whatever this is, we don't want it. */
2159                         TelnetRequest(TN_DONT, option);
2160                         break;
2161                     }
2162                     break;
2163                   case TN_WONT:
2164                     if (appData.debugMode)
2165                       fprintf(debugFP, "\n<WONT ");
2166                     switch (option = (unsigned char) buf[++i]) {
2167                       case TN_ECHO:
2168                         if (appData.debugMode)
2169                           fprintf(debugFP, "ECHO ");
2170                         /* Reply only if this is a change, according
2171                            to the protocol rules. */
2172                         if (!remoteEchoOption) break;
2173                         EchoOn();
2174                         TelnetRequest(TN_DONT, TN_ECHO);
2175                         remoteEchoOption = FALSE;
2176                         break;
2177                       default:
2178                         if (appData.debugMode)
2179                           fprintf(debugFP, "%d ", (unsigned char) option);
2180                         /* Whatever this is, it must already be turned
2181                            off, because we never agree to turn on
2182                            anything non-default, so according to the
2183                            protocol rules, we don't reply. */
2184                         break;
2185                     }
2186                     break;
2187                   case TN_DO:
2188                     if (appData.debugMode)
2189                       fprintf(debugFP, "\n<DO ");
2190                     switch (option = (unsigned char) buf[++i]) {
2191                       default:
2192                         /* Whatever this is, we refuse to do it. */
2193                         if (appData.debugMode)
2194                           fprintf(debugFP, "%d ", option);
2195                         TelnetRequest(TN_WONT, option);
2196                         break;
2197                     }
2198                     break;
2199                   case TN_DONT:
2200                     if (appData.debugMode)
2201                       fprintf(debugFP, "\n<DONT ");
2202                     switch (option = (unsigned char) buf[++i]) {
2203                       default:
2204                         if (appData.debugMode)
2205                           fprintf(debugFP, "%d ", option);
2206                         /* Whatever this is, we are already not doing
2207                            it, because we never agree to do anything
2208                            non-default, so according to the protocol
2209                            rules, we don't reply. */
2210                         break;
2211                     }
2212                     break;
2213                   case TN_IAC:
2214                     if (appData.debugMode)
2215                       fprintf(debugFP, "\n<IAC ");
2216                     /* Doubled IAC; pass it through */
2217                     i--;
2218                     break;
2219                   default:
2220                     if (appData.debugMode)
2221                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2222                     /* Drop all other telnet commands on the floor */
2223                     break;
2224                 }
2225                 if (oldi > next_out)
2226                   SendToPlayer(&buf[next_out], oldi - next_out);
2227                 if (++i > next_out)
2228                   next_out = i;
2229                 continue;
2230             }
2231
2232             /* OK, this at least will *usually* work */
2233             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2234                 loggedOn = TRUE;
2235             }
2236
2237             if (loggedOn && !intfSet) {
2238                 if (ics_type == ICS_ICC) {
2239                   sprintf(str,
2240                           "/set-quietly interface %s\n/set-quietly style 12\n",
2241                           programVersion);
2242
2243                 } else if (ics_type == ICS_CHESSNET) {
2244                   sprintf(str, "/style 12\n");
2245                 } else {
2246                   strcpy(str, "alias $ @\n$set interface ");
2247                   strcat(str, programVersion);
2248                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2249 #ifdef WIN32
2250                   strcat(str, "$iset nohighlight 1\n");
2251 #endif
2252                   strcat(str, "$iset lock 1\n$style 12\n");
2253                 }
2254                 SendToICS(str);
2255                 intfSet = TRUE;
2256             }
2257
2258             if (started == STARTED_COMMENT) {
2259                 /* Accumulate characters in comment */
2260                 parse[parse_pos++] = buf[i];
2261                 if (buf[i] == '\n') {
2262                     parse[parse_pos] = NULLCHAR;
2263                     if(!suppressKibitz) // [HGM] kibitz
2264                         AppendComment(forwardMostMove, StripHighlight(parse));
2265                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2266                         int nrDigit = 0, nrAlph = 0, i;
2267                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2268                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2269                         parse[parse_pos] = NULLCHAR;
2270                         // try to be smart: if it does not look like search info, it should go to
2271                         // ICS interaction window after all, not to engine-output window.
2272                         for(i=0; i<parse_pos; i++) { // count letters and digits
2273                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2274                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2275                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2276                         }
2277                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2278                             int depth=0; float score;
2279                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2280                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2281                                 pvInfoList[forwardMostMove-1].depth = depth;
2282                                 pvInfoList[forwardMostMove-1].score = 100*score;
2283                             }
2284                             OutputKibitz(suppressKibitz, parse);
2285                         } else {
2286                             char tmp[MSG_SIZ];
2287                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2288                             SendToPlayer(tmp, strlen(tmp));
2289                         }
2290                     }
2291                     started = STARTED_NONE;
2292                 } else {
2293                     /* Don't match patterns against characters in chatter */
2294                     i++;
2295                     continue;
2296                 }
2297             }
2298             if (started == STARTED_CHATTER) {
2299                 if (buf[i] != '\n') {
2300                     /* Don't match patterns against characters in chatter */
2301                     i++;
2302                     continue;
2303                 }
2304                 started = STARTED_NONE;
2305             }
2306
2307             /* Kludge to deal with rcmd protocol */
2308             if (firstTime && looking_at(buf, &i, "\001*")) {
2309                 DisplayFatalError(&buf[1], 0, 1);
2310                 continue;
2311             } else {
2312                 firstTime = FALSE;
2313             }
2314
2315             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2316                 ics_type = ICS_ICC;
2317                 ics_prefix = "/";
2318                 if (appData.debugMode)
2319                   fprintf(debugFP, "ics_type %d\n", ics_type);
2320                 continue;
2321             }
2322             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2323                 ics_type = ICS_FICS;
2324                 ics_prefix = "$";
2325                 if (appData.debugMode)
2326                   fprintf(debugFP, "ics_type %d\n", ics_type);
2327                 continue;
2328             }
2329             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2330                 ics_type = ICS_CHESSNET;
2331                 ics_prefix = "/";
2332                 if (appData.debugMode)
2333                   fprintf(debugFP, "ics_type %d\n", ics_type);
2334                 continue;
2335             }
2336
2337             if (!loggedOn &&
2338                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2339                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2340                  looking_at(buf, &i, "will be \"*\""))) {
2341               strcpy(ics_handle, star_match[0]);
2342               continue;
2343             }
2344
2345             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2346               char buf[MSG_SIZ];
2347               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2348               DisplayIcsInteractionTitle(buf);
2349               have_set_title = TRUE;
2350             }
2351
2352             /* skip finger notes */
2353             if (started == STARTED_NONE &&
2354                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2355                  (buf[i] == '1' && buf[i+1] == '0')) &&
2356                 buf[i+2] == ':' && buf[i+3] == ' ') {
2357               started = STARTED_CHATTER;
2358               i += 3;
2359               continue;
2360             }
2361
2362             /* skip formula vars */
2363             if (started == STARTED_NONE &&
2364                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2365               started = STARTED_CHATTER;
2366               i += 3;
2367               continue;
2368             }
2369
2370             oldi = i;
2371             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2372             if (appData.autoKibitz && started == STARTED_NONE &&
2373                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2374                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2375                 if(looking_at(buf, &i, "* kibitzes: ") &&
2376                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2377                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2378                         suppressKibitz = TRUE;
2379                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2380                                 && (gameMode == IcsPlayingWhite)) ||
2381                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2382                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2383                             started = STARTED_CHATTER; // own kibitz we simply discard
2384                         else {
2385                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2386                             parse_pos = 0; parse[0] = NULLCHAR;
2387                             savingComment = TRUE;
2388                             suppressKibitz = gameMode != IcsObserving ? 2 :
2389                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2390                         }
2391                         continue;
2392                 } else
2393                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2394                     started = STARTED_CHATTER;
2395                     suppressKibitz = TRUE;
2396                 }
2397             } // [HGM] kibitz: end of patch
2398
2399             if (appData.zippyTalk || appData.zippyPlay) {
2400                 /* [DM] Backup address for color zippy lines */
2401                 backup = i;
2402 #if ZIPPY
2403        #ifdef WIN32
2404                if (loggedOn == TRUE)
2405                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2406                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2407        #else
2408                 if (ZippyControl(buf, &i) ||
2409                     ZippyConverse(buf, &i) ||
2410                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2411                       loggedOn = TRUE;
2412                       if (!appData.colorize) continue;
2413                 }
2414        #endif
2415 #endif
2416             } // [DM] 'else { ' deleted
2417                 if (/* Don't color "message" or "messages" output */
2418                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2419                     looking_at(buf, &i, "*. * at *:*: ") ||
2420                     looking_at(buf, &i, "--* (*:*): ") ||
2421                     /* Regular tells and says */
2422                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2423                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2424                     looking_at(buf, &i, "* says: ") ||
2425                     /* Message notifications (same color as tells) */
2426                     looking_at(buf, &i, "* has left a message ") ||
2427                     looking_at(buf, &i, "* just sent you a message:\n") ||
2428                     /* Whispers and kibitzes */
2429                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2430                     looking_at(buf, &i, "* kibitzes: ") ||
2431                     /* Channel tells */
2432                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2433
2434                   if (tkind == 1 && strchr(star_match[0], ':')) {
2435                       /* Avoid "tells you:" spoofs in channels */
2436                      tkind = 3;
2437                   }
2438                   if (star_match[0][0] == NULLCHAR ||
2439                       strchr(star_match[0], ' ') ||
2440                       (tkind == 3 && strchr(star_match[1], ' '))) {
2441                     /* Reject bogus matches */
2442                     i = oldi;
2443                   } else {
2444                     if (appData.colorize) {
2445                       if (oldi > next_out) {
2446                         SendToPlayer(&buf[next_out], oldi - next_out);
2447                         next_out = oldi;
2448                       }
2449                       switch (tkind) {
2450                       case 1:
2451                         Colorize(ColorTell, FALSE);
2452                         curColor = ColorTell;
2453                         break;
2454                       case 2:
2455                         Colorize(ColorKibitz, FALSE);
2456                         curColor = ColorKibitz;
2457                         break;
2458                       case 3:
2459                         p = strrchr(star_match[1], '(');
2460                         if (p == NULL) {
2461                           p = star_match[1];
2462                         } else {
2463                           p++;
2464                         }
2465                         if (atoi(p) == 1) {
2466                           Colorize(ColorChannel1, FALSE);
2467                           curColor = ColorChannel1;
2468                         } else {
2469                           Colorize(ColorChannel, FALSE);
2470                           curColor = ColorChannel;
2471                         }
2472                         break;
2473                       case 5:
2474                         curColor = ColorNormal;
2475                         break;
2476                       }
2477                     }
2478                     if (started == STARTED_NONE && appData.autoComment &&
2479                         (gameMode == IcsObserving ||
2480                          gameMode == IcsPlayingWhite ||
2481                          gameMode == IcsPlayingBlack)) {
2482                       parse_pos = i - oldi;
2483                       memcpy(parse, &buf[oldi], parse_pos);
2484                       parse[parse_pos] = NULLCHAR;
2485                       started = STARTED_COMMENT;
2486                       savingComment = TRUE;
2487                     } else {
2488                       started = STARTED_CHATTER;
2489                       savingComment = FALSE;
2490                     }
2491                     loggedOn = TRUE;
2492                     continue;
2493                   }
2494                 }
2495
2496                 if (looking_at(buf, &i, "* s-shouts: ") ||
2497                     looking_at(buf, &i, "* c-shouts: ")) {
2498                     if (appData.colorize) {
2499                         if (oldi > next_out) {
2500                             SendToPlayer(&buf[next_out], oldi - next_out);
2501                             next_out = oldi;
2502                         }
2503                         Colorize(ColorSShout, FALSE);
2504                         curColor = ColorSShout;
2505                     }
2506                     loggedOn = TRUE;
2507                     started = STARTED_CHATTER;
2508                     continue;
2509                 }
2510
2511                 if (looking_at(buf, &i, "--->")) {
2512                     loggedOn = TRUE;
2513                     continue;
2514                 }
2515
2516                 if (looking_at(buf, &i, "* shouts: ") ||
2517                     looking_at(buf, &i, "--> ")) {
2518                     if (appData.colorize) {
2519                         if (oldi > next_out) {
2520                             SendToPlayer(&buf[next_out], oldi - next_out);
2521                             next_out = oldi;
2522                         }
2523                         Colorize(ColorShout, FALSE);
2524                         curColor = ColorShout;
2525                     }
2526                     loggedOn = TRUE;
2527                     started = STARTED_CHATTER;
2528                     continue;
2529                 }
2530
2531                 if (looking_at( buf, &i, "Challenge:")) {
2532                     if (appData.colorize) {
2533                         if (oldi > next_out) {
2534                             SendToPlayer(&buf[next_out], oldi - next_out);
2535                             next_out = oldi;
2536                         }
2537                         Colorize(ColorChallenge, FALSE);
2538                         curColor = ColorChallenge;
2539                     }
2540                     loggedOn = TRUE;
2541                     continue;
2542                 }
2543
2544                 if (looking_at(buf, &i, "* offers you") ||
2545                     looking_at(buf, &i, "* offers to be") ||
2546                     looking_at(buf, &i, "* would like to") ||
2547                     looking_at(buf, &i, "* requests to") ||
2548                     looking_at(buf, &i, "Your opponent offers") ||
2549                     looking_at(buf, &i, "Your opponent requests")) {
2550
2551                     if (appData.colorize) {
2552                         if (oldi > next_out) {
2553                             SendToPlayer(&buf[next_out], oldi - next_out);
2554                             next_out = oldi;
2555                         }
2556                         Colorize(ColorRequest, FALSE);
2557                         curColor = ColorRequest;
2558                     }
2559                     continue;
2560                 }
2561
2562                 if (looking_at(buf, &i, "* (*) seeking")) {
2563                     if (appData.colorize) {
2564                         if (oldi > next_out) {
2565                             SendToPlayer(&buf[next_out], oldi - next_out);
2566                             next_out = oldi;
2567                         }
2568                         Colorize(ColorSeek, FALSE);
2569                         curColor = ColorSeek;
2570                     }
2571                     continue;
2572             }
2573
2574             if (looking_at(buf, &i, "\\   ")) {
2575                 if (prevColor != ColorNormal) {
2576                     if (oldi > next_out) {
2577                         SendToPlayer(&buf[next_out], oldi - next_out);
2578                         next_out = oldi;
2579                     }
2580                     Colorize(prevColor, TRUE);
2581                     curColor = prevColor;
2582                 }
2583                 if (savingComment) {
2584                     parse_pos = i - oldi;
2585                     memcpy(parse, &buf[oldi], parse_pos);
2586                     parse[parse_pos] = NULLCHAR;
2587                     started = STARTED_COMMENT;
2588                 } else {
2589                     started = STARTED_CHATTER;
2590                 }
2591                 continue;
2592             }
2593
2594             if (looking_at(buf, &i, "Black Strength :") ||
2595                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2596                 looking_at(buf, &i, "<10>") ||
2597                 looking_at(buf, &i, "#@#")) {
2598                 /* Wrong board style */
2599                 loggedOn = TRUE;
2600                 SendToICS(ics_prefix);
2601                 SendToICS("set style 12\n");
2602                 SendToICS(ics_prefix);
2603                 SendToICS("refresh\n");
2604                 continue;
2605             }
2606
2607             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2608                 ICSInitScript();
2609                 have_sent_ICS_logon = 1;
2610                 continue;
2611             }
2612
2613             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2614                 (looking_at(buf, &i, "\n<12> ") ||
2615                  looking_at(buf, &i, "<12> "))) {
2616                 loggedOn = TRUE;
2617                 if (oldi > next_out) {
2618                     SendToPlayer(&buf[next_out], oldi - next_out);
2619                 }
2620                 next_out = i;
2621                 started = STARTED_BOARD;
2622                 parse_pos = 0;
2623                 continue;
2624             }
2625
2626             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2627                 looking_at(buf, &i, "<b1> ")) {
2628                 if (oldi > next_out) {
2629                     SendToPlayer(&buf[next_out], oldi - next_out);
2630                 }
2631                 next_out = i;
2632                 started = STARTED_HOLDINGS;
2633                 parse_pos = 0;
2634                 continue;
2635             }
2636
2637             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2638                 loggedOn = TRUE;
2639                 /* Header for a move list -- first line */
2640
2641                 switch (ics_getting_history) {
2642                   case H_FALSE:
2643                     switch (gameMode) {
2644                       case IcsIdle:
2645                       case BeginningOfGame:
2646                         /* User typed "moves" or "oldmoves" while we
2647                            were idle.  Pretend we asked for these
2648                            moves and soak them up so user can step
2649                            through them and/or save them.
2650                            */
2651                         Reset(FALSE, TRUE);
2652                         gameMode = IcsObserving;
2653                         ModeHighlight();
2654                         ics_gamenum = -1;
2655                         ics_getting_history = H_GOT_UNREQ_HEADER;
2656                         break;
2657                       case EditGame: /*?*/
2658                       case EditPosition: /*?*/
2659                         /* Should above feature work in these modes too? */
2660                         /* For now it doesn't */
2661                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2662                         break;
2663                       default:
2664                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2665                         break;
2666                     }
2667                     break;
2668                   case H_REQUESTED:
2669                     /* Is this the right one? */
2670                     if (gameInfo.white && gameInfo.black &&
2671                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2672                         strcmp(gameInfo.black, star_match[2]) == 0) {
2673                         /* All is well */
2674                         ics_getting_history = H_GOT_REQ_HEADER;
2675                     }
2676                     break;
2677                   case H_GOT_REQ_HEADER:
2678                   case H_GOT_UNREQ_HEADER:
2679                   case H_GOT_UNWANTED_HEADER:
2680                   case H_GETTING_MOVES:
2681                     /* Should not happen */
2682                     DisplayError(_("Error gathering move list: two headers"), 0);
2683                     ics_getting_history = H_FALSE;
2684                     break;
2685                 }
2686
2687                 /* Save player ratings into gameInfo if needed */
2688                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2689                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2690                     (gameInfo.whiteRating == -1 ||
2691                      gameInfo.blackRating == -1)) {
2692
2693                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2694                     gameInfo.blackRating = string_to_rating(star_match[3]);
2695                     if (appData.debugMode)
2696                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2697                               gameInfo.whiteRating, gameInfo.blackRating);
2698                 }
2699                 continue;
2700             }
2701
2702             if (looking_at(buf, &i,
2703               "* * match, initial time: * minute*, increment: * second")) {
2704                 /* Header for a move list -- second line */
2705                 /* Initial board will follow if this is a wild game */
2706                 if (gameInfo.event != NULL) free(gameInfo.event);
2707                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2708                 gameInfo.event = StrSave(str);
2709                 /* [HGM] we switched variant. Translate boards if needed. */
2710                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2711                 continue;
2712             }
2713
2714             if (looking_at(buf, &i, "Move  ")) {
2715                 /* Beginning of a move list */
2716                 switch (ics_getting_history) {
2717                   case H_FALSE:
2718                     /* Normally should not happen */
2719                     /* Maybe user hit reset while we were parsing */
2720                     break;
2721                   case H_REQUESTED:
2722                     /* Happens if we are ignoring a move list that is not
2723                      * the one we just requested.  Common if the user
2724                      * tries to observe two games without turning off
2725                      * getMoveList */
2726                     break;
2727                   case H_GETTING_MOVES:
2728                     /* Should not happen */
2729                     DisplayError(_("Error gathering move list: nested"), 0);
2730                     ics_getting_history = H_FALSE;
2731                     break;
2732                   case H_GOT_REQ_HEADER:
2733                     ics_getting_history = H_GETTING_MOVES;
2734                     started = STARTED_MOVES;
2735                     parse_pos = 0;
2736                     if (oldi > next_out) {
2737                         SendToPlayer(&buf[next_out], oldi - next_out);
2738                     }
2739                     break;
2740                   case H_GOT_UNREQ_HEADER:
2741                     ics_getting_history = H_GETTING_MOVES;
2742                     started = STARTED_MOVES_NOHIDE;
2743                     parse_pos = 0;
2744                     break;
2745                   case H_GOT_UNWANTED_HEADER:
2746                     ics_getting_history = H_FALSE;
2747                     break;
2748                 }
2749                 continue;
2750             }
2751
2752             if (looking_at(buf, &i, "% ") ||
2753                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2754                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2755                 savingComment = FALSE;
2756                 switch (started) {
2757                   case STARTED_MOVES:
2758                   case STARTED_MOVES_NOHIDE:
2759                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2760                     parse[parse_pos + i - oldi] = NULLCHAR;
2761                     ParseGameHistory(parse);
2762 #if ZIPPY
2763                     if (appData.zippyPlay && first.initDone) {
2764                         FeedMovesToProgram(&first, forwardMostMove);
2765                         if (gameMode == IcsPlayingWhite) {
2766                             if (WhiteOnMove(forwardMostMove)) {
2767                                 if (first.sendTime) {
2768                                   if (first.useColors) {
2769                                     SendToProgram("black\n", &first);
2770                                   }
2771                                   SendTimeRemaining(&first, TRUE);
2772                                 }
2773 #if 0
2774                                 if (first.useColors) {
2775                                   SendToProgram("white\ngo\n", &first);
2776                                 } else {
2777                                   SendToProgram("go\n", &first);
2778                                 }
2779 #else
2780                                 if (first.useColors) {
2781                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2782                                 }
2783                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2784 #endif
2785                                 first.maybeThinking = TRUE;
2786                             } else {
2787                                 if (first.usePlayother) {
2788                                   if (first.sendTime) {
2789                                     SendTimeRemaining(&first, TRUE);
2790                                   }
2791                                   SendToProgram("playother\n", &first);
2792                                   firstMove = FALSE;
2793                                 } else {
2794                                   firstMove = TRUE;
2795                                 }
2796                             }
2797                         } else if (gameMode == IcsPlayingBlack) {
2798                             if (!WhiteOnMove(forwardMostMove)) {
2799                                 if (first.sendTime) {
2800                                   if (first.useColors) {
2801                                     SendToProgram("white\n", &first);
2802                                   }
2803                                   SendTimeRemaining(&first, FALSE);
2804                                 }
2805 #if 0
2806                                 if (first.useColors) {
2807                                   SendToProgram("black\ngo\n", &first);
2808                                 } else {
2809                                   SendToProgram("go\n", &first);
2810                                 }
2811 #else
2812                                 if (first.useColors) {
2813                                   SendToProgram("black\n", &first);
2814                                 }
2815                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2816 #endif
2817                                 first.maybeThinking = TRUE;
2818                             } else {
2819                                 if (first.usePlayother) {
2820                                   if (first.sendTime) {
2821                                     SendTimeRemaining(&first, FALSE);
2822                                   }
2823                                   SendToProgram("playother\n", &first);
2824                                   firstMove = FALSE;
2825                                 } else {
2826                                   firstMove = TRUE;
2827                                 }
2828                             }
2829                         }
2830                     }
2831 #endif
2832                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2833                         /* Moves came from oldmoves or moves command
2834                            while we weren't doing anything else.
2835                            */
2836                         currentMove = forwardMostMove;
2837                         ClearHighlights();/*!!could figure this out*/
2838                         flipView = appData.flipView;
2839                         DrawPosition(FALSE, boards[currentMove]);
2840                         DisplayBothClocks();
2841                         sprintf(str, "%s vs. %s",
2842                                 gameInfo.white, gameInfo.black);
2843                         DisplayTitle(str);
2844                         gameMode = IcsIdle;
2845                     } else {
2846                         /* Moves were history of an active game */
2847                         if (gameInfo.resultDetails != NULL) {
2848                             free(gameInfo.resultDetails);
2849                             gameInfo.resultDetails = NULL;
2850                         }
2851                     }
2852                     HistorySet(parseList, backwardMostMove,
2853                                forwardMostMove, currentMove-1);
2854                     DisplayMove(currentMove - 1);
2855                     if (started == STARTED_MOVES) next_out = i;
2856                     started = STARTED_NONE;
2857                     ics_getting_history = H_FALSE;
2858                     break;
2859
2860                   case STARTED_OBSERVE:
2861                     started = STARTED_NONE;
2862                     SendToICS(ics_prefix);
2863                     SendToICS("refresh\n");
2864                     break;
2865
2866                   default:
2867                     break;
2868                 }
2869                 if(bookHit) { // [HGM] book: simulate book reply
2870                     static char bookMove[MSG_SIZ]; // a bit generous?
2871
2872                     programStats.nodes = programStats.depth = programStats.time =
2873                     programStats.score = programStats.got_only_move = 0;
2874                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2875
2876                     strcpy(bookMove, "move ");
2877                     strcat(bookMove, bookHit);
2878                     HandleMachineMove(bookMove, &first);
2879                 }
2880                 continue;
2881             }
2882
2883             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2884                  started == STARTED_HOLDINGS ||
2885                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2886                 /* Accumulate characters in move list or board */
2887                 parse[parse_pos++] = buf[i];
2888             }
2889
2890             /* Start of game messages.  Mostly we detect start of game
2891                when the first board image arrives.  On some versions
2892                of the ICS, though, we need to do a "refresh" after starting
2893                to observe in order to get the current board right away. */
2894             if (looking_at(buf, &i, "Adding game * to observation list")) {
2895                 started = STARTED_OBSERVE;
2896                 continue;
2897             }
2898
2899             /* Handle auto-observe */
2900             if (appData.autoObserve &&
2901                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2902                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2903                 char *player;
2904                 /* Choose the player that was highlighted, if any. */
2905                 if (star_match[0][0] == '\033' ||
2906                     star_match[1][0] != '\033') {
2907                     player = star_match[0];
2908                 } else {
2909                     player = star_match[2];
2910                 }
2911                 sprintf(str, "%sobserve %s\n",
2912                         ics_prefix, StripHighlightAndTitle(player));
2913                 SendToICS(str);
2914
2915                 /* Save ratings from notify string */
2916                 strcpy(player1Name, star_match[0]);
2917                 player1Rating = string_to_rating(star_match[1]);
2918                 strcpy(player2Name, star_match[2]);
2919                 player2Rating = string_to_rating(star_match[3]);
2920
2921                 if (appData.debugMode)
2922                   fprintf(debugFP,
2923                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2924                           player1Name, player1Rating,
2925                           player2Name, player2Rating);
2926
2927                 continue;
2928             }
2929
2930             /* Deal with automatic examine mode after a game,
2931                and with IcsObserving -> IcsExamining transition */
2932             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2933                 looking_at(buf, &i, "has made you an examiner of game *")) {
2934
2935                 int gamenum = atoi(star_match[0]);
2936                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2937                     gamenum == ics_gamenum) {
2938                     /* We were already playing or observing this game;
2939                        no need to refetch history */
2940                     gameMode = IcsExamining;
2941                     if (pausing) {
2942                         pauseExamForwardMostMove = forwardMostMove;
2943                     } else if (currentMove < forwardMostMove) {
2944                         ForwardInner(forwardMostMove);
2945                     }
2946                 } else {
2947                     /* I don't think this case really can happen */
2948                     SendToICS(ics_prefix);
2949                     SendToICS("refresh\n");
2950                 }
2951                 continue;
2952             }
2953
2954             /* Error messages */
2955 //          if (ics_user_moved) {
2956             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2957                 if (looking_at(buf, &i, "Illegal move") ||
2958                     looking_at(buf, &i, "Not a legal move") ||
2959                     looking_at(buf, &i, "Your king is in check") ||
2960                     looking_at(buf, &i, "It isn't your turn") ||
2961                     looking_at(buf, &i, "It is not your move")) {
2962                     /* Illegal move */
2963                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2964                         currentMove = --forwardMostMove;
2965                         DisplayMove(currentMove - 1); /* before DMError */
2966                         DrawPosition(FALSE, boards[currentMove]);
2967                         SwitchClocks();
2968                         DisplayBothClocks();
2969                     }
2970                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2971                     ics_user_moved = 0;
2972                     continue;
2973                 }
2974             }
2975
2976             if (looking_at(buf, &i, "still have time") ||
2977                 looking_at(buf, &i, "not out of time") ||
2978                 looking_at(buf, &i, "either player is out of time") ||
2979                 looking_at(buf, &i, "has timeseal; checking")) {
2980                 /* We must have called his flag a little too soon */
2981                 whiteFlag = blackFlag = FALSE;
2982                 continue;
2983             }
2984
2985             if (looking_at(buf, &i, "added * seconds to") ||
2986                 looking_at(buf, &i, "seconds were added to")) {
2987                 /* Update the clocks */
2988                 SendToICS(ics_prefix);
2989                 SendToICS("refresh\n");
2990                 continue;
2991             }
2992
2993             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2994                 ics_clock_paused = TRUE;
2995                 StopClocks();
2996                 continue;
2997             }
2998
2999             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3000                 ics_clock_paused = FALSE;
3001                 StartClocks();
3002                 continue;
3003             }
3004
3005             /* Grab player ratings from the Creating: message.
3006                Note we have to check for the special case when
3007                the ICS inserts things like [white] or [black]. */
3008             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3009                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3010                 /* star_matches:
3011                    0    player 1 name (not necessarily white)
3012                    1    player 1 rating
3013                    2    empty, white, or black (IGNORED)
3014                    3    player 2 name (not necessarily black)
3015                    4    player 2 rating
3016
3017                    The names/ratings are sorted out when the game
3018                    actually starts (below).
3019                 */
3020                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3021                 player1Rating = string_to_rating(star_match[1]);
3022                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3023                 player2Rating = string_to_rating(star_match[4]);
3024
3025                 if (appData.debugMode)
3026                   fprintf(debugFP,
3027                           "Ratings from 'Creating:' %s %d, %s %d\n",
3028                           player1Name, player1Rating,
3029                           player2Name, player2Rating);
3030
3031                 continue;
3032             }
3033
3034             /* Improved generic start/end-of-game messages */
3035             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3036                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3037                 /* If tkind == 0: */
3038                 /* star_match[0] is the game number */
3039                 /*           [1] is the white player's name */
3040                 /*           [2] is the black player's name */
3041                 /* For end-of-game: */
3042                 /*           [3] is the reason for the game end */
3043                 /*           [4] is a PGN end game-token, preceded by " " */
3044                 /* For start-of-game: */
3045                 /*           [3] begins with "Creating" or "Continuing" */
3046                 /*           [4] is " *" or empty (don't care). */
3047                 int gamenum = atoi(star_match[0]);
3048                 char *whitename, *blackname, *why, *endtoken;
3049                 ChessMove endtype = (ChessMove) 0;
3050
3051                 if (tkind == 0) {
3052                   whitename = star_match[1];
3053                   blackname = star_match[2];
3054                   why = star_match[3];
3055                   endtoken = star_match[4];
3056                 } else {
3057                   whitename = star_match[1];
3058                   blackname = star_match[3];
3059                   why = star_match[5];
3060                   endtoken = star_match[6];
3061                 }
3062
3063                 /* Game start messages */
3064                 if (strncmp(why, "Creating ", 9) == 0 ||
3065                     strncmp(why, "Continuing ", 11) == 0) {
3066                     gs_gamenum = gamenum;
3067                     strcpy(gs_kind, strchr(why, ' ') + 1);
3068 #if ZIPPY
3069                     if (appData.zippyPlay) {
3070                         ZippyGameStart(whitename, blackname);
3071                     }
3072 #endif /*ZIPPY*/
3073                     continue;
3074                 }
3075
3076                 /* Game end messages */
3077                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3078                     ics_gamenum != gamenum) {
3079                     continue;
3080                 }
3081                 while (endtoken[0] == ' ') endtoken++;
3082                 switch (endtoken[0]) {
3083                   case '*':
3084                   default:
3085                     endtype = GameUnfinished;
3086                     break;
3087                   case '0':
3088                     endtype = BlackWins;
3089                     break;
3090                   case '1':
3091                     if (endtoken[1] == '/')
3092                       endtype = GameIsDrawn;
3093                     else
3094                       endtype = WhiteWins;
3095                     break;
3096                 }
3097                 GameEnds(endtype, why, GE_ICS);
3098 #if ZIPPY
3099                 if (appData.zippyPlay && first.initDone) {
3100                     ZippyGameEnd(endtype, why);
3101                     if (first.pr == NULL) {
3102                       /* Start the next process early so that we'll
3103                          be ready for the next challenge */
3104                       StartChessProgram(&first);
3105                     }
3106                     /* Send "new" early, in case this command takes
3107                        a long time to finish, so that we'll be ready
3108                        for the next challenge. */
3109                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3110                     Reset(TRUE, TRUE);
3111                 }
3112 #endif /*ZIPPY*/
3113                 continue;
3114             }
3115
3116             if (looking_at(buf, &i, "Removing game * from observation") ||
3117                 looking_at(buf, &i, "no longer observing game *") ||
3118                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3119                 if (gameMode == IcsObserving &&
3120                     atoi(star_match[0]) == ics_gamenum)
3121                   {
3122                       /* icsEngineAnalyze */
3123                       if (appData.icsEngineAnalyze) {
3124                             ExitAnalyzeMode();
3125                             ModeHighlight();
3126                       }
3127                       StopClocks();
3128                       gameMode = IcsIdle;
3129                       ics_gamenum = -1;
3130                       ics_user_moved = FALSE;
3131                   }
3132                 continue;
3133             }
3134
3135             if (looking_at(buf, &i, "no longer examining game *")) {
3136                 if (gameMode == IcsExamining &&
3137                     atoi(star_match[0]) == ics_gamenum)
3138                   {
3139                       gameMode = IcsIdle;
3140                       ics_gamenum = -1;
3141                       ics_user_moved = FALSE;
3142                   }
3143                 continue;
3144             }
3145
3146             /* Advance leftover_start past any newlines we find,
3147                so only partial lines can get reparsed */
3148             if (looking_at(buf, &i, "\n")) {
3149                 prevColor = curColor;
3150                 if (curColor != ColorNormal) {
3151                     if (oldi > next_out) {
3152                         SendToPlayer(&buf[next_out], oldi - next_out);
3153                         next_out = oldi;
3154                     }
3155                     Colorize(ColorNormal, FALSE);
3156                     curColor = ColorNormal;
3157                 }
3158                 if (started == STARTED_BOARD) {
3159                     started = STARTED_NONE;
3160                     parse[parse_pos] = NULLCHAR;
3161                     ParseBoard12(parse);
3162                     ics_user_moved = 0;
3163
3164                     /* Send premove here */
3165                     if (appData.premove) {
3166                       char str[MSG_SIZ];
3167                       if (currentMove == 0 &&
3168                           gameMode == IcsPlayingWhite &&
3169                           appData.premoveWhite) {
3170                         sprintf(str, "%s%s\n", ics_prefix,
3171                                 appData.premoveWhiteText);
3172                         if (appData.debugMode)
3173                           fprintf(debugFP, "Sending premove:\n");
3174                         SendToICS(str);
3175                       } else if (currentMove == 1 &&
3176                                  gameMode == IcsPlayingBlack &&
3177                                  appData.premoveBlack) {
3178                         sprintf(str, "%s%s\n", ics_prefix,
3179                                 appData.premoveBlackText);
3180                         if (appData.debugMode)
3181                           fprintf(debugFP, "Sending premove:\n");
3182                         SendToICS(str);
3183                       } else if (gotPremove) {
3184                         gotPremove = 0;
3185                         ClearPremoveHighlights();
3186                         if (appData.debugMode)
3187                           fprintf(debugFP, "Sending premove:\n");
3188                           UserMoveEvent(premoveFromX, premoveFromY,
3189                                         premoveToX, premoveToY,
3190                                         premovePromoChar);
3191                       }
3192                     }
3193
3194                     /* Usually suppress following prompt */
3195                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3196                         if (looking_at(buf, &i, "*% ")) {
3197                             savingComment = FALSE;
3198                         }
3199                     }
3200                     next_out = i;
3201                 } else if (started == STARTED_HOLDINGS) {
3202                     int gamenum;
3203                     char new_piece[MSG_SIZ];
3204                     started = STARTED_NONE;
3205                     parse[parse_pos] = NULLCHAR;
3206                     if (appData.debugMode)
3207                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3208                                                         parse, currentMove);
3209                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3210                         gamenum == ics_gamenum) {
3211                         if (gameInfo.variant == VariantNormal) {
3212                           /* [HGM] We seem to switch variant during a game!
3213                            * Presumably no holdings were displayed, so we have
3214                            * to move the position two files to the right to
3215                            * create room for them!
3216                            */
3217                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3218                           /* Get a move list just to see the header, which
3219                              will tell us whether this is really bug or zh */
3220                           if (ics_getting_history == H_FALSE) {
3221                             ics_getting_history = H_REQUESTED;
3222                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3223                             SendToICS(str);
3224                           }
3225                         }
3226                         new_piece[0] = NULLCHAR;
3227                         sscanf(parse, "game %d white [%s black [%s <- %s",
3228                                &gamenum, white_holding, black_holding,
3229                                new_piece);
3230                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3231                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3232                         /* [HGM] copy holdings to board holdings area */
3233                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3234                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3235 #if ZIPPY
3236                         if (appData.zippyPlay && first.initDone) {
3237                             ZippyHoldings(white_holding, black_holding,
3238                                           new_piece);
3239                         }
3240 #endif /*ZIPPY*/
3241                         if (tinyLayout || smallLayout) {
3242                             char wh[16], bh[16];
3243                             PackHolding(wh, white_holding);
3244                             PackHolding(bh, black_holding);
3245                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3246                                     gameInfo.white, gameInfo.black);
3247                         } else {
3248                             sprintf(str, "%s [%s] vs. %s [%s]",
3249                                     gameInfo.white, white_holding,
3250                                     gameInfo.black, black_holding);
3251                         }
3252
3253                         DrawPosition(FALSE, boards[currentMove]);
3254                         DisplayTitle(str);
3255                     }
3256                     /* Suppress following prompt */
3257                     if (looking_at(buf, &i, "*% ")) {
3258                         savingComment = FALSE;
3259                     }
3260                     next_out = i;
3261                 }
3262                 continue;
3263             }
3264
3265             i++;                /* skip unparsed character and loop back */
3266         }
3267
3268         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3269             started != STARTED_HOLDINGS && i > next_out) {
3270             SendToPlayer(&buf[next_out], i - next_out);
3271             next_out = i;
3272         }
3273         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3274
3275         leftover_len = buf_len - leftover_start;
3276         /* if buffer ends with something we couldn't parse,
3277            reparse it after appending the next read */
3278
3279     } else if (count == 0) {
3280         RemoveInputSource(isr);
3281         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3282     } else {
3283         DisplayFatalError(_("Error reading from ICS"), error, 1);
3284     }
3285 }
3286
3287
3288 /* Board style 12 looks like this:
3289
3290    <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
3291
3292  * The "<12> " is stripped before it gets to this routine.  The two
3293  * trailing 0's (flip state and clock ticking) are later addition, and
3294  * some chess servers may not have them, or may have only the first.
3295  * Additional trailing fields may be added in the future.
3296  */
3297
3298 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3299
3300 #define RELATION_OBSERVING_PLAYED    0
3301 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3302 #define RELATION_PLAYING_MYMOVE      1
3303 #define RELATION_PLAYING_NOTMYMOVE  -1
3304 #define RELATION_EXAMINING           2
3305 #define RELATION_ISOLATED_BOARD     -3
3306 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3307
3308 void
3309 ParseBoard12(string)
3310      char *string;
3311 {
3312     GameMode newGameMode;
3313     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3314     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3315     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3316     char to_play, board_chars[200];
3317     char move_str[500], str[500], elapsed_time[500];
3318     char black[32], white[32];
3319     Board board;
3320     int prevMove = currentMove;
3321     int ticking = 2;
3322     ChessMove moveType;
3323     int fromX, fromY, toX, toY;
3324     char promoChar;
3325     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3326     char *bookHit = NULL; // [HGM] book
3327
3328     fromX = fromY = toX = toY = -1;
3329
3330     newGame = FALSE;
3331
3332     if (appData.debugMode)
3333       fprintf(debugFP, _("Parsing board: %s\n"), string);
3334
3335     move_str[0] = NULLCHAR;
3336     elapsed_time[0] = NULLCHAR;
3337     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3338         int  i = 0, j;
3339         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3340             if(string[i] == ' ') { ranks++; files = 0; }
3341             else files++;
3342             i++;
3343         }
3344         for(j = 0; j <i; j++) board_chars[j] = string[j];
3345         board_chars[i] = '\0';
3346         string += i + 1;
3347     }
3348     n = sscanf(string, PATTERN, &to_play, &double_push,
3349                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3350                &gamenum, white, black, &relation, &basetime, &increment,
3351                &white_stren, &black_stren, &white_time, &black_time,
3352                &moveNum, str, elapsed_time, move_str, &ics_flip,
3353                &ticking);
3354
3355     if (n < 21) {
3356         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3357         DisplayError(str, 0);
3358         return;
3359     }
3360
3361     /* Convert the move number to internal form */
3362     moveNum = (moveNum - 1) * 2;
3363     if (to_play == 'B') moveNum++;
3364     if (moveNum >= MAX_MOVES) {
3365       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3366                         0, 1);
3367       return;
3368     }
3369
3370     switch (relation) {
3371       case RELATION_OBSERVING_PLAYED:
3372       case RELATION_OBSERVING_STATIC:
3373         if (gamenum == -1) {
3374             /* Old ICC buglet */
3375             relation = RELATION_OBSERVING_STATIC;
3376         }
3377         newGameMode = IcsObserving;
3378         break;
3379       case RELATION_PLAYING_MYMOVE:
3380       case RELATION_PLAYING_NOTMYMOVE:
3381         newGameMode =
3382           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3383             IcsPlayingWhite : IcsPlayingBlack;
3384         break;
3385       case RELATION_EXAMINING:
3386         newGameMode = IcsExamining;
3387         break;
3388       case RELATION_ISOLATED_BOARD:
3389       default:
3390         /* Just display this board.  If user was doing something else,
3391            we will forget about it until the next board comes. */
3392         newGameMode = IcsIdle;
3393         break;
3394       case RELATION_STARTING_POSITION:
3395         newGameMode = gameMode;
3396         break;
3397     }
3398
3399     /* Modify behavior for initial board display on move listing
3400        of wild games.
3401        */
3402     switch (ics_getting_history) {
3403       case H_FALSE:
3404       case H_REQUESTED:
3405         break;
3406       case H_GOT_REQ_HEADER:
3407       case H_GOT_UNREQ_HEADER:
3408         /* This is the initial position of the current game */
3409         gamenum = ics_gamenum;
3410         moveNum = 0;            /* old ICS bug workaround */
3411         if (to_play == 'B') {
3412           startedFromSetupPosition = TRUE;
3413           blackPlaysFirst = TRUE;
3414           moveNum = 1;
3415           if (forwardMostMove == 0) forwardMostMove = 1;
3416           if (backwardMostMove == 0) backwardMostMove = 1;
3417           if (currentMove == 0) currentMove = 1;
3418         }
3419         newGameMode = gameMode;
3420         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3421         break;
3422       case H_GOT_UNWANTED_HEADER:
3423         /* This is an initial board that we don't want */
3424         return;
3425       case H_GETTING_MOVES:
3426         /* Should not happen */
3427         DisplayError(_("Error gathering move list: extra board"), 0);
3428         ics_getting_history = H_FALSE;
3429         return;
3430     }
3431
3432     /* Take action if this is the first board of a new game, or of a
3433        different game than is currently being displayed.  */
3434     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3435         relation == RELATION_ISOLATED_BOARD) {
3436
3437         /* Forget the old game and get the history (if any) of the new one */
3438         if (gameMode != BeginningOfGame) {
3439           Reset(FALSE, TRUE);
3440         }
3441         newGame = TRUE;
3442         if (appData.autoRaiseBoard) BoardToTop();
3443         prevMove = -3;
3444         if (gamenum == -1) {
3445             newGameMode = IcsIdle;
3446         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3447                    appData.getMoveList) {
3448             /* Need to get game history */
3449             ics_getting_history = H_REQUESTED;
3450             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3451             SendToICS(str);
3452         }
3453
3454         /* Initially flip the board to have black on the bottom if playing
3455            black or if the ICS flip flag is set, but let the user change
3456            it with the Flip View button. */
3457         flipView = appData.autoFlipView ?
3458           (newGameMode == IcsPlayingBlack) || ics_flip :
3459           appData.flipView;
3460
3461         /* Done with values from previous mode; copy in new ones */
3462         gameMode = newGameMode;
3463         ModeHighlight();
3464         ics_gamenum = gamenum;
3465         if (gamenum == gs_gamenum) {
3466             int klen = strlen(gs_kind);
3467             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3468             sprintf(str, "ICS %s", gs_kind);
3469             gameInfo.event = StrSave(str);
3470         } else {
3471             gameInfo.event = StrSave("ICS game");
3472         }
3473         gameInfo.site = StrSave(appData.icsHost);
3474         gameInfo.date = PGNDate();
3475         gameInfo.round = StrSave("-");
3476         gameInfo.white = StrSave(white);
3477         gameInfo.black = StrSave(black);
3478         timeControl = basetime * 60 * 1000;
3479         timeControl_2 = 0;
3480         timeIncrement = increment * 1000;
3481         movesPerSession = 0;
3482         gameInfo.timeControl = TimeControlTagValue();
3483         VariantSwitch(board, StringToVariant(gameInfo.event) );
3484   if (appData.debugMode) {
3485     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3486     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3487     setbuf(debugFP, NULL);
3488   }
3489
3490         gameInfo.outOfBook = NULL;
3491
3492         /* Do we have the ratings? */
3493         if (strcmp(player1Name, white) == 0 &&
3494             strcmp(player2Name, black) == 0) {
3495             if (appData.debugMode)
3496               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3497                       player1Rating, player2Rating);
3498             gameInfo.whiteRating = player1Rating;
3499             gameInfo.blackRating = player2Rating;
3500         } else if (strcmp(player2Name, white) == 0 &&
3501                    strcmp(player1Name, black) == 0) {
3502             if (appData.debugMode)
3503               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3504                       player2Rating, player1Rating);
3505             gameInfo.whiteRating = player2Rating;
3506             gameInfo.blackRating = player1Rating;
3507         }
3508         player1Name[0] = player2Name[0] = NULLCHAR;
3509
3510         /* Silence shouts if requested */
3511         if (appData.quietPlay &&
3512             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3513             SendToICS(ics_prefix);
3514             SendToICS("set shout 0\n");
3515         }
3516     }
3517
3518     /* Deal with midgame name changes */
3519     if (!newGame) {
3520         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3521             if (gameInfo.white) free(gameInfo.white);
3522             gameInfo.white = StrSave(white);
3523         }
3524         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3525             if (gameInfo.black) free(gameInfo.black);
3526             gameInfo.black = StrSave(black);
3527         }
3528     }
3529
3530     /* Throw away game result if anything actually changes in examine mode */
3531     if (gameMode == IcsExamining && !newGame) {
3532         gameInfo.result = GameUnfinished;
3533         if (gameInfo.resultDetails != NULL) {
3534             free(gameInfo.resultDetails);
3535             gameInfo.resultDetails = NULL;
3536         }
3537     }
3538
3539     /* In pausing && IcsExamining mode, we ignore boards coming
3540        in if they are in a different variation than we are. */
3541     if (pauseExamInvalid) return;
3542     if (pausing && gameMode == IcsExamining) {
3543         if (moveNum <= pauseExamForwardMostMove) {
3544             pauseExamInvalid = TRUE;
3545             forwardMostMove = pauseExamForwardMostMove;
3546             return;
3547         }
3548     }
3549
3550   if (appData.debugMode) {
3551     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3552   }
3553     /* Parse the board */
3554     for (k = 0; k < ranks; k++) {
3555       for (j = 0; j < files; j++)
3556         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3557       if(gameInfo.holdingsWidth > 1) {
3558            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3559            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3560       }
3561     }
3562     CopyBoard(boards[moveNum], board);
3563     if (moveNum == 0) {
3564         startedFromSetupPosition =
3565           !CompareBoards(board, initialPosition);
3566         if(startedFromSetupPosition)
3567             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3568     }
3569
3570     /* [HGM] Set castling rights. Take the outermost Rooks,
3571        to make it also work for FRC opening positions. Note that board12
3572        is really defective for later FRC positions, as it has no way to
3573        indicate which Rook can castle if they are on the same side of King.
3574        For the initial position we grant rights to the outermost Rooks,
3575        and remember thos rights, and we then copy them on positions
3576        later in an FRC game. This means WB might not recognize castlings with
3577        Rooks that have moved back to their original position as illegal,
3578        but in ICS mode that is not its job anyway.
3579     */
3580     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3581     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3582
3583         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3584             if(board[0][i] == WhiteRook) j = i;
3585         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3586         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3587             if(board[0][i] == WhiteRook) j = i;
3588         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3589         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3590             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3591         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3593             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3594         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3595
3596         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3597         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3598             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3599         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3600             if(board[BOARD_HEIGHT-1][k] == bKing)
3601                 initialRights[5] = castlingRights[moveNum][5] = k;
3602     } else { int r;
3603         r = castlingRights[moveNum][0] = initialRights[0];
3604         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3605         r = castlingRights[moveNum][1] = initialRights[1];
3606         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3607         r = castlingRights[moveNum][3] = initialRights[3];
3608         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3609         r = castlingRights[moveNum][4] = initialRights[4];
3610         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3611         /* wildcastle kludge: always assume King has rights */
3612         r = castlingRights[moveNum][2] = initialRights[2];
3613         r = castlingRights[moveNum][5] = initialRights[5];
3614     }
3615     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3616     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3617
3618
3619     if (ics_getting_history == H_GOT_REQ_HEADER ||
3620         ics_getting_history == H_GOT_UNREQ_HEADER) {
3621         /* This was an initial position from a move list, not
3622            the current position */
3623         return;
3624     }
3625
3626     /* Update currentMove and known move number limits */
3627     newMove = newGame || moveNum > forwardMostMove;
3628
3629     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3630     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3631         takeback = forwardMostMove - moveNum;
3632         for (i = 0; i < takeback; i++) {
3633              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3634              SendToProgram("undo\n", &first);
3635         }
3636     }
3637
3638     if (newGame) {
3639         forwardMostMove = backwardMostMove = currentMove = moveNum;
3640         if (gameMode == IcsExamining && moveNum == 0) {
3641           /* Workaround for ICS limitation: we are not told the wild
3642              type when starting to examine a game.  But if we ask for
3643              the move list, the move list header will tell us */
3644             ics_getting_history = H_REQUESTED;
3645             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3646             SendToICS(str);
3647         }
3648     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3649                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3650         forwardMostMove = moveNum;
3651         if (!pausing || currentMove > forwardMostMove)
3652           currentMove = forwardMostMove;
3653     } else {
3654         /* New part of history that is not contiguous with old part */
3655         if (pausing && gameMode == IcsExamining) {
3656             pauseExamInvalid = TRUE;
3657             forwardMostMove = pauseExamForwardMostMove;
3658             return;
3659         }
3660         forwardMostMove = backwardMostMove = currentMove = moveNum;
3661         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3662             ics_getting_history = H_REQUESTED;
3663             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3664             SendToICS(str);
3665         }
3666     }
3667
3668     /* Update the clocks */
3669     if (strchr(elapsed_time, '.')) {
3670       /* Time is in ms */
3671       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3672       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3673     } else {
3674       /* Time is in seconds */
3675       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3676       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3677     }
3678
3679
3680 #if ZIPPY
3681     if (appData.zippyPlay && newGame &&
3682         gameMode != IcsObserving && gameMode != IcsIdle &&
3683         gameMode != IcsExamining)
3684       ZippyFirstBoard(moveNum, basetime, increment);
3685 #endif
3686
3687     /* Put the move on the move list, first converting
3688        to canonical algebraic form. */
3689     if (moveNum > 0) {
3690   if (appData.debugMode) {
3691     if (appData.debugMode) { int f = forwardMostMove;
3692         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3693                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3694     }
3695     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3696     fprintf(debugFP, "moveNum = %d\n", moveNum);
3697     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3698     setbuf(debugFP, NULL);
3699   }
3700         if (moveNum <= backwardMostMove) {
3701             /* We don't know what the board looked like before
3702                this move.  Punt. */
3703             strcpy(parseList[moveNum - 1], move_str);
3704             strcat(parseList[moveNum - 1], " ");
3705             strcat(parseList[moveNum - 1], elapsed_time);
3706             moveList[moveNum - 1][0] = NULLCHAR;
3707         } else if (strcmp(move_str, "none") == 0) {
3708             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3709             /* Again, we don't know what the board looked like;
3710                this is really the start of the game. */
3711             parseList[moveNum - 1][0] = NULLCHAR;
3712             moveList[moveNum - 1][0] = NULLCHAR;
3713             backwardMostMove = moveNum;
3714             startedFromSetupPosition = TRUE;
3715             fromX = fromY = toX = toY = -1;
3716         } else {
3717           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3718           //                 So we parse the long-algebraic move string in stead of the SAN move
3719           int valid; char buf[MSG_SIZ], *prom;
3720
3721           // str looks something like "Q/a1-a2"; kill the slash
3722           if(str[1] == '/')
3723                 sprintf(buf, "%c%s", str[0], str+2);
3724           else  strcpy(buf, str); // might be castling
3725           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3726                 strcat(buf, prom); // long move lacks promo specification!
3727           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3728                 if(appData.debugMode)
3729                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3730                 strcpy(move_str, buf);
3731           }
3732           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3733                                 &fromX, &fromY, &toX, &toY, &promoChar)
3734                || ParseOneMove(buf, moveNum - 1, &moveType,
3735                                 &fromX, &fromY, &toX, &toY, &promoChar);
3736           // end of long SAN patch
3737           if (valid) {
3738             (void) CoordsToAlgebraic(boards[moveNum - 1],
3739                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3740                                      fromY, fromX, toY, toX, promoChar,
3741                                      parseList[moveNum-1]);
3742             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3743                              castlingRights[moveNum]) ) {
3744               case MT_NONE:
3745               case MT_STALEMATE:
3746               default:
3747                 break;
3748               case MT_CHECK:
3749                 if(gameInfo.variant != VariantShogi)
3750                     strcat(parseList[moveNum - 1], "+");
3751                 break;
3752               case MT_CHECKMATE:
3753               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3754                 strcat(parseList[moveNum - 1], "#");
3755                 break;
3756             }
3757             strcat(parseList[moveNum - 1], " ");
3758             strcat(parseList[moveNum - 1], elapsed_time);
3759             /* currentMoveString is set as a side-effect of ParseOneMove */
3760             strcpy(moveList[moveNum - 1], currentMoveString);
3761             strcat(moveList[moveNum - 1], "\n");
3762           } else {
3763             /* Move from ICS was illegal!?  Punt. */
3764   if (appData.debugMode) {
3765     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3766     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3767   }
3768 #if 0
3769             if (appData.testLegality && appData.debugMode) {
3770                 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3771                 DisplayError(str, 0);
3772             }
3773 #endif
3774             strcpy(parseList[moveNum - 1], move_str);
3775             strcat(parseList[moveNum - 1], " ");
3776             strcat(parseList[moveNum - 1], elapsed_time);
3777             moveList[moveNum - 1][0] = NULLCHAR;
3778             fromX = fromY = toX = toY = -1;
3779           }
3780         }
3781   if (appData.debugMode) {
3782     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3783     setbuf(debugFP, NULL);
3784   }
3785
3786 #if ZIPPY
3787         /* Send move to chess program (BEFORE animating it). */
3788         if (appData.zippyPlay && !newGame && newMove &&
3789            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3790
3791             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3792                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3793                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3794                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3795                             move_str);
3796                     DisplayError(str, 0);
3797                 } else {
3798                     if (first.sendTime) {
3799                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3800                     }
3801                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3802                     if (firstMove && !bookHit) {
3803                         firstMove = FALSE;
3804                         if (first.useColors) {
3805                           SendToProgram(gameMode == IcsPlayingWhite ?
3806                                         "white\ngo\n" :
3807                                         "black\ngo\n", &first);
3808                         } else {
3809                           SendToProgram("go\n", &first);
3810                         }
3811                         first.maybeThinking = TRUE;
3812                     }
3813                 }
3814             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3815               if (moveList[moveNum - 1][0] == NULLCHAR) {
3816                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3817                 DisplayError(str, 0);
3818               } else {
3819                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3820                 SendMoveToProgram(moveNum - 1, &first);
3821               }
3822             }
3823         }
3824 #endif
3825     }
3826
3827     if (moveNum > 0 && !gotPremove) {
3828         /* If move comes from a remote source, animate it.  If it
3829            isn't remote, it will have already been animated. */
3830         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3831             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3832         }
3833         if (!pausing && appData.highlightLastMove) {
3834             SetHighlights(fromX, fromY, toX, toY);
3835         }
3836     }
3837
3838     /* Start the clocks */
3839     whiteFlag = blackFlag = FALSE;
3840     appData.clockMode = !(basetime == 0 && increment == 0);
3841     if (ticking == 0) {
3842       ics_clock_paused = TRUE;
3843       StopClocks();
3844     } else if (ticking == 1) {
3845       ics_clock_paused = FALSE;
3846     }
3847     if (gameMode == IcsIdle ||
3848         relation == RELATION_OBSERVING_STATIC ||
3849         relation == RELATION_EXAMINING ||
3850         ics_clock_paused)
3851       DisplayBothClocks();
3852     else
3853       StartClocks();
3854
3855     /* Display opponents and material strengths */
3856     if (gameInfo.variant != VariantBughouse &&
3857         gameInfo.variant != VariantCrazyhouse) {
3858         if (tinyLayout || smallLayout) {
3859             if(gameInfo.variant == VariantNormal)
3860                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3861                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3862                     basetime, increment);
3863             else
3864                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3865                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3866                     basetime, increment, (int) gameInfo.variant);
3867         } else {
3868             if(gameInfo.variant == VariantNormal)
3869                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3870                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3871                     basetime, increment);
3872             else
3873                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3874                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3875                     basetime, increment, VariantName(gameInfo.variant));
3876         }
3877         DisplayTitle(str);
3878   if (appData.debugMode) {
3879     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3880   }
3881     }
3882
3883
3884     /* Display the board */
3885     if (!pausing) {
3886
3887       if (appData.premove)
3888           if (!gotPremove ||
3889              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3890              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3891               ClearPremoveHighlights();
3892
3893       DrawPosition(FALSE, boards[currentMove]);
3894       DisplayMove(moveNum - 1);
3895       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3896             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3897               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3898         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3899       }
3900     }
3901
3902     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3903 #if ZIPPY
3904     if(bookHit) { // [HGM] book: simulate book reply
3905         static char bookMove[MSG_SIZ]; // a bit generous?
3906
3907         programStats.nodes = programStats.depth = programStats.time =
3908         programStats.score = programStats.got_only_move = 0;
3909         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3910
3911         strcpy(bookMove, "move ");
3912         strcat(bookMove, bookHit);
3913         HandleMachineMove(bookMove, &first);
3914     }
3915 #endif
3916 }
3917
3918 void
3919 GetMoveListEvent()
3920 {
3921     char buf[MSG_SIZ];
3922     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3923         ics_getting_history = H_REQUESTED;
3924         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3925         SendToICS(buf);
3926     }
3927 }
3928
3929 void
3930 AnalysisPeriodicEvent(force)
3931      int force;
3932 {
3933     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3934          && !force) || !appData.periodicUpdates)
3935       return;
3936
3937     /* Send . command to Crafty to collect stats */
3938     SendToProgram(".\n", &first);
3939
3940     /* Don't send another until we get a response (this makes
3941        us stop sending to old Crafty's which don't understand
3942        the "." command (sending illegal cmds resets node count & time,
3943        which looks bad)) */
3944     programStats.ok_to_send = 0;
3945 }
3946
3947 void
3948 SendMoveToProgram(moveNum, cps)
3949      int moveNum;
3950      ChessProgramState *cps;
3951 {
3952     char buf[MSG_SIZ];
3953
3954     if (cps->useUsermove) {
3955       SendToProgram("usermove ", cps);
3956     }
3957     if (cps->useSAN) {
3958       char *space;
3959       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3960         int len = space - parseList[moveNum];
3961         memcpy(buf, parseList[moveNum], len);
3962         buf[len++] = '\n';
3963         buf[len] = NULLCHAR;
3964       } else {
3965         sprintf(buf, "%s\n", parseList[moveNum]);
3966       }
3967       SendToProgram(buf, cps);
3968     } else {
3969       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3970         AlphaRank(moveList[moveNum], 4);
3971         SendToProgram(moveList[moveNum], cps);
3972         AlphaRank(moveList[moveNum], 4); // and back
3973       } else
3974       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3975        * the engine. It would be nice to have a better way to identify castle
3976        * moves here. */
3977       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3978                                                                          && cps->useOOCastle) {
3979         int fromX = moveList[moveNum][0] - AAA;
3980         int fromY = moveList[moveNum][1] - ONE;
3981         int toX = moveList[moveNum][2] - AAA;
3982         int toY = moveList[moveNum][3] - ONE;
3983         if((boards[moveNum][fromY][fromX] == WhiteKing
3984             && boards[moveNum][toY][toX] == WhiteRook)
3985            || (boards[moveNum][fromY][fromX] == BlackKing
3986                && boards[moveNum][toY][toX] == BlackRook)) {
3987           if(toX > fromX) SendToProgram("O-O\n", cps);
3988           else SendToProgram("O-O-O\n", cps);
3989         }
3990         else SendToProgram(moveList[moveNum], cps);
3991       }
3992       else SendToProgram(moveList[moveNum], cps);
3993       /* End of additions by Tord */
3994     }
3995
3996     /* [HGM] setting up the opening has brought engine in force mode! */
3997     /*       Send 'go' if we are in a mode where machine should play. */
3998     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
3999         (gameMode == TwoMachinesPlay   ||
4000 #ifdef ZIPPY
4001          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4002 #endif
4003          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4004         SendToProgram("go\n", cps);
4005   if (appData.debugMode) {
4006     fprintf(debugFP, "(extra)\n");
4007   }
4008     }
4009     setboardSpoiledMachineBlack = 0;
4010 }
4011
4012 void
4013 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4014      ChessMove moveType;
4015      int fromX, fromY, toX, toY;
4016 {
4017     char user_move[MSG_SIZ];
4018
4019     switch (moveType) {
4020       default:
4021         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4022                 (int)moveType, fromX, fromY, toX, toY);
4023         DisplayError(user_move + strlen("say "), 0);
4024         break;
4025       case WhiteKingSideCastle:
4026       case BlackKingSideCastle:
4027       case WhiteQueenSideCastleWild:
4028       case BlackQueenSideCastleWild:
4029       /* PUSH Fabien */
4030       case WhiteHSideCastleFR:
4031       case BlackHSideCastleFR:
4032       /* POP Fabien */
4033         sprintf(user_move, "o-o\n");
4034         break;
4035       case WhiteQueenSideCastle:
4036       case BlackQueenSideCastle:
4037       case WhiteKingSideCastleWild:
4038       case BlackKingSideCastleWild:
4039       /* PUSH Fabien */
4040       case WhiteASideCastleFR:
4041       case BlackASideCastleFR:
4042       /* POP Fabien */
4043         sprintf(user_move, "o-o-o\n");
4044         break;
4045       case WhitePromotionQueen:
4046       case BlackPromotionQueen:
4047       case WhitePromotionRook:
4048       case BlackPromotionRook:
4049       case WhitePromotionBishop:
4050       case BlackPromotionBishop:
4051       case WhitePromotionKnight:
4052       case BlackPromotionKnight:
4053       case WhitePromotionKing:
4054       case BlackPromotionKing:
4055       case WhitePromotionChancellor:
4056       case BlackPromotionChancellor:
4057       case WhitePromotionArchbishop:
4058       case BlackPromotionArchbishop:
4059         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4060             sprintf(user_move, "%c%c%c%c=%c\n",
4061                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4062                 PieceToChar(WhiteFerz));
4063         else if(gameInfo.variant == VariantGreat)
4064             sprintf(user_move, "%c%c%c%c=%c\n",
4065                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4066                 PieceToChar(WhiteMan));
4067         else
4068             sprintf(user_move, "%c%c%c%c=%c\n",
4069                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4070                 PieceToChar(PromoPiece(moveType)));
4071         break;
4072       case WhiteDrop:
4073       case BlackDrop:
4074         sprintf(user_move, "%c@%c%c\n",
4075                 ToUpper(PieceToChar((ChessSquare) fromX)),
4076                 AAA + toX, ONE + toY);
4077         break;
4078       case NormalMove:
4079       case WhiteCapturesEnPassant:
4080       case BlackCapturesEnPassant:
4081       case IllegalMove:  /* could be a variant we don't quite understand */
4082         sprintf(user_move, "%c%c%c%c\n",
4083                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4084         break;
4085     }
4086     SendToICS(user_move);
4087 }
4088
4089 void
4090 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4091      int rf, ff, rt, ft;
4092      char promoChar;
4093      char move[7];
4094 {
4095     if (rf == DROP_RANK) {
4096         sprintf(move, "%c@%c%c\n",
4097                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4098     } else {
4099         if (promoChar == 'x' || promoChar == NULLCHAR) {
4100             sprintf(move, "%c%c%c%c\n",
4101                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4102         } else {
4103             sprintf(move, "%c%c%c%c%c\n",
4104                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4105         }
4106     }
4107 }
4108
4109 void
4110 ProcessICSInitScript(f)
4111      FILE *f;
4112 {
4113     char buf[MSG_SIZ];
4114
4115     while (fgets(buf, MSG_SIZ, f)) {
4116         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4117     }
4118
4119     fclose(f);
4120 }
4121
4122
4123 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4124 void
4125 AlphaRank(char *move, int n)
4126 {
4127 //    char *p = move, c; int x, y;
4128
4129     if (appData.debugMode) {
4130         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4131     }
4132
4133     if(move[1]=='*' &&
4134        move[2]>='0' && move[2]<='9' &&
4135        move[3]>='a' && move[3]<='x'    ) {
4136         move[1] = '@';
4137         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4138         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4139     } else
4140     if(move[0]>='0' && move[0]<='9' &&
4141        move[1]>='a' && move[1]<='x' &&
4142        move[2]>='0' && move[2]<='9' &&
4143        move[3]>='a' && move[3]<='x'    ) {
4144         /* input move, Shogi -> normal */
4145         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4146         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4147         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4148         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4149     } else
4150     if(move[1]=='@' &&
4151        move[3]>='0' && move[3]<='9' &&
4152        move[2]>='a' && move[2]<='x'    ) {
4153         move[1] = '*';
4154         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4155         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4156     } else
4157     if(
4158        move[0]>='a' && move[0]<='x' &&
4159        move[3]>='0' && move[3]<='9' &&
4160        move[2]>='a' && move[2]<='x'    ) {
4161          /* output move, normal -> Shogi */
4162         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4163         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4164         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4165         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4166         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4167     }
4168     if (appData.debugMode) {
4169         fprintf(debugFP, "   out = '%s'\n", move);
4170     }
4171 }
4172
4173 /* Parser for moves from gnuchess, ICS, or user typein box */
4174 Boolean
4175 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4176      char *move;
4177      int moveNum;
4178      ChessMove *moveType;
4179      int *fromX, *fromY, *toX, *toY;
4180      char *promoChar;
4181 {
4182     if (appData.debugMode) {
4183         fprintf(debugFP, "move to parse: %s\n", move);
4184     }
4185     *moveType = yylexstr(moveNum, move);
4186
4187     switch (*moveType) {
4188       case WhitePromotionChancellor:
4189       case BlackPromotionChancellor:
4190       case WhitePromotionArchbishop:
4191       case BlackPromotionArchbishop:
4192       case WhitePromotionQueen:
4193       case BlackPromotionQueen:
4194       case WhitePromotionRook:
4195       case BlackPromotionRook:
4196       case WhitePromotionBishop:
4197       case BlackPromotionBishop:
4198       case WhitePromotionKnight:
4199       case BlackPromotionKnight:
4200       case WhitePromotionKing:
4201       case BlackPromotionKing:
4202       case NormalMove:
4203       case WhiteCapturesEnPassant:
4204       case BlackCapturesEnPassant:
4205       case WhiteKingSideCastle:
4206       case WhiteQueenSideCastle:
4207       case BlackKingSideCastle:
4208       case BlackQueenSideCastle:
4209       case WhiteKingSideCastleWild:
4210       case WhiteQueenSideCastleWild:
4211       case BlackKingSideCastleWild:
4212       case BlackQueenSideCastleWild:
4213       /* Code added by Tord: */
4214       case WhiteHSideCastleFR:
4215       case WhiteASideCastleFR:
4216       case BlackHSideCastleFR:
4217       case BlackASideCastleFR:
4218       /* End of code added by Tord */
4219       case IllegalMove:         /* bug or odd chess variant */
4220         *fromX = currentMoveString[0] - AAA;
4221         *fromY = currentMoveString[1] - ONE;
4222         *toX = currentMoveString[2] - AAA;
4223         *toY = currentMoveString[3] - ONE;
4224         *promoChar = currentMoveString[4];
4225         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4226             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4227     if (appData.debugMode) {
4228         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4229     }
4230             *fromX = *fromY = *toX = *toY = 0;
4231             return FALSE;
4232         }
4233         if (appData.testLegality) {
4234           return (*moveType != IllegalMove);
4235         } else {
4236           return !(fromX == fromY && toX == toY);
4237         }
4238
4239       case WhiteDrop:
4240       case BlackDrop:
4241         *fromX = *moveType == WhiteDrop ?
4242           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4243           (int) CharToPiece(ToLower(currentMoveString[0]));
4244         *fromY = DROP_RANK;
4245         *toX = currentMoveString[2] - AAA;
4246         *toY = currentMoveString[3] - ONE;
4247         *promoChar = NULLCHAR;
4248         return TRUE;
4249
4250       case AmbiguousMove:
4251       case ImpossibleMove:
4252       case (ChessMove) 0:       /* end of file */
4253       case ElapsedTime:
4254       case Comment:
4255       case PGNTag:
4256       case NAG:
4257       case WhiteWins:
4258       case BlackWins:
4259       case GameIsDrawn:
4260       default:
4261     if (appData.debugMode) {
4262         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4263     }
4264         /* bug? */
4265         *fromX = *fromY = *toX = *toY = 0;
4266         *promoChar = NULLCHAR;
4267         return FALSE;
4268     }
4269 }
4270
4271 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4272 // All positions will have equal probability, but the current method will not provide a unique
4273 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4274 #define DARK 1
4275 #define LITE 2
4276 #define ANY 3
4277
4278 int squaresLeft[4];
4279 int piecesLeft[(int)BlackPawn];
4280 int seed, nrOfShuffles;
4281
4282 void GetPositionNumber()
4283 {       // sets global variable seed
4284         int i;
4285
4286         seed = appData.defaultFrcPosition;
4287         if(seed < 0) { // randomize based on time for negative FRC position numbers
4288                 for(i=0; i<50; i++) seed += random();
4289                 seed = random() ^ random() >> 8 ^ random() << 8;
4290                 if(seed<0) seed = -seed;
4291         }
4292 }
4293
4294 int put(Board board, int pieceType, int rank, int n, int shade)
4295 // put the piece on the (n-1)-th empty squares of the given shade
4296 {
4297         int i;
4298
4299         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4300                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4301                         board[rank][i] = (ChessSquare) pieceType;
4302                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4303                         squaresLeft[ANY]--;
4304                         piecesLeft[pieceType]--;
4305                         return i;
4306                 }
4307         }
4308         return -1;
4309 }
4310
4311
4312 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4313 // calculate where the next piece goes, (any empty square), and put it there
4314 {
4315         int i;
4316
4317         i = seed % squaresLeft[shade];
4318         nrOfShuffles *= squaresLeft[shade];
4319         seed /= squaresLeft[shade];
4320         put(board, pieceType, rank, i, shade);
4321 }
4322
4323 void AddTwoPieces(Board board, int pieceType, int rank)
4324 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4325 {
4326         int i, n=squaresLeft[ANY], j=n-1, k;
4327
4328         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4329         i = seed % k;  // pick one
4330         nrOfShuffles *= k;
4331         seed /= k;
4332         while(i >= j) i -= j--;
4333         j = n - 1 - j; i += j;
4334         put(board, pieceType, rank, j, ANY);
4335         put(board, pieceType, rank, i, ANY);
4336 }
4337
4338 void SetUpShuffle(Board board, int number)
4339 {
4340         int i, p, first=1;
4341
4342         GetPositionNumber(); nrOfShuffles = 1;
4343
4344         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4345         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4346         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4347
4348         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4349
4350         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4351             p = (int) board[0][i];
4352             if(p < (int) BlackPawn) piecesLeft[p] ++;
4353             board[0][i] = EmptySquare;
4354         }
4355
4356         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4357             // shuffles restricted to allow normal castling put KRR first
4358             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4359                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4360             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4361                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4362             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4363                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4364             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4365                 put(board, WhiteRook, 0, 0, ANY);
4366             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4367         }
4368
4369         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4370             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4371             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4372                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4373                 while(piecesLeft[p] >= 2) {
4374                     AddOnePiece(board, p, 0, LITE);
4375                     AddOnePiece(board, p, 0, DARK);
4376                 }
4377                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4378             }
4379
4380         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4381             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4382             // but we leave King and Rooks for last, to possibly obey FRC restriction
4383             if(p == (int)WhiteRook) continue;
4384             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4385             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4386         }
4387
4388         // now everything is placed, except perhaps King (Unicorn) and Rooks
4389
4390         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4391             // Last King gets castling rights
4392             while(piecesLeft[(int)WhiteUnicorn]) {
4393                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4394                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4395             }
4396
4397             while(piecesLeft[(int)WhiteKing]) {
4398                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4399                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4400             }
4401
4402
4403         } else {
4404             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4405             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4406         }
4407
4408         // Only Rooks can be left; simply place them all
4409         while(piecesLeft[(int)WhiteRook]) {
4410                 i = put(board, WhiteRook, 0, 0, ANY);
4411                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4412                         if(first) {
4413                                 first=0;
4414                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4415                         }
4416                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4417                 }
4418         }
4419         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4420             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4421         }
4422
4423         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4424 }
4425
4426 int SetCharTable( char *table, const char * map )
4427 /* [HGM] moved here from winboard.c because of its general usefulness */
4428 /*       Basically a safe strcpy that uses the last character as King */
4429 {
4430     int result = FALSE; int NrPieces;
4431
4432     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4433                     && NrPieces >= 12 && !(NrPieces&1)) {
4434         int i; /* [HGM] Accept even length from 12 to 34 */
4435
4436         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4437         for( i=0; i<NrPieces/2-1; i++ ) {
4438             table[i] = map[i];
4439             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4440         }
4441         table[(int) WhiteKing]  = map[NrPieces/2-1];
4442         table[(int) BlackKing]  = map[NrPieces-1];
4443
4444         result = TRUE;
4445     }
4446
4447     return result;
4448 }
4449
4450 void Prelude(Board board)
4451 {       // [HGM] superchess: random selection of exo-pieces
4452         int i, j, k; ChessSquare p;
4453         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4454
4455         GetPositionNumber(); // use FRC position number
4456
4457         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4458             SetCharTable(pieceToChar, appData.pieceToCharTable);
4459             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4460                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4461         }
4462
4463         j = seed%4;                 seed /= 4;
4464         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4465         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4466         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4467         j = seed%3 + (seed%3 >= j); seed /= 3;
4468         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4469         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4470         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4471         j = seed%3;                 seed /= 3;
4472         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4473         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4474         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4475         j = seed%2 + (seed%2 >= j); seed /= 2;
4476         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4477         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4478         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4479         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4480         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4481         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4482         put(board, exoPieces[0],    0, 0, ANY);
4483         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4484 }
4485
4486 void
4487 InitPosition(redraw)
4488      int redraw;
4489 {
4490     ChessSquare (* pieces)[BOARD_SIZE];
4491     int i, j, pawnRow, overrule,
4492     oldx = gameInfo.boardWidth,
4493     oldy = gameInfo.boardHeight,
4494     oldh = gameInfo.holdingsWidth,
4495     oldv = gameInfo.variant;
4496
4497     currentMove = forwardMostMove = backwardMostMove = 0;
4498     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4499
4500     /* [AS] Initialize pv info list [HGM] and game status */
4501     {
4502         for( i=0; i<MAX_MOVES; i++ ) {
4503             pvInfoList[i].depth = 0;
4504             epStatus[i]=EP_NONE;
4505             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4506         }
4507
4508         initialRulePlies = 0; /* 50-move counter start */
4509
4510         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4511         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4512     }
4513
4514
4515     /* [HGM] logic here is completely changed. In stead of full positions */
4516     /* the initialized data only consist of the two backranks. The switch */
4517     /* selects which one we will use, which is than copied to the Board   */
4518     /* initialPosition, which for the rest is initialized by Pawns and    */
4519     /* empty squares. This initial position is then copied to boards[0],  */
4520     /* possibly after shuffling, so that it remains available.            */
4521
4522     gameInfo.holdingsWidth = 0; /* default board sizes */
4523     gameInfo.boardWidth    = 8;
4524     gameInfo.boardHeight   = 8;
4525     gameInfo.holdingsSize  = 0;
4526     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4527     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4528     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4529
4530     switch (gameInfo.variant) {
4531     case VariantFischeRandom:
4532       shuffleOpenings = TRUE;
4533     default:
4534       pieces = FIDEArray;
4535       break;
4536     case VariantShatranj:
4537       pieces = ShatranjArray;
4538       nrCastlingRights = 0;
4539       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4540       break;
4541     case VariantTwoKings:
4542       pieces = twoKingsArray;
4543       break;
4544     case VariantCapaRandom:
4545       shuffleOpenings = TRUE;
4546     case VariantCapablanca:
4547       pieces = CapablancaArray;
4548       gameInfo.boardWidth = 10;
4549       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4550       break;
4551     case VariantGothic:
4552       pieces = GothicArray;
4553       gameInfo.boardWidth = 10;
4554       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4555       break;
4556     case VariantJanus:
4557       pieces = JanusArray;
4558       gameInfo.boardWidth = 10;
4559       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4560       nrCastlingRights = 6;
4561         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4562         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4563         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4564         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4565         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4566         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4567       break;
4568     case VariantFalcon:
4569       pieces = FalconArray;
4570       gameInfo.boardWidth = 10;
4571       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4572       break;
4573     case VariantXiangqi:
4574       pieces = XiangqiArray;
4575       gameInfo.boardWidth  = 9;
4576       gameInfo.boardHeight = 10;
4577       nrCastlingRights = 0;
4578       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4579       break;
4580     case VariantShogi:
4581       pieces = ShogiArray;
4582       gameInfo.boardWidth  = 9;
4583       gameInfo.boardHeight = 9;
4584       gameInfo.holdingsSize = 7;
4585       nrCastlingRights = 0;
4586       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4587       break;
4588     case VariantCourier:
4589       pieces = CourierArray;
4590       gameInfo.boardWidth  = 12;
4591       nrCastlingRights = 0;
4592       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4593       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4594       break;
4595     case VariantKnightmate:
4596       pieces = KnightmateArray;
4597       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4598       break;
4599     case VariantFairy:
4600       pieces = fairyArray;
4601       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4602       break;
4603     case VariantGreat:
4604       pieces = GreatArray;
4605       gameInfo.boardWidth = 10;
4606       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4607       gameInfo.holdingsSize = 8;
4608       break;
4609     case VariantSuper:
4610       pieces = FIDEArray;
4611       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4612       gameInfo.holdingsSize = 8;
4613       startedFromSetupPosition = TRUE;
4614       break;
4615     case VariantCrazyhouse:
4616     case VariantBughouse:
4617       pieces = FIDEArray;
4618       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4619       gameInfo.holdingsSize = 5;
4620       break;
4621     case VariantWildCastle:
4622       pieces = FIDEArray;
4623       /* !!?shuffle with kings guaranteed to be on d or e file */
4624       shuffleOpenings = 1;
4625       break;
4626     case VariantNoCastle:
4627       pieces = FIDEArray;
4628       nrCastlingRights = 0;
4629       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4630       /* !!?unconstrained back-rank shuffle */
4631       shuffleOpenings = 1;
4632       break;
4633     }
4634
4635     overrule = 0;
4636     if(appData.NrFiles >= 0) {
4637         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4638         gameInfo.boardWidth = appData.NrFiles;
4639     }
4640     if(appData.NrRanks >= 0) {
4641         gameInfo.boardHeight = appData.NrRanks;
4642     }
4643     if(appData.holdingsSize >= 0) {
4644         i = appData.holdingsSize;
4645         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4646         gameInfo.holdingsSize = i;
4647     }
4648     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4649     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4650         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4651
4652     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4653     if(pawnRow < 1) pawnRow = 1;
4654
4655     /* User pieceToChar list overrules defaults */
4656     if(appData.pieceToCharTable != NULL)
4657         SetCharTable(pieceToChar, appData.pieceToCharTable);
4658
4659     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4660
4661         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4662             s = (ChessSquare) 0; /* account holding counts in guard band */
4663         for( i=0; i<BOARD_HEIGHT; i++ )
4664             initialPosition[i][j] = s;
4665
4666         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4667         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4668         initialPosition[pawnRow][j] = WhitePawn;
4669         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4670         if(gameInfo.variant == VariantXiangqi) {
4671             if(j&1) {
4672                 initialPosition[pawnRow][j] =
4673                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4674                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4675                    initialPosition[2][j] = WhiteCannon;
4676                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4677                 }
4678             }
4679         }
4680         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4681     }
4682     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4683
4684             j=BOARD_LEFT+1;
4685             initialPosition[1][j] = WhiteBishop;
4686             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4687             j=BOARD_RGHT-2;
4688             initialPosition[1][j] = WhiteRook;
4689             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4690     }
4691
4692     if( nrCastlingRights == -1) {
4693         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4694         /*       This sets default castling rights from none to normal corners   */
4695         /* Variants with other castling rights must set them themselves above    */
4696         nrCastlingRights = 6;
4697
4698         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4699         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4700         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4701         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4702         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4703         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4704      }
4705
4706      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4707      if(gameInfo.variant == VariantGreat) { // promotion commoners
4708         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4709         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4710         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4711         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4712      }
4713 #if 0
4714     if(gameInfo.variant == VariantFischeRandom) {
4715       if( appData.defaultFrcPosition < 0 ) {
4716         ShuffleFRC( initialPosition );
4717       }
4718       else {
4719         SetupFRC( initialPosition, appData.defaultFrcPosition );
4720       }
4721       startedFromSetupPosition = TRUE;
4722     } else
4723 #else
4724   if (appData.debugMode) {
4725     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4726   }
4727     if(shuffleOpenings) {
4728         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4729         startedFromSetupPosition = TRUE;
4730     }
4731 #endif
4732     if(startedFromPositionFile) {
4733       /* [HGM] loadPos: use PositionFile for every new game */
4734       CopyBoard(initialPosition, filePosition);
4735       for(i=0; i<nrCastlingRights; i++)
4736           castlingRights[0][i] = initialRights[i] = fileRights[i];
4737       startedFromSetupPosition = TRUE;
4738     }
4739
4740     CopyBoard(boards[0], initialPosition);
4741     if(oldx != gameInfo.boardWidth ||
4742        oldy != gameInfo.boardHeight ||
4743        oldh != gameInfo.holdingsWidth
4744 #ifdef GOTHIC
4745        || oldv == VariantGothic ||        // For licensing popups
4746        gameInfo.variant == VariantGothic
4747 #endif
4748 #ifdef FALCON
4749        || oldv == VariantFalcon ||
4750        gameInfo.variant == VariantFalcon
4751 #endif
4752                                          )
4753       {
4754             InitDrawingSizes(-2 ,0);
4755       }
4756
4757     if (redraw)
4758       DrawPosition(TRUE, boards[currentMove]);
4759
4760 }
4761
4762 void
4763 SendBoard(cps, moveNum)
4764      ChessProgramState *cps;
4765      int moveNum;
4766 {
4767     char message[MSG_SIZ];
4768
4769     if (cps->useSetboard) {
4770       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4771       sprintf(message, "setboard %s\n", fen);
4772       SendToProgram(message, cps);
4773       free(fen);
4774
4775     } else {
4776       ChessSquare *bp;
4777       int i, j;
4778       /* Kludge to set black to move, avoiding the troublesome and now
4779        * deprecated "black" command.
4780        */
4781       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4782
4783       SendToProgram("edit\n", cps);
4784       SendToProgram("#\n", cps);
4785       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4786         bp = &boards[moveNum][i][BOARD_LEFT];
4787         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4788           if ((int) *bp < (int) BlackPawn) {
4789             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4790                     AAA + j, ONE + i);
4791             if(message[0] == '+' || message[0] == '~') {
4792                 sprintf(message, "%c%c%c+\n",
4793                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4794                         AAA + j, ONE + i);
4795             }
4796             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4797                 message[1] = BOARD_RGHT   - 1 - j + '1';
4798                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4799             }
4800             SendToProgram(message, cps);
4801           }
4802         }
4803       }
4804
4805       SendToProgram("c\n", cps);
4806       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4807         bp = &boards[moveNum][i][BOARD_LEFT];
4808         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4809           if (((int) *bp != (int) EmptySquare)
4810               && ((int) *bp >= (int) BlackPawn)) {
4811             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4812                     AAA + j, ONE + i);
4813             if(message[0] == '+' || message[0] == '~') {
4814                 sprintf(message, "%c%c%c+\n",
4815                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4816                         AAA + j, ONE + i);
4817             }
4818             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4819                 message[1] = BOARD_RGHT   - 1 - j + '1';
4820                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4821             }
4822             SendToProgram(message, cps);
4823           }
4824         }
4825       }
4826
4827       SendToProgram(".\n", cps);
4828     }
4829     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4830 }
4831
4832 int
4833 IsPromotion(fromX, fromY, toX, toY)
4834      int fromX, fromY, toX, toY;
4835 {
4836     /* [HGM] add Shogi promotions */
4837     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4838     ChessSquare piece;
4839
4840     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4841       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4842    /* [HGM] Note to self: line above also weeds out drops */
4843     piece = boards[currentMove][fromY][fromX];
4844     if(gameInfo.variant == VariantShogi) {
4845         promotionZoneSize = 3;
4846         highestPromotingPiece = (int)WhiteKing;
4847         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4848            and if in normal chess we then allow promotion to King, why not
4849            allow promotion of other piece in Shogi?                         */
4850     }
4851     if((int)piece >= BlackPawn) {
4852         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4853              return FALSE;
4854         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4855     } else {
4856         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4857            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4858     }
4859     return ( (int)piece <= highestPromotingPiece );
4860 }
4861
4862 int
4863 InPalace(row, column)
4864      int row, column;
4865 {   /* [HGM] for Xiangqi */
4866     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4867          column < (BOARD_WIDTH + 4)/2 &&
4868          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4869     return FALSE;
4870 }
4871
4872 int
4873 PieceForSquare (x, y)
4874      int x;
4875      int y;
4876 {
4877   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4878      return -1;
4879   else
4880      return boards[currentMove][y][x];
4881 }
4882
4883 int
4884 OKToStartUserMove(x, y)
4885      int x, y;
4886 {
4887     ChessSquare from_piece;
4888     int white_piece;
4889
4890     if (matchMode) return FALSE;
4891     if (gameMode == EditPosition) return TRUE;
4892
4893     if (x >= 0 && y >= 0)
4894       from_piece = boards[currentMove][y][x];
4895     else
4896       from_piece = EmptySquare;
4897
4898     if (from_piece == EmptySquare) return FALSE;
4899
4900     white_piece = (int)from_piece >= (int)WhitePawn &&
4901       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4902
4903     switch (gameMode) {
4904       case PlayFromGameFile:
4905       case AnalyzeFile:
4906       case TwoMachinesPlay:
4907       case EndOfGame:
4908         return FALSE;
4909
4910       case IcsObserving:
4911       case IcsIdle:
4912         return FALSE;
4913
4914       case MachinePlaysWhite:
4915       case IcsPlayingBlack:
4916         if (appData.zippyPlay) return FALSE;
4917         if (white_piece) {
4918             DisplayMoveError(_("You are playing Black"));
4919             return FALSE;
4920         }
4921         break;
4922
4923       case MachinePlaysBlack:
4924       case IcsPlayingWhite:
4925         if (appData.zippyPlay) return FALSE;
4926         if (!white_piece) {
4927             DisplayMoveError(_("You are playing White"));
4928             return FALSE;
4929         }
4930         break;
4931
4932       case EditGame:
4933         if (!white_piece && WhiteOnMove(currentMove)) {
4934             DisplayMoveError(_("It is White's turn"));
4935             return FALSE;
4936         }
4937         if (white_piece && !WhiteOnMove(currentMove)) {
4938             DisplayMoveError(_("It is Black's turn"));
4939             return FALSE;
4940         }
4941         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4942             /* Editing correspondence game history */
4943             /* Could disallow this or prompt for confirmation */
4944             cmailOldMove = -1;
4945         }
4946         if (currentMove < forwardMostMove) {
4947             /* Discarding moves */
4948             /* Could prompt for confirmation here,
4949                but I don't think that's such a good idea */
4950             forwardMostMove = currentMove;
4951         }
4952         break;
4953
4954       case BeginningOfGame:
4955         if (appData.icsActive) return FALSE;
4956         if (!appData.noChessProgram) {
4957             if (!white_piece) {
4958                 DisplayMoveError(_("You are playing White"));
4959                 return FALSE;
4960             }
4961         }
4962         break;
4963
4964       case Training:
4965         if (!white_piece && WhiteOnMove(currentMove)) {
4966             DisplayMoveError(_("It is White's turn"));
4967             return FALSE;
4968         }
4969         if (white_piece && !WhiteOnMove(currentMove)) {
4970             DisplayMoveError(_("It is Black's turn"));
4971             return FALSE;
4972         }
4973         break;
4974
4975       default:
4976       case IcsExamining:
4977         break;
4978     }
4979     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4980         && gameMode != AnalyzeFile && gameMode != Training) {
4981         DisplayMoveError(_("Displayed position is not current"));
4982         return FALSE;
4983     }
4984     return TRUE;
4985 }
4986
4987 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4988 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4989 int lastLoadGameUseList = FALSE;
4990 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4991 ChessMove lastLoadGameStart = (ChessMove) 0;
4992
4993
4994 ChessMove
4995 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4996      int fromX, fromY, toX, toY;
4997      int promoChar;
4998 {
4999     ChessMove moveType;
5000     ChessSquare pdown, pup;
5001
5002     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5003     if ((fromX == toX) && (fromY == toY)) {
5004         return ImpossibleMove;
5005     }
5006
5007     /* [HGM] suppress all moves into holdings area and guard band */
5008     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5009             return ImpossibleMove;
5010
5011     /* [HGM] <sameColor> moved to here from winboard.c */
5012     /* note: this code seems to exist for filtering out some obviously illegal premoves */
5013     pdown = boards[currentMove][fromY][fromX];
5014     pup = boards[currentMove][toY][toX];
5015     if (    gameMode != EditPosition &&
5016             (WhitePawn <= pdown && pdown < BlackPawn &&
5017              WhitePawn <= pup && pup < BlackPawn  ||
5018              BlackPawn <= pdown && pdown < EmptySquare &&
5019              BlackPawn <= pup && pup < EmptySquare
5020             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5021                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5022                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  )
5023         )           )
5024          return ImpossibleMove;
5025
5026     /* Check if the user is playing in turn.  This is complicated because we
5027        let the user "pick up" a piece before it is his turn.  So the piece he
5028        tried to pick up may have been captured by the time he puts it down!
5029        Therefore we use the color the user is supposed to be playing in this
5030        test, not the color of the piece that is currently on the starting
5031        square---except in EditGame mode, where the user is playing both
5032        sides; fortunately there the capture race can't happen.  (It can
5033        now happen in IcsExamining mode, but that's just too bad.  The user
5034        will get a somewhat confusing message in that case.)
5035        */
5036
5037     switch (gameMode) {
5038       case PlayFromGameFile:
5039       case AnalyzeFile:
5040       case TwoMachinesPlay:
5041       case EndOfGame:
5042       case IcsObserving:
5043       case IcsIdle:
5044         /* We switched into a game mode where moves are not accepted,
5045            perhaps while the mouse button was down. */
5046         return ImpossibleMove;
5047
5048       case MachinePlaysWhite:
5049         /* User is moving for Black */
5050         if (WhiteOnMove(currentMove)) {
5051             DisplayMoveError(_("It is White's turn"));
5052             return ImpossibleMove;
5053         }
5054         break;
5055
5056       case MachinePlaysBlack:
5057         /* User is moving for White */
5058         if (!WhiteOnMove(currentMove)) {
5059             DisplayMoveError(_("It is Black's turn"));
5060             return ImpossibleMove;
5061         }
5062         break;
5063
5064       case EditGame:
5065       case IcsExamining:
5066       case BeginningOfGame:
5067       case AnalyzeMode:
5068       case Training:
5069         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5070             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5071             /* User is moving for Black */
5072             if (WhiteOnMove(currentMove)) {
5073                 DisplayMoveError(_("It is White's turn"));
5074                 return ImpossibleMove;
5075             }
5076         } else {
5077             /* User is moving for White */
5078             if (!WhiteOnMove(currentMove)) {
5079                 DisplayMoveError(_("It is Black's turn"));
5080                 return ImpossibleMove;
5081             }
5082         }
5083         break;
5084
5085       case IcsPlayingBlack:
5086         /* User is moving for Black */
5087         if (WhiteOnMove(currentMove)) {
5088             if (!appData.premove) {
5089                 DisplayMoveError(_("It is White's turn"));
5090             } else if (toX >= 0 && toY >= 0) {
5091                 premoveToX = toX;
5092                 premoveToY = toY;
5093                 premoveFromX = fromX;
5094                 premoveFromY = fromY;
5095                 premovePromoChar = promoChar;
5096                 gotPremove = 1;
5097                 if (appData.debugMode)
5098                     fprintf(debugFP, "Got premove: fromX %d,"
5099                             "fromY %d, toX %d, toY %d\n",
5100                             fromX, fromY, toX, toY);
5101             }
5102             return ImpossibleMove;
5103         }
5104         break;
5105
5106       case IcsPlayingWhite:
5107         /* User is moving for White */
5108         if (!WhiteOnMove(currentMove)) {
5109             if (!appData.premove) {
5110                 DisplayMoveError(_("It is Black's turn"));
5111             } else if (toX >= 0 && toY >= 0) {
5112                 premoveToX = toX;
5113                 premoveToY = toY;
5114                 premoveFromX = fromX;
5115                 premoveFromY = fromY;
5116                 premovePromoChar = promoChar;
5117                 gotPremove = 1;
5118                 if (appData.debugMode)
5119                     fprintf(debugFP, "Got premove: fromX %d,"
5120                             "fromY %d, toX %d, toY %d\n",
5121                             fromX, fromY, toX, toY);
5122             }
5123             return ImpossibleMove;
5124         }
5125         break;
5126
5127       default:
5128         break;
5129
5130       case EditPosition:
5131         /* EditPosition, empty square, or different color piece;
5132            click-click move is possible */
5133         if (toX == -2 || toY == -2) {
5134             boards[0][fromY][fromX] = EmptySquare;
5135             return AmbiguousMove;
5136         } else if (toX >= 0 && toY >= 0) {
5137             boards[0][toY][toX] = boards[0][fromY][fromX];
5138             boards[0][fromY][fromX] = EmptySquare;
5139             return AmbiguousMove;
5140         }
5141         return ImpossibleMove;
5142     }
5143
5144     /* [HGM] If move started in holdings, it means a drop */
5145     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5146          if( pup != EmptySquare ) return ImpossibleMove;
5147          if(appData.testLegality) {
5148              /* it would be more logical if LegalityTest() also figured out
5149               * which drops are legal. For now we forbid pawns on back rank.
5150               * Shogi is on its own here...
5151               */
5152              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5153                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5154                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5155          }
5156          return WhiteDrop; /* Not needed to specify white or black yet */
5157     }
5158
5159     userOfferedDraw = FALSE;
5160
5161     /* [HGM] always test for legality, to get promotion info */
5162     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5163                           epStatus[currentMove], castlingRights[currentMove],
5164                                          fromY, fromX, toY, toX, promoChar);
5165
5166     /* [HGM] but possibly ignore an IllegalMove result */
5167     if (appData.testLegality) {
5168         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5169             DisplayMoveError(_("Illegal move"));
5170             return ImpossibleMove;
5171         }
5172     }
5173     if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5174     return moveType;
5175     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5176        function is made into one that returns an OK move type if FinishMove
5177        should be called. This to give the calling driver routine the
5178        opportunity to finish the userMove input with a promotion popup,
5179        without bothering the user with this for invalid or illegal moves */
5180
5181 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5182 }
5183
5184 /* Common tail of UserMoveEvent and DropMenuEvent */
5185 int
5186 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5187      ChessMove moveType;
5188      int fromX, fromY, toX, toY;
5189      /*char*/int promoChar;
5190 {
5191   char *bookHit = 0;
5192
5193   if(appData.debugMode)
5194     fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5195
5196   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5197     {
5198       // [HGM] superchess: suppress promotions to non-available piece
5199       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5200       if(WhiteOnMove(currentMove))
5201         {
5202           if(!boards[currentMove][k][BOARD_WIDTH-2])
5203             return 0;
5204         }
5205       else
5206         {
5207           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5208             return 0;
5209         }
5210     }
5211
5212   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5213      move type in caller when we know the move is a legal promotion */
5214   if(moveType == NormalMove && promoChar)
5215     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5216
5217   if(appData.debugMode)
5218     fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5219
5220   /* [HGM] convert drag-and-drop piece drops to standard form */
5221   if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1)
5222     {
5223       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5224       fromX = boards[currentMove][fromY][fromX];
5225       fromY = DROP_RANK;
5226     }
5227
5228   /* [HGM] <popupFix> The following if has been moved here from
5229      UserMoveEvent(). Because it seemed to belon here (why not allow
5230      piece drops in training games?), and because it can only be
5231      performed after it is known to what we promote. */
5232   if (gameMode == Training)
5233     {
5234       /* compare the move played on the board to the next move in the
5235        * game. If they match, display the move and the opponent's response.
5236        * If they don't match, display an error message.
5237        */
5238       int saveAnimate;
5239       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5240       CopyBoard(testBoard, boards[currentMove]);
5241       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5242
5243       if (CompareBoards(testBoard, boards[currentMove+1]))
5244         {
5245           ForwardInner(currentMove+1);
5246
5247           /* Autoplay the opponent's response.
5248            * if appData.animate was TRUE when Training mode was entered,
5249            * the response will be animated.
5250            */
5251           saveAnimate = appData.animate;
5252           appData.animate = animateTraining;
5253           ForwardInner(currentMove+1);
5254           appData.animate = saveAnimate;
5255
5256           /* check for the end of the game */
5257           if (currentMove >= forwardMostMove)
5258             {
5259               gameMode = PlayFromGameFile;
5260               ModeHighlight();
5261               SetTrainingModeOff();
5262               DisplayInformation(_("End of game"));
5263             }
5264         }
5265       else
5266         {
5267           DisplayError(_("Incorrect move"), 0);
5268         }
5269       return 1;
5270     }
5271
5272   /* Ok, now we know that the move is good, so we can kill
5273      the previous line in Analysis Mode */
5274   if (gameMode == AnalyzeMode && currentMove < forwardMostMove)
5275     {
5276       forwardMostMove = currentMove;
5277     }
5278
5279   /* If we need the chess program but it's dead, restart it */
5280   ResurrectChessProgram();
5281
5282   /* A user move restarts a paused game*/
5283   if (pausing)
5284     PauseEvent();
5285
5286   thinkOutput[0] = NULLCHAR;
5287
5288   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5289
5290   if (gameMode == BeginningOfGame)
5291     {
5292       if (appData.noChessProgram)
5293         {
5294           gameMode = EditGame;
5295           SetGameInfo();
5296         }
5297       else
5298         {
5299           char buf[MSG_SIZ];
5300           gameMode = MachinePlaysBlack;
5301           StartClocks();
5302           SetGameInfo();
5303           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5304           DisplayTitle(buf);
5305           if (first.sendName)
5306             {
5307               sprintf(buf, "name %s\n", gameInfo.white);
5308               SendToProgram(buf, &first);
5309             }
5310           StartClocks();
5311         }
5312       ModeHighlight();
5313     }
5314   if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5315
5316   /* Relay move to ICS or chess engine */
5317   if (appData.icsActive)
5318     {
5319       if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5320           gameMode == IcsExamining)
5321         {
5322           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5323           ics_user_moved = 1;
5324         }
5325     }
5326   else
5327     {
5328       if (first.sendTime && (gameMode == BeginningOfGame ||
5329                              gameMode == MachinePlaysWhite ||
5330                              gameMode == MachinePlaysBlack))
5331         {
5332           SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5333         }
5334       if (gameMode != EditGame && gameMode != PlayFromGameFile)
5335         {
5336           // [HGM] book: if program might be playing, let it use book
5337           bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5338           first.maybeThinking = TRUE;
5339         }
5340       else
5341         SendMoveToProgram(forwardMostMove-1, &first);
5342       if (currentMove == cmailOldMove + 1)
5343         {
5344           cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5345         }
5346     }
5347
5348   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5349
5350   switch (gameMode)
5351     {
5352     case EditGame:
5353       switch (MateTest(boards[currentMove], PosFlags(currentMove),
5354                        EP_UNKNOWN, castlingRights[currentMove]) )
5355         {
5356         case MT_NONE:
5357         case MT_CHECK:
5358           break;
5359         case MT_CHECKMATE:
5360         case MT_STAINMATE:
5361           if (WhiteOnMove(currentMove))
5362             {
5363               GameEnds(BlackWins, "Black mates", GE_PLAYER);
5364             }
5365           else
5366             {
5367               GameEnds(WhiteWins, "White mates", GE_PLAYER);
5368             }
5369           break;
5370         case MT_STALEMATE:
5371           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5372           break;
5373     }
5374       break;
5375
5376     case MachinePlaysBlack:
5377     case MachinePlaysWhite:
5378       /* disable certain menu options while machine is thinking */
5379       SetMachineThinkingEnables();
5380       break;
5381
5382     default:
5383       break;
5384     }
5385
5386   if(bookHit)
5387     { // [HGM] book: simulate book reply
5388       static char bookMove[MSG_SIZ]; // a bit generous?
5389
5390       programStats.nodes = programStats.depth = programStats.time =
5391         programStats.score = programStats.got_only_move = 0;
5392       sprintf(programStats.movelist, "%s (xbook)", bookHit);
5393
5394       strcpy(bookMove, "move ");
5395       strcat(bookMove, bookHit);
5396       HandleMachineMove(bookMove, &first);
5397     }
5398
5399   return 1;
5400 }
5401
5402 void
5403 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5404      int fromX, fromY, toX, toY;
5405      int promoChar;
5406 {
5407     /* [HGM] This routine was added to allow calling of its two logical
5408        parts from other modules in the old way. Before, UserMoveEvent()
5409        automatically called FinishMove() if the move was OK, and returned
5410        otherwise. I separated the two, in order to make it possible to
5411        slip a promotion popup in between. But that it always needs two
5412        calls, to the first part, (now called UserMoveTest() ), and to
5413        FinishMove if the first part succeeded. Calls that do not need
5414        to do anything in between, can call this routine the old way.
5415     */
5416   ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5417   if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5418   if(moveType != ImpossibleMove)
5419     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5420 }
5421
5422 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5423 {
5424 //    char * hint = lastHint;
5425     FrontEndProgramStats stats;
5426
5427     stats.which = cps == &first ? 0 : 1;
5428     stats.depth = cpstats->depth;
5429     stats.nodes = cpstats->nodes;
5430     stats.score = cpstats->score;
5431     stats.time = cpstats->time;
5432     stats.pv = cpstats->movelist;
5433     stats.hint = lastHint;
5434     stats.an_move_index = 0;
5435     stats.an_move_count = 0;
5436
5437     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5438         stats.hint = cpstats->move_name;
5439         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5440         stats.an_move_count = cpstats->nr_moves;
5441     }
5442
5443     SetProgramStats( &stats );
5444 }
5445
5446 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5447 {   // [HGM] book: this routine intercepts moves to simulate book replies
5448     char *bookHit = NULL;
5449
5450     //first determine if the incoming move brings opponent into his book
5451     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5452         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5453     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5454     if(bookHit != NULL && !cps->bookSuspend) {
5455         // make sure opponent is not going to reply after receiving move to book position
5456         SendToProgram("force\n", cps);
5457         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5458     }
5459     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5460     // now arrange restart after book miss
5461     if(bookHit) {
5462         // after a book hit we never send 'go', and the code after the call to this routine
5463         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5464         char buf[MSG_SIZ];
5465         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5466         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5467         SendToProgram(buf, cps);
5468         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5469     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5470         SendToProgram("go\n", cps);
5471         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5472     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5473         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5474             SendToProgram("go\n", cps);
5475         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5476     }
5477     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5478 }
5479
5480 char *savedMessage;
5481 ChessProgramState *savedState;
5482 void DeferredBookMove(void)
5483 {
5484         if(savedState->lastPing != savedState->lastPong)
5485                     ScheduleDelayedEvent(DeferredBookMove, 10);
5486         else
5487         HandleMachineMove(savedMessage, savedState);
5488 }
5489
5490 void
5491 HandleMachineMove(message, cps)
5492      char *message;
5493      ChessProgramState *cps;
5494 {
5495     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5496     char realname[MSG_SIZ];
5497     int fromX, fromY, toX, toY;
5498     ChessMove moveType;
5499     char promoChar;
5500     char *p;
5501     int machineWhite;
5502     char *bookHit;
5503
5504 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5505     /*
5506      * Kludge to ignore BEL characters
5507      */
5508     while (*message == '\007') message++;
5509
5510     /*
5511      * [HGM] engine debug message: ignore lines starting with '#' character
5512      */
5513     if(cps->debug && *message == '#') return;
5514
5515     /*
5516      * Look for book output
5517      */
5518     if (cps == &first && bookRequested) {
5519         if (message[0] == '\t' || message[0] == ' ') {
5520             /* Part of the book output is here; append it */
5521             strcat(bookOutput, message);
5522             strcat(bookOutput, "  \n");
5523             return;
5524         } else if (bookOutput[0] != NULLCHAR) {
5525             /* All of book output has arrived; display it */
5526             char *p = bookOutput;
5527             while (*p != NULLCHAR) {
5528                 if (*p == '\t') *p = ' ';
5529                 p++;
5530             }
5531             DisplayInformation(bookOutput);
5532             bookRequested = FALSE;
5533             /* Fall through to parse the current output */
5534         }
5535     }
5536
5537     /*
5538      * Look for machine move.
5539      */
5540     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5541         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5542     {
5543         /* This method is only useful on engines that support ping */
5544         if (cps->lastPing != cps->lastPong) {
5545           if (gameMode == BeginningOfGame) {
5546             /* Extra move from before last new; ignore */
5547             if (appData.debugMode) {
5548                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5549             }
5550           } else {
5551             if (appData.debugMode) {
5552                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5553                         cps->which, gameMode);
5554             }
5555
5556             SendToProgram("undo\n", cps);
5557           }
5558           return;
5559         }
5560
5561         switch (gameMode) {
5562           case BeginningOfGame:
5563             /* Extra move from before last reset; ignore */
5564             if (appData.debugMode) {
5565                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5566             }
5567             return;
5568
5569           case EndOfGame:
5570           case IcsIdle:
5571           default:
5572             /* Extra move after we tried to stop.  The mode test is
5573                not a reliable way of detecting this problem, but it's
5574                the best we can do on engines that don't support ping.
5575             */
5576             if (appData.debugMode) {
5577                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5578                         cps->which, gameMode);
5579             }
5580             SendToProgram("undo\n", cps);
5581             return;
5582
5583           case MachinePlaysWhite:
5584           case IcsPlayingWhite:
5585             machineWhite = TRUE;
5586             break;
5587
5588           case MachinePlaysBlack:
5589           case IcsPlayingBlack:
5590             machineWhite = FALSE;
5591             break;
5592
5593           case TwoMachinesPlay:
5594             machineWhite = (cps->twoMachinesColor[0] == 'w');
5595             break;
5596         }
5597         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5598             if (appData.debugMode) {
5599                 fprintf(debugFP,
5600                         "Ignoring move out of turn by %s, gameMode %d"
5601                         ", forwardMost %d\n",
5602                         cps->which, gameMode, forwardMostMove);
5603             }
5604             return;
5605         }
5606
5607     if (appData.debugMode) { int f = forwardMostMove;
5608         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5609                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5610     }
5611         if(cps->alphaRank) AlphaRank(machineMove, 4);
5612         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5613                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5614             /* Machine move could not be parsed; ignore it. */
5615             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5616                     machineMove, cps->which);
5617             DisplayError(buf1, 0);
5618             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5619                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5620             if (gameMode == TwoMachinesPlay) {
5621               GameEnds(machineWhite ? BlackWins : WhiteWins,
5622                        buf1, GE_XBOARD);
5623             }
5624             return;
5625         }
5626
5627         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5628         /* So we have to redo legality test with true e.p. status here,  */
5629         /* to make sure an illegal e.p. capture does not slip through,   */
5630         /* to cause a forfeit on a justified illegal-move complaint      */
5631         /* of the opponent.                                              */
5632         if( gameMode==TwoMachinesPlay && appData.testLegality
5633             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5634                                                               ) {
5635            ChessMove moveType;
5636            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5637                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5638                              fromY, fromX, toY, toX, promoChar);
5639             if (appData.debugMode) {
5640                 int i;
5641                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5642                     castlingRights[forwardMostMove][i], castlingRank[i]);
5643                 fprintf(debugFP, "castling rights\n");
5644             }
5645             if(moveType == IllegalMove) {
5646                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5647                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5648                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5649                            buf1, GE_XBOARD);
5650                 return;
5651            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5652            /* [HGM] Kludge to handle engines that send FRC-style castling
5653               when they shouldn't (like TSCP-Gothic) */
5654            switch(moveType) {
5655              case WhiteASideCastleFR:
5656              case BlackASideCastleFR:
5657                toX+=2;
5658                currentMoveString[2]++;
5659                break;
5660              case WhiteHSideCastleFR:
5661              case BlackHSideCastleFR:
5662                toX--;
5663                currentMoveString[2]--;
5664                break;
5665              default: ; // nothing to do, but suppresses warning of pedantic compilers
5666            }
5667         }
5668         hintRequested = FALSE;
5669         lastHint[0] = NULLCHAR;
5670         bookRequested = FALSE;
5671         /* Program may be pondering now */
5672         cps->maybeThinking = TRUE;
5673         if (cps->sendTime == 2) cps->sendTime = 1;
5674         if (cps->offeredDraw) cps->offeredDraw--;
5675
5676 #if ZIPPY
5677         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5678             first.initDone) {
5679           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5680           ics_user_moved = 1;
5681           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5682                 char buf[3*MSG_SIZ];
5683
5684                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",
5685                         programStats.score / 100.,
5686                         programStats.depth,
5687                         programStats.time / 100.,
5688                         u64ToDouble(programStats.nodes),
5689                         u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
5690                         programStats.movelist);
5691                 SendToICS(buf);
5692           }
5693         }
5694 #endif
5695         /* currentMoveString is set as a side-effect of ParseOneMove */
5696         strcpy(machineMove, currentMoveString);
5697         strcat(machineMove, "\n");
5698         strcpy(moveList[forwardMostMove], machineMove);
5699
5700         /* [AS] Save move info and clear stats for next move */
5701         pvInfoList[ forwardMostMove ].score = programStats.score;
5702         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5703         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5704         ClearProgramStats();
5705         thinkOutput[0] = NULLCHAR;
5706         hiddenThinkOutputState = 0;
5707
5708         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5709
5710         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5711         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5712             int count = 0;
5713
5714             while( count < adjudicateLossPlies ) {
5715                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5716
5717                 if( count & 1 ) {
5718                     score = -score; /* Flip score for winning side */
5719                 }
5720
5721                 if( score > adjudicateLossThreshold ) {
5722                     break;
5723                 }
5724
5725                 count++;
5726             }
5727
5728             if( count >= adjudicateLossPlies ) {
5729                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5730
5731                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5732                     "Xboard adjudication",
5733                     GE_XBOARD );
5734
5735                 return;
5736             }
5737         }
5738
5739         if( gameMode == TwoMachinesPlay ) {
5740           // [HGM] some adjudications useful with buggy engines
5741             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5742           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5743
5744
5745             if( appData.testLegality )
5746             {   /* [HGM] Some more adjudications for obstinate engines */
5747                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5748                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5749                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5750                 static int moveCount = 6;
5751                 ChessMove result;
5752                 char *reason = NULL;
5753
5754                 /* Count what is on board. */
5755                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5756                 {   ChessSquare p = boards[forwardMostMove][i][j];
5757                     int m=i;
5758
5759                     switch((int) p)
5760                     {   /* count B,N,R and other of each side */
5761                         case WhiteKing:
5762                         case BlackKing:
5763                              NrK++; break; // [HGM] atomic: count Kings
5764                         case WhiteKnight:
5765                              NrWN++; break;
5766                         case WhiteBishop:
5767                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5768                              bishopsColor |= 1 << ((i^j)&1);
5769                              NrWB++; break;
5770                         case BlackKnight:
5771                              NrBN++; break;
5772                         case BlackBishop:
5773                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5774                              bishopsColor |= 1 << ((i^j)&1);
5775                              NrBB++; break;
5776                         case WhiteRook:
5777                              NrWR++; break;
5778                         case BlackRook:
5779                              NrBR++; break;
5780                         case WhiteQueen:
5781                              NrWQ++; break;
5782                         case BlackQueen:
5783                              NrBQ++; break;
5784                         case EmptySquare:
5785                              break;
5786                         case BlackPawn:
5787                              m = 7-i;
5788                         case WhitePawn:
5789                              PawnAdvance += m; NrPawns++;
5790                     }
5791                     NrPieces += (p != EmptySquare);
5792                     NrW += ((int)p < (int)BlackPawn);
5793                     if(gameInfo.variant == VariantXiangqi &&
5794                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5795                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5796                         NrW -= ((int)p < (int)BlackPawn);
5797                     }
5798                 }
5799
5800                 /* Some material-based adjudications that have to be made before stalemate test */
5801                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5802                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5803                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5804                      if(appData.checkMates) {
5805                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5806                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5807                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5808                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5809                          return;
5810                      }
5811                 }
5812
5813                 /* Bare King in Shatranj (loses) or Losers (wins) */
5814                 if( NrW == 1 || NrPieces - NrW == 1) {
5815                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5816                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5817                      if(appData.checkMates) {
5818                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5819                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5820                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5821                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5822                          return;
5823                      }
5824                   } else
5825                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5826                   {    /* bare King */
5827                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5828                         if(appData.checkMates) {
5829                             /* but only adjudicate if adjudication enabled */
5830                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5831                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5832                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5833                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5834                             return;
5835                         }
5836                   }
5837                 } else bare = 1;
5838
5839
5840             // don't wait for engine to announce game end if we can judge ourselves
5841             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5842                                        castlingRights[forwardMostMove]) ) {
5843               case MT_CHECK:
5844                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5845                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5846                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5847                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5848                             checkCnt++;
5849                         if(checkCnt >= 2) {
5850                             reason = "Xboard adjudication: 3rd check";
5851                             epStatus[forwardMostMove] = EP_CHECKMATE;
5852                             break;
5853                         }
5854                     }
5855                 }
5856               case MT_NONE:
5857               default:
5858                 break;
5859               case MT_STALEMATE:
5860               case MT_STAINMATE:
5861                 reason = "Xboard adjudication: Stalemate";
5862                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5863                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5864                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5865                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5866                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5867                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5868                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5869                                                                         EP_CHECKMATE : EP_WINS);
5870                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5871                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5872                 }
5873                 break;
5874               case MT_CHECKMATE:
5875                 reason = "Xboard adjudication: Checkmate";
5876                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5877                 break;
5878             }
5879
5880                 switch(i = epStatus[forwardMostMove]) {
5881                     case EP_STALEMATE:
5882                         result = GameIsDrawn; break;
5883                     case EP_CHECKMATE:
5884                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5885                     case EP_WINS:
5886                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5887                     default:
5888                         result = (ChessMove) 0;
5889                 }
5890                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5891                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5892                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5893                     GameEnds( result, reason, GE_XBOARD );
5894                     return;
5895                 }
5896
5897                 /* Next absolutely insufficient mating material. */
5898                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5899                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5900                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5901                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5902                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5903
5904                      /* always flag draws, for judging claims */
5905                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5906
5907                      if(appData.materialDraws) {
5908                          /* but only adjudicate them if adjudication enabled */
5909                          SendToProgram("force\n", cps->other); // suppress reply
5910                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5911                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5912                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5913                          return;
5914                      }
5915                 }
5916
5917                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5918                 if(NrPieces == 4 &&
5919                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5920                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5921                    || NrWN==2 || NrBN==2     /* KNNK */
5922                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5923                   ) ) {
5924                      if(--moveCount < 0 && appData.trivialDraws)
5925                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5926                           SendToProgram("force\n", cps->other); // suppress reply
5927                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5928                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5929                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5930                           return;
5931                      }
5932                 } else moveCount = 6;
5933             }
5934           }
5935 #if 1
5936     if (appData.debugMode) { int i;
5937       fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5938               forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5939               appData.drawRepeats);
5940       for( i=forwardMostMove; i>=backwardMostMove; i-- )
5941            fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5942
5943     }
5944 #endif
5945                 /* Check for rep-draws */
5946                 count = 0;
5947                 for(k = forwardMostMove-2;
5948                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5949                         epStatus[k] < EP_UNKNOWN &&
5950                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5951                     k-=2)
5952                 {   int rights=0;
5953 #if 0
5954     if (appData.debugMode) {
5955       fprintf(debugFP, " loop\n");
5956     }
5957 #endif
5958                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5959 #if 0
5960     if (appData.debugMode) {
5961       fprintf(debugFP, "match\n");
5962     }
5963 #endif
5964                         /* compare castling rights */
5965                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5966                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5967                                 rights++; /* King lost rights, while rook still had them */
5968                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5969                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5970                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5971                                    rights++; /* but at least one rook lost them */
5972                         }
5973                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5974                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5975                                 rights++;
5976                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5977                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5978                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5979                                    rights++;
5980                         }
5981 #if 0
5982     if (appData.debugMode) {
5983       for(i=0; i<nrCastlingRights; i++)
5984       fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5985     }
5986
5987     if (appData.debugMode) {
5988       fprintf(debugFP, " %d %d\n", rights, k);
5989     }
5990 #endif
5991                         if( rights == 0 && ++count > appData.drawRepeats-2
5992                             && appData.drawRepeats > 1) {
5993                              /* adjudicate after user-specified nr of repeats */
5994                              SendToProgram("force\n", cps->other); // suppress reply
5995                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5996                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5997                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5998                                 // [HGM] xiangqi: check for forbidden perpetuals
5999                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6000                                 for(m=forwardMostMove; m>k; m-=2) {
6001                                     if(MateTest(boards[m], PosFlags(m),
6002                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6003                                         ourPerpetual = 0; // the current mover did not always check
6004                                     if(MateTest(boards[m-1], PosFlags(m-1),
6005                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6006                                         hisPerpetual = 0; // the opponent did not always check
6007                                 }
6008                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6009                                                                         ourPerpetual, hisPerpetual);
6010                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6011                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6012                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6013                                     return;
6014                                 }
6015                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6016                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6017                                 // Now check for perpetual chases
6018                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6019                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6020                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6021                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6022                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6023                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6024                                         return;
6025                                     }
6026                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6027                                         break; // Abort repetition-checking loop.
6028                                 }
6029                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6030                              }
6031                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6032                              return;
6033                         }
6034                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6035                              epStatus[forwardMostMove] = EP_REP_DRAW;
6036                     }
6037                 }
6038
6039                 /* Now we test for 50-move draws. Determine ply count */
6040                 count = forwardMostMove;
6041                 /* look for last irreversble move */
6042                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6043                     count--;
6044                 /* if we hit starting position, add initial plies */
6045                 if( count == backwardMostMove )
6046                     count -= initialRulePlies;
6047                 count = forwardMostMove - count;
6048                 if( count >= 100)
6049                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6050                          /* this is used to judge if draw claims are legal */
6051                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6052                          SendToProgram("force\n", cps->other); // suppress reply
6053                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6054                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6055                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6056                          return;
6057                 }
6058
6059                 /* if draw offer is pending, treat it as a draw claim
6060                  * when draw condition present, to allow engines a way to
6061                  * claim draws before making their move to avoid a race
6062                  * condition occurring after their move
6063                  */
6064                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6065                          char *p = NULL;
6066                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6067                              p = "Draw claim: 50-move rule";
6068                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6069                              p = "Draw claim: 3-fold repetition";
6070                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6071                              p = "Draw claim: insufficient mating material";
6072                          if( p != NULL ) {
6073                              SendToProgram("force\n", cps->other); // suppress reply
6074                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6075                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6076                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6077                              return;
6078                          }
6079                 }
6080
6081
6082                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6083                     SendToProgram("force\n", cps->other); // suppress reply
6084                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6085                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6086
6087                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6088
6089                     return;
6090                 }
6091         }
6092
6093         bookHit = NULL;
6094         if (gameMode == TwoMachinesPlay) {
6095             /* [HGM] relaying draw offers moved to after reception of move */
6096             /* and interpreting offer as claim if it brings draw condition */
6097             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6098                 SendToProgram("draw\n", cps->other);
6099             }
6100             if (cps->other->sendTime) {
6101                 SendTimeRemaining(cps->other,
6102                                   cps->other->twoMachinesColor[0] == 'w');
6103             }
6104             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6105             if (firstMove && !bookHit) {
6106                 firstMove = FALSE;
6107                 if (cps->other->useColors) {
6108                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6109                 }
6110                 SendToProgram("go\n", cps->other);
6111             }
6112             cps->other->maybeThinking = TRUE;
6113         }
6114
6115         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6116
6117         if (!pausing && appData.ringBellAfterMoves) {
6118             RingBell();
6119         }
6120
6121         /*
6122          * Reenable menu items that were disabled while
6123          * machine was thinking
6124          */
6125         if (gameMode != TwoMachinesPlay)
6126             SetUserThinkingEnables();
6127
6128         // [HGM] book: after book hit opponent has received move and is now in force mode
6129         // force the book reply into it, and then fake that it outputted this move by jumping
6130         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6131         if(bookHit) {
6132                 static char bookMove[MSG_SIZ]; // a bit generous?
6133
6134                 strcpy(bookMove, "move ");
6135                 strcat(bookMove, bookHit);
6136                 message = bookMove;
6137                 cps = cps->other;
6138                 programStats.nodes = programStats.depth = programStats.time =
6139                 programStats.score = programStats.got_only_move = 0;
6140                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6141
6142                 if(cps->lastPing != cps->lastPong) {
6143                     savedMessage = message; // args for deferred call
6144                     savedState = cps;
6145                     ScheduleDelayedEvent(DeferredBookMove, 10);
6146                     return;
6147                 }
6148                 goto FakeBookMove;
6149         }
6150
6151         return;
6152     }
6153
6154     /* Set special modes for chess engines.  Later something general
6155      *  could be added here; for now there is just one kludge feature,
6156      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6157      *  when "xboard" is given as an interactive command.
6158      */
6159     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6160         cps->useSigint = FALSE;
6161         cps->useSigterm = FALSE;
6162     }
6163
6164     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6165      * want this, I was asked to put it in, and obliged.
6166      */
6167     if (!strncmp(message, "setboard ", 9)) {
6168         Board initial_position; int i;
6169
6170         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6171
6172         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6173             DisplayError(_("Bad FEN received from engine"), 0);
6174             return ;
6175         } else {
6176            Reset(FALSE, FALSE);
6177            CopyBoard(boards[0], initial_position);
6178            initialRulePlies = FENrulePlies;
6179            epStatus[0] = FENepStatus;
6180            for( i=0; i<nrCastlingRights; i++ )
6181                 castlingRights[0][i] = FENcastlingRights[i];
6182            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6183            else gameMode = MachinePlaysBlack;
6184            DrawPosition(FALSE, boards[currentMove]);
6185         }
6186         return;
6187     }
6188
6189     /*
6190      * Look for communication commands
6191      */
6192     if (!strncmp(message, "telluser ", 9)) {
6193         DisplayNote(message + 9);
6194         return;
6195     }
6196     if (!strncmp(message, "tellusererror ", 14)) {
6197         DisplayError(message + 14, 0);
6198         return;
6199     }
6200     if (!strncmp(message, "tellopponent ", 13)) {
6201       if (appData.icsActive) {
6202         if (loggedOn) {
6203           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6204           SendToICS(buf1);
6205         }
6206       } else {
6207         DisplayNote(message + 13);
6208       }
6209       return;
6210     }
6211     if (!strncmp(message, "tellothers ", 11)) {
6212       if (appData.icsActive) {
6213         if (loggedOn) {
6214           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6215           SendToICS(buf1);
6216         }
6217       }
6218       return;
6219     }
6220     if (!strncmp(message, "tellall ", 8)) {
6221       if (appData.icsActive) {
6222         if (loggedOn) {
6223           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6224           SendToICS(buf1);
6225         }
6226       } else {
6227         DisplayNote(message + 8);
6228       }
6229       return;
6230     }
6231     if (strncmp(message, "warning", 7) == 0) {
6232         /* Undocumented feature, use tellusererror in new code */
6233         DisplayError(message, 0);
6234         return;
6235     }
6236     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6237         strcpy(realname, cps->tidy);
6238         strcat(realname, " query");
6239         AskQuestion(realname, buf2, buf1, cps->pr);
6240         return;
6241     }
6242     /* Commands from the engine directly to ICS.  We don't allow these to be
6243      *  sent until we are logged on. Crafty kibitzes have been known to
6244      *  interfere with the login process.
6245      */
6246     if (loggedOn) {
6247         if (!strncmp(message, "tellics ", 8)) {
6248             SendToICS(message + 8);
6249             SendToICS("\n");
6250             return;
6251         }
6252         if (!strncmp(message, "tellicsnoalias ", 15)) {
6253             SendToICS(ics_prefix);
6254             SendToICS(message + 15);
6255             SendToICS("\n");
6256             return;
6257         }
6258         /* The following are for backward compatibility only */
6259         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6260             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6261             SendToICS(ics_prefix);
6262             SendToICS(message);
6263             SendToICS("\n");
6264             return;
6265         }
6266     }
6267     if (strncmp(message, "feature ", 8) == 0) {
6268       ParseFeatures(message+8, cps);
6269     }
6270     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6271         return;
6272     }
6273     /*
6274      * If the move is illegal, cancel it and redraw the board.
6275      * Also deal with other error cases.  Matching is rather loose
6276      * here to accommodate engines written before the spec.
6277      */
6278     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6279         strncmp(message, "Error", 5) == 0) {
6280         if (StrStr(message, "name") ||
6281             StrStr(message, "rating") || StrStr(message, "?") ||
6282             StrStr(message, "result") || StrStr(message, "board") ||
6283             StrStr(message, "bk") || StrStr(message, "computer") ||
6284             StrStr(message, "variant") || StrStr(message, "hint") ||
6285             StrStr(message, "random") || StrStr(message, "depth") ||
6286             StrStr(message, "accepted")) {
6287             return;
6288         }
6289         if (StrStr(message, "protover")) {
6290           /* Program is responding to input, so it's apparently done
6291              initializing, and this error message indicates it is
6292              protocol version 1.  So we don't need to wait any longer
6293              for it to initialize and send feature commands. */
6294           FeatureDone(cps, 1);
6295           cps->protocolVersion = 1;
6296           return;
6297         }
6298         cps->maybeThinking = FALSE;
6299
6300         if (StrStr(message, "draw")) {
6301             /* Program doesn't have "draw" command */
6302             cps->sendDrawOffers = 0;
6303             return;
6304         }
6305         if (cps->sendTime != 1 &&
6306             (StrStr(message, "time") || StrStr(message, "otim"))) {
6307           /* Program apparently doesn't have "time" or "otim" command */
6308           cps->sendTime = 0;
6309           return;
6310         }
6311         if (StrStr(message, "analyze")) {
6312             cps->analysisSupport = FALSE;
6313             cps->analyzing = FALSE;
6314             Reset(FALSE, TRUE);
6315             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6316             DisplayError(buf2, 0);
6317             return;
6318         }
6319         if (StrStr(message, "(no matching move)st")) {
6320           /* Special kludge for GNU Chess 4 only */
6321           cps->stKludge = TRUE;
6322           SendTimeControl(cps, movesPerSession, timeControl,
6323                           timeIncrement, appData.searchDepth,
6324                           searchTime);
6325           return;
6326         }
6327         if (StrStr(message, "(no matching move)sd")) {
6328           /* Special kludge for GNU Chess 4 only */
6329           cps->sdKludge = TRUE;
6330           SendTimeControl(cps, movesPerSession, timeControl,
6331                           timeIncrement, appData.searchDepth,
6332                           searchTime);
6333           return;
6334         }
6335         if (!StrStr(message, "llegal")) {
6336             return;
6337         }
6338         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6339             gameMode == IcsIdle) return;
6340         if (forwardMostMove <= backwardMostMove) return;
6341 #if 0
6342         /* Following removed: it caused a bug where a real illegal move
6343            message in analyze mored would be ignored. */
6344         if (cps == &first && programStats.ok_to_send == 0) {
6345             /* Bogus message from Crafty responding to "."  This filtering
6346                can miss some of the bad messages, but fortunately the bug
6347                is fixed in current Crafty versions, so it doesn't matter. */
6348             return;
6349         }
6350 #endif
6351         if (pausing) PauseEvent();
6352         if (gameMode == PlayFromGameFile) {
6353             /* Stop reading this game file */
6354             gameMode = EditGame;
6355             ModeHighlight();
6356         }
6357         currentMove = --forwardMostMove;
6358         DisplayMove(currentMove-1); /* before DisplayMoveError */
6359         SwitchClocks();
6360         DisplayBothClocks();
6361         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6362                 parseList[currentMove], cps->which);
6363         DisplayMoveError(buf1);
6364         DrawPosition(FALSE, boards[currentMove]);
6365
6366         /* [HGM] illegal-move claim should forfeit game when Xboard */
6367         /* only passes fully legal moves                            */
6368         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6369             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6370                                 "False illegal-move claim", GE_XBOARD );
6371         }
6372         return;
6373     }
6374     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6375         /* Program has a broken "time" command that
6376            outputs a string not ending in newline.
6377            Don't use it. */
6378         cps->sendTime = 0;
6379     }
6380
6381     /*
6382      * If chess program startup fails, exit with an error message.
6383      * Attempts to recover here are futile.
6384      */
6385     if ((StrStr(message, "unknown host") != NULL)
6386         || (StrStr(message, "No remote directory") != NULL)
6387         || (StrStr(message, "not found") != NULL)
6388         || (StrStr(message, "No such file") != NULL)
6389         || (StrStr(message, "can't alloc") != NULL)
6390         || (StrStr(message, "Permission denied") != NULL)) {
6391
6392         cps->maybeThinking = FALSE;
6393         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6394                 cps->which, cps->program, cps->host, message);
6395         RemoveInputSource(cps->isr);
6396         DisplayFatalError(buf1, 0, 1);
6397         return;
6398     }
6399
6400     /*
6401      * Look for hint output
6402      */
6403     if (sscanf(message, "Hint: %s", buf1) == 1) {
6404         if (cps == &first && hintRequested) {
6405             hintRequested = FALSE;
6406             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6407                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6408                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6409                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6410                                     fromY, fromX, toY, toX, promoChar, buf1);
6411                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6412                 DisplayInformation(buf2);
6413             } else {
6414                 /* Hint move could not be parsed!? */
6415               snprintf(buf2, sizeof(buf2),
6416                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6417                         buf1, cps->which);
6418                 DisplayError(buf2, 0);
6419             }
6420         } else {
6421             strcpy(lastHint, buf1);
6422         }
6423         return;
6424     }
6425
6426     /*
6427      * Ignore other messages if game is not in progress
6428      */
6429     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6430         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6431
6432     /*
6433      * look for win, lose, draw, or draw offer
6434      */
6435     if (strncmp(message, "1-0", 3) == 0) {
6436         char *p, *q, *r = "";
6437         p = strchr(message, '{');
6438         if (p) {
6439             q = strchr(p, '}');
6440             if (q) {
6441                 *q = NULLCHAR;
6442                 r = p + 1;
6443             }
6444         }
6445         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6446         return;
6447     } else if (strncmp(message, "0-1", 3) == 0) {
6448         char *p, *q, *r = "";
6449         p = strchr(message, '{');
6450         if (p) {
6451             q = strchr(p, '}');
6452             if (q) {
6453                 *q = NULLCHAR;
6454                 r = p + 1;
6455             }
6456         }
6457         /* Kludge for Arasan 4.1 bug */
6458         if (strcmp(r, "Black resigns") == 0) {
6459             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6460             return;
6461         }
6462         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6463         return;
6464     } else if (strncmp(message, "1/2", 3) == 0) {
6465         char *p, *q, *r = "";
6466         p = strchr(message, '{');
6467         if (p) {
6468             q = strchr(p, '}');
6469             if (q) {
6470                 *q = NULLCHAR;
6471                 r = p + 1;
6472             }
6473         }
6474
6475         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6476         return;
6477
6478     } else if (strncmp(message, "White resign", 12) == 0) {
6479         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6480         return;
6481     } else if (strncmp(message, "Black resign", 12) == 0) {
6482         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6483         return;
6484     } else if (strncmp(message, "White matches", 13) == 0 ||
6485                strncmp(message, "Black matches", 13) == 0   ) {
6486         /* [HGM] ignore GNUShogi noises */
6487         return;
6488     } else if (strncmp(message, "White", 5) == 0 &&
6489                message[5] != '(' &&
6490                StrStr(message, "Black") == NULL) {
6491         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6492         return;
6493     } else if (strncmp(message, "Black", 5) == 0 &&
6494                message[5] != '(') {
6495         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6496         return;
6497     } else if (strcmp(message, "resign") == 0 ||
6498                strcmp(message, "computer resigns") == 0) {
6499         switch (gameMode) {
6500           case MachinePlaysBlack:
6501           case IcsPlayingBlack:
6502             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6503             break;
6504           case MachinePlaysWhite:
6505           case IcsPlayingWhite:
6506             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6507             break;
6508           case TwoMachinesPlay:
6509             if (cps->twoMachinesColor[0] == 'w')
6510               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6511             else
6512               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6513             break;
6514           default:
6515             /* can't happen */
6516             break;
6517         }
6518         return;
6519     } else if (strncmp(message, "opponent mates", 14) == 0) {
6520         switch (gameMode) {
6521           case MachinePlaysBlack:
6522           case IcsPlayingBlack:
6523             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6524             break;
6525           case MachinePlaysWhite:
6526           case IcsPlayingWhite:
6527             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6528             break;
6529           case TwoMachinesPlay:
6530             if (cps->twoMachinesColor[0] == 'w')
6531               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6532             else
6533               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6534             break;
6535           default:
6536             /* can't happen */
6537             break;
6538         }
6539         return;
6540     } else if (strncmp(message, "computer mates", 14) == 0) {
6541         switch (gameMode) {
6542           case MachinePlaysBlack:
6543           case IcsPlayingBlack:
6544             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6545             break;
6546           case MachinePlaysWhite:
6547           case IcsPlayingWhite:
6548             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6549             break;
6550           case TwoMachinesPlay:
6551             if (cps->twoMachinesColor[0] == 'w')
6552               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6553             else
6554               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6555             break;
6556           default:
6557             /* can't happen */
6558             break;
6559         }
6560         return;
6561     } else if (strncmp(message, "checkmate", 9) == 0) {
6562         if (WhiteOnMove(forwardMostMove)) {
6563             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6564         } else {
6565             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6566         }
6567         return;
6568     } else if (strstr(message, "Draw") != NULL ||
6569                strstr(message, "game is a draw") != NULL) {
6570         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6571         return;
6572     } else if (strstr(message, "offer") != NULL &&
6573                strstr(message, "draw") != NULL) {
6574 #if ZIPPY
6575         if (appData.zippyPlay && first.initDone) {
6576             /* Relay offer to ICS */
6577             SendToICS(ics_prefix);
6578             SendToICS("draw\n");
6579         }
6580 #endif
6581         cps->offeredDraw = 2; /* valid until this engine moves twice */
6582         if (gameMode == TwoMachinesPlay) {
6583             if (cps->other->offeredDraw) {
6584                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6585             /* [HGM] in two-machine mode we delay relaying draw offer      */
6586             /* until after we also have move, to see if it is really claim */
6587             }
6588 #if 0
6589               else {
6590                 if (cps->other->sendDrawOffers) {
6591                     SendToProgram("draw\n", cps->other);
6592                 }
6593             }
6594 #endif
6595         } else if (gameMode == MachinePlaysWhite ||
6596                    gameMode == MachinePlaysBlack) {
6597           if (userOfferedDraw) {
6598             DisplayInformation(_("Machine accepts your draw offer"));
6599             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6600           } else {
6601             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6602           }
6603         }
6604     }
6605
6606
6607     /*
6608      * Look for thinking output
6609      */
6610     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6611           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6612                                 ) {
6613         int plylev, mvleft, mvtot, curscore, time;
6614         char mvname[MOVE_LEN];
6615         u64 nodes; // [DM]
6616         char plyext;
6617         int ignore = FALSE;
6618         int prefixHint = FALSE;
6619         mvname[0] = NULLCHAR;
6620
6621         switch (gameMode) {
6622           case MachinePlaysBlack:
6623           case IcsPlayingBlack:
6624             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6625             break;
6626           case MachinePlaysWhite:
6627           case IcsPlayingWhite:
6628             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6629             break;
6630           case AnalyzeMode:
6631           case AnalyzeFile:
6632             break;
6633           case IcsObserving: /* [DM] icsEngineAnalyze */
6634             if (!appData.icsEngineAnalyze) ignore = TRUE;
6635             break;
6636           case TwoMachinesPlay:
6637             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6638                 ignore = TRUE;
6639             }
6640             break;
6641           default:
6642             ignore = TRUE;
6643             break;
6644         }
6645
6646         if (!ignore) {
6647             buf1[0] = NULLCHAR;
6648             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6649                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6650
6651                 if (plyext != ' ' && plyext != '\t') {
6652                     time *= 100;
6653                 }
6654
6655                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6656                 if( cps->scoreIsAbsolute &&
6657                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6658                 {
6659                     curscore = -curscore;
6660                 }
6661
6662
6663                 programStats.depth = plylev;
6664                 programStats.nodes = nodes;
6665                 programStats.time = time;
6666                 programStats.score = curscore;
6667                 programStats.got_only_move = 0;
6668
6669                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6670                         int ticklen;
6671
6672                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6673                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6674                         if(WhiteOnMove(forwardMostMove))
6675                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6676                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6677                 }
6678
6679                 /* Buffer overflow protection */
6680                 if (buf1[0] != NULLCHAR) {
6681                     if (strlen(buf1) >= sizeof(programStats.movelist)
6682                         && appData.debugMode) {
6683                         fprintf(debugFP,
6684                                 "PV is too long; using the first %d bytes.\n",
6685                                 sizeof(programStats.movelist) - 1);
6686                     }
6687
6688                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6689                 } else {
6690                     sprintf(programStats.movelist, " no PV\n");
6691                 }
6692
6693                 if (programStats.seen_stat) {
6694                     programStats.ok_to_send = 1;
6695                 }
6696
6697                 if (strchr(programStats.movelist, '(') != NULL) {
6698                     programStats.line_is_book = 1;
6699                     programStats.nr_moves = 0;
6700                     programStats.moves_left = 0;
6701                 } else {
6702                     programStats.line_is_book = 0;
6703                 }
6704
6705                 SendProgramStatsToFrontend( cps, &programStats );
6706
6707                 /*
6708                     [AS] Protect the thinkOutput buffer from overflow... this
6709                     is only useful if buf1 hasn't overflowed first!
6710                 */
6711                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6712                         plylev,
6713                         (gameMode == TwoMachinesPlay ?
6714                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6715                         ((double) curscore) / 100.0,
6716                         prefixHint ? lastHint : "",
6717                         prefixHint ? " " : "" );
6718
6719                 if( buf1[0] != NULLCHAR ) {
6720                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6721
6722                     if( strlen(buf1) > max_len ) {
6723                         if( appData.debugMode) {
6724                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6725                         }
6726                         buf1[max_len+1] = '\0';
6727                     }
6728
6729                     strcat( thinkOutput, buf1 );
6730                 }
6731
6732                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6733                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6734                     DisplayMove(currentMove - 1);
6735                     DisplayAnalysis();
6736                 }
6737                 return;
6738
6739             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6740                 /* crafty (9.25+) says "(only move) <move>"
6741                  * if there is only 1 legal move
6742                  */
6743                 sscanf(p, "(only move) %s", buf1);
6744                 sprintf(thinkOutput, "%s (only move)", buf1);
6745                 sprintf(programStats.movelist, "%s (only move)", buf1);
6746                 programStats.depth = 1;
6747                 programStats.nr_moves = 1;
6748                 programStats.moves_left = 1;
6749                 programStats.nodes = 1;
6750                 programStats.time = 1;
6751                 programStats.got_only_move = 1;
6752
6753                 /* Not really, but we also use this member to
6754                    mean "line isn't going to change" (Crafty
6755                    isn't searching, so stats won't change) */
6756                 programStats.line_is_book = 1;
6757
6758                 SendProgramStatsToFrontend( cps, &programStats );
6759
6760                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6761                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6762                     DisplayMove(currentMove - 1);
6763                     DisplayAnalysis();
6764                 }
6765                 return;
6766             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6767                               &time, &nodes, &plylev, &mvleft,
6768                               &mvtot, mvname) >= 5) {
6769                 /* The stat01: line is from Crafty (9.29+) in response
6770                    to the "." command */
6771                 programStats.seen_stat = 1;
6772                 cps->maybeThinking = TRUE;
6773
6774                 if (programStats.got_only_move || !appData.periodicUpdates)
6775                   return;
6776
6777                 programStats.depth = plylev;
6778                 programStats.time = time;
6779                 programStats.nodes = nodes;
6780                 programStats.moves_left = mvleft;
6781                 programStats.nr_moves = mvtot;
6782                 strcpy(programStats.move_name, mvname);
6783                 programStats.ok_to_send = 1;
6784                 programStats.movelist[0] = '\0';
6785
6786                 SendProgramStatsToFrontend( cps, &programStats );
6787
6788                 DisplayAnalysis();
6789                 return;
6790
6791             } else if (strncmp(message,"++",2) == 0) {
6792                 /* Crafty 9.29+ outputs this */
6793                 programStats.got_fail = 2;
6794                 return;
6795
6796             } else if (strncmp(message,"--",2) == 0) {
6797                 /* Crafty 9.29+ outputs this */
6798                 programStats.got_fail = 1;
6799                 return;
6800
6801             } else if (thinkOutput[0] != NULLCHAR &&
6802                        strncmp(message, "    ", 4) == 0) {
6803                 unsigned message_len;
6804
6805                 p = message;
6806                 while (*p && *p == ' ') p++;
6807
6808                 message_len = strlen( p );
6809
6810                 /* [AS] Avoid buffer overflow */
6811                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6812                     strcat(thinkOutput, " ");
6813                     strcat(thinkOutput, p);
6814                 }
6815
6816                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6817                     strcat(programStats.movelist, " ");
6818                     strcat(programStats.movelist, p);
6819                 }
6820
6821                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6822                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6823                     DisplayMove(currentMove - 1);
6824                     DisplayAnalysis();
6825                 }
6826                 return;
6827             }
6828         }
6829         else {
6830             buf1[0] = NULLCHAR;
6831
6832             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6833                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6834             {
6835                 ChessProgramStats cpstats;
6836
6837                 if (plyext != ' ' && plyext != '\t') {
6838                     time *= 100;
6839                 }
6840
6841                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6842                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6843                     curscore = -curscore;
6844                 }
6845
6846                 cpstats.depth = plylev;
6847                 cpstats.nodes = nodes;
6848                 cpstats.time = time;
6849                 cpstats.score = curscore;
6850                 cpstats.got_only_move = 0;
6851                 cpstats.movelist[0] = '\0';
6852
6853                 if (buf1[0] != NULLCHAR) {
6854                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6855                 }
6856
6857                 cpstats.ok_to_send = 0;
6858                 cpstats.line_is_book = 0;
6859                 cpstats.nr_moves = 0;
6860                 cpstats.moves_left = 0;
6861
6862                 SendProgramStatsToFrontend( cps, &cpstats );
6863             }
6864         }
6865     }
6866 }
6867
6868
6869 /* Parse a game score from the character string "game", and
6870    record it as the history of the current game.  The game
6871    score is NOT assumed to start from the standard position.
6872    The display is not updated in any way.
6873    */
6874 void
6875 ParseGameHistory(game)
6876      char *game;
6877 {
6878     ChessMove moveType;
6879     int fromX, fromY, toX, toY, boardIndex;
6880     char promoChar;
6881     char *p, *q;
6882     char buf[MSG_SIZ];
6883
6884     if (appData.debugMode)
6885       fprintf(debugFP, "Parsing game history: %s\n", game);
6886
6887     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6888     gameInfo.site = StrSave(appData.icsHost);
6889     gameInfo.date = PGNDate();
6890     gameInfo.round = StrSave("-");
6891
6892     /* Parse out names of players */
6893     while (*game == ' ') game++;
6894     p = buf;
6895     while (*game != ' ') *p++ = *game++;
6896     *p = NULLCHAR;
6897     gameInfo.white = StrSave(buf);
6898     while (*game == ' ') game++;
6899     p = buf;
6900     while (*game != ' ' && *game != '\n') *p++ = *game++;
6901     *p = NULLCHAR;
6902     gameInfo.black = StrSave(buf);
6903
6904     /* Parse moves */
6905     boardIndex = blackPlaysFirst ? 1 : 0;
6906     yynewstr(game);
6907     for (;;) {
6908         yyboardindex = boardIndex;
6909         moveType = (ChessMove) yylex();
6910         switch (moveType) {
6911           case IllegalMove:             /* maybe suicide chess, etc. */
6912   if (appData.debugMode) {
6913     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6914     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6915     setbuf(debugFP, NULL);
6916   }
6917           case WhitePromotionChancellor:
6918           case BlackPromotionChancellor:
6919           case WhitePromotionArchbishop:
6920           case BlackPromotionArchbishop:
6921           case WhitePromotionQueen:
6922           case BlackPromotionQueen:
6923           case WhitePromotionRook:
6924           case BlackPromotionRook:
6925           case WhitePromotionBishop:
6926           case BlackPromotionBishop:
6927           case WhitePromotionKnight:
6928           case BlackPromotionKnight:
6929           case WhitePromotionKing:
6930           case BlackPromotionKing:
6931           case NormalMove:
6932           case WhiteCapturesEnPassant:
6933           case BlackCapturesEnPassant:
6934           case WhiteKingSideCastle:
6935           case WhiteQueenSideCastle:
6936           case BlackKingSideCastle:
6937           case BlackQueenSideCastle:
6938           case WhiteKingSideCastleWild:
6939           case WhiteQueenSideCastleWild:
6940           case BlackKingSideCastleWild:
6941           case BlackQueenSideCastleWild:
6942           /* PUSH Fabien */
6943           case WhiteHSideCastleFR:
6944           case WhiteASideCastleFR:
6945           case BlackHSideCastleFR:
6946           case BlackASideCastleFR:
6947           /* POP Fabien */
6948             fromX = currentMoveString[0] - AAA;
6949             fromY = currentMoveString[1] - ONE;
6950             toX = currentMoveString[2] - AAA;
6951             toY = currentMoveString[3] - ONE;
6952             promoChar = currentMoveString[4];
6953             break;
6954           case WhiteDrop:
6955           case BlackDrop:
6956             fromX = moveType == WhiteDrop ?
6957               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6958             (int) CharToPiece(ToLower(currentMoveString[0]));
6959             fromY = DROP_RANK;
6960             toX = currentMoveString[2] - AAA;
6961             toY = currentMoveString[3] - ONE;
6962             promoChar = NULLCHAR;
6963             break;
6964           case AmbiguousMove:
6965             /* bug? */
6966             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6967   if (appData.debugMode) {
6968     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6969     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6970     setbuf(debugFP, NULL);
6971   }
6972             DisplayError(buf, 0);
6973             return;
6974           case ImpossibleMove:
6975             /* bug? */
6976             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6977   if (appData.debugMode) {
6978     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6979     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6980     setbuf(debugFP, NULL);
6981   }
6982             DisplayError(buf, 0);
6983             return;
6984           case (ChessMove) 0:   /* end of file */
6985             if (boardIndex < backwardMostMove) {
6986                 /* Oops, gap.  How did that happen? */
6987                 DisplayError(_("Gap in move list"), 0);
6988                 return;
6989             }
6990             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6991             if (boardIndex > forwardMostMove) {
6992                 forwardMostMove = boardIndex;
6993             }
6994             return;
6995           case ElapsedTime:
6996             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6997                 strcat(parseList[boardIndex-1], " ");
6998                 strcat(parseList[boardIndex-1], yy_text);
6999             }
7000             continue;
7001           case Comment:
7002           case PGNTag:
7003           case NAG:
7004           default:
7005             /* ignore */
7006             continue;
7007           case WhiteWins:
7008           case BlackWins:
7009           case GameIsDrawn:
7010           case GameUnfinished:
7011             if (gameMode == IcsExamining) {
7012                 if (boardIndex < backwardMostMove) {
7013                     /* Oops, gap.  How did that happen? */
7014                     return;
7015                 }
7016                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7017                 return;
7018             }
7019             gameInfo.result = moveType;
7020             p = strchr(yy_text, '{');
7021             if (p == NULL) p = strchr(yy_text, '(');
7022             if (p == NULL) {
7023                 p = yy_text;
7024                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7025             } else {
7026                 q = strchr(p, *p == '{' ? '}' : ')');
7027                 if (q != NULL) *q = NULLCHAR;
7028                 p++;
7029             }
7030             gameInfo.resultDetails = StrSave(p);
7031             continue;
7032         }
7033         if (boardIndex >= forwardMostMove &&
7034             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7035             backwardMostMove = blackPlaysFirst ? 1 : 0;
7036             return;
7037         }
7038         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7039                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7040                                  parseList[boardIndex]);
7041         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7042         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7043         /* currentMoveString is set as a side-effect of yylex */
7044         strcpy(moveList[boardIndex], currentMoveString);
7045         strcat(moveList[boardIndex], "\n");
7046         boardIndex++;
7047         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7048                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7049         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7050                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7051           case MT_NONE:
7052           case MT_STALEMATE:
7053           default:
7054             break;
7055           case MT_CHECK:
7056             if(gameInfo.variant != VariantShogi)
7057                 strcat(parseList[boardIndex - 1], "+");
7058             break;
7059           case MT_CHECKMATE:
7060           case MT_STAINMATE:
7061             strcat(parseList[boardIndex - 1], "#");
7062             break;
7063         }
7064     }
7065 }
7066
7067
7068 /* Apply a move to the given board  */
7069 void
7070 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7071      int fromX, fromY, toX, toY;
7072      int promoChar;
7073      Board board;
7074      char *castling;
7075      char *ep;
7076 {
7077   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7078
7079     /* [HGM] compute & store e.p. status and castling rights for new position */
7080     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7081     { int i;
7082
7083       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7084       oldEP = *ep;
7085       *ep = EP_NONE;
7086
7087       if( board[toY][toX] != EmptySquare )
7088            *ep = EP_CAPTURE;
7089
7090       if( board[fromY][fromX] == WhitePawn ) {
7091            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7092                *ep = EP_PAWN_MOVE;
7093            if( toY-fromY==2) {
7094                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7095                         gameInfo.variant != VariantBerolina || toX < fromX)
7096                       *ep = toX | berolina;
7097                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7098                         gameInfo.variant != VariantBerolina || toX > fromX)
7099                       *ep = toX;
7100            }
7101       } else
7102       if( board[fromY][fromX] == BlackPawn ) {
7103            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7104                *ep = EP_PAWN_MOVE;
7105            if( toY-fromY== -2) {
7106                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7107                         gameInfo.variant != VariantBerolina || toX < fromX)
7108                       *ep = toX | berolina;
7109                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7110                         gameInfo.variant != VariantBerolina || toX > fromX)
7111                       *ep = toX;
7112            }
7113        }
7114
7115        for(i=0; i<nrCastlingRights; i++) {
7116            if(castling[i] == fromX && castlingRank[i] == fromY ||
7117               castling[i] == toX   && castlingRank[i] == toY
7118              ) castling[i] = -1; // revoke for moved or captured piece
7119        }
7120
7121     }
7122
7123   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7124   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7125        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7126
7127   if (fromX == toX && fromY == toY) return;
7128
7129   if (fromY == DROP_RANK) {
7130         /* must be first */
7131         piece = board[toY][toX] = (ChessSquare) fromX;
7132   } else {
7133      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7134      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7135      if(gameInfo.variant == VariantKnightmate)
7136          king += (int) WhiteUnicorn - (int) WhiteKing;
7137
7138     /* Code added by Tord: */
7139     /* FRC castling assumed when king captures friendly rook. */
7140     if (board[fromY][fromX] == WhiteKing &&
7141              board[toY][toX] == WhiteRook) {
7142       board[fromY][fromX] = EmptySquare;
7143       board[toY][toX] = EmptySquare;
7144       if(toX > fromX) {
7145         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7146       } else {
7147         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7148       }
7149     } else if (board[fromY][fromX] == BlackKing &&
7150                board[toY][toX] == BlackRook) {
7151       board[fromY][fromX] = EmptySquare;
7152       board[toY][toX] = EmptySquare;
7153       if(toX > fromX) {
7154         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7155       } else {
7156         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7157       }
7158     /* End of code added by Tord */
7159
7160     } else if (board[fromY][fromX] == king
7161         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7162         && toY == fromY && toX > fromX+1) {
7163         board[fromY][fromX] = EmptySquare;
7164         board[toY][toX] = king;
7165         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7166         board[fromY][BOARD_RGHT-1] = EmptySquare;
7167     } else if (board[fromY][fromX] == king
7168         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7169                && toY == fromY && toX < fromX-1) {
7170         board[fromY][fromX] = EmptySquare;
7171         board[toY][toX] = king;
7172         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7173         board[fromY][BOARD_LEFT] = EmptySquare;
7174     } else if (board[fromY][fromX] == WhitePawn
7175                && toY == BOARD_HEIGHT-1
7176                && gameInfo.variant != VariantXiangqi
7177                ) {
7178         /* white pawn promotion */
7179         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7180         if (board[toY][toX] == EmptySquare) {
7181             board[toY][toX] = WhiteQueen;
7182         }
7183         if(gameInfo.variant==VariantBughouse ||
7184            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7185             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7186         board[fromY][fromX] = EmptySquare;
7187     } else if ((fromY == BOARD_HEIGHT-4)
7188                && (toX != fromX)
7189                && gameInfo.variant != VariantXiangqi
7190                && gameInfo.variant != VariantBerolina
7191                && (board[fromY][fromX] == WhitePawn)
7192                && (board[toY][toX] == EmptySquare)) {
7193         board[fromY][fromX] = EmptySquare;
7194         board[toY][toX] = WhitePawn;
7195         captured = board[toY - 1][toX];
7196         board[toY - 1][toX] = EmptySquare;
7197     } else if ((fromY == BOARD_HEIGHT-4)
7198                && (toX == fromX)
7199                && gameInfo.variant == VariantBerolina
7200                && (board[fromY][fromX] == WhitePawn)
7201                && (board[toY][toX] == EmptySquare)) {
7202         board[fromY][fromX] = EmptySquare;
7203         board[toY][toX] = WhitePawn;
7204         if(oldEP & EP_BEROLIN_A) {
7205                 captured = board[fromY][fromX-1];
7206                 board[fromY][fromX-1] = EmptySquare;
7207         }else{  captured = board[fromY][fromX+1];
7208                 board[fromY][fromX+1] = EmptySquare;
7209         }
7210     } else if (board[fromY][fromX] == king
7211         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7212                && toY == fromY && toX > fromX+1) {
7213         board[fromY][fromX] = EmptySquare;
7214         board[toY][toX] = king;
7215         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7216         board[fromY][BOARD_RGHT-1] = EmptySquare;
7217     } else if (board[fromY][fromX] == king
7218         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7219                && toY == fromY && toX < fromX-1) {
7220         board[fromY][fromX] = EmptySquare;
7221         board[toY][toX] = king;
7222         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7223         board[fromY][BOARD_LEFT] = EmptySquare;
7224     } else if (fromY == 7 && fromX == 3
7225                && board[fromY][fromX] == BlackKing
7226                && toY == 7 && toX == 5) {
7227         board[fromY][fromX] = EmptySquare;
7228         board[toY][toX] = BlackKing;
7229         board[fromY][7] = EmptySquare;
7230         board[toY][4] = BlackRook;
7231     } else if (fromY == 7 && fromX == 3
7232                && board[fromY][fromX] == BlackKing
7233                && toY == 7 && toX == 1) {
7234         board[fromY][fromX] = EmptySquare;
7235         board[toY][toX] = BlackKing;
7236         board[fromY][0] = EmptySquare;
7237         board[toY][2] = BlackRook;
7238     } else if (board[fromY][fromX] == BlackPawn
7239                && toY == 0
7240                && gameInfo.variant != VariantXiangqi
7241                ) {
7242         /* black pawn promotion */
7243         board[0][toX] = CharToPiece(ToLower(promoChar));
7244         if (board[0][toX] == EmptySquare) {
7245             board[0][toX] = BlackQueen;
7246         }
7247         if(gameInfo.variant==VariantBughouse ||
7248            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7249             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7250         board[fromY][fromX] = EmptySquare;
7251     } else if ((fromY == 3)
7252                && (toX != fromX)
7253                && gameInfo.variant != VariantXiangqi
7254                && gameInfo.variant != VariantBerolina
7255                && (board[fromY][fromX] == BlackPawn)
7256                && (board[toY][toX] == EmptySquare)) {
7257         board[fromY][fromX] = EmptySquare;
7258         board[toY][toX] = BlackPawn;
7259         captured = board[toY + 1][toX];
7260         board[toY + 1][toX] = EmptySquare;
7261     } else if ((fromY == 3)
7262                && (toX == fromX)
7263                && gameInfo.variant == VariantBerolina
7264                && (board[fromY][fromX] == BlackPawn)
7265                && (board[toY][toX] == EmptySquare)) {
7266         board[fromY][fromX] = EmptySquare;
7267         board[toY][toX] = BlackPawn;
7268         if(oldEP & EP_BEROLIN_A) {
7269                 captured = board[fromY][fromX-1];
7270                 board[fromY][fromX-1] = EmptySquare;
7271         }else{  captured = board[fromY][fromX+1];
7272                 board[fromY][fromX+1] = EmptySquare;
7273         }
7274     } else {
7275         board[toY][toX] = board[fromY][fromX];
7276         board[fromY][fromX] = EmptySquare;
7277     }
7278
7279     /* [HGM] now we promote for Shogi, if needed */
7280     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7281         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7282   }
7283
7284     if (gameInfo.holdingsWidth != 0) {
7285
7286       /* !!A lot more code needs to be written to support holdings  */
7287       /* [HGM] OK, so I have written it. Holdings are stored in the */
7288       /* penultimate board files, so they are automaticlly stored   */
7289       /* in the game history.                                       */
7290       if (fromY == DROP_RANK) {
7291         /* Delete from holdings, by decreasing count */
7292         /* and erasing image if necessary            */
7293         p = (int) fromX;
7294         if(p < (int) BlackPawn) { /* white drop */
7295              p -= (int)WhitePawn;
7296              if(p >= gameInfo.holdingsSize) p = 0;
7297              if(--board[p][BOARD_WIDTH-2] == 0)
7298                   board[p][BOARD_WIDTH-1] = EmptySquare;
7299         } else {                  /* black drop */
7300              p -= (int)BlackPawn;
7301              if(p >= gameInfo.holdingsSize) p = 0;
7302              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7303                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7304         }
7305       }
7306       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7307           && gameInfo.variant != VariantBughouse        ) {
7308         /* [HGM] holdings: Add to holdings, if holdings exist */
7309         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7310                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7311                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7312         }
7313         p = (int) captured;
7314         if (p >= (int) BlackPawn) {
7315           p -= (int)BlackPawn;
7316           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7317                   /* in Shogi restore piece to its original  first */
7318                   captured = (ChessSquare) (DEMOTED captured);
7319                   p = DEMOTED p;
7320           }
7321           p = PieceToNumber((ChessSquare)p);
7322           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7323           board[p][BOARD_WIDTH-2]++;
7324           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7325         } else {
7326           p -= (int)WhitePawn;
7327           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7328                   captured = (ChessSquare) (DEMOTED captured);
7329                   p = DEMOTED p;
7330           }
7331           p = PieceToNumber((ChessSquare)p);
7332           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7333           board[BOARD_HEIGHT-1-p][1]++;
7334           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7335         }
7336       }
7337
7338     } else if (gameInfo.variant == VariantAtomic) {
7339       if (captured != EmptySquare) {
7340         int y, x;
7341         for (y = toY-1; y <= toY+1; y++) {
7342           for (x = toX-1; x <= toX+1; x++) {
7343             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7344                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7345               board[y][x] = EmptySquare;
7346             }
7347           }
7348         }
7349         board[toY][toX] = EmptySquare;
7350       }
7351     }
7352     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7353         /* [HGM] Shogi promotions */
7354         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7355     }
7356
7357     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7358                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7359         // [HGM] superchess: take promotion piece out of holdings
7360         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7361         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7362             if(!--board[k][BOARD_WIDTH-2])
7363                 board[k][BOARD_WIDTH-1] = EmptySquare;
7364         } else {
7365             if(!--board[BOARD_HEIGHT-1-k][1])
7366                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7367         }
7368     }
7369
7370 }
7371
7372 /* Updates forwardMostMove */
7373 void
7374 MakeMove(fromX, fromY, toX, toY, promoChar)
7375      int fromX, fromY, toX, toY;
7376      int promoChar;
7377 {
7378 //    forwardMostMove++; // [HGM] bare: moved downstream
7379
7380     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7381         int timeLeft; static int lastLoadFlag=0; int king, piece;
7382         piece = boards[forwardMostMove][fromY][fromX];
7383         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7384         if(gameInfo.variant == VariantKnightmate)
7385             king += (int) WhiteUnicorn - (int) WhiteKing;
7386         if(forwardMostMove == 0) {
7387             if(blackPlaysFirst)
7388                 fprintf(serverMoves, "%s;", second.tidy);
7389             fprintf(serverMoves, "%s;", first.tidy);
7390             if(!blackPlaysFirst)
7391                 fprintf(serverMoves, "%s;", second.tidy);
7392         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7393         lastLoadFlag = loadFlag;
7394         // print base move
7395         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7396         // print castling suffix
7397         if( toY == fromY && piece == king ) {
7398             if(toX-fromX > 1)
7399                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7400             if(fromX-toX >1)
7401                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7402         }
7403         // e.p. suffix
7404         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7405              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7406              boards[forwardMostMove][toY][toX] == EmptySquare
7407              && fromX != toX )
7408                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7409         // promotion suffix
7410         if(promoChar != NULLCHAR)
7411                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7412         if(!loadFlag) {
7413             fprintf(serverMoves, "/%d/%d",
7414                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7415             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7416             else                      timeLeft = blackTimeRemaining/1000;
7417             fprintf(serverMoves, "/%d", timeLeft);
7418         }
7419         fflush(serverMoves);
7420     }
7421
7422     if (forwardMostMove+1 >= MAX_MOVES) {
7423       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7424                         0, 1);
7425       return;
7426     }
7427     SwitchClocks();
7428     timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7429     timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7430     if (commentList[forwardMostMove+1] != NULL) {
7431         free(commentList[forwardMostMove+1]);
7432         commentList[forwardMostMove+1] = NULL;
7433     }
7434     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7435     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7436     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7437                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7438     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7439     gameInfo.result = GameUnfinished;
7440     if (gameInfo.resultDetails != NULL) {
7441         free(gameInfo.resultDetails);
7442         gameInfo.resultDetails = NULL;
7443     }
7444     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7445                               moveList[forwardMostMove - 1]);
7446     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7447                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7448                              fromY, fromX, toY, toX, promoChar,
7449                              parseList[forwardMostMove - 1]);
7450     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7451                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7452                             castlingRights[forwardMostMove]) ) {
7453       case MT_NONE:
7454       case MT_STALEMATE:
7455       default:
7456         break;
7457       case MT_CHECK:
7458         if(gameInfo.variant != VariantShogi)
7459             strcat(parseList[forwardMostMove - 1], "+");
7460         break;
7461       case MT_CHECKMATE:
7462       case MT_STAINMATE:
7463         strcat(parseList[forwardMostMove - 1], "#");
7464         break;
7465     }
7466     if (appData.debugMode) {
7467         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7468     }
7469
7470 }
7471
7472 /* Updates currentMove if not pausing */
7473 void
7474 ShowMove(fromX, fromY, toX, toY)
7475 {
7476     int instant = (gameMode == PlayFromGameFile) ?
7477         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7478
7479     if(appData.noGUI) return;
7480
7481     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7482       {
7483         if (!instant)
7484           {
7485             if (forwardMostMove == currentMove + 1)
7486               {
7487 //TODO
7488 //              AnimateMove(boards[forwardMostMove - 1],
7489 //                          fromX, fromY, toX, toY);
7490               }
7491             if (appData.highlightLastMove)
7492               {
7493                 SetHighlights(fromX, fromY, toX, toY);
7494               }
7495           }
7496         currentMove = forwardMostMove;
7497     }
7498
7499     if (instant) return;
7500
7501     DisplayMove(currentMove - 1);
7502     DrawPosition(FALSE, boards[currentMove]);
7503     DisplayBothClocks();
7504     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7505
7506     return;
7507 }
7508
7509 void SendEgtPath(ChessProgramState *cps)
7510 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7511         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7512
7513         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7514
7515         while(*p) {
7516             char c, *q = name+1, *r, *s;
7517
7518             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7519             while(*p && *p != ',') *q++ = *p++;
7520             *q++ = ':'; *q = 0;
7521             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7522                 strcmp(name, ",nalimov:") == 0 ) {
7523                 // take nalimov path from the menu-changeable option first, if it is defined
7524                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7525                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7526             } else
7527             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7528                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7529                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7530                 s = r = StrStr(s, ":") + 1; // beginning of path info
7531                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7532                 c = *r; *r = 0;             // temporarily null-terminate path info
7533                     *--q = 0;               // strip of trailig ':' from name
7534                     sprintf(buf, "egtbpath %s %s\n", name+1, s);
7535                 *r = c;
7536                 SendToProgram(buf,cps);     // send egtbpath command for this format
7537             }
7538             if(*p == ',') p++; // read away comma to position for next format name
7539         }
7540 }
7541
7542 void
7543 InitChessProgram(cps, setup)
7544      ChessProgramState *cps;
7545      int setup; /* [HGM] needed to setup FRC opening position */
7546 {
7547     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7548     if (appData.noChessProgram) return;
7549     hintRequested = FALSE;
7550     bookRequested = FALSE;
7551
7552     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7553     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7554     if(cps->memSize) { /* [HGM] memory */
7555         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7556         SendToProgram(buf, cps);
7557     }
7558     SendEgtPath(cps); /* [HGM] EGT */
7559     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7560         sprintf(buf, "cores %d\n", appData.smpCores);
7561         SendToProgram(buf, cps);
7562     }
7563
7564     SendToProgram(cps->initString, cps);
7565     if (gameInfo.variant != VariantNormal &&
7566         gameInfo.variant != VariantLoadable
7567         /* [HGM] also send variant if board size non-standard */
7568         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7569                                             ) {
7570       char *v = VariantName(gameInfo.variant);
7571       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7572         /* [HGM] in protocol 1 we have to assume all variants valid */
7573         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7574         DisplayFatalError(buf, 0, 1);
7575         return;
7576       }
7577
7578       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7579       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7580       if( gameInfo.variant == VariantXiangqi )
7581            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7582       if( gameInfo.variant == VariantShogi )
7583            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7584       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7585            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7586       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7587                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7588            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7589       if( gameInfo.variant == VariantCourier )
7590            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7591       if( gameInfo.variant == VariantSuper )
7592            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7593       if( gameInfo.variant == VariantGreat )
7594            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7595
7596       if(overruled) {
7597            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7598                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7599            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7600            if(StrStr(cps->variants, b) == NULL) {
7601                // specific sized variant not known, check if general sizing allowed
7602                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7603                    if(StrStr(cps->variants, "boardsize") == NULL) {
7604                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7605                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7606                        DisplayFatalError(buf, 0, 1);
7607                        return;
7608                    }
7609                    /* [HGM] here we really should compare with the maximum supported board size */
7610                }
7611            }
7612       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7613       sprintf(buf, "variant %s\n", b);
7614       SendToProgram(buf, cps);
7615     }
7616     currentlyInitializedVariant = gameInfo.variant;
7617
7618     /* [HGM] send opening position in FRC to first engine */
7619     if(setup) {
7620           SendToProgram("force\n", cps);
7621           SendBoard(cps, 0);
7622           /* engine is now in force mode! Set flag to wake it up after first move. */
7623           setboardSpoiledMachineBlack = 1;
7624     }
7625
7626     if (cps->sendICS) {
7627       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7628       SendToProgram(buf, cps);
7629     }
7630     cps->maybeThinking = FALSE;
7631     cps->offeredDraw = 0;
7632     if (!appData.icsActive) {
7633         SendTimeControl(cps, movesPerSession, timeControl,
7634                         timeIncrement, appData.searchDepth,
7635                         searchTime);
7636     }
7637     if (appData.showThinking
7638         // [HGM] thinking: four options require thinking output to be sent
7639         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7640                                 ) {
7641         SendToProgram("post\n", cps);
7642     }
7643     SendToProgram("hard\n", cps);
7644     if (!appData.ponderNextMove) {
7645         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7646            it without being sure what state we are in first.  "hard"
7647            is not a toggle, so that one is OK.
7648          */
7649         SendToProgram("easy\n", cps);
7650     }
7651     if (cps->usePing) {
7652       sprintf(buf, "ping %d\n", ++cps->lastPing);
7653       SendToProgram(buf, cps);
7654     }
7655     cps->initDone = TRUE;
7656 }
7657
7658
7659 void
7660 StartChessProgram(cps)
7661      ChessProgramState *cps;
7662 {
7663     char buf[MSG_SIZ];
7664     int err;
7665
7666     if (appData.noChessProgram) return;
7667     cps->initDone = FALSE;
7668
7669     if (strcmp(cps->host, "localhost") == 0) {
7670         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7671     } else if (*appData.remoteShell == NULLCHAR) {
7672         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7673     } else {
7674         if (*appData.remoteUser == NULLCHAR) {
7675           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7676                     cps->program);
7677         } else {
7678           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7679                     cps->host, appData.remoteUser, cps->program);
7680         }
7681         err = StartChildProcess(buf, "", &cps->pr);
7682     }
7683
7684     if (err != 0) {
7685         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7686         DisplayFatalError(buf, err, 1);
7687         cps->pr = NoProc;
7688         cps->isr = NULL;
7689         return;
7690     }
7691
7692     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7693     if (cps->protocolVersion > 1) {
7694       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7695       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7696       cps->comboCnt = 0;  //                and values of combo boxes
7697       SendToProgram(buf, cps);
7698     } else {
7699       SendToProgram("xboard\n", cps);
7700     }
7701 }
7702
7703
7704 void
7705 TwoMachinesEventIfReady P((void))
7706 {
7707   if (first.lastPing != first.lastPong) {
7708     DisplayMessage("", _("Waiting for first chess program"));
7709     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7710     return;
7711   }
7712   if (second.lastPing != second.lastPong) {
7713     DisplayMessage("", _("Waiting for second chess program"));
7714     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7715     return;
7716   }
7717   ThawUI();
7718   TwoMachinesEvent();
7719 }
7720
7721 void
7722 NextMatchGame P((void))
7723 {
7724     int index; /* [HGM] autoinc: step lod index during match */
7725     Reset(FALSE, TRUE);
7726     if (*appData.loadGameFile != NULLCHAR) {
7727         index = appData.loadGameIndex;
7728         if(index < 0) { // [HGM] autoinc
7729             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7730             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7731         }
7732         LoadGameFromFile(appData.loadGameFile,
7733                          index,
7734                          appData.loadGameFile, FALSE);
7735     } else if (*appData.loadPositionFile != NULLCHAR) {
7736         index = appData.loadPositionIndex;
7737         if(index < 0) { // [HGM] autoinc
7738             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7739             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7740         }
7741         LoadPositionFromFile(appData.loadPositionFile,
7742                              index,
7743                              appData.loadPositionFile);
7744     }
7745     TwoMachinesEventIfReady();
7746 }
7747
7748 void UserAdjudicationEvent( int result )
7749 {
7750     ChessMove gameResult = GameIsDrawn;
7751
7752     if( result > 0 ) {
7753         gameResult = WhiteWins;
7754     }
7755     else if( result < 0 ) {
7756         gameResult = BlackWins;
7757     }
7758
7759     if( gameMode == TwoMachinesPlay ) {
7760         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7761     }
7762 }
7763
7764
7765 void
7766 GameEnds(result, resultDetails, whosays)
7767      ChessMove result;
7768      char *resultDetails;
7769      int whosays;
7770 {
7771     GameMode nextGameMode;
7772     int isIcsGame;
7773     char buf[MSG_SIZ];
7774
7775     if(endingGame) return; /* [HGM] crash: forbid recursion */
7776     endingGame = 1;
7777
7778     if (appData.debugMode) {
7779       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7780               result, resultDetails ? resultDetails : "(null)", whosays);
7781     }
7782
7783     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7784         /* If we are playing on ICS, the server decides when the
7785            game is over, but the engine can offer to draw, claim
7786            a draw, or resign.
7787          */
7788 #if ZIPPY
7789         if (appData.zippyPlay && first.initDone) {
7790             if (result == GameIsDrawn) {
7791                 /* In case draw still needs to be claimed */
7792                 SendToICS(ics_prefix);
7793                 SendToICS("draw\n");
7794             } else if (StrCaseStr(resultDetails, "resign")) {
7795                 SendToICS(ics_prefix);
7796                 SendToICS("resign\n");
7797             }
7798         }
7799 #endif
7800         endingGame = 0; /* [HGM] crash */
7801         return;
7802     }
7803
7804     /* If we're loading the game from a file, stop */
7805     if (whosays == GE_FILE) {
7806       (void) StopLoadGameTimer();
7807       gameFileFP = NULL;
7808     }
7809
7810     /* Cancel draw offers */
7811     first.offeredDraw = second.offeredDraw = 0;
7812
7813     /* If this is an ICS game, only ICS can really say it's done;
7814        if not, anyone can. */
7815     isIcsGame = (gameMode == IcsPlayingWhite ||
7816                  gameMode == IcsPlayingBlack ||
7817                  gameMode == IcsObserving    ||
7818                  gameMode == IcsExamining);
7819
7820     if (!isIcsGame || whosays == GE_ICS) {
7821         /* OK -- not an ICS game, or ICS said it was done */
7822         StopClocks();
7823         if (!isIcsGame && !appData.noChessProgram)
7824           SetUserThinkingEnables();
7825
7826         /* [HGM] if a machine claims the game end we verify this claim */
7827         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7828             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7829                 char claimer;
7830                 ChessMove trueResult = (ChessMove) -1;
7831
7832                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7833                                             first.twoMachinesColor[0] :
7834                                             second.twoMachinesColor[0] ;
7835
7836                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7837                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7838                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7839                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7840                 } else
7841                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7842                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7843                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7844                 } else
7845                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7846                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7847                 }
7848
7849                 // now verify win claims, but not in drop games, as we don't understand those yet
7850                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7851                                                  || gameInfo.variant == VariantGreat) &&
7852                     (result == WhiteWins && claimer == 'w' ||
7853                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7854                       if (appData.debugMode) {
7855                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7856                                 result, epStatus[forwardMostMove], forwardMostMove);
7857                       }
7858                       if(result != trueResult) {
7859                               sprintf(buf, "False win claim: '%s'", resultDetails);
7860                               result = claimer == 'w' ? BlackWins : WhiteWins;
7861                               resultDetails = buf;
7862                       }
7863                 } else
7864                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7865                     && (forwardMostMove <= backwardMostMove ||
7866                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7867                         (claimer=='b')==(forwardMostMove&1))
7868                                                                                   ) {
7869                       /* [HGM] verify: draws that were not flagged are false claims */
7870                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7871                       result = claimer == 'w' ? BlackWins : WhiteWins;
7872                       resultDetails = buf;
7873                 }
7874                 /* (Claiming a loss is accepted no questions asked!) */
7875             }
7876
7877             /* [HGM] bare: don't allow bare King to win */
7878             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7879                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7880                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7881                && result != GameIsDrawn)
7882             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7883                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7884                         int p = (int)boards[forwardMostMove][i][j] - color;
7885                         if(p >= 0 && p <= (int)WhiteKing) k++;
7886                 }
7887                 if (appData.debugMode) {
7888                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7889                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7890                 }
7891                 if(k <= 1) {
7892                         result = GameIsDrawn;
7893                         sprintf(buf, "%s but bare king", resultDetails);
7894                         resultDetails = buf;
7895                 }
7896             }
7897         }
7898
7899         if(serverMoves != NULL && !loadFlag) { char c = '=';
7900             if(result==WhiteWins) c = '+';
7901             if(result==BlackWins) c = '-';
7902             if(resultDetails != NULL)
7903                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7904         }
7905         if (resultDetails != NULL) {
7906             gameInfo.result = result;
7907             gameInfo.resultDetails = StrSave(resultDetails);
7908
7909             /* display last move only if game was not loaded from file */
7910             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7911                 DisplayMove(currentMove - 1);
7912
7913             if (forwardMostMove != 0) {
7914                 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
7915                     if (*appData.saveGameFile != NULLCHAR) {
7916                         SaveGameToFile(appData.saveGameFile, TRUE);
7917                     } else if (appData.autoSaveGames) {
7918                         AutoSaveGame();
7919                     }
7920                     if (*appData.savePositionFile != NULLCHAR) {
7921                         SavePositionToFile(appData.savePositionFile);
7922                     }
7923                 }
7924             }
7925
7926             /* Tell program how game ended in case it is learning */
7927             /* [HGM] Moved this to after saving the PGN, just in case */
7928             /* engine died and we got here through time loss. In that */
7929             /* case we will get a fatal error writing the pipe, which */
7930             /* would otherwise lose us the PGN.                       */
7931             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7932             /* output during GameEnds should never be fatal anymore   */
7933             if (gameMode == MachinePlaysWhite ||
7934                 gameMode == MachinePlaysBlack ||
7935                 gameMode == TwoMachinesPlay ||
7936                 gameMode == IcsPlayingWhite ||
7937                 gameMode == IcsPlayingBlack ||
7938                 gameMode == BeginningOfGame) {
7939                 char buf[MSG_SIZ];
7940                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7941                         resultDetails);
7942                 if (first.pr != NoProc) {
7943                     SendToProgram(buf, &first);
7944                 }
7945                 if (second.pr != NoProc &&
7946                     gameMode == TwoMachinesPlay) {
7947                     SendToProgram(buf, &second);
7948                 }
7949             }
7950         }
7951
7952         if (appData.icsActive) {
7953             if (appData.quietPlay &&
7954                 (gameMode == IcsPlayingWhite ||
7955                  gameMode == IcsPlayingBlack)) {
7956                 SendToICS(ics_prefix);
7957                 SendToICS("set shout 1\n");
7958             }
7959             nextGameMode = IcsIdle;
7960             ics_user_moved = FALSE;
7961             /* clean up premove.  It's ugly when the game has ended and the
7962              * premove highlights are still on the board.
7963              */
7964             if (gotPremove) {
7965               gotPremove = FALSE;
7966               ClearPremoveHighlights();
7967               DrawPosition(FALSE, boards[currentMove]);
7968             }
7969             if (whosays == GE_ICS) {
7970                 switch (result) {
7971                 case WhiteWins:
7972                     if (gameMode == IcsPlayingWhite)
7973                         PlayIcsWinSound();
7974                     else if(gameMode == IcsPlayingBlack)
7975                         PlayIcsLossSound();
7976                     break;
7977                 case BlackWins:
7978                     if (gameMode == IcsPlayingBlack)
7979                         PlayIcsWinSound();
7980                     else if(gameMode == IcsPlayingWhite)
7981                         PlayIcsLossSound();
7982                     break;
7983                 case GameIsDrawn:
7984                     PlayIcsDrawSound();
7985                     break;
7986                 default:
7987                     PlayIcsUnfinishedSound();
7988                 }
7989             }
7990         } else if (gameMode == EditGame ||
7991                    gameMode == PlayFromGameFile ||
7992                    gameMode == AnalyzeMode ||
7993                    gameMode == AnalyzeFile) {
7994             nextGameMode = gameMode;
7995         } else {
7996             nextGameMode = EndOfGame;
7997         }
7998         pausing = FALSE;
7999         ModeHighlight();
8000     } else {
8001         nextGameMode = gameMode;
8002     }
8003
8004     if (appData.noChessProgram) {
8005         gameMode = nextGameMode;
8006         ModeHighlight();
8007         endingGame = 0; /* [HGM] crash */
8008         return;
8009     }
8010
8011     if (first.reuse) {
8012         /* Put first chess program into idle state */
8013         if (first.pr != NoProc &&
8014             (gameMode == MachinePlaysWhite ||
8015              gameMode == MachinePlaysBlack ||
8016              gameMode == TwoMachinesPlay ||
8017              gameMode == IcsPlayingWhite ||
8018              gameMode == IcsPlayingBlack ||
8019              gameMode == BeginningOfGame)) {
8020             SendToProgram("force\n", &first);
8021             if (first.usePing) {
8022               char buf[MSG_SIZ];
8023               sprintf(buf, "ping %d\n", ++first.lastPing);
8024               SendToProgram(buf, &first);
8025             }
8026         }
8027     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8028         /* Kill off first chess program */
8029         if (first.isr != NULL)
8030           RemoveInputSource(first.isr);
8031         first.isr = NULL;
8032
8033         if (first.pr != NoProc) {
8034             ExitAnalyzeMode();
8035             DoSleep( appData.delayBeforeQuit );
8036             SendToProgram("quit\n", &first);
8037             DoSleep( appData.delayAfterQuit );
8038             DestroyChildProcess(first.pr, first.useSigterm);
8039         }
8040         first.pr = NoProc;
8041     }
8042     if (second.reuse) {
8043         /* Put second chess program into idle state */
8044         if (second.pr != NoProc &&
8045             gameMode == TwoMachinesPlay) {
8046             SendToProgram("force\n", &second);
8047             if (second.usePing) {
8048               char buf[MSG_SIZ];
8049               sprintf(buf, "ping %d\n", ++second.lastPing);
8050               SendToProgram(buf, &second);
8051             }
8052         }
8053     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8054         /* Kill off second chess program */
8055         if (second.isr != NULL)
8056           RemoveInputSource(second.isr);
8057         second.isr = NULL;
8058
8059         if (second.pr != NoProc) {
8060             DoSleep( appData.delayBeforeQuit );
8061             SendToProgram("quit\n", &second);
8062             DoSleep( appData.delayAfterQuit );
8063             DestroyChildProcess(second.pr, second.useSigterm);
8064         }
8065         second.pr = NoProc;
8066     }
8067
8068     if (matchMode && gameMode == TwoMachinesPlay) {
8069         switch (result) {
8070         case WhiteWins:
8071           if (first.twoMachinesColor[0] == 'w') {
8072             first.matchWins++;
8073           } else {
8074             second.matchWins++;
8075           }
8076           break;
8077         case BlackWins:
8078           if (first.twoMachinesColor[0] == 'b') {
8079             first.matchWins++;
8080           } else {
8081             second.matchWins++;
8082           }
8083           break;
8084         default:
8085           break;
8086         }
8087         if (matchGame < appData.matchGames) {
8088             char *tmp;
8089             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8090                 tmp = first.twoMachinesColor;
8091                 first.twoMachinesColor = second.twoMachinesColor;
8092                 second.twoMachinesColor = tmp;
8093             }
8094             gameMode = nextGameMode;
8095             matchGame++;
8096             if(appData.matchPause>10000 || appData.matchPause<10)
8097                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8098             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8099             endingGame = 0; /* [HGM] crash */
8100             return;
8101         } else {
8102             char buf[MSG_SIZ];
8103             gameMode = nextGameMode;
8104             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8105                     first.tidy, second.tidy,
8106                     first.matchWins, second.matchWins,
8107                     appData.matchGames - (first.matchWins + second.matchWins));
8108             DisplayFatalError(buf, 0, 0);
8109         }
8110     }
8111     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8112         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8113       ExitAnalyzeMode();
8114     gameMode = nextGameMode;
8115     ModeHighlight();
8116     endingGame = 0;  /* [HGM] crash */
8117 }
8118
8119 /* Assumes program was just initialized (initString sent).
8120    Leaves program in force mode. */
8121 void
8122 FeedMovesToProgram(cps, upto)
8123      ChessProgramState *cps;
8124      int upto;
8125 {
8126     int i;
8127
8128     if (appData.debugMode)
8129       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8130               startedFromSetupPosition ? "position and " : "",
8131               backwardMostMove, upto, cps->which);
8132     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8133         // [HGM] variantswitch: make engine aware of new variant
8134         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8135                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8136         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8137         SendToProgram(buf, cps);
8138         currentlyInitializedVariant = gameInfo.variant;
8139     }
8140     SendToProgram("force\n", cps);
8141     if (startedFromSetupPosition) {
8142         SendBoard(cps, backwardMostMove);
8143     if (appData.debugMode) {
8144         fprintf(debugFP, "feedMoves\n");
8145     }
8146     }
8147     for (i = backwardMostMove; i < upto; i++) {
8148         SendMoveToProgram(i, cps);
8149     }
8150 }
8151
8152
8153 void
8154 ResurrectChessProgram()
8155 {
8156      /* The chess program may have exited.
8157         If so, restart it and feed it all the moves made so far. */
8158
8159     if (appData.noChessProgram || first.pr != NoProc) return;
8160
8161     StartChessProgram(&first);
8162     InitChessProgram(&first, FALSE);
8163     FeedMovesToProgram(&first, currentMove);
8164
8165     if (!first.sendTime) {
8166         /* can't tell gnuchess what its clock should read,
8167            so we bow to its notion. */
8168         ResetClocks();
8169         timeRemaining[0][currentMove] = whiteTimeRemaining;
8170         timeRemaining[1][currentMove] = blackTimeRemaining;
8171     }
8172
8173     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8174                 appData.icsEngineAnalyze) && first.analysisSupport) {
8175       SendToProgram("analyze\n", &first);
8176       first.analyzing = TRUE;
8177     }
8178 }
8179
8180 /*
8181  * Button procedures
8182  */
8183 void
8184 Reset(redraw, init)
8185      int redraw, init;
8186 {
8187     int i;
8188
8189     if (appData.debugMode) {
8190         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8191                 redraw, init, gameMode);
8192     }
8193     pausing = pauseExamInvalid = FALSE;
8194     startedFromSetupPosition = blackPlaysFirst = FALSE;
8195     firstMove = TRUE;
8196     whiteFlag = blackFlag = FALSE;
8197     userOfferedDraw = FALSE;
8198     hintRequested = bookRequested = FALSE;
8199     first.maybeThinking = FALSE;
8200     second.maybeThinking = FALSE;
8201     first.bookSuspend = FALSE; // [HGM] book
8202     second.bookSuspend = FALSE;
8203     thinkOutput[0] = NULLCHAR;
8204     lastHint[0] = NULLCHAR;
8205     ClearGameInfo(&gameInfo);
8206     gameInfo.variant = StringToVariant(appData.variant);
8207     ics_user_moved = ics_clock_paused = FALSE;
8208     ics_getting_history = H_FALSE;
8209     ics_gamenum = -1;
8210     white_holding[0] = black_holding[0] = NULLCHAR;
8211     ClearProgramStats();
8212     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8213
8214     ResetFrontEnd();
8215     ClearHighlights();
8216     flipView = appData.flipView;
8217     ClearPremoveHighlights();
8218     gotPremove = FALSE;
8219     alarmSounded = FALSE;
8220
8221     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8222     if(appData.serverMovesName != NULL) {
8223         /* [HGM] prepare to make moves file for broadcasting */
8224         clock_t t = clock();
8225         if(serverMoves != NULL) fclose(serverMoves);
8226         serverMoves = fopen(appData.serverMovesName, "r");
8227         if(serverMoves != NULL) {
8228             fclose(serverMoves);
8229             /* delay 15 sec before overwriting, so all clients can see end */
8230             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8231         }
8232         serverMoves = fopen(appData.serverMovesName, "w");
8233     }
8234
8235     ExitAnalyzeMode();
8236     gameMode = BeginningOfGame;
8237     ModeHighlight();
8238
8239     if(appData.icsActive) gameInfo.variant = VariantNormal;
8240     InitPosition(redraw);
8241     for (i = 0; i < MAX_MOVES; i++) {
8242         if (commentList[i] != NULL) {
8243             free(commentList[i]);
8244             commentList[i] = NULL;
8245         }
8246     }
8247
8248     ResetClocks();
8249     timeRemaining[0][0] = whiteTimeRemaining;
8250     timeRemaining[1][0] = blackTimeRemaining;
8251     if (first.pr == NULL) {
8252         StartChessProgram(&first);
8253     }
8254     if (init) {
8255             InitChessProgram(&first, startedFromSetupPosition);
8256     }
8257
8258     GUI_DisplayTitle("");
8259     DisplayMessage("", "");
8260     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8261
8262 }
8263
8264 void
8265 AutoPlayGameLoop()
8266 {
8267     for (;;) {
8268         if (!AutoPlayOneMove())
8269           return;
8270         if (matchMode || appData.timeDelay == 0)
8271           continue;
8272         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8273           return;
8274         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8275         break;
8276     }
8277 }
8278
8279
8280 int
8281 AutoPlayOneMove()
8282 {
8283     int fromX, fromY, toX, toY;
8284
8285     if (appData.debugMode) {
8286       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8287     }
8288
8289     if (gameMode != PlayFromGameFile)
8290       return FALSE;
8291
8292     if (currentMove >= forwardMostMove) {
8293       gameMode = EditGame;
8294       ModeHighlight();
8295
8296       /* [AS] Clear current move marker at the end of a game */
8297       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8298
8299       return FALSE;
8300     }
8301
8302     toX = moveList[currentMove][2] - AAA;
8303     toY = moveList[currentMove][3] - ONE;
8304
8305     if (moveList[currentMove][1] == '@') {
8306         if (appData.highlightLastMove) {
8307             SetHighlights(-1, -1, toX, toY);
8308         }
8309     } else {
8310         fromX = moveList[currentMove][0] - AAA;
8311         fromY = moveList[currentMove][1] - ONE;
8312
8313         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8314
8315         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8316
8317         if (appData.highlightLastMove) {
8318             SetHighlights(fromX, fromY, toX, toY);
8319         }
8320     }
8321     DisplayMove(currentMove);
8322     SendMoveToProgram(currentMove++, &first);
8323     DisplayBothClocks();
8324     DrawPosition(FALSE, boards[currentMove]);
8325     // [HGM] PV info: always display, routine tests if empty
8326     DisplayComment(currentMove - 1, commentList[currentMove]);
8327     return TRUE;
8328 }
8329
8330
8331 int
8332 LoadGameOneMove(readAhead)
8333      ChessMove readAhead;
8334 {
8335     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8336     char promoChar = NULLCHAR;
8337     ChessMove moveType;
8338     char move[MSG_SIZ];
8339     char *p, *q;
8340
8341     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8342         gameMode != AnalyzeMode && gameMode != Training) {
8343         gameFileFP = NULL;
8344         return FALSE;
8345     }
8346
8347     yyboardindex = forwardMostMove;
8348     if (readAhead != (ChessMove)0) {
8349       moveType = readAhead;
8350     } else {
8351       if (gameFileFP == NULL)
8352           return FALSE;
8353       moveType = (ChessMove) yylex();
8354     }
8355
8356     done = FALSE;
8357     switch (moveType) {
8358       case Comment:
8359         if (appData.debugMode)
8360           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8361         p = yy_text;
8362         if (*p == '{' || *p == '[' || *p == '(') {
8363             p[strlen(p) - 1] = NULLCHAR;
8364             p++;
8365         }
8366
8367         /* append the comment but don't display it */
8368         while (*p == '\n') p++;
8369         AppendComment(currentMove, p);
8370         return TRUE;
8371
8372       case WhiteCapturesEnPassant:
8373       case BlackCapturesEnPassant:
8374       case WhitePromotionChancellor:
8375       case BlackPromotionChancellor:
8376       case WhitePromotionArchbishop:
8377       case BlackPromotionArchbishop:
8378       case WhitePromotionCentaur:
8379       case BlackPromotionCentaur:
8380       case WhitePromotionQueen:
8381       case BlackPromotionQueen:
8382       case WhitePromotionRook:
8383       case BlackPromotionRook:
8384       case WhitePromotionBishop:
8385       case BlackPromotionBishop:
8386       case WhitePromotionKnight:
8387       case BlackPromotionKnight:
8388       case WhitePromotionKing:
8389       case BlackPromotionKing:
8390       case NormalMove:
8391       case WhiteKingSideCastle:
8392       case WhiteQueenSideCastle:
8393       case BlackKingSideCastle:
8394       case BlackQueenSideCastle:
8395       case WhiteKingSideCastleWild:
8396       case WhiteQueenSideCastleWild:
8397       case BlackKingSideCastleWild:
8398       case BlackQueenSideCastleWild:
8399       /* PUSH Fabien */
8400       case WhiteHSideCastleFR:
8401       case WhiteASideCastleFR:
8402       case BlackHSideCastleFR:
8403       case BlackASideCastleFR:
8404       /* POP Fabien */
8405         if (appData.debugMode)
8406           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8407         fromX = currentMoveString[0] - AAA;
8408         fromY = currentMoveString[1] - ONE;
8409         toX = currentMoveString[2] - AAA;
8410         toY = currentMoveString[3] - ONE;
8411         promoChar = currentMoveString[4];
8412         break;
8413
8414       case WhiteDrop:
8415       case BlackDrop:
8416         if (appData.debugMode)
8417           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8418         fromX = moveType == WhiteDrop ?
8419           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8420         (int) CharToPiece(ToLower(currentMoveString[0]));
8421         fromY = DROP_RANK;
8422         toX = currentMoveString[2] - AAA;
8423         toY = currentMoveString[3] - ONE;
8424         break;
8425
8426       case WhiteWins:
8427       case BlackWins:
8428       case GameIsDrawn:
8429       case GameUnfinished:
8430         if (appData.debugMode)
8431           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8432         p = strchr(yy_text, '{');
8433         if (p == NULL) p = strchr(yy_text, '(');
8434         if (p == NULL) {
8435             p = yy_text;
8436             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8437         } else {
8438             q = strchr(p, *p == '{' ? '}' : ')');
8439             if (q != NULL) *q = NULLCHAR;
8440             p++;
8441         }
8442         GameEnds(moveType, p, GE_FILE);
8443         done = TRUE;
8444         if (cmailMsgLoaded) {
8445             ClearHighlights();
8446             flipView = WhiteOnMove(currentMove);
8447             if (moveType == GameUnfinished) flipView = !flipView;
8448             if (appData.debugMode)
8449               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8450         }
8451         break;
8452
8453       case (ChessMove) 0:       /* end of file */
8454         if (appData.debugMode)
8455           fprintf(debugFP, "Parser hit end of file\n");
8456         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8457                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8458           case MT_NONE:
8459           case MT_CHECK:
8460             break;
8461           case MT_CHECKMATE:
8462           case MT_STAINMATE:
8463             if (WhiteOnMove(currentMove)) {
8464                 GameEnds(BlackWins, "Black mates", GE_FILE);
8465             } else {
8466                 GameEnds(WhiteWins, "White mates", GE_FILE);
8467             }
8468             break;
8469           case MT_STALEMATE:
8470             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8471             break;
8472         }
8473         done = TRUE;
8474         break;
8475
8476       case MoveNumberOne:
8477         if (lastLoadGameStart == GNUChessGame) {
8478             /* GNUChessGames have numbers, but they aren't move numbers */
8479             if (appData.debugMode)
8480               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8481                       yy_text, (int) moveType);
8482             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8483         }
8484         /* else fall thru */
8485
8486       case XBoardGame:
8487       case GNUChessGame:
8488       case PGNTag:
8489         /* Reached start of next game in file */
8490         if (appData.debugMode)
8491           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8492         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8493                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8494           case MT_NONE:
8495           case MT_CHECK:
8496             break;
8497           case MT_CHECKMATE:
8498           case MT_STAINMATE:
8499             if (WhiteOnMove(currentMove)) {
8500                 GameEnds(BlackWins, "Black mates", GE_FILE);
8501             } else {
8502                 GameEnds(WhiteWins, "White mates", GE_FILE);
8503             }
8504             break;
8505           case MT_STALEMATE:
8506             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8507             break;
8508         }
8509         done = TRUE;
8510         break;
8511
8512       case PositionDiagram:     /* should not happen; ignore */
8513       case ElapsedTime:         /* ignore */
8514       case NAG:                 /* ignore */
8515         if (appData.debugMode)
8516           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8517                   yy_text, (int) moveType);
8518         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8519
8520       case IllegalMove:
8521         if (appData.testLegality) {
8522             if (appData.debugMode)
8523               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8524             sprintf(move, _("Illegal move: %d.%s%s"),
8525                     (forwardMostMove / 2) + 1,
8526                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8527             DisplayError(move, 0);
8528             done = TRUE;
8529         } else {
8530             if (appData.debugMode)
8531               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8532                       yy_text, currentMoveString);
8533             fromX = currentMoveString[0] - AAA;
8534             fromY = currentMoveString[1] - ONE;
8535             toX = currentMoveString[2] - AAA;
8536             toY = currentMoveString[3] - ONE;
8537             promoChar = currentMoveString[4];
8538         }
8539         break;
8540
8541       case AmbiguousMove:
8542         if (appData.debugMode)
8543           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8544         sprintf(move, _("Ambiguous move: %d.%s%s"),
8545                 (forwardMostMove / 2) + 1,
8546                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8547         DisplayError(move, 0);
8548         done = TRUE;
8549         break;
8550
8551       default:
8552       case ImpossibleMove:
8553         if (appData.debugMode)
8554           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8555         sprintf(move, _("Illegal move: %d.%s%s"),
8556                 (forwardMostMove / 2) + 1,
8557                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8558         DisplayError(move, 0);
8559         done = TRUE;
8560         break;
8561     }
8562
8563     if (done) {
8564         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8565             DrawPosition(FALSE, boards[currentMove]);
8566             DisplayBothClocks();
8567             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8568               DisplayComment(currentMove - 1, commentList[currentMove]);
8569         }
8570         (void) StopLoadGameTimer();
8571         gameFileFP = NULL;
8572         cmailOldMove = forwardMostMove;
8573         return FALSE;
8574     } else {
8575         /* currentMoveString is set as a side-effect of yylex */
8576         strcat(currentMoveString, "\n");
8577         strcpy(moveList[forwardMostMove], currentMoveString);
8578
8579         thinkOutput[0] = NULLCHAR;
8580         MakeMove(fromX, fromY, toX, toY, promoChar);
8581         currentMove = forwardMostMove;
8582         return TRUE;
8583     }
8584 }
8585
8586 /* Load the nth game from the given file */
8587 int
8588 LoadGameFromFile(filename, n, title, useList)
8589      char *filename;
8590      int n;
8591      char *title;
8592      /*Boolean*/ int useList;
8593 {
8594     FILE *f;
8595     char buf[MSG_SIZ];
8596
8597     if (strcmp(filename, "-") == 0) {
8598         f = stdin;
8599         title = "stdin";
8600     } else {
8601         f = fopen(filename, "rb");
8602         if (f == NULL) {
8603           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8604             DisplayError(buf, errno);
8605             return FALSE;
8606         }
8607     }
8608     if (fseek(f, 0, 0) == -1) {
8609         /* f is not seekable; probably a pipe */
8610         useList = FALSE;
8611     }
8612     if (useList && n == 0) {
8613         int error = GameListBuild(f);
8614         if (error) {
8615             DisplayError(_("Cannot build game list"), error);
8616         } else if (!ListEmpty(&gameList) &&
8617                    ((ListGame *) gameList.tailPred)->number > 1) {
8618             GameListPopUp(f, title);
8619             return TRUE;
8620         }
8621         GameListDestroy();
8622         n = 1;
8623     }
8624     if (n == 0) n = 1;
8625     return LoadGame(f, n, title, FALSE);
8626 }
8627
8628
8629 void
8630 MakeRegisteredMove()
8631 {
8632     int fromX, fromY, toX, toY;
8633     char promoChar;
8634     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8635         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8636           case CMAIL_MOVE:
8637           case CMAIL_DRAW:
8638             if (appData.debugMode)
8639               fprintf(debugFP, "Restoring %s for game %d\n",
8640                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8641
8642             thinkOutput[0] = NULLCHAR;
8643             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8644             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8645             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8646             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8647             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8648             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8649             MakeMove(fromX, fromY, toX, toY, promoChar);
8650             ShowMove(fromX, fromY, toX, toY);
8651
8652             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8653                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8654               case MT_NONE:
8655               case MT_CHECK:
8656                 break;
8657
8658               case MT_CHECKMATE:
8659               case MT_STAINMATE:
8660                 if (WhiteOnMove(currentMove)) {
8661                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8662                 } else {
8663                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8664                 }
8665                 break;
8666
8667               case MT_STALEMATE:
8668                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8669                 break;
8670             }
8671
8672             break;
8673
8674           case CMAIL_RESIGN:
8675             if (WhiteOnMove(currentMove)) {
8676                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8677             } else {
8678                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8679             }
8680             break;
8681
8682           case CMAIL_ACCEPT:
8683             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8684             break;
8685
8686           default:
8687             break;
8688         }
8689     }
8690
8691     return;
8692 }
8693
8694 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8695 int
8696 CmailLoadGame(f, gameNumber, title, useList)
8697      FILE *f;
8698      int gameNumber;
8699      char *title;
8700      int useList;
8701 {
8702     int retVal;
8703
8704     if (gameNumber > nCmailGames) {
8705         DisplayError(_("No more games in this message"), 0);
8706         return FALSE;
8707     }
8708     if (f == lastLoadGameFP) {
8709         int offset = gameNumber - lastLoadGameNumber;
8710         if (offset == 0) {
8711             cmailMsg[0] = NULLCHAR;
8712             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8713                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8714                 nCmailMovesRegistered--;
8715             }
8716             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8717             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8718                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8719             }
8720         } else {
8721             if (! RegisterMove()) return FALSE;
8722         }
8723     }
8724
8725     retVal = LoadGame(f, gameNumber, title, useList);
8726
8727     /* Make move registered during previous look at this game, if any */
8728     MakeRegisteredMove();
8729
8730     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8731         commentList[currentMove]
8732           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8733         DisplayComment(currentMove - 1, commentList[currentMove]);
8734     }
8735
8736     return retVal;
8737 }
8738
8739 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8740 int
8741 ReloadGame(offset)
8742      int offset;
8743 {
8744     int gameNumber = lastLoadGameNumber + offset;
8745     if (lastLoadGameFP == NULL) {
8746         DisplayError(_("No game has been loaded yet"), 0);
8747         return FALSE;
8748     }
8749     if (gameNumber <= 0) {
8750         DisplayError(_("Can't back up any further"), 0);
8751         return FALSE;
8752     }
8753     if (cmailMsgLoaded) {
8754         return CmailLoadGame(lastLoadGameFP, gameNumber,
8755                              lastLoadGameTitle, lastLoadGameUseList);
8756     } else {
8757         return LoadGame(lastLoadGameFP, gameNumber,
8758                         lastLoadGameTitle, lastLoadGameUseList);
8759     }
8760 }
8761
8762
8763
8764 /* Load the nth game from open file f */
8765 int
8766 LoadGame(f, gameNumber, title, useList)
8767      FILE *f;
8768      int gameNumber;
8769      char *title;
8770      int useList;
8771 {
8772     ChessMove cm;
8773     char buf[MSG_SIZ];
8774     int gn = gameNumber;
8775     ListGame *lg = NULL;
8776     int numPGNTags = 0;
8777     int err;
8778     GameMode oldGameMode;
8779     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8780
8781     if (appData.debugMode)
8782         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8783
8784     if (gameMode == Training )
8785         SetTrainingModeOff();
8786
8787     oldGameMode = gameMode;
8788     if (gameMode != BeginningOfGame) {
8789       Reset(FALSE, TRUE);
8790     }
8791
8792     gameFileFP = f;
8793     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8794         fclose(lastLoadGameFP);
8795     }
8796
8797     if (useList) {
8798         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8799
8800         if (lg) {
8801             fseek(f, lg->offset, 0);
8802             GameListHighlight(gameNumber);
8803             gn = 1;
8804         }
8805         else {
8806             DisplayError(_("Game number out of range"), 0);
8807             return FALSE;
8808         }
8809     } else {
8810         GameListDestroy();
8811         if (fseek(f, 0, 0) == -1) {
8812             if (f == lastLoadGameFP ?
8813                 gameNumber == lastLoadGameNumber + 1 :
8814                 gameNumber == 1) {
8815                 gn = 1;
8816             } else {
8817                 DisplayError(_("Can't seek on game file"), 0);
8818                 return FALSE;
8819             }
8820         }
8821     }
8822     lastLoadGameFP = f;
8823     lastLoadGameNumber = gameNumber;
8824     strcpy(lastLoadGameTitle, title);
8825     lastLoadGameUseList = useList;
8826
8827     yynewfile(f);
8828
8829     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8830       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8831                 lg->gameInfo.black);
8832             DisplayTitle(buf);
8833     } else if (*title != NULLCHAR) {
8834         if (gameNumber > 1) {
8835             sprintf(buf, "%s %d", title, gameNumber);
8836             DisplayTitle(buf);
8837         } else {
8838             DisplayTitle(title);
8839         }
8840     }
8841
8842     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8843         gameMode = PlayFromGameFile;
8844         ModeHighlight();
8845     }
8846
8847     currentMove = forwardMostMove = backwardMostMove = 0;
8848     CopyBoard(boards[0], initialPosition);
8849     StopClocks();
8850
8851     /*
8852      * Skip the first gn-1 games in the file.
8853      * Also skip over anything that precedes an identifiable
8854      * start of game marker, to avoid being confused by
8855      * garbage at the start of the file.  Currently
8856      * recognized start of game markers are the move number "1",
8857      * the pattern "gnuchess .* game", the pattern
8858      * "^[#;%] [^ ]* game file", and a PGN tag block.
8859      * A game that starts with one of the latter two patterns
8860      * will also have a move number 1, possibly
8861      * following a position diagram.
8862      * 5-4-02: Let's try being more lenient and allowing a game to
8863      * start with an unnumbered move.  Does that break anything?
8864      */
8865     cm = lastLoadGameStart = (ChessMove) 0;
8866     while (gn > 0) {
8867         yyboardindex = forwardMostMove;
8868         cm = (ChessMove) yylex();
8869         switch (cm) {
8870           case (ChessMove) 0:
8871             if (cmailMsgLoaded) {
8872                 nCmailGames = CMAIL_MAX_GAMES - gn;
8873             } else {
8874                 Reset(TRUE, TRUE);
8875                 DisplayError(_("Game not found in file"), 0);
8876             }
8877             return FALSE;
8878
8879           case GNUChessGame:
8880           case XBoardGame:
8881             gn--;
8882             lastLoadGameStart = cm;
8883             break;
8884
8885           case MoveNumberOne:
8886             switch (lastLoadGameStart) {
8887               case GNUChessGame:
8888               case XBoardGame:
8889               case PGNTag:
8890                 break;
8891               case MoveNumberOne:
8892               case (ChessMove) 0:
8893                 gn--;           /* count this game */
8894                 lastLoadGameStart = cm;
8895                 break;
8896               default:
8897                 /* impossible */
8898                 break;
8899             }
8900             break;
8901
8902           case PGNTag:
8903             switch (lastLoadGameStart) {
8904               case GNUChessGame:
8905               case PGNTag:
8906               case MoveNumberOne:
8907               case (ChessMove) 0:
8908                 gn--;           /* count this game */
8909                 lastLoadGameStart = cm;
8910                 break;
8911               case XBoardGame:
8912                 lastLoadGameStart = cm; /* game counted already */
8913                 break;
8914               default:
8915                 /* impossible */
8916                 break;
8917             }
8918             if (gn > 0) {
8919                 do {
8920                     yyboardindex = forwardMostMove;
8921                     cm = (ChessMove) yylex();
8922                 } while (cm == PGNTag || cm == Comment);
8923             }
8924             break;
8925
8926           case WhiteWins:
8927           case BlackWins:
8928           case GameIsDrawn:
8929             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8930                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8931                     != CMAIL_OLD_RESULT) {
8932                     nCmailResults ++ ;
8933                     cmailResult[  CMAIL_MAX_GAMES
8934                                 - gn - 1] = CMAIL_OLD_RESULT;
8935                 }
8936             }
8937             break;
8938
8939           case NormalMove:
8940             /* Only a NormalMove can be at the start of a game
8941              * without a position diagram. */
8942             if (lastLoadGameStart == (ChessMove) 0) {
8943               gn--;
8944               lastLoadGameStart = MoveNumberOne;
8945             }
8946             break;
8947
8948           default:
8949             break;
8950         }
8951     }
8952
8953     if (appData.debugMode)
8954       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8955
8956     if (cm == XBoardGame) {
8957         /* Skip any header junk before position diagram and/or move 1 */
8958         for (;;) {
8959             yyboardindex = forwardMostMove;
8960             cm = (ChessMove) yylex();
8961
8962             if (cm == (ChessMove) 0 ||
8963                 cm == GNUChessGame || cm == XBoardGame) {
8964                 /* Empty game; pretend end-of-file and handle later */
8965                 cm = (ChessMove) 0;
8966                 break;
8967             }
8968
8969             if (cm == MoveNumberOne || cm == PositionDiagram ||
8970                 cm == PGNTag || cm == Comment)
8971               break;
8972         }
8973     } else if (cm == GNUChessGame) {
8974         if (gameInfo.event != NULL) {
8975             free(gameInfo.event);
8976         }
8977         gameInfo.event = StrSave(yy_text);
8978     }
8979
8980     startedFromSetupPosition = FALSE;
8981     while (cm == PGNTag) {
8982         if (appData.debugMode)
8983           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8984         err = ParsePGNTag(yy_text, &gameInfo);
8985         if (!err) numPGNTags++;
8986
8987         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8988         if(gameInfo.variant != oldVariant) {
8989             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8990             InitPosition(TRUE);
8991             oldVariant = gameInfo.variant;
8992             if (appData.debugMode)
8993               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8994         }
8995
8996
8997         if (gameInfo.fen != NULL) {
8998           Board initial_position;
8999           startedFromSetupPosition = TRUE;
9000           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9001             Reset(TRUE, TRUE);
9002             DisplayError(_("Bad FEN position in file"), 0);
9003             return FALSE;
9004           }
9005           CopyBoard(boards[0], initial_position);
9006           if (blackPlaysFirst) {
9007             currentMove = forwardMostMove = backwardMostMove = 1;
9008             CopyBoard(boards[1], initial_position);
9009             strcpy(moveList[0], "");
9010             strcpy(parseList[0], "");
9011             timeRemaining[0][1] = whiteTimeRemaining;
9012             timeRemaining[1][1] = blackTimeRemaining;
9013             if (commentList[0] != NULL) {
9014               commentList[1] = commentList[0];
9015               commentList[0] = NULL;
9016             }
9017           } else {
9018             currentMove = forwardMostMove = backwardMostMove = 0;
9019           }
9020           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9021           {   int i;
9022               initialRulePlies = FENrulePlies;
9023               epStatus[forwardMostMove] = FENepStatus;
9024               for( i=0; i< nrCastlingRights; i++ )
9025                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9026           }
9027           yyboardindex = forwardMostMove;
9028           free(gameInfo.fen);
9029           gameInfo.fen = NULL;
9030         }
9031
9032         yyboardindex = forwardMostMove;
9033         cm = (ChessMove) yylex();
9034
9035         /* Handle comments interspersed among the tags */
9036         while (cm == Comment) {
9037             char *p;
9038             if (appData.debugMode)
9039               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9040             p = yy_text;
9041             if (*p == '{' || *p == '[' || *p == '(') {
9042                 p[strlen(p) - 1] = NULLCHAR;
9043                 p++;
9044             }
9045             while (*p == '\n') p++;
9046             AppendComment(currentMove, p);
9047             yyboardindex = forwardMostMove;
9048             cm = (ChessMove) yylex();
9049         }
9050     }
9051
9052     /* don't rely on existence of Event tag since if game was
9053      * pasted from clipboard the Event tag may not exist
9054      */
9055     if (numPGNTags > 0){
9056         char *tags;
9057         if (gameInfo.variant == VariantNormal) {
9058           gameInfo.variant = StringToVariant(gameInfo.event);
9059         }
9060         if (!matchMode) {
9061           if( appData.autoDisplayTags ) {
9062             tags = PGNTags(&gameInfo);
9063             TagsPopUp(tags, CmailMsg());
9064             free(tags);
9065           }
9066         }
9067     } else {
9068         /* Make something up, but don't display it now */
9069         SetGameInfo();
9070         TagsPopDown();
9071     }
9072
9073     if (cm == PositionDiagram) {
9074         int i, j;
9075         char *p;
9076         Board initial_position;
9077
9078         if (appData.debugMode)
9079           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9080
9081         if (!startedFromSetupPosition) {
9082             p = yy_text;
9083             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9084               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9085                 switch (*p) {
9086                   case '[':
9087                   case '-':
9088                   case ' ':
9089                   case '\t':
9090                   case '\n':
9091                   case '\r':
9092                     break;
9093                   default:
9094                     initial_position[i][j++] = CharToPiece(*p);
9095                     break;
9096                 }
9097             while (*p == ' ' || *p == '\t' ||
9098                    *p == '\n' || *p == '\r') p++;
9099
9100             if (strncmp(p, "black", strlen("black"))==0)
9101               blackPlaysFirst = TRUE;
9102             else
9103               blackPlaysFirst = FALSE;
9104             startedFromSetupPosition = TRUE;
9105
9106             CopyBoard(boards[0], initial_position);
9107             if (blackPlaysFirst) {
9108                 currentMove = forwardMostMove = backwardMostMove = 1;
9109                 CopyBoard(boards[1], initial_position);
9110                 strcpy(moveList[0], "");
9111                 strcpy(parseList[0], "");
9112                 timeRemaining[0][1] = whiteTimeRemaining;
9113                 timeRemaining[1][1] = blackTimeRemaining;
9114                 if (commentList[0] != NULL) {
9115                     commentList[1] = commentList[0];
9116                     commentList[0] = NULL;
9117                 }
9118             } else {
9119                 currentMove = forwardMostMove = backwardMostMove = 0;
9120             }
9121         }
9122         yyboardindex = forwardMostMove;
9123         cm = (ChessMove) yylex();
9124     }
9125
9126     if (first.pr == NoProc) {
9127         StartChessProgram(&first);
9128     }
9129     InitChessProgram(&first, FALSE);
9130     SendToProgram("force\n", &first);
9131     if (startedFromSetupPosition) {
9132         SendBoard(&first, forwardMostMove);
9133     if (appData.debugMode) {
9134         fprintf(debugFP, "Load Game\n");
9135     }
9136         DisplayBothClocks();
9137     }
9138
9139     /* [HGM] server: flag to write setup moves in broadcast file as one */
9140     loadFlag = appData.suppressLoadMoves;
9141
9142     while (cm == Comment) {
9143         char *p;
9144         if (appData.debugMode)
9145           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9146         p = yy_text;
9147         if (*p == '{' || *p == '[' || *p == '(') {
9148             p[strlen(p) - 1] = NULLCHAR;
9149             p++;
9150         }
9151         while (*p == '\n') p++;
9152         AppendComment(currentMove, p);
9153         yyboardindex = forwardMostMove;
9154         cm = (ChessMove) yylex();
9155     }
9156
9157     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9158         cm == WhiteWins || cm == BlackWins ||
9159         cm == GameIsDrawn || cm == GameUnfinished) {
9160         DisplayMessage("", _("No moves in game"));
9161         if (cmailMsgLoaded) {
9162             if (appData.debugMode)
9163               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9164             ClearHighlights();
9165             flipView = FALSE;
9166         }
9167         DrawPosition(FALSE, boards[currentMove]);
9168         DisplayBothClocks();
9169         gameMode = EditGame;
9170         ModeHighlight();
9171         gameFileFP = NULL;
9172         cmailOldMove = 0;
9173         return TRUE;
9174     }
9175
9176     // [HGM] PV info: routine tests if comment empty
9177     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9178         DisplayComment(currentMove - 1, commentList[currentMove]);
9179     }
9180     if (!matchMode && appData.timeDelay != 0)
9181       DrawPosition(FALSE, boards[currentMove]);
9182
9183     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9184       programStats.ok_to_send = 1;
9185     }
9186
9187     /* if the first token after the PGN tags is a move
9188      * and not move number 1, retrieve it from the parser
9189      */
9190     if (cm != MoveNumberOne)
9191         LoadGameOneMove(cm);
9192
9193     /* load the remaining moves from the file */
9194     while (LoadGameOneMove((ChessMove)0)) {
9195       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9196       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9197     }
9198
9199     /* rewind to the start of the game */
9200     currentMove = backwardMostMove;
9201
9202     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9203
9204     if (oldGameMode == AnalyzeFile ||
9205         oldGameMode == AnalyzeMode) {
9206       AnalyzeFileEvent();
9207     }
9208
9209     if (matchMode || appData.timeDelay == 0) {
9210       ToEndEvent();
9211       gameMode = EditGame;
9212       ModeHighlight();
9213     } else if (appData.timeDelay > 0) {
9214       AutoPlayGameLoop();
9215     }
9216
9217     if (appData.debugMode)
9218         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9219
9220     loadFlag = 0; /* [HGM] true game starts */
9221     return TRUE;
9222 }
9223
9224 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9225 int
9226 ReloadPosition(offset)
9227      int offset;
9228 {
9229     int positionNumber = lastLoadPositionNumber + offset;
9230     if (lastLoadPositionFP == NULL) {
9231         DisplayError(_("No position has been loaded yet"), 0);
9232         return FALSE;
9233     }
9234     if (positionNumber <= 0) {
9235         DisplayError(_("Can't back up any further"), 0);
9236         return FALSE;
9237     }
9238     return LoadPosition(lastLoadPositionFP, positionNumber,
9239                         lastLoadPositionTitle);
9240 }
9241
9242 /* Load the nth position from the given file */
9243 int
9244 LoadPositionFromFile(filename, n, title)
9245      char *filename;
9246      int n;
9247      char *title;
9248 {
9249     FILE *f;
9250     char buf[MSG_SIZ];
9251
9252     if (strcmp(filename, "-") == 0) {
9253         return LoadPosition(stdin, n, "stdin");
9254     } else {
9255         f = fopen(filename, "rb");
9256         if (f == NULL) {
9257             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9258             DisplayError(buf, errno);
9259             return FALSE;
9260         } else {
9261             return LoadPosition(f, n, title);
9262         }
9263     }
9264 }
9265
9266 /* Load the nth position from the given open file, and close it */
9267 int
9268 LoadPosition(f, positionNumber, title)
9269      FILE *f;
9270      int positionNumber;
9271      char *title;
9272 {
9273     char *p, line[MSG_SIZ];
9274     Board initial_position;
9275     int i, j, fenMode, pn;
9276
9277     if (gameMode == Training )
9278         SetTrainingModeOff();
9279
9280     if (gameMode != BeginningOfGame) {
9281         Reset(FALSE, TRUE);
9282     }
9283     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9284         fclose(lastLoadPositionFP);
9285     }
9286     if (positionNumber == 0) positionNumber = 1;
9287     lastLoadPositionFP = f;
9288     lastLoadPositionNumber = positionNumber;
9289     strcpy(lastLoadPositionTitle, title);
9290     if (first.pr == NoProc) {
9291       StartChessProgram(&first);
9292       InitChessProgram(&first, FALSE);
9293     }
9294     pn = positionNumber;
9295     if (positionNumber < 0) {
9296         /* Negative position number means to seek to that byte offset */
9297         if (fseek(f, -positionNumber, 0) == -1) {
9298             DisplayError(_("Can't seek on position file"), 0);
9299             return FALSE;
9300         };
9301         pn = 1;
9302     } else {
9303         if (fseek(f, 0, 0) == -1) {
9304             if (f == lastLoadPositionFP ?
9305                 positionNumber == lastLoadPositionNumber + 1 :
9306                 positionNumber == 1) {
9307                 pn = 1;
9308             } else {
9309                 DisplayError(_("Can't seek on position file"), 0);
9310                 return FALSE;
9311             }
9312         }
9313     }
9314     /* See if this file is FEN or old-style xboard */
9315     if (fgets(line, MSG_SIZ, f) == NULL) {
9316         DisplayError(_("Position not found in file"), 0);
9317         return FALSE;
9318     }
9319 #if 0
9320     switch (line[0]) {
9321       case '#':  case 'x':
9322       default:
9323         fenMode = FALSE;
9324         break;
9325       case 'p':  case 'n':  case 'b':  case 'r':  case 'q':  case 'k':
9326       case 'P':  case 'N':  case 'B':  case 'R':  case 'Q':  case 'K':
9327       case '1':  case '2':  case '3':  case '4':  case '5':  case '6':
9328       case '7':  case '8':  case '9':
9329       case 'H':  case 'A':  case 'M':  case 'h':  case 'a':  case 'm':
9330       case 'E':  case 'F':  case 'G':  case 'e':  case 'f':  case 'g':
9331       case 'C':  case 'W':             case 'c':  case 'w':
9332         fenMode = TRUE;
9333         break;
9334     }
9335 #else
9336     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9337     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9338 #endif
9339
9340     if (pn >= 2) {
9341         if (fenMode || line[0] == '#') pn--;
9342         while (pn > 0) {
9343             /* skip positions before number pn */
9344             if (fgets(line, MSG_SIZ, f) == NULL) {
9345                 Reset(TRUE, TRUE);
9346                 DisplayError(_("Position not found in file"), 0);
9347                 return FALSE;
9348             }
9349             if (fenMode || line[0] == '#') pn--;
9350         }
9351     }
9352
9353     if (fenMode) {
9354         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9355             DisplayError(_("Bad FEN position in file"), 0);
9356             return FALSE;
9357         }
9358     } else {
9359         (void) fgets(line, MSG_SIZ, f);
9360         (void) fgets(line, MSG_SIZ, f);
9361
9362         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9363             (void) fgets(line, MSG_SIZ, f);
9364             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9365                 if (*p == ' ')
9366                   continue;
9367                 initial_position[i][j++] = CharToPiece(*p);
9368             }
9369         }
9370
9371         blackPlaysFirst = FALSE;
9372         if (!feof(f)) {
9373             (void) fgets(line, MSG_SIZ, f);
9374             if (strncmp(line, "black", strlen("black"))==0)
9375               blackPlaysFirst = TRUE;
9376         }
9377     }
9378     startedFromSetupPosition = TRUE;
9379
9380     SendToProgram("force\n", &first);
9381     CopyBoard(boards[0], initial_position);
9382     if (blackPlaysFirst) {
9383         currentMove = forwardMostMove = backwardMostMove = 1;
9384         strcpy(moveList[0], "");
9385         strcpy(parseList[0], "");
9386         CopyBoard(boards[1], initial_position);
9387         DisplayMessage("", _("Black to play"));
9388     } else {
9389         currentMove = forwardMostMove = backwardMostMove = 0;
9390         DisplayMessage("", _("White to play"));
9391     }
9392           /* [HGM] copy FEN attributes as well */
9393           {   int i;
9394               initialRulePlies = FENrulePlies;
9395               epStatus[forwardMostMove] = FENepStatus;
9396               for( i=0; i< nrCastlingRights; i++ )
9397                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9398           }
9399     SendBoard(&first, forwardMostMove);
9400     if (appData.debugMode) {
9401 int i, j;
9402   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9403   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9404         fprintf(debugFP, "Load Position\n");
9405     }
9406
9407     if (positionNumber > 1) {
9408         sprintf(line, "%s %d", title, positionNumber);
9409         DisplayTitle(line);
9410     } else {
9411         DisplayTitle(title);
9412     }
9413     gameMode = EditGame;
9414     ModeHighlight();
9415     ResetClocks();
9416     timeRemaining[0][1] = whiteTimeRemaining;
9417     timeRemaining[1][1] = blackTimeRemaining;
9418     DrawPosition(FALSE, boards[currentMove]);
9419
9420     return TRUE;
9421 }
9422
9423
9424 void
9425 CopyPlayerNameIntoFileName(dest, src)
9426      char **dest, *src;
9427 {
9428     while (*src != NULLCHAR && *src != ',') {
9429         if (*src == ' ') {
9430             *(*dest)++ = '_';
9431             src++;
9432         } else {
9433             *(*dest)++ = *src++;
9434         }
9435     }
9436 }
9437
9438 char *DefaultFileName(ext)
9439      char *ext;
9440 {
9441     static char def[MSG_SIZ];
9442     char *p;
9443
9444     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9445         p = def;
9446         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9447         *p++ = '-';
9448         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9449         *p++ = '.';
9450         strcpy(p, ext);
9451     } else {
9452         def[0] = NULLCHAR;
9453     }
9454     return def;
9455 }
9456
9457 /* Save the current game to the given file */
9458 int
9459 SaveGameToFile(filename, append)
9460      char *filename;
9461      int append;
9462 {
9463     FILE *f;
9464     char buf[MSG_SIZ];
9465
9466     if (strcmp(filename, "-") == 0) {
9467         return SaveGame(stdout, 0, NULL);
9468     } else {
9469         f = fopen(filename, append ? "a" : "w");
9470         if (f == NULL) {
9471             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9472             DisplayError(buf, errno);
9473             return FALSE;
9474         } else {
9475             return SaveGame(f, 0, NULL);
9476         }
9477     }
9478 }
9479
9480 char *
9481 SavePart(str)
9482      char *str;
9483 {
9484     static char buf[MSG_SIZ];
9485     char *p;
9486
9487     p = strchr(str, ' ');
9488     if (p == NULL) return str;
9489     strncpy(buf, str, p - str);
9490     buf[p - str] = NULLCHAR;
9491     return buf;
9492 }
9493
9494 #define PGN_MAX_LINE 75
9495
9496 #define PGN_SIDE_WHITE  0
9497 #define PGN_SIDE_BLACK  1
9498
9499 /* [AS] */
9500 static int FindFirstMoveOutOfBook( int side )
9501 {
9502     int result = -1;
9503
9504     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9505         int index = backwardMostMove;
9506         int has_book_hit = 0;
9507
9508         if( (index % 2) != side ) {
9509             index++;
9510         }
9511
9512         while( index < forwardMostMove ) {
9513             /* Check to see if engine is in book */
9514             int depth = pvInfoList[index].depth;
9515             int score = pvInfoList[index].score;
9516             int in_book = 0;
9517
9518             if( depth <= 2 ) {
9519                 in_book = 1;
9520             }
9521             else if( score == 0 && depth == 63 ) {
9522                 in_book = 1; /* Zappa */
9523             }
9524             else if( score == 2 && depth == 99 ) {
9525                 in_book = 1; /* Abrok */
9526             }
9527
9528             has_book_hit += in_book;
9529
9530             if( ! in_book ) {
9531                 result = index;
9532
9533                 break;
9534             }
9535
9536             index += 2;
9537         }
9538     }
9539
9540     return result;
9541 }
9542
9543 /* [AS] */
9544 void GetOutOfBookInfo( char * buf )
9545 {
9546     int oob[2];
9547     int i;
9548     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9549
9550     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9551     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9552
9553     *buf = '\0';
9554
9555     if( oob[0] >= 0 || oob[1] >= 0 ) {
9556         for( i=0; i<2; i++ ) {
9557             int idx = oob[i];
9558
9559             if( idx >= 0 ) {
9560                 if( i > 0 && oob[0] >= 0 ) {
9561                     strcat( buf, "   " );
9562                 }
9563
9564                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9565                 sprintf( buf+strlen(buf), "%s%.2f",
9566                     pvInfoList[idx].score >= 0 ? "+" : "",
9567                     pvInfoList[idx].score / 100.0 );
9568             }
9569         }
9570     }
9571 }
9572
9573 /* Save game in PGN style and close the file */
9574 int
9575 SaveGamePGN(f)
9576      FILE *f;
9577 {
9578     int i, offset, linelen, newblock;
9579     time_t tm;
9580 //    char *movetext;
9581     char numtext[32];
9582     int movelen, numlen, blank;
9583     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9584
9585     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9586
9587     tm = time((time_t *) NULL);
9588
9589     PrintPGNTags(f, &gameInfo);
9590
9591     if (backwardMostMove > 0 || startedFromSetupPosition) {
9592         char *fen = PositionToFEN(backwardMostMove, NULL);
9593         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9594         fprintf(f, "\n{--------------\n");
9595         PrintPosition(f, backwardMostMove);
9596         fprintf(f, "--------------}\n");
9597         free(fen);
9598     }
9599     else {
9600         /* [AS] Out of book annotation */
9601         if( appData.saveOutOfBookInfo ) {
9602             char buf[64];
9603
9604             GetOutOfBookInfo( buf );
9605
9606             if( buf[0] != '\0' ) {
9607                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9608             }
9609         }
9610
9611         fprintf(f, "\n");
9612     }
9613
9614     i = backwardMostMove;
9615     linelen = 0;
9616     newblock = TRUE;
9617
9618     while (i < forwardMostMove) {
9619         /* Print comments preceding this move */
9620         if (commentList[i] != NULL) {
9621             if (linelen > 0) fprintf(f, "\n");
9622             fprintf(f, "{\n%s}\n", commentList[i]);
9623             linelen = 0;
9624             newblock = TRUE;
9625         }
9626
9627         /* Format move number */
9628         if ((i % 2) == 0) {
9629             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9630         } else {
9631             if (newblock) {
9632                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9633             } else {
9634                 numtext[0] = NULLCHAR;
9635             }
9636         }
9637         numlen = strlen(numtext);
9638         newblock = FALSE;
9639
9640         /* Print move number */
9641         blank = linelen > 0 && numlen > 0;
9642         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9643             fprintf(f, "\n");
9644             linelen = 0;
9645             blank = 0;
9646         }
9647         if (blank) {
9648             fprintf(f, " ");
9649             linelen++;
9650         }
9651         fprintf(f, numtext);
9652         linelen += numlen;
9653
9654         /* Get move */
9655         strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited
9656         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9657         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9658                 int p = movelen - 1;
9659                 if(move_buffer[p] == ' ') p--;
9660                 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9661                     while(p && move_buffer[--p] != '(');
9662                     if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9663                 }
9664         }
9665
9666         /* Print move */
9667         blank = linelen > 0 && movelen > 0;
9668         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9669             fprintf(f, "\n");
9670             linelen = 0;
9671             blank = 0;
9672         }
9673         if (blank) {
9674             fprintf(f, " ");
9675             linelen++;
9676         }
9677         fprintf(f, move_buffer);
9678         linelen += movelen;
9679
9680         /* [AS] Add PV info if present */
9681         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9682             /* [HGM] add time */
9683             char buf[MSG_SIZ]; int seconds = 0;
9684
9685 #if 1
9686             if(i >= backwardMostMove) {
9687                 if(WhiteOnMove(i))
9688                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9689                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9690                 else
9691                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9692                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9693             }
9694             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9695 #else
9696             seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9697 #endif
9698
9699             if( seconds <= 0) buf[0] = 0; else
9700             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9701                 seconds = (seconds + 4)/10; // round to full seconds
9702                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9703                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9704             }
9705
9706             sprintf( move_buffer, "{%s%.2f/%d%s}",
9707                 pvInfoList[i].score >= 0 ? "+" : "",
9708                 pvInfoList[i].score / 100.0,
9709                 pvInfoList[i].depth,
9710                 buf );
9711
9712             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9713
9714             /* Print score/depth */
9715             blank = linelen > 0 && movelen > 0;
9716             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9717                 fprintf(f, "\n");
9718                 linelen = 0;
9719                 blank = 0;
9720             }
9721             if (blank) {
9722                 fprintf(f, " ");
9723                 linelen++;
9724             }
9725             fprintf(f, move_buffer);
9726             linelen += movelen;
9727         }
9728
9729         i++;
9730     }
9731
9732     /* Start a new line */
9733     if (linelen > 0) fprintf(f, "\n");
9734
9735     /* Print comments after last move */
9736     if (commentList[i] != NULL) {
9737         fprintf(f, "{\n%s}\n", commentList[i]);
9738     }
9739
9740     /* Print result */
9741     if (gameInfo.resultDetails != NULL &&
9742         gameInfo.resultDetails[0] != NULLCHAR) {
9743         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9744                 PGNResult(gameInfo.result));
9745     } else {
9746         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9747     }
9748
9749     fclose(f);
9750     return TRUE;
9751 }
9752
9753 /* Save game in old style and close the file */
9754 int
9755 SaveGameOldStyle(f)
9756      FILE *f;
9757 {
9758     int i, offset;
9759     time_t tm;
9760
9761     tm = time((time_t *) NULL);
9762
9763     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9764     PrintOpponents(f);
9765
9766     if (backwardMostMove > 0 || startedFromSetupPosition) {
9767         fprintf(f, "\n[--------------\n");
9768         PrintPosition(f, backwardMostMove);
9769         fprintf(f, "--------------]\n");
9770     } else {
9771         fprintf(f, "\n");
9772     }
9773
9774     i = backwardMostMove;
9775     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9776
9777     while (i < forwardMostMove) {
9778         if (commentList[i] != NULL) {
9779             fprintf(f, "[%s]\n", commentList[i]);
9780         }
9781
9782         if ((i % 2) == 1) {
9783             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9784             i++;
9785         } else {
9786             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9787             i++;
9788             if (commentList[i] != NULL) {
9789                 fprintf(f, "\n");
9790                 continue;
9791             }
9792             if (i >= forwardMostMove) {
9793                 fprintf(f, "\n");
9794                 break;
9795             }
9796             fprintf(f, "%s\n", parseList[i]);
9797             i++;
9798         }
9799     }
9800
9801     if (commentList[i] != NULL) {
9802         fprintf(f, "[%s]\n", commentList[i]);
9803     }
9804
9805     /* This isn't really the old style, but it's close enough */
9806     if (gameInfo.resultDetails != NULL &&
9807         gameInfo.resultDetails[0] != NULLCHAR) {
9808         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9809                 gameInfo.resultDetails);
9810     } else {
9811         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9812     }
9813
9814     fclose(f);
9815     return TRUE;
9816 }
9817
9818 /* Save the current game to open file f and close the file */
9819 int
9820 SaveGame(f, dummy, dummy2)
9821      FILE *f;
9822      int dummy;
9823      char *dummy2;
9824 {
9825     if (gameMode == EditPosition) EditPositionDone();
9826     if (appData.oldSaveStyle)
9827       return SaveGameOldStyle(f);
9828     else
9829       return SaveGamePGN(f);
9830 }
9831
9832 /* Save the current position to the given file */
9833 int
9834 SavePositionToFile(filename)
9835      char *filename;
9836 {
9837     FILE *f;
9838     char buf[MSG_SIZ];
9839
9840     if (strcmp(filename, "-") == 0) {
9841         return SavePosition(stdout, 0, NULL);
9842     } else {
9843         f = fopen(filename, "a");
9844         if (f == NULL) {
9845             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9846             DisplayError(buf, errno);
9847             return FALSE;
9848         } else {
9849             SavePosition(f, 0, NULL);
9850             return TRUE;
9851         }
9852     }
9853 }
9854
9855 /* Save the current position to the given open file and close the file */
9856 int
9857 SavePosition(f, dummy, dummy2)
9858      FILE *f;
9859      int dummy;
9860      char *dummy2;
9861 {
9862     time_t tm;
9863     char *fen;
9864
9865     if (appData.oldSaveStyle) {
9866         tm = time((time_t *) NULL);
9867
9868         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9869         PrintOpponents(f);
9870         fprintf(f, "[--------------\n");
9871         PrintPosition(f, currentMove);
9872         fprintf(f, "--------------]\n");
9873     } else {
9874         fen = PositionToFEN(currentMove, NULL);
9875         fprintf(f, "%s\n", fen);
9876         free(fen);
9877     }
9878     fclose(f);
9879     return TRUE;
9880 }
9881
9882 void
9883 ReloadCmailMsgEvent(unregister)
9884      int unregister;
9885 {
9886 #if !WIN32
9887     static char *inFilename = NULL;
9888     static char *outFilename;
9889     int i;
9890     struct stat inbuf, outbuf;
9891     int status;
9892
9893     /* Any registered moves are unregistered if unregister is set, */
9894     /* i.e. invoked by the signal handler */
9895     if (unregister) {
9896         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9897             cmailMoveRegistered[i] = FALSE;
9898             if (cmailCommentList[i] != NULL) {
9899                 free(cmailCommentList[i]);
9900                 cmailCommentList[i] = NULL;
9901             }
9902         }
9903         nCmailMovesRegistered = 0;
9904     }
9905
9906     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9907         cmailResult[i] = CMAIL_NOT_RESULT;
9908     }
9909     nCmailResults = 0;
9910
9911     if (inFilename == NULL) {
9912         /* Because the filenames are static they only get malloced once  */
9913         /* and they never get freed                                      */
9914         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9915         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9916
9917         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9918         sprintf(outFilename, "%s.out", appData.cmailGameName);
9919     }
9920
9921     status = stat(outFilename, &outbuf);
9922     if (status < 0) {
9923         cmailMailedMove = FALSE;
9924     } else {
9925         status = stat(inFilename, &inbuf);
9926         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9927     }
9928
9929     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9930        counts the games, notes how each one terminated, etc.
9931
9932        It would be nice to remove this kludge and instead gather all
9933        the information while building the game list.  (And to keep it
9934        in the game list nodes instead of having a bunch of fixed-size
9935        parallel arrays.)  Note this will require getting each game's
9936        termination from the PGN tags, as the game list builder does
9937        not process the game moves.  --mann
9938        */
9939     cmailMsgLoaded = TRUE;
9940     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9941
9942     /* Load first game in the file or popup game menu */
9943     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9944
9945 #endif /* !WIN32 */
9946     return;
9947 }
9948
9949 int
9950 RegisterMove()
9951 {
9952     FILE *f;
9953     char string[MSG_SIZ];
9954
9955     if (   cmailMailedMove
9956         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9957         return TRUE;            /* Allow free viewing  */
9958     }
9959
9960     /* Unregister move to ensure that we don't leave RegisterMove        */
9961     /* with the move registered when the conditions for registering no   */
9962     /* longer hold                                                       */
9963     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9964         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9965         nCmailMovesRegistered --;
9966
9967         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9968           {
9969               free(cmailCommentList[lastLoadGameNumber - 1]);
9970               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9971           }
9972     }
9973
9974     if (cmailOldMove == -1) {
9975         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9976         return FALSE;
9977     }
9978
9979     if (currentMove > cmailOldMove + 1) {
9980         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9981         return FALSE;
9982     }
9983
9984     if (currentMove < cmailOldMove) {
9985         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9986         return FALSE;
9987     }
9988
9989     if (forwardMostMove > currentMove) {
9990         /* Silently truncate extra moves */
9991         TruncateGame();
9992     }
9993
9994     if (   (currentMove == cmailOldMove + 1)
9995         || (   (currentMove == cmailOldMove)
9996             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9997                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9998         if (gameInfo.result != GameUnfinished) {
9999             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10000         }
10001
10002         if (commentList[currentMove] != NULL) {
10003             cmailCommentList[lastLoadGameNumber - 1]
10004               = StrSave(commentList[currentMove]);
10005         }
10006         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10007
10008         if (appData.debugMode)
10009           fprintf(debugFP, "Saving %s for game %d\n",
10010                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10011
10012         sprintf(string,
10013                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10014
10015         f = fopen(string, "w");
10016         if (appData.oldSaveStyle) {
10017             SaveGameOldStyle(f); /* also closes the file */
10018
10019             sprintf(string, "%s.pos.out", appData.cmailGameName);
10020             f = fopen(string, "w");
10021             SavePosition(f, 0, NULL); /* also closes the file */
10022         } else {
10023             fprintf(f, "{--------------\n");
10024             PrintPosition(f, currentMove);
10025             fprintf(f, "--------------}\n\n");
10026
10027             SaveGame(f, 0, NULL); /* also closes the file*/
10028         }
10029
10030         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10031         nCmailMovesRegistered ++;
10032     } else if (nCmailGames == 1) {
10033         DisplayError(_("You have not made a move yet"), 0);
10034         return FALSE;
10035     }
10036
10037     return TRUE;
10038 }
10039
10040 void
10041 MailMoveEvent()
10042 {
10043 #if !WIN32
10044     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10045     FILE *commandOutput;
10046     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10047     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10048     int nBuffers;
10049     int i;
10050     int archived;
10051     char *arcDir;
10052
10053     if (! cmailMsgLoaded) {
10054         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10055         return;
10056     }
10057
10058     if (nCmailGames == nCmailResults) {
10059         DisplayError(_("No unfinished games"), 0);
10060         return;
10061     }
10062
10063 #if CMAIL_PROHIBIT_REMAIL
10064     if (cmailMailedMove) {
10065         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);
10066         DisplayError(msg, 0);
10067         return;
10068     }
10069 #endif
10070
10071     if (! (cmailMailedMove || RegisterMove())) return;
10072
10073     if (   cmailMailedMove
10074         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10075         sprintf(string, partCommandString,
10076                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10077         commandOutput = popen(string, "r");
10078
10079         if (commandOutput == NULL) {
10080             DisplayError(_("Failed to invoke cmail"), 0);
10081         } else {
10082             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10083                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10084             }
10085             if (nBuffers > 1) {
10086                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10087                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10088                 nBytes = MSG_SIZ - 1;
10089             } else {
10090                 (void) memcpy(msg, buffer, nBytes);
10091             }
10092             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10093
10094             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10095                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10096
10097                 archived = TRUE;
10098                 for (i = 0; i < nCmailGames; i ++) {
10099                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10100                         archived = FALSE;
10101                     }
10102                 }
10103                 if (   archived
10104                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10105                         != NULL)) {
10106                     sprintf(buffer, "%s/%s.%s.archive",
10107                             arcDir,
10108                             appData.cmailGameName,
10109                             gameInfo.date);
10110                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10111                     cmailMsgLoaded = FALSE;
10112                 }
10113             }
10114
10115             DisplayInformation(msg);
10116             pclose(commandOutput);
10117         }
10118     } else {
10119         if ((*cmailMsg) != '\0') {
10120             DisplayInformation(cmailMsg);
10121         }
10122     }
10123
10124     return;
10125 #endif /* !WIN32 */
10126 }
10127
10128 char *
10129 CmailMsg()
10130 {
10131 #if WIN32
10132     return NULL;
10133 #else
10134     int  prependComma = 0;
10135     char number[5];
10136     char string[MSG_SIZ];       /* Space for game-list */
10137     int  i;
10138
10139     if (!cmailMsgLoaded) return "";
10140
10141     if (cmailMailedMove) {
10142         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10143     } else {
10144         /* Create a list of games left */
10145         sprintf(string, "[");
10146         for (i = 0; i < nCmailGames; i ++) {
10147             if (! (   cmailMoveRegistered[i]
10148                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10149                 if (prependComma) {
10150                     sprintf(number, ",%d", i + 1);
10151                 } else {
10152                     sprintf(number, "%d", i + 1);
10153                     prependComma = 1;
10154                 }
10155
10156                 strcat(string, number);
10157             }
10158         }
10159         strcat(string, "]");
10160
10161         if (nCmailMovesRegistered + nCmailResults == 0) {
10162             switch (nCmailGames) {
10163               case 1:
10164                 sprintf(cmailMsg,
10165                         _("Still need to make move for game\n"));
10166                 break;
10167
10168               case 2:
10169                 sprintf(cmailMsg,
10170                         _("Still need to make moves for both games\n"));
10171                 break;
10172
10173               default:
10174                 sprintf(cmailMsg,
10175                         _("Still need to make moves for all %d games\n"),
10176                         nCmailGames);
10177                 break;
10178             }
10179         } else {
10180             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10181               case 1:
10182                 sprintf(cmailMsg,
10183                         _("Still need to make a move for game %s\n"),
10184                         string);
10185                 break;
10186
10187               case 0:
10188                 if (nCmailResults == nCmailGames) {
10189                     sprintf(cmailMsg, _("No unfinished games\n"));
10190                 } else {
10191                     sprintf(cmailMsg, _("Ready to send mail\n"));
10192                 }
10193                 break;
10194
10195               default:
10196                 sprintf(cmailMsg,
10197                         _("Still need to make moves for games %s\n"),
10198                         string);
10199             }
10200         }
10201     }
10202     return cmailMsg;
10203 #endif /* WIN32 */
10204 }
10205
10206 void
10207 ResetGameEvent()
10208 {
10209     if (gameMode == Training)
10210       SetTrainingModeOff();
10211
10212     Reset(TRUE, TRUE);
10213     cmailMsgLoaded = FALSE;
10214     if (appData.icsActive) {
10215       SendToICS(ics_prefix);
10216       SendToICS("refresh\n");
10217     }
10218 }
10219
10220 void
10221 ExitEvent(status)
10222      int status;
10223 {
10224     exiting++;
10225     if (exiting > 2) {
10226       /* Give up on clean exit */
10227       exit(status);
10228     }
10229     if (exiting > 1) {
10230       /* Keep trying for clean exit */
10231       return;
10232     }
10233
10234     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10235
10236     if (telnetISR != NULL) {
10237       RemoveInputSource(telnetISR);
10238     }
10239     if (icsPR != NoProc) {
10240       DestroyChildProcess(icsPR, TRUE);
10241     }
10242 #if 0
10243     /* Save game if resource set and not already saved by GameEnds() */
10244     if ((gameInfo.resultDetails == NULL || errorExitFlag )
10245                              && forwardMostMove > 0) {
10246       if (*appData.saveGameFile != NULLCHAR) {
10247         SaveGameToFile(appData.saveGameFile, TRUE);
10248       } else if (appData.autoSaveGames) {
10249         AutoSaveGame();
10250       }
10251       if (*appData.savePositionFile != NULLCHAR) {
10252         SavePositionToFile(appData.savePositionFile);
10253       }
10254     }
10255     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10256 #else
10257     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10258     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10259 #endif
10260     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10261     /* make sure this other one finishes before killing it!                  */
10262     if(endingGame) { int count = 0;
10263         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10264         while(endingGame && count++ < 10) DoSleep(1);
10265         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10266     }
10267
10268     /* Kill off chess programs */
10269     if (first.pr != NoProc) {
10270         ExitAnalyzeMode();
10271
10272         DoSleep( appData.delayBeforeQuit );
10273         SendToProgram("quit\n", &first);
10274         DoSleep( appData.delayAfterQuit );
10275         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10276     }
10277     if (second.pr != NoProc) {
10278         DoSleep( appData.delayBeforeQuit );
10279         SendToProgram("quit\n", &second);
10280         DoSleep( appData.delayAfterQuit );
10281         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10282     }
10283     if (first.isr != NULL) {
10284         RemoveInputSource(first.isr);
10285     }
10286     if (second.isr != NULL) {
10287         RemoveInputSource(second.isr);
10288     }
10289
10290     ShutDownFrontEnd();
10291     exit(status);
10292 }
10293
10294 void
10295 PauseEvent()
10296 {
10297     if (appData.debugMode)
10298         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10299     if (pausing) {
10300         pausing = FALSE;
10301         ModeHighlight();
10302         if (gameMode == MachinePlaysWhite ||
10303             gameMode == MachinePlaysBlack) {
10304             StartClocks();
10305         } else {
10306             DisplayBothClocks();
10307         }
10308         if (gameMode == PlayFromGameFile) {
10309             if (appData.timeDelay >= 0)
10310                 AutoPlayGameLoop();
10311         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10312             Reset(FALSE, TRUE);
10313             SendToICS(ics_prefix);
10314             SendToICS("refresh\n");
10315         } else if (currentMove < forwardMostMove) {
10316             ForwardInner(forwardMostMove);
10317         }
10318         pauseExamInvalid = FALSE;
10319     } else {
10320         switch (gameMode) {
10321           default:
10322             return;
10323           case IcsExamining:
10324             pauseExamForwardMostMove = forwardMostMove;
10325             pauseExamInvalid = FALSE;
10326             /* fall through */
10327           case IcsObserving:
10328           case IcsPlayingWhite:
10329           case IcsPlayingBlack:
10330             pausing = TRUE;
10331             ModeHighlight();
10332             return;
10333           case PlayFromGameFile:
10334             (void) StopLoadGameTimer();
10335             pausing = TRUE;
10336             ModeHighlight();
10337             break;
10338           case BeginningOfGame:
10339             if (appData.icsActive) return;
10340             /* else fall through */
10341           case MachinePlaysWhite:
10342           case MachinePlaysBlack:
10343           case TwoMachinesPlay:
10344             if (forwardMostMove == 0)
10345               return;           /* don't pause if no one has moved */
10346             if ((gameMode == MachinePlaysWhite &&
10347                  !WhiteOnMove(forwardMostMove)) ||
10348                 (gameMode == MachinePlaysBlack &&
10349                  WhiteOnMove(forwardMostMove))) {
10350                 StopClocks();
10351             }
10352             pausing = TRUE;
10353             ModeHighlight();
10354             break;
10355         }
10356     }
10357 }
10358
10359 void
10360 EditCommentEvent()
10361 {
10362     char title[MSG_SIZ];
10363
10364     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10365         strcpy(title, _("Edit comment"));
10366     } else {
10367         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10368                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10369                 parseList[currentMove - 1]);
10370     }
10371
10372     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10373 }
10374
10375
10376 void
10377 EditTagsEvent()
10378 {
10379     char *tags = PGNTags(&gameInfo);
10380     EditTagsPopUp(tags);
10381     free(tags);
10382 }
10383
10384 void
10385 AnalyzeModeEvent()
10386 {
10387     if (appData.noChessProgram || gameMode == AnalyzeMode)
10388       return;
10389
10390     if (gameMode != AnalyzeFile) {
10391         if (!appData.icsEngineAnalyze) {
10392                EditGameEvent();
10393                if (gameMode != EditGame) return;
10394         }
10395         ResurrectChessProgram();
10396         SendToProgram("analyze\n", &first);
10397         first.analyzing = TRUE;
10398         /*first.maybeThinking = TRUE;*/
10399         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10400         AnalysisPopUp(_("Analysis"),
10401                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10402     }
10403     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10404     pausing = FALSE;
10405     ModeHighlight();
10406     SetGameInfo();
10407
10408     StartAnalysisClock();
10409     GetTimeMark(&lastNodeCountTime);
10410     lastNodeCount = 0;
10411 }
10412
10413 void
10414 AnalyzeFileEvent()
10415 {
10416     if (appData.noChessProgram || gameMode == AnalyzeFile)
10417       return;
10418
10419     if (gameMode != AnalyzeMode) {
10420         EditGameEvent();
10421         if (gameMode != EditGame) return;
10422         ResurrectChessProgram();
10423         SendToProgram("analyze\n", &first);
10424         first.analyzing = TRUE;
10425         /*first.maybeThinking = TRUE;*/
10426         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10427         AnalysisPopUp(_("Analysis"),
10428                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10429     }
10430     gameMode = AnalyzeFile;
10431     pausing = FALSE;
10432     ModeHighlight();
10433     SetGameInfo();
10434
10435     StartAnalysisClock();
10436     GetTimeMark(&lastNodeCountTime);
10437     lastNodeCount = 0;
10438 }
10439
10440 void
10441 MachineWhiteEvent()
10442 {
10443     char buf[MSG_SIZ];
10444     char *bookHit = NULL;
10445
10446     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10447       return;
10448
10449
10450     if (gameMode == PlayFromGameFile ||
10451         gameMode == TwoMachinesPlay  ||
10452         gameMode == Training         ||
10453         gameMode == AnalyzeMode      ||
10454         gameMode == EndOfGame)
10455         EditGameEvent();
10456
10457     if (gameMode == EditPosition)
10458         EditPositionDone();
10459
10460     if (!WhiteOnMove(currentMove)) {
10461         DisplayError(_("It is not White's turn"), 0);
10462         return;
10463     }
10464
10465     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10466       ExitAnalyzeMode();
10467
10468     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10469         gameMode == AnalyzeFile)
10470         TruncateGame();
10471
10472     ResurrectChessProgram();    /* in case it isn't running */
10473     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10474         gameMode = MachinePlaysWhite;
10475         ResetClocks();
10476     } else
10477     gameMode = MachinePlaysWhite;
10478     pausing = FALSE;
10479     ModeHighlight();
10480     SetGameInfo();
10481     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10482     DisplayTitle(buf);
10483     if (first.sendName) {
10484       sprintf(buf, "name %s\n", gameInfo.black);
10485       SendToProgram(buf, &first);
10486     }
10487     if (first.sendTime) {
10488       if (first.useColors) {
10489         SendToProgram("black\n", &first); /*gnu kludge*/
10490       }
10491       SendTimeRemaining(&first, TRUE);
10492     }
10493     if (first.useColors) {
10494       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10495     }
10496     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10497     SetMachineThinkingEnables();
10498     first.maybeThinking = TRUE;
10499     StartClocks();
10500
10501     if (appData.autoFlipView && !flipView) {
10502       flipView = !flipView;
10503       DrawPosition(FALSE, NULL);
10504       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10505     }
10506
10507     if(bookHit) { // [HGM] book: simulate book reply
10508         static char bookMove[MSG_SIZ]; // a bit generous?
10509
10510         programStats.nodes = programStats.depth = programStats.time =
10511         programStats.score = programStats.got_only_move = 0;
10512         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10513
10514         strcpy(bookMove, "move ");
10515         strcat(bookMove, bookHit);
10516         HandleMachineMove(bookMove, &first);
10517     }
10518 }
10519
10520 void
10521 MachineBlackEvent()
10522 {
10523     char buf[MSG_SIZ];
10524    char *bookHit = NULL;
10525
10526     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10527         return;
10528
10529
10530     if (gameMode == PlayFromGameFile ||
10531         gameMode == TwoMachinesPlay  ||
10532         gameMode == Training         ||
10533         gameMode == AnalyzeMode      ||
10534         gameMode == EndOfGame)
10535         EditGameEvent();
10536
10537     if (gameMode == EditPosition)
10538         EditPositionDone();
10539
10540     if (WhiteOnMove(currentMove)) {
10541         DisplayError(_("It is not Black's turn"), 0);
10542         return;
10543     }
10544
10545     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10546       ExitAnalyzeMode();
10547
10548     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10549         gameMode == AnalyzeFile)
10550         TruncateGame();
10551
10552     ResurrectChessProgram();    /* in case it isn't running */
10553     gameMode = MachinePlaysBlack;
10554     pausing = FALSE;
10555     ModeHighlight();
10556     SetGameInfo();
10557     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10558     DisplayTitle(buf);
10559     if (first.sendName) {
10560       sprintf(buf, "name %s\n", gameInfo.white);
10561       SendToProgram(buf, &first);
10562     }
10563     if (first.sendTime) {
10564       if (first.useColors) {
10565         SendToProgram("white\n", &first); /*gnu kludge*/
10566       }
10567       SendTimeRemaining(&first, FALSE);
10568     }
10569     if (first.useColors) {
10570       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10571     }
10572     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10573     SetMachineThinkingEnables();
10574     first.maybeThinking = TRUE;
10575     StartClocks();
10576
10577     if (appData.autoFlipView && flipView) {
10578       flipView = !flipView;
10579       DrawPosition(FALSE, NULL);
10580       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10581     }
10582     if(bookHit) { // [HGM] book: simulate book reply
10583         static char bookMove[MSG_SIZ]; // a bit generous?
10584
10585         programStats.nodes = programStats.depth = programStats.time =
10586         programStats.score = programStats.got_only_move = 0;
10587         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10588
10589         strcpy(bookMove, "move ");
10590         strcat(bookMove, bookHit);
10591         HandleMachineMove(bookMove, &first);
10592     }
10593 }
10594
10595
10596 void
10597 DisplayTwoMachinesTitle()
10598 {
10599     char buf[MSG_SIZ];
10600     if (appData.matchGames > 0) {
10601         if (first.twoMachinesColor[0] == 'w') {
10602             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10603                     gameInfo.white, gameInfo.black,
10604                     first.matchWins, second.matchWins,
10605                     matchGame - 1 - (first.matchWins + second.matchWins));
10606         } else {
10607             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10608                     gameInfo.white, gameInfo.black,
10609                     second.matchWins, first.matchWins,
10610                     matchGame - 1 - (first.matchWins + second.matchWins));
10611         }
10612     } else {
10613         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10614     }
10615     DisplayTitle(buf);
10616 }
10617
10618 void
10619 TwoMachinesEvent P((void))
10620 {
10621     int i;
10622     char buf[MSG_SIZ];
10623     ChessProgramState *onmove;
10624     char *bookHit = NULL;
10625
10626     if (appData.noChessProgram) return;
10627
10628     switch (gameMode) {
10629       case TwoMachinesPlay:
10630         return;
10631       case MachinePlaysWhite:
10632       case MachinePlaysBlack:
10633         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10634             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10635             return;
10636         }
10637         /* fall through */
10638       case BeginningOfGame:
10639       case PlayFromGameFile:
10640       case EndOfGame:
10641         EditGameEvent();
10642         if (gameMode != EditGame) return;
10643         break;
10644       case EditPosition:
10645         EditPositionDone();
10646         break;
10647       case AnalyzeMode:
10648       case AnalyzeFile:
10649         ExitAnalyzeMode();
10650         break;
10651       case EditGame:
10652       default:
10653         break;
10654     }
10655
10656     forwardMostMove = currentMove;
10657     ResurrectChessProgram();    /* in case first program isn't running */
10658
10659     if (second.pr == NULL) {
10660         StartChessProgram(&second);
10661         if (second.protocolVersion == 1) {
10662           TwoMachinesEventIfReady();
10663         } else {
10664           /* kludge: allow timeout for initial "feature" command */
10665           FreezeUI();
10666           DisplayMessage("", _("Starting second chess program"));
10667           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10668         }
10669         return;
10670     }
10671     DisplayMessage("", "");
10672     InitChessProgram(&second, FALSE);
10673     SendToProgram("force\n", &second);
10674     if (startedFromSetupPosition) {
10675         SendBoard(&second, backwardMostMove);
10676     if (appData.debugMode) {
10677         fprintf(debugFP, "Two Machines\n");
10678     }
10679     }
10680     for (i = backwardMostMove; i < forwardMostMove; i++) {
10681         SendMoveToProgram(i, &second);
10682     }
10683
10684     gameMode = TwoMachinesPlay;
10685     pausing = FALSE;
10686     ModeHighlight();
10687     SetGameInfo();
10688     DisplayTwoMachinesTitle();
10689     firstMove = TRUE;
10690     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10691         onmove = &first;
10692     } else {
10693         onmove = &second;
10694     }
10695
10696     SendToProgram(first.computerString, &first);
10697     if (first.sendName) {
10698       sprintf(buf, "name %s\n", second.tidy);
10699       SendToProgram(buf, &first);
10700     }
10701     SendToProgram(second.computerString, &second);
10702     if (second.sendName) {
10703       sprintf(buf, "name %s\n", first.tidy);
10704       SendToProgram(buf, &second);
10705     }
10706
10707     ResetClocks();
10708     if (!first.sendTime || !second.sendTime) {
10709         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10710         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10711     }
10712     if (onmove->sendTime) {
10713       if (onmove->useColors) {
10714         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10715       }
10716       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10717     }
10718     if (onmove->useColors) {
10719       SendToProgram(onmove->twoMachinesColor, onmove);
10720     }
10721     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10722 //    SendToProgram("go\n", onmove);
10723     onmove->maybeThinking = TRUE;
10724     SetMachineThinkingEnables();
10725
10726     StartClocks();
10727
10728     if(bookHit) { // [HGM] book: simulate book reply
10729         static char bookMove[MSG_SIZ]; // a bit generous?
10730
10731         programStats.nodes = programStats.depth = programStats.time =
10732         programStats.score = programStats.got_only_move = 0;
10733         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10734
10735         strcpy(bookMove, "move ");
10736         strcat(bookMove, bookHit);
10737         HandleMachineMove(bookMove, &first);
10738     }
10739 }
10740
10741 void
10742 TrainingEvent()
10743 {
10744     if (gameMode == Training) {
10745       SetTrainingModeOff();
10746       gameMode = PlayFromGameFile;
10747       DisplayMessage("", _("Training mode off"));
10748     } else {
10749       gameMode = Training;
10750       animateTraining = appData.animate;
10751
10752       /* make sure we are not already at the end of the game */
10753       if (currentMove < forwardMostMove) {
10754         SetTrainingModeOn();
10755         DisplayMessage("", _("Training mode on"));
10756       } else {
10757         gameMode = PlayFromGameFile;
10758         DisplayError(_("Already at end of game"), 0);
10759       }
10760     }
10761     ModeHighlight();
10762 }
10763
10764 void
10765 IcsClientEvent()
10766 {
10767     if (!appData.icsActive) return;
10768     switch (gameMode) {
10769       case IcsPlayingWhite:
10770       case IcsPlayingBlack:
10771       case IcsObserving:
10772       case IcsIdle:
10773       case BeginningOfGame:
10774       case IcsExamining:
10775         return;
10776
10777       case EditGame:
10778         break;
10779
10780       case EditPosition:
10781         EditPositionDone();
10782         break;
10783
10784       case AnalyzeMode:
10785       case AnalyzeFile:
10786         ExitAnalyzeMode();
10787         break;
10788
10789       default:
10790         EditGameEvent();
10791         break;
10792     }
10793
10794     gameMode = IcsIdle;
10795     ModeHighlight();
10796     return;
10797 }
10798
10799
10800 void
10801 EditGameEvent()
10802 {
10803     int i;
10804
10805     switch (gameMode) {
10806       case Training:
10807         SetTrainingModeOff();
10808         break;
10809       case MachinePlaysWhite:
10810       case MachinePlaysBlack:
10811       case BeginningOfGame:
10812         SendToProgram("force\n", &first);
10813         SetUserThinkingEnables();
10814         break;
10815       case PlayFromGameFile:
10816         (void) StopLoadGameTimer();
10817         if (gameFileFP != NULL) {
10818             gameFileFP = NULL;
10819         }
10820         break;
10821       case EditPosition:
10822         EditPositionDone();
10823         break;
10824       case AnalyzeMode:
10825       case AnalyzeFile:
10826         ExitAnalyzeMode();
10827         SendToProgram("force\n", &first);
10828         break;
10829       case TwoMachinesPlay:
10830         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10831         ResurrectChessProgram();
10832         SetUserThinkingEnables();
10833         break;
10834       case EndOfGame:
10835         ResurrectChessProgram();
10836         break;
10837       case IcsPlayingBlack:
10838       case IcsPlayingWhite:
10839         DisplayError(_("Warning: You are still playing a game"), 0);
10840         break;
10841       case IcsObserving:
10842         DisplayError(_("Warning: You are still observing a game"), 0);
10843         break;
10844       case IcsExamining:
10845         DisplayError(_("Warning: You are still examining a game"), 0);
10846         break;
10847       case IcsIdle:
10848         break;
10849       case EditGame:
10850       default:
10851         return;
10852     }
10853
10854     pausing = FALSE;
10855     StopClocks();
10856     first.offeredDraw = second.offeredDraw = 0;
10857
10858     if (gameMode == PlayFromGameFile) {
10859         whiteTimeRemaining = timeRemaining[0][currentMove];
10860         blackTimeRemaining = timeRemaining[1][currentMove];
10861         DisplayTitle("");
10862     }
10863
10864     if (gameMode == MachinePlaysWhite ||
10865         gameMode == MachinePlaysBlack ||
10866         gameMode == TwoMachinesPlay ||
10867         gameMode == EndOfGame) {
10868         i = forwardMostMove;
10869         while (i > currentMove) {
10870             SendToProgram("undo\n", &first);
10871             i--;
10872         }
10873         whiteTimeRemaining = timeRemaining[0][currentMove];
10874         blackTimeRemaining = timeRemaining[1][currentMove];
10875         DisplayBothClocks();
10876         if (whiteFlag || blackFlag) {
10877             whiteFlag = blackFlag = 0;
10878         }
10879         DisplayTitle("");
10880     }
10881
10882     gameMode = EditGame;
10883     ModeHighlight();
10884     SetGameInfo();
10885 }
10886
10887
10888 void
10889 EditPositionEvent()
10890 {
10891     if (gameMode == EditPosition) {
10892         EditGameEvent();
10893         return;
10894     }
10895
10896     EditGameEvent();
10897     if (gameMode != EditGame) return;
10898
10899     gameMode = EditPosition;
10900     ModeHighlight();
10901     SetGameInfo();
10902     if (currentMove > 0)
10903       CopyBoard(boards[0], boards[currentMove]);
10904
10905     blackPlaysFirst = !WhiteOnMove(currentMove);
10906     ResetClocks();
10907     currentMove = forwardMostMove = backwardMostMove = 0;
10908     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10909     DisplayMove(-1);
10910 }
10911
10912 void
10913 ExitAnalyzeMode()
10914 {
10915     /* [DM] icsEngineAnalyze - possible call from other functions */
10916     if (appData.icsEngineAnalyze) {
10917         appData.icsEngineAnalyze = FALSE;
10918
10919         DisplayMessage("",_("Close ICS engine analyze..."));
10920     }
10921     if (first.analysisSupport && first.analyzing) {
10922       SendToProgram("exit\n", &first);
10923       first.analyzing = FALSE;
10924     }
10925     AnalysisPopDown();
10926     thinkOutput[0] = NULLCHAR;
10927 }
10928
10929 void
10930 EditPositionDone()
10931 {
10932     startedFromSetupPosition = TRUE;
10933     InitChessProgram(&first, FALSE);
10934     SendToProgram("force\n", &first);
10935     if (blackPlaysFirst) {
10936         strcpy(moveList[0], "");
10937         strcpy(parseList[0], "");
10938         currentMove = forwardMostMove = backwardMostMove = 1;
10939         CopyBoard(boards[1], boards[0]);
10940         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10941         { int i;
10942           epStatus[1] = epStatus[0];
10943           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10944         }
10945     } else {
10946         currentMove = forwardMostMove = backwardMostMove = 0;
10947     }
10948     SendBoard(&first, forwardMostMove);
10949     if (appData.debugMode) {
10950         fprintf(debugFP, "EditPosDone\n");
10951     }
10952     DisplayTitle("");
10953     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10954     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10955     gameMode = EditGame;
10956     ModeHighlight();
10957     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10958     ClearHighlights(); /* [AS] */
10959 }
10960
10961 /* Pause for `ms' milliseconds */
10962 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10963 void
10964 TimeDelay(ms)
10965      long ms;
10966 {
10967     TimeMark m1, m2;
10968
10969     GetTimeMark(&m1);
10970     do {
10971         GetTimeMark(&m2);
10972     } while (SubtractTimeMarks(&m2, &m1) < ms);
10973 }
10974
10975 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10976 void
10977 SendMultiLineToICS(buf)
10978      char *buf;
10979 {
10980     char temp[MSG_SIZ+1], *p;
10981     int len;
10982
10983     len = strlen(buf);
10984     if (len > MSG_SIZ)
10985       len = MSG_SIZ;
10986
10987     strncpy(temp, buf, len);
10988     temp[len] = 0;
10989
10990     p = temp;
10991     while (*p) {
10992         if (*p == '\n' || *p == '\r')
10993           *p = ' ';
10994         ++p;
10995     }
10996
10997     strcat(temp, "\n");
10998     SendToICS(temp);
10999     SendToPlayer(temp, strlen(temp));
11000 }
11001
11002 void
11003 SetWhiteToPlayEvent()
11004 {
11005     if (gameMode == EditPosition) {
11006         blackPlaysFirst = FALSE;
11007         DisplayBothClocks();    /* works because currentMove is 0 */
11008     } else if (gameMode == IcsExamining) {
11009         SendToICS(ics_prefix);
11010         SendToICS("tomove white\n");
11011     }
11012 }
11013
11014 void
11015 SetBlackToPlayEvent()
11016 {
11017     if (gameMode == EditPosition) {
11018         blackPlaysFirst = TRUE;
11019         currentMove = 1;        /* kludge */
11020         DisplayBothClocks();
11021         currentMove = 0;
11022     } else if (gameMode == IcsExamining) {
11023         SendToICS(ics_prefix);
11024         SendToICS("tomove black\n");
11025     }
11026 }
11027
11028 void
11029 EditPositionMenuEvent(selection, x, y)
11030      ChessSquare selection;
11031      int x, y;
11032 {
11033     char buf[MSG_SIZ];
11034     ChessSquare piece = boards[0][y][x];
11035
11036     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11037
11038     switch (selection) {
11039       case ClearBoard:
11040         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11041             SendToICS(ics_prefix);
11042             SendToICS("bsetup clear\n");
11043         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11044             SendToICS(ics_prefix);
11045             SendToICS("clearboard\n");
11046         } else {
11047             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11048                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11049                 for (y = 0; y < BOARD_HEIGHT; y++) {
11050                     if (gameMode == IcsExamining) {
11051                         if (boards[currentMove][y][x] != EmptySquare) {
11052                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11053                                     AAA + x, ONE + y);
11054                             SendToICS(buf);
11055                         }
11056                     } else {
11057                         boards[0][y][x] = p;
11058                     }
11059                 }
11060             }
11061         }
11062         if (gameMode == EditPosition) {
11063             DrawPosition(FALSE, boards[0]);
11064         }
11065         break;
11066
11067       case WhitePlay:
11068         SetWhiteToPlayEvent();
11069         break;
11070
11071       case BlackPlay:
11072         SetBlackToPlayEvent();
11073         break;
11074
11075       case EmptySquare:
11076         if (gameMode == IcsExamining) {
11077             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11078             SendToICS(buf);
11079         } else {
11080             boards[0][y][x] = EmptySquare;
11081             DrawPosition(FALSE, boards[0]);
11082         }
11083         break;
11084
11085       case PromotePiece:
11086         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11087            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11088             selection = (ChessSquare) (PROMOTED piece);
11089         } else if(piece == EmptySquare) selection = WhiteSilver;
11090         else selection = (ChessSquare)((int)piece - 1);
11091         goto defaultlabel;
11092
11093       case DemotePiece:
11094         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11095            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11096             selection = (ChessSquare) (DEMOTED piece);
11097         } else if(piece == EmptySquare) selection = BlackSilver;
11098         else selection = (ChessSquare)((int)piece + 1);
11099         goto defaultlabel;
11100
11101       case WhiteQueen:
11102       case BlackQueen:
11103         if(gameInfo.variant == VariantShatranj ||
11104            gameInfo.variant == VariantXiangqi  ||
11105            gameInfo.variant == VariantCourier    )
11106             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11107         goto defaultlabel;
11108
11109       case WhiteKing:
11110       case BlackKing:
11111         if(gameInfo.variant == VariantXiangqi)
11112             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11113         if(gameInfo.variant == VariantKnightmate)
11114             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11115       default:
11116         defaultlabel:
11117         if (gameMode == IcsExamining) {
11118             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11119                     PieceToChar(selection), AAA + x, ONE + y);
11120             SendToICS(buf);
11121         } else {
11122             boards[0][y][x] = selection;
11123             DrawPosition(FALSE, boards[0]);
11124         }
11125         break;
11126     }
11127 }
11128
11129
11130 void
11131 DropMenuEvent(selection, x, y)
11132      ChessSquare selection;
11133      int x, y;
11134 {
11135     ChessMove moveType;
11136
11137     switch (gameMode) {
11138       case IcsPlayingWhite:
11139       case MachinePlaysBlack:
11140         if (!WhiteOnMove(currentMove)) {
11141             DisplayMoveError(_("It is Black's turn"));
11142             return;
11143         }
11144         moveType = WhiteDrop;
11145         break;
11146       case IcsPlayingBlack:
11147       case MachinePlaysWhite:
11148         if (WhiteOnMove(currentMove)) {
11149             DisplayMoveError(_("It is White's turn"));
11150             return;
11151         }
11152         moveType = BlackDrop;
11153         break;
11154       case EditGame:
11155         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11156         break;
11157       default:
11158         return;
11159     }
11160
11161     if (moveType == BlackDrop && selection < BlackPawn) {
11162       selection = (ChessSquare) ((int) selection
11163                                  + (int) BlackPawn - (int) WhitePawn);
11164     }
11165     if (boards[currentMove][y][x] != EmptySquare) {
11166         DisplayMoveError(_("That square is occupied"));
11167         return;
11168     }
11169
11170     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11171 }
11172
11173 void
11174 AcceptEvent()
11175 {
11176     /* Accept a pending offer of any kind from opponent */
11177
11178     if (appData.icsActive) {
11179         SendToICS(ics_prefix);
11180         SendToICS("accept\n");
11181     } else if (cmailMsgLoaded) {
11182         if (currentMove == cmailOldMove &&
11183             commentList[cmailOldMove] != NULL &&
11184             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11185                    "Black offers a draw" : "White offers a draw")) {
11186             TruncateGame();
11187             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11188             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11189         } else {
11190             DisplayError(_("There is no pending offer on this move"), 0);
11191             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11192         }
11193     } else {
11194         /* Not used for offers from chess program */
11195     }
11196 }
11197
11198 void
11199 DeclineEvent()
11200 {
11201     /* Decline a pending offer of any kind from opponent */
11202
11203     if (appData.icsActive) {
11204         SendToICS(ics_prefix);
11205         SendToICS("decline\n");
11206     } else if (cmailMsgLoaded) {
11207         if (currentMove == cmailOldMove &&
11208             commentList[cmailOldMove] != NULL &&
11209             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11210                    "Black offers a draw" : "White offers a draw")) {
11211 #ifdef NOTDEF
11212             AppendComment(cmailOldMove, "Draw declined");
11213             DisplayComment(cmailOldMove - 1, "Draw declined");
11214 #endif /*NOTDEF*/
11215         } else {
11216             DisplayError(_("There is no pending offer on this move"), 0);
11217         }
11218     } else {
11219         /* Not used for offers from chess program */
11220     }
11221 }
11222
11223 void
11224 RematchEvent()
11225 {
11226     /* Issue ICS rematch command */
11227     if (appData.icsActive) {
11228         SendToICS(ics_prefix);
11229         SendToICS("rematch\n");
11230     }
11231 }
11232
11233 void
11234 CallFlagEvent()
11235 {
11236     /* Call your opponent's flag (claim a win on time) */
11237     if (appData.icsActive) {
11238         SendToICS(ics_prefix);
11239         SendToICS("flag\n");
11240     } else {
11241         switch (gameMode) {
11242           default:
11243             return;
11244           case MachinePlaysWhite:
11245             if (whiteFlag) {
11246                 if (blackFlag)
11247                   GameEnds(GameIsDrawn, "Both players ran out of time",
11248                            GE_PLAYER);
11249                 else
11250                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11251             } else {
11252                 DisplayError(_("Your opponent is not out of time"), 0);
11253             }
11254             break;
11255           case MachinePlaysBlack:
11256             if (blackFlag) {
11257                 if (whiteFlag)
11258                   GameEnds(GameIsDrawn, "Both players ran out of time",
11259                            GE_PLAYER);
11260                 else
11261                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11262             } else {
11263                 DisplayError(_("Your opponent is not out of time"), 0);
11264             }
11265             break;
11266         }
11267     }
11268 }
11269
11270 void
11271 DrawEvent()
11272 {
11273     /* Offer draw or accept pending draw offer from opponent */
11274
11275     if (appData.icsActive) {
11276         /* Note: tournament rules require draw offers to be
11277            made after you make your move but before you punch
11278            your clock.  Currently ICS doesn't let you do that;
11279            instead, you immediately punch your clock after making
11280            a move, but you can offer a draw at any time. */
11281
11282         SendToICS(ics_prefix);
11283         SendToICS("draw\n");
11284     } else if (cmailMsgLoaded) {
11285         if (currentMove == cmailOldMove &&
11286             commentList[cmailOldMove] != NULL &&
11287             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11288                    "Black offers a draw" : "White offers a draw")) {
11289             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11290             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11291         } else if (currentMove == cmailOldMove + 1) {
11292             char *offer = WhiteOnMove(cmailOldMove) ?
11293               "White offers a draw" : "Black offers a draw";
11294             AppendComment(currentMove, offer);
11295             DisplayComment(currentMove - 1, offer);
11296             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11297         } else {
11298             DisplayError(_("You must make your move before offering a draw"), 0);
11299             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11300         }
11301     } else if (first.offeredDraw) {
11302         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11303     } else {
11304         if (first.sendDrawOffers) {
11305             SendToProgram("draw\n", &first);
11306             userOfferedDraw = TRUE;
11307         }
11308     }
11309 }
11310
11311 void
11312 AdjournEvent()
11313 {
11314     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11315
11316     if (appData.icsActive) {
11317         SendToICS(ics_prefix);
11318         SendToICS("adjourn\n");
11319     } else {
11320         /* Currently GNU Chess doesn't offer or accept Adjourns */
11321     }
11322 }
11323
11324
11325 void
11326 AbortEvent()
11327 {
11328     /* Offer Abort or accept pending Abort offer from opponent */
11329
11330     if (appData.icsActive) {
11331         SendToICS(ics_prefix);
11332         SendToICS("abort\n");
11333     } else {
11334         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11335     }
11336 }
11337
11338 void
11339 ResignEvent()
11340 {
11341     /* Resign.  You can do this even if it's not your turn. */
11342
11343     if (appData.icsActive) {
11344         SendToICS(ics_prefix);
11345         SendToICS("resign\n");
11346     } else {
11347         switch (gameMode) {
11348           case MachinePlaysWhite:
11349             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11350             break;
11351           case MachinePlaysBlack:
11352             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11353             break;
11354           case EditGame:
11355             if (cmailMsgLoaded) {
11356                 TruncateGame();
11357                 if (WhiteOnMove(cmailOldMove)) {
11358                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11359                 } else {
11360                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11361                 }
11362                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11363             }
11364             break;
11365           default:
11366             break;
11367         }
11368     }
11369 }
11370
11371
11372 void
11373 StopObservingEvent()
11374 {
11375     /* Stop observing current games */
11376     SendToICS(ics_prefix);
11377     SendToICS("unobserve\n");
11378 }
11379
11380 void
11381 StopExaminingEvent()
11382 {
11383     /* Stop observing current game */
11384     SendToICS(ics_prefix);
11385     SendToICS("unexamine\n");
11386 }
11387
11388 void
11389 ForwardInner(target)
11390      int target;
11391 {
11392     int limit;
11393
11394     if (appData.debugMode)
11395         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11396                 target, currentMove, forwardMostMove);
11397
11398     if (gameMode == EditPosition)
11399       return;
11400
11401     if (gameMode == PlayFromGameFile && !pausing)
11402       PauseEvent();
11403
11404     if (gameMode == IcsExamining && pausing)
11405       limit = pauseExamForwardMostMove;
11406     else
11407       limit = forwardMostMove;
11408
11409     if (target > limit) target = limit;
11410
11411     if (target > 0 && moveList[target - 1][0]) {
11412         int fromX, fromY, toX, toY;
11413         toX = moveList[target - 1][2] - AAA;
11414         toY = moveList[target - 1][3] - ONE;
11415         if (moveList[target - 1][1] == '@') {
11416             if (appData.highlightLastMove) {
11417                 SetHighlights(-1, -1, toX, toY);
11418             }
11419         } else {
11420             fromX = moveList[target - 1][0] - AAA;
11421             fromY = moveList[target - 1][1] - ONE;
11422             if (target == currentMove + 1) {
11423                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11424             }
11425             if (appData.highlightLastMove) {
11426                 SetHighlights(fromX, fromY, toX, toY);
11427             }
11428         }
11429     }
11430     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11431         gameMode == Training || gameMode == PlayFromGameFile ||
11432         gameMode == AnalyzeFile) {
11433         while (currentMove < target) {
11434             SendMoveToProgram(currentMove++, &first);
11435         }
11436     } else {
11437         currentMove = target;
11438     }
11439
11440     if (gameMode == EditGame || gameMode == EndOfGame) {
11441         whiteTimeRemaining = timeRemaining[0][currentMove];
11442         blackTimeRemaining = timeRemaining[1][currentMove];
11443     }
11444     DisplayBothClocks();
11445     DisplayMove(currentMove - 1);
11446     DrawPosition(FALSE, boards[currentMove]);
11447     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11448     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11449         DisplayComment(currentMove - 1, commentList[currentMove]);
11450     }
11451 }
11452
11453
11454 void
11455 ForwardEvent()
11456 {
11457     if (gameMode == IcsExamining && !pausing) {
11458         SendToICS(ics_prefix);
11459         SendToICS("forward\n");
11460     } else {
11461         ForwardInner(currentMove + 1);
11462     }
11463 }
11464
11465 void
11466 ToEndEvent()
11467 {
11468     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11469         /* to optimze, we temporarily turn off analysis mode while we feed
11470          * the remaining moves to the engine. Otherwise we get analysis output
11471          * after each move.
11472          */
11473         if (first.analysisSupport) {
11474           SendToProgram("exit\nforce\n", &first);
11475           first.analyzing = FALSE;
11476         }
11477     }
11478
11479     if (gameMode == IcsExamining && !pausing) {
11480         SendToICS(ics_prefix);
11481         SendToICS("forward 999999\n");
11482     } else {
11483         ForwardInner(forwardMostMove);
11484     }
11485
11486     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11487         /* we have fed all the moves, so reactivate analysis mode */
11488         SendToProgram("analyze\n", &first);
11489         first.analyzing = TRUE;
11490         /*first.maybeThinking = TRUE;*/
11491         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11492     }
11493 }
11494
11495 void
11496 BackwardInner(target)
11497      int target;
11498 {
11499     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11500
11501     if (appData.debugMode)
11502         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11503                 target, currentMove, forwardMostMove);
11504
11505     if (gameMode == EditPosition) return;
11506     if (currentMove <= backwardMostMove) {
11507         ClearHighlights();
11508         DrawPosition(full_redraw, boards[currentMove]);
11509         return;
11510     }
11511     if (gameMode == PlayFromGameFile && !pausing)
11512       PauseEvent();
11513
11514     if (moveList[target][0]) {
11515         int fromX, fromY, toX, toY;
11516         toX = moveList[target][2] - AAA;
11517         toY = moveList[target][3] - ONE;
11518         if (moveList[target][1] == '@') {
11519             if (appData.highlightLastMove) {
11520                 SetHighlights(-1, -1, toX, toY);
11521             }
11522         } else {
11523             fromX = moveList[target][0] - AAA;
11524             fromY = moveList[target][1] - ONE;
11525             if (target == currentMove - 1) {
11526                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11527             }
11528             if (appData.highlightLastMove) {
11529                 SetHighlights(fromX, fromY, toX, toY);
11530             }
11531         }
11532     }
11533     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11534         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11535         while (currentMove > target) {
11536             SendToProgram("undo\n", &first);
11537             currentMove--;
11538         }
11539     } else {
11540         currentMove = target;
11541     }
11542
11543     if (gameMode == EditGame || gameMode == EndOfGame) {
11544         whiteTimeRemaining = timeRemaining[0][currentMove];
11545         blackTimeRemaining = timeRemaining[1][currentMove];
11546     }
11547     DisplayBothClocks();
11548     DisplayMove(currentMove - 1);
11549     DrawPosition(full_redraw, boards[currentMove]);
11550     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11551     // [HGM] PV info: routine tests if comment empty
11552     DisplayComment(currentMove - 1, commentList[currentMove]);
11553 }
11554
11555 void
11556 BackwardEvent()
11557 {
11558     if (gameMode == IcsExamining && !pausing) {
11559         SendToICS(ics_prefix);
11560         SendToICS("backward\n");
11561     } else {
11562         BackwardInner(currentMove - 1);
11563     }
11564 }
11565
11566 void
11567 ToStartEvent()
11568 {
11569     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11570         /* to optimze, we temporarily turn off analysis mode while we undo
11571          * all the moves. Otherwise we get analysis output after each undo.
11572          */
11573         if (first.analysisSupport) {
11574           SendToProgram("exit\nforce\n", &first);
11575           first.analyzing = FALSE;
11576         }
11577     }
11578
11579     if (gameMode == IcsExamining && !pausing) {
11580         SendToICS(ics_prefix);
11581         SendToICS("backward 999999\n");
11582     } else {
11583         BackwardInner(backwardMostMove);
11584     }
11585
11586     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11587         /* we have fed all the moves, so reactivate analysis mode */
11588         SendToProgram("analyze\n", &first);
11589         first.analyzing = TRUE;
11590         /*first.maybeThinking = TRUE;*/
11591         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11592     }
11593 }
11594
11595 void
11596 ToNrEvent(int to)
11597 {
11598   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11599   if (to >= forwardMostMove) to = forwardMostMove;
11600   if (to <= backwardMostMove) to = backwardMostMove;
11601   if (to < currentMove) {
11602     BackwardInner(to);
11603   } else {
11604     ForwardInner(to);
11605   }
11606 }
11607
11608 void
11609 RevertEvent()
11610 {
11611     if (gameMode != IcsExamining) {
11612         DisplayError(_("You are not examining a game"), 0);
11613         return;
11614     }
11615     if (pausing) {
11616         DisplayError(_("You can't revert while pausing"), 0);
11617         return;
11618     }
11619     SendToICS(ics_prefix);
11620     SendToICS("revert\n");
11621 }
11622
11623 void
11624 RetractMoveEvent()
11625 {
11626     switch (gameMode) {
11627       case MachinePlaysWhite:
11628       case MachinePlaysBlack:
11629         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11630             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11631             return;
11632         }
11633         if (forwardMostMove < 2) return;
11634         currentMove = forwardMostMove = forwardMostMove - 2;
11635         whiteTimeRemaining = timeRemaining[0][currentMove];
11636         blackTimeRemaining = timeRemaining[1][currentMove];
11637         DisplayBothClocks();
11638         DisplayMove(currentMove - 1);
11639         ClearHighlights();/*!! could figure this out*/
11640         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11641         SendToProgram("remove\n", &first);
11642         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11643         break;
11644
11645       case BeginningOfGame:
11646       default:
11647         break;
11648
11649       case IcsPlayingWhite:
11650       case IcsPlayingBlack:
11651         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11652             SendToICS(ics_prefix);
11653             SendToICS("takeback 2\n");
11654         } else {
11655             SendToICS(ics_prefix);
11656             SendToICS("takeback 1\n");
11657         }
11658         break;
11659     }
11660 }
11661
11662 void
11663 MoveNowEvent()
11664 {
11665     ChessProgramState *cps;
11666
11667     switch (gameMode) {
11668       case MachinePlaysWhite:
11669         if (!WhiteOnMove(forwardMostMove)) {
11670             DisplayError(_("It is your turn"), 0);
11671             return;
11672         }
11673         cps = &first;
11674         break;
11675       case MachinePlaysBlack:
11676         if (WhiteOnMove(forwardMostMove)) {
11677             DisplayError(_("It is your turn"), 0);
11678             return;
11679         }
11680         cps = &first;
11681         break;
11682       case TwoMachinesPlay:
11683         if (WhiteOnMove(forwardMostMove) ==
11684             (first.twoMachinesColor[0] == 'w')) {
11685             cps = &first;
11686         } else {
11687             cps = &second;
11688         }
11689         break;
11690       case BeginningOfGame:
11691       default:
11692         return;
11693     }
11694     SendToProgram("?\n", cps);
11695 }
11696
11697 void
11698 TruncateGameEvent()
11699 {
11700     EditGameEvent();
11701     if (gameMode != EditGame) return;
11702     TruncateGame();
11703 }
11704
11705 void
11706 TruncateGame()
11707 {
11708     if (forwardMostMove > currentMove) {
11709         if (gameInfo.resultDetails != NULL) {
11710             free(gameInfo.resultDetails);
11711             gameInfo.resultDetails = NULL;
11712             gameInfo.result = GameUnfinished;
11713         }
11714         forwardMostMove = currentMove;
11715         HistorySet(parseList, backwardMostMove, forwardMostMove,
11716                    currentMove-1);
11717     }
11718 }
11719
11720 void
11721 HintEvent()
11722 {
11723     if (appData.noChessProgram) return;
11724     switch (gameMode) {
11725       case MachinePlaysWhite:
11726         if (WhiteOnMove(forwardMostMove)) {
11727             DisplayError(_("Wait until your turn"), 0);
11728             return;
11729         }
11730         break;
11731       case BeginningOfGame:
11732       case MachinePlaysBlack:
11733         if (!WhiteOnMove(forwardMostMove)) {
11734             DisplayError(_("Wait until your turn"), 0);
11735             return;
11736         }
11737         break;
11738       default:
11739         DisplayError(_("No hint available"), 0);
11740         return;
11741     }
11742     SendToProgram("hint\n", &first);
11743     hintRequested = TRUE;
11744 }
11745
11746 void
11747 BookEvent()
11748 {
11749     if (appData.noChessProgram) return;
11750     switch (gameMode) {
11751       case MachinePlaysWhite:
11752         if (WhiteOnMove(forwardMostMove)) {
11753             DisplayError(_("Wait until your turn"), 0);
11754             return;
11755         }
11756         break;
11757       case BeginningOfGame:
11758       case MachinePlaysBlack:
11759         if (!WhiteOnMove(forwardMostMove)) {
11760             DisplayError(_("Wait until your turn"), 0);
11761             return;
11762         }
11763         break;
11764       case EditPosition:
11765         EditPositionDone();
11766         break;
11767       case TwoMachinesPlay:
11768         return;
11769       default:
11770         break;
11771     }
11772     SendToProgram("bk\n", &first);
11773     bookOutput[0] = NULLCHAR;
11774     bookRequested = TRUE;
11775 }
11776
11777 void
11778 AboutGameEvent()
11779 {
11780     char *tags = PGNTags(&gameInfo);
11781     TagsPopUp(tags, CmailMsg());
11782     free(tags);
11783 }
11784
11785 /* end button procedures */
11786
11787 void
11788 PrintPosition(fp, move)
11789      FILE *fp;
11790      int move;
11791 {
11792     int i, j;
11793
11794     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11795         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11796             char c = PieceToChar(boards[move][i][j]);
11797             fputc(c == 'x' ? '.' : c, fp);
11798             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11799         }
11800     }
11801     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11802       fprintf(fp, "white to play\n");
11803     else
11804       fprintf(fp, "black to play\n");
11805 }
11806
11807 void
11808 PrintOpponents(fp)
11809      FILE *fp;
11810 {
11811     if (gameInfo.white != NULL) {
11812         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11813     } else {
11814         fprintf(fp, "\n");
11815     }
11816 }
11817
11818 /* Find last component of program's own name, using some heuristics */
11819 void
11820 TidyProgramName(prog, host, buf)
11821      char *prog, *host, buf[MSG_SIZ];
11822 {
11823     char *p, *q;
11824     int local = (strcmp(host, "localhost") == 0);
11825     while (!local && (p = strchr(prog, ';')) != NULL) {
11826         p++;
11827         while (*p == ' ') p++;
11828         prog = p;
11829     }
11830     if (*prog == '"' || *prog == '\'') {
11831         q = strchr(prog + 1, *prog);
11832     } else {
11833         q = strchr(prog, ' ');
11834     }
11835     if (q == NULL) q = prog + strlen(prog);
11836     p = q;
11837     while (p >= prog && *p != '/' && *p != '\\') p--;
11838     p++;
11839     if(p == prog && *p == '"') p++;
11840     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11841     memcpy(buf, p, q - p);
11842     buf[q - p] = NULLCHAR;
11843     if (!local) {
11844         strcat(buf, "@");
11845         strcat(buf, host);
11846     }
11847 }
11848
11849 char *
11850 TimeControlTagValue()
11851 {
11852     char buf[MSG_SIZ];
11853     if (!appData.clockMode) {
11854         strcpy(buf, "-");
11855     } else if (movesPerSession > 0) {
11856         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11857     } else if (timeIncrement == 0) {
11858         sprintf(buf, "%ld", timeControl/1000);
11859     } else {
11860         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11861     }
11862     return StrSave(buf);
11863 }
11864
11865 void
11866 SetGameInfo()
11867 {
11868     /* This routine is used only for certain modes */
11869     VariantClass v = gameInfo.variant;
11870     ClearGameInfo(&gameInfo);
11871     gameInfo.variant = v;
11872
11873     switch (gameMode) {
11874       case MachinePlaysWhite:
11875         gameInfo.event = StrSave( appData.pgnEventHeader );
11876         gameInfo.site = StrSave(HostName());
11877         gameInfo.date = PGNDate();
11878         gameInfo.round = StrSave("-");
11879         gameInfo.white = StrSave(first.tidy);
11880         gameInfo.black = StrSave(UserName());
11881         gameInfo.timeControl = TimeControlTagValue();
11882         break;
11883
11884       case MachinePlaysBlack:
11885         gameInfo.event = StrSave( appData.pgnEventHeader );
11886         gameInfo.site = StrSave(HostName());
11887         gameInfo.date = PGNDate();
11888         gameInfo.round = StrSave("-");
11889         gameInfo.white = StrSave(UserName());
11890         gameInfo.black = StrSave(first.tidy);
11891         gameInfo.timeControl = TimeControlTagValue();
11892         break;
11893
11894       case TwoMachinesPlay:
11895         gameInfo.event = StrSave( appData.pgnEventHeader );
11896         gameInfo.site = StrSave(HostName());
11897         gameInfo.date = PGNDate();
11898         if (matchGame > 0) {
11899             char buf[MSG_SIZ];
11900             sprintf(buf, "%d", matchGame);
11901             gameInfo.round = StrSave(buf);
11902         } else {
11903             gameInfo.round = StrSave("-");
11904         }
11905         if (first.twoMachinesColor[0] == 'w') {
11906             gameInfo.white = StrSave(first.tidy);
11907             gameInfo.black = StrSave(second.tidy);
11908         } else {
11909             gameInfo.white = StrSave(second.tidy);
11910             gameInfo.black = StrSave(first.tidy);
11911         }
11912         gameInfo.timeControl = TimeControlTagValue();
11913         break;
11914
11915       case EditGame:
11916         gameInfo.event = StrSave("Edited game");
11917         gameInfo.site = StrSave(HostName());
11918         gameInfo.date = PGNDate();
11919         gameInfo.round = StrSave("-");
11920         gameInfo.white = StrSave("-");
11921         gameInfo.black = StrSave("-");
11922         break;
11923
11924       case EditPosition:
11925         gameInfo.event = StrSave("Edited position");
11926         gameInfo.site = StrSave(HostName());
11927         gameInfo.date = PGNDate();
11928         gameInfo.round = StrSave("-");
11929         gameInfo.white = StrSave("-");
11930         gameInfo.black = StrSave("-");
11931         break;
11932
11933       case IcsPlayingWhite:
11934       case IcsPlayingBlack:
11935       case IcsObserving:
11936       case IcsExamining:
11937         break;
11938
11939       case PlayFromGameFile:
11940         gameInfo.event = StrSave("Game from non-PGN file");
11941         gameInfo.site = StrSave(HostName());
11942         gameInfo.date = PGNDate();
11943         gameInfo.round = StrSave("-");
11944         gameInfo.white = StrSave("?");
11945         gameInfo.black = StrSave("?");
11946         break;
11947
11948       default:
11949         break;
11950     }
11951 }
11952
11953 void
11954 ReplaceComment(index, text)
11955      int index;
11956      char *text;
11957 {
11958     int len;
11959
11960     while (*text == '\n') text++;
11961     len = strlen(text);
11962     while (len > 0 && text[len - 1] == '\n') len--;
11963
11964     if (commentList[index] != NULL)
11965       free(commentList[index]);
11966
11967     if (len == 0) {
11968         commentList[index] = NULL;
11969         return;
11970     }
11971     commentList[index] = (char *) malloc(len + 2);
11972     strncpy(commentList[index], text, len);
11973     commentList[index][len] = '\n';
11974     commentList[index][len + 1] = NULLCHAR;
11975 }
11976
11977 void
11978 CrushCRs(text)
11979      char *text;
11980 {
11981   char *p = text;
11982   char *q = text;
11983   char ch;
11984
11985   do {
11986     ch = *p++;
11987     if (ch == '\r') continue;
11988     *q++ = ch;
11989   } while (ch != '\0');
11990 }
11991
11992 void
11993 AppendComment(index, text)
11994      int index;
11995      char *text;
11996 {
11997     int oldlen, len;
11998     char *old;
11999
12000     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12001
12002     CrushCRs(text);
12003     while (*text == '\n') text++;
12004     len = strlen(text);
12005     while (len > 0 && text[len - 1] == '\n') len--;
12006
12007     if (len == 0) return;
12008
12009     if (commentList[index] != NULL) {
12010         old = commentList[index];
12011         oldlen = strlen(old);
12012         commentList[index] = (char *) malloc(oldlen + len + 2);
12013         strcpy(commentList[index], old);
12014         free(old);
12015         strncpy(&commentList[index][oldlen], text, len);
12016         commentList[index][oldlen + len] = '\n';
12017         commentList[index][oldlen + len + 1] = NULLCHAR;
12018     } else {
12019         commentList[index] = (char *) malloc(len + 2);
12020         strncpy(commentList[index], text, len);
12021         commentList[index][len] = '\n';
12022         commentList[index][len + 1] = NULLCHAR;
12023     }
12024 }
12025
12026 static char * FindStr( char * text, char * sub_text )
12027 {
12028     char * result = strstr( text, sub_text );
12029
12030     if( result != NULL ) {
12031         result += strlen( sub_text );
12032     }
12033
12034     return result;
12035 }
12036
12037 /* [AS] Try to extract PV info from PGN comment */
12038 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12039 char *GetInfoFromComment( int index, char * text )
12040 {
12041     char * sep = text;
12042
12043     if( text != NULL && index > 0 ) {
12044         int score = 0;
12045         int depth = 0;
12046         int time = -1, sec = 0, deci;
12047         char * s_eval = FindStr( text, "[%eval " );
12048         char * s_emt = FindStr( text, "[%emt " );
12049
12050         if( s_eval != NULL || s_emt != NULL ) {
12051             /* New style */
12052             char delim;
12053
12054             if( s_eval != NULL ) {
12055                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12056                     return text;
12057                 }
12058
12059                 if( delim != ']' ) {
12060                     return text;
12061                 }
12062             }
12063
12064             if( s_emt != NULL ) {
12065             }
12066         }
12067         else {
12068             /* We expect something like: [+|-]nnn.nn/dd */
12069             int score_lo = 0;
12070
12071             sep = strchr( text, '/' );
12072             if( sep == NULL || sep < (text+4) ) {
12073                 return text;
12074             }
12075
12076             time = -1; sec = -1; deci = -1;
12077             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12078                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12079                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12080                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12081                 return text;
12082             }
12083
12084             if( score_lo < 0 || score_lo >= 100 ) {
12085                 return text;
12086             }
12087
12088             if(sec >= 0) time = 600*time + 10*sec; else
12089             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12090
12091             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12092
12093             /* [HGM] PV time: now locate end of PV info */
12094             while( *++sep >= '0' && *sep <= '9'); // strip depth
12095             if(time >= 0)
12096             while( *++sep >= '0' && *sep <= '9'); // strip time
12097             if(sec >= 0)
12098             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12099             if(deci >= 0)
12100             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12101             while(*sep == ' ') sep++;
12102         }
12103
12104         if( depth <= 0 ) {
12105             return text;
12106         }
12107
12108         if( time < 0 ) {
12109             time = -1;
12110         }
12111
12112         pvInfoList[index-1].depth = depth;
12113         pvInfoList[index-1].score = score;
12114         pvInfoList[index-1].time  = 10*time; // centi-sec
12115     }
12116     return sep;
12117 }
12118
12119 void
12120 SendToProgram(message, cps)
12121      char *message;
12122      ChessProgramState *cps;
12123 {
12124     int count, outCount, error;
12125     char buf[MSG_SIZ];
12126
12127     if (cps->pr == NULL) return;
12128     Attention(cps);
12129
12130     if (appData.debugMode) {
12131         TimeMark now;
12132         GetTimeMark(&now);
12133         fprintf(debugFP, "%ld >%-6s: %s",
12134                 SubtractTimeMarks(&now, &programStartTime),
12135                 cps->which, message);
12136     }
12137
12138     count = strlen(message);
12139     outCount = OutputToProcess(cps->pr, message, count, &error);
12140     if (outCount < count && !exiting
12141                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12142         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12143         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12144             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12145                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12146                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12147             } else {
12148                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12149             }
12150             gameInfo.resultDetails = buf;
12151         }
12152         DisplayFatalError(buf, error, 1);
12153     }
12154 }
12155
12156 void
12157 ReceiveFromProgram(isr, closure, message, count, error)
12158      InputSourceRef isr;
12159      VOIDSTAR closure;
12160      char *message;
12161      int count;
12162      int error;
12163 {
12164     char *end_str;
12165     char buf[MSG_SIZ];
12166     ChessProgramState *cps = (ChessProgramState *)closure;
12167
12168     if (isr != cps->isr) return; /* Killed intentionally */
12169     if (count <= 0) {
12170         if (count == 0) {
12171             sprintf(buf,
12172                     _("Error: %s chess program (%s) exited unexpectedly"),
12173                     cps->which, cps->program);
12174         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12175                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12176                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12177                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12178                 } else {
12179                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12180                 }
12181                 gameInfo.resultDetails = buf;
12182             }
12183             RemoveInputSource(cps->isr);
12184             DisplayFatalError(buf, 0, 1);
12185         } else {
12186             sprintf(buf,
12187                     _("Error reading from %s chess program (%s)"),
12188                     cps->which, cps->program);
12189             RemoveInputSource(cps->isr);
12190
12191             /* [AS] Program is misbehaving badly... kill it */
12192             if( count == -2 ) {
12193                 DestroyChildProcess( cps->pr, 9 );
12194                 cps->pr = NoProc;
12195             }
12196
12197             DisplayFatalError(buf, error, 1);
12198         }
12199         return;
12200     }
12201
12202     if ((end_str = strchr(message, '\r')) != NULL)
12203       *end_str = NULLCHAR;
12204     if ((end_str = strchr(message, '\n')) != NULL)
12205       *end_str = NULLCHAR;
12206
12207     if (appData.debugMode) {
12208         TimeMark now; int print = 1;
12209         char *quote = ""; char c; int i;
12210
12211         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12212                 char start = message[0];
12213                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12214                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12215                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12216                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12217                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12218                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12219                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12220                         { quote = "# "; print = (appData.engineComments == 2); }
12221                 message[0] = start; // restore original message
12222         }
12223         if(print) {
12224                 GetTimeMark(&now);
12225                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12226                         SubtractTimeMarks(&now, &programStartTime), cps->which,
12227                         quote,
12228                         message);
12229         }
12230     }
12231
12232     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12233     if (appData.icsEngineAnalyze) {
12234         if (strstr(message, "whisper") != NULL ||
12235              strstr(message, "kibitz") != NULL ||
12236             strstr(message, "tellics") != NULL) return;
12237     }
12238
12239     HandleMachineMove(message, cps);
12240 }
12241
12242
12243 void
12244 SendTimeControl(cps, mps, tc, inc, sd, st)
12245      ChessProgramState *cps;
12246      int mps, inc, sd, st;
12247      long tc;
12248 {
12249     char buf[MSG_SIZ];
12250     int seconds;
12251
12252     if( timeControl_2 > 0 ) {
12253         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12254             tc = timeControl_2;
12255         }
12256     }
12257     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12258     inc /= cps->timeOdds;
12259     st  /= cps->timeOdds;
12260
12261     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12262
12263     if (st > 0) {
12264       /* Set exact time per move, normally using st command */
12265       if (cps->stKludge) {
12266         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12267         seconds = st % 60;
12268         if (seconds == 0) {
12269           sprintf(buf, "level 1 %d\n", st/60);
12270         } else {
12271           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12272         }
12273       } else {
12274         sprintf(buf, "st %d\n", st);
12275       }
12276     } else {
12277       /* Set conventional or incremental time control, using level command */
12278       if (seconds == 0) {
12279         /* Note old gnuchess bug -- minutes:seconds used to not work.
12280            Fixed in later versions, but still avoid :seconds
12281            when seconds is 0. */
12282         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12283       } else {
12284         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12285                 seconds, inc/1000);
12286       }
12287     }
12288     SendToProgram(buf, cps);
12289
12290     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12291     /* Orthogonally, limit search to given depth */
12292     if (sd > 0) {
12293       if (cps->sdKludge) {
12294         sprintf(buf, "depth\n%d\n", sd);
12295       } else {
12296         sprintf(buf, "sd %d\n", sd);
12297       }
12298       SendToProgram(buf, cps);
12299     }
12300
12301     if(cps->nps > 0) { /* [HGM] nps */
12302         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12303         else {
12304                 sprintf(buf, "nps %d\n", cps->nps);
12305               SendToProgram(buf, cps);
12306         }
12307     }
12308 }
12309
12310 ChessProgramState *WhitePlayer()
12311 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12312 {
12313     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12314        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12315         return &second;
12316     return &first;
12317 }
12318
12319 void
12320 SendTimeRemaining(cps, machineWhite)
12321      ChessProgramState *cps;
12322      int /*boolean*/ machineWhite;
12323 {
12324     char message[MSG_SIZ];
12325     long time, otime;
12326
12327     /* Note: this routine must be called when the clocks are stopped
12328        or when they have *just* been set or switched; otherwise
12329        it will be off by the time since the current tick started.
12330     */
12331     if (machineWhite) {
12332         time = whiteTimeRemaining / 10;
12333         otime = blackTimeRemaining / 10;
12334     } else {
12335         time = blackTimeRemaining / 10;
12336         otime = whiteTimeRemaining / 10;
12337     }
12338     /* [HGM] translate opponent's time by time-odds factor */
12339     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12340     if (appData.debugMode) {
12341         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12342     }
12343
12344     if (time <= 0) time = 1;
12345     if (otime <= 0) otime = 1;
12346
12347     sprintf(message, "time %ld\n", time);
12348     SendToProgram(message, cps);
12349
12350     sprintf(message, "otim %ld\n", otime);
12351     SendToProgram(message, cps);
12352 }
12353
12354 int
12355 BoolFeature(p, name, loc, cps)
12356      char **p;
12357      char *name;
12358      int *loc;
12359      ChessProgramState *cps;
12360 {
12361   char buf[MSG_SIZ];
12362   int len = strlen(name);
12363   int val;
12364   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12365     (*p) += len + 1;
12366     sscanf(*p, "%d", &val);
12367     *loc = (val != 0);
12368     while (**p && **p != ' ') (*p)++;
12369     sprintf(buf, "accepted %s\n", name);
12370     SendToProgram(buf, cps);
12371     return TRUE;
12372   }
12373   return FALSE;
12374 }
12375
12376 int
12377 IntFeature(p, name, loc, cps)
12378      char **p;
12379      char *name;
12380      int *loc;
12381      ChessProgramState *cps;
12382 {
12383   char buf[MSG_SIZ];
12384   int len = strlen(name);
12385   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12386     (*p) += len + 1;
12387     sscanf(*p, "%d", loc);
12388     while (**p && **p != ' ') (*p)++;
12389     sprintf(buf, "accepted %s\n", name);
12390     SendToProgram(buf, cps);
12391     return TRUE;
12392   }
12393   return FALSE;
12394 }
12395
12396 int
12397 StringFeature(p, name, loc, cps)
12398      char **p;
12399      char *name;
12400      char loc[];
12401      ChessProgramState *cps;
12402 {
12403   char buf[MSG_SIZ];
12404   int len = strlen(name);
12405   if (strncmp((*p), name, len) == 0
12406       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12407     (*p) += len + 2;
12408     sscanf(*p, "%[^\"]", loc);
12409     while (**p && **p != '\"') (*p)++;
12410     if (**p == '\"') (*p)++;
12411     sprintf(buf, "accepted %s\n", name);
12412     SendToProgram(buf, cps);
12413     return TRUE;
12414   }
12415   return FALSE;
12416 }
12417
12418 int
12419 ParseOption(Option *opt, ChessProgramState *cps)
12420 // [HGM] options: process the string that defines an engine option, and determine
12421 // name, type, default value, and allowed value range
12422 {
12423         char *p, *q, buf[MSG_SIZ];
12424         int n, min = (-1)<<31, max = 1<<31, def;
12425
12426         if(p = strstr(opt->name, " -spin ")) {
12427             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12428             if(max < min) max = min; // enforce consistency
12429             if(def < min) def = min;
12430             if(def > max) def = max;
12431             opt->value = def;
12432             opt->min = min;
12433             opt->max = max;
12434             opt->type = Spin;
12435         } else if(p = strstr(opt->name, " -string ")) {
12436             opt->textValue = p+9;
12437             opt->type = TextBox;
12438         } else if(p = strstr(opt->name, " -check ")) {
12439             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12440             opt->value = (def != 0);
12441             opt->type = CheckBox;
12442         } else if(p = strstr(opt->name, " -combo ")) {
12443             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12444             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12445             opt->value = n = 0;
12446             while(q = StrStr(q, " /// ")) {
12447                 n++; *q = 0;    // count choices, and null-terminate each of them
12448                 q += 5;
12449                 if(*q == '*') { // remember default, which is marked with * prefix
12450                     q++;
12451                     opt->value = n;
12452                 }
12453                 cps->comboList[cps->comboCnt++] = q;
12454             }
12455             cps->comboList[cps->comboCnt++] = NULL;
12456             opt->max = n + 1;
12457             opt->type = ComboBox;
12458         } else if(p = strstr(opt->name, " -button")) {
12459             opt->type = Button;
12460         } else if(p = strstr(opt->name, " -save")) {
12461             opt->type = SaveButton;
12462         } else return FALSE;
12463         *p = 0; // terminate option name
12464         // now look if the command-line options define a setting for this engine option.
12465         if(cps->optionSettings && cps->optionSettings[0])
12466             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12467         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12468                 sprintf(buf, "option %s", p);
12469                 if(p = strstr(buf, ",")) *p = 0;
12470                 strcat(buf, "\n");
12471                 SendToProgram(buf, cps);
12472         }
12473         return TRUE;
12474 }
12475
12476 void
12477 FeatureDone(cps, val)
12478      ChessProgramState* cps;
12479      int val;
12480 {
12481   DelayedEventCallback cb = GetDelayedEvent();
12482   if ((cb == InitBackEnd3 && cps == &first) ||
12483       (cb == TwoMachinesEventIfReady && cps == &second)) {
12484     CancelDelayedEvent();
12485     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12486   }
12487   cps->initDone = val;
12488 }
12489
12490 /* Parse feature command from engine */
12491 void
12492 ParseFeatures(args, cps)
12493      char* args;
12494      ChessProgramState *cps;
12495 {
12496   char *p = args;
12497   char *q;
12498   int val;
12499   char buf[MSG_SIZ];
12500
12501   for (;;) {
12502     while (*p == ' ') p++;
12503     if (*p == NULLCHAR) return;
12504
12505     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12506     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12507     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12508     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12509     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12510     if (BoolFeature(&p, "reuse", &val, cps)) {
12511       /* Engine can disable reuse, but can't enable it if user said no */
12512       if (!val) cps->reuse = FALSE;
12513       continue;
12514     }
12515     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12516     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12517       if (gameMode == TwoMachinesPlay) {
12518         DisplayTwoMachinesTitle();
12519       } else {
12520         DisplayTitle("");
12521       }
12522       continue;
12523     }
12524     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12525     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12526     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12527     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12528     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12529     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12530     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12531     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12532     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12533     if (IntFeature(&p, "done", &val, cps)) {
12534       FeatureDone(cps, val);
12535       continue;
12536     }
12537     /* Added by Tord: */
12538     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12539     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12540     /* End of additions by Tord */
12541
12542     /* [HGM] added features: */
12543     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12544     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12545     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12546     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12547     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12548     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12549     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12550         ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
12551         if(cps->nrOptions >= MAX_OPTIONS) {
12552             cps->nrOptions--;
12553             sprintf(buf, "%s engine has too many options\n", cps->which);
12554             DisplayError(buf, 0);
12555         }
12556         continue;
12557     }
12558     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12559     /* End of additions by HGM */
12560
12561     /* unknown feature: complain and skip */
12562     q = p;
12563     while (*q && *q != '=') q++;
12564     sprintf(buf, "rejected %.*s\n", q-p, p);
12565     SendToProgram(buf, cps);
12566     p = q;
12567     if (*p == '=') {
12568       p++;
12569       if (*p == '\"') {
12570         p++;
12571         while (*p && *p != '\"') p++;
12572         if (*p == '\"') p++;
12573       } else {
12574         while (*p && *p != ' ') p++;
12575       }
12576     }
12577   }
12578
12579 }
12580
12581 void
12582 PeriodicUpdatesEvent(newState)
12583      int newState;
12584 {
12585     if (newState == appData.periodicUpdates)
12586       return;
12587
12588     appData.periodicUpdates=newState;
12589
12590     /* Display type changes, so update it now */
12591     DisplayAnalysis();
12592
12593     /* Get the ball rolling again... */
12594     if (newState) {
12595         AnalysisPeriodicEvent(1);
12596         StartAnalysisClock();
12597     }
12598 }
12599
12600 void
12601 PonderNextMoveEvent(newState)
12602      int newState;
12603 {
12604     if (newState == appData.ponderNextMove) return;
12605     if (gameMode == EditPosition) EditPositionDone();
12606     if (newState) {
12607         SendToProgram("hard\n", &first);
12608         if (gameMode == TwoMachinesPlay) {
12609             SendToProgram("hard\n", &second);
12610         }
12611     } else {
12612         SendToProgram("easy\n", &first);
12613         thinkOutput[0] = NULLCHAR;
12614         if (gameMode == TwoMachinesPlay) {
12615             SendToProgram("easy\n", &second);
12616         }
12617     }
12618     appData.ponderNextMove = newState;
12619 }
12620
12621 void
12622 NewSettingEvent(option, command, value)
12623      char *command;
12624      int option, value;
12625 {
12626     char buf[MSG_SIZ];
12627
12628     if (gameMode == EditPosition) EditPositionDone();
12629     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12630     SendToProgram(buf, &first);
12631     if (gameMode == TwoMachinesPlay) {
12632         SendToProgram(buf, &second);
12633     }
12634 }
12635
12636 void
12637 ShowThinkingEvent()
12638 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12639 {
12640     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12641     int newState = appData.showThinking
12642         // [HGM] thinking: other features now need thinking output as well
12643         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12644
12645     if (oldState == newState) return;
12646     oldState = newState;
12647     if (gameMode == EditPosition) EditPositionDone();
12648     if (oldState) {
12649         SendToProgram("post\n", &first);
12650         if (gameMode == TwoMachinesPlay) {
12651             SendToProgram("post\n", &second);
12652         }
12653     } else {
12654         SendToProgram("nopost\n", &first);
12655         thinkOutput[0] = NULLCHAR;
12656         if (gameMode == TwoMachinesPlay) {
12657             SendToProgram("nopost\n", &second);
12658         }
12659     }
12660 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12661 }
12662
12663 void
12664 AskQuestionEvent(title, question, replyPrefix, which)
12665      char *title; char *question; char *replyPrefix; char *which;
12666 {
12667   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12668   if (pr == NoProc) return;
12669   AskQuestion(title, question, replyPrefix, pr);
12670 }
12671
12672 void
12673 DisplayMove(moveNumber)
12674      int moveNumber;
12675 {
12676     char message[MSG_SIZ];
12677     char res[MSG_SIZ];
12678     char cpThinkOutput[MSG_SIZ];
12679
12680     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12681
12682     if (moveNumber == forwardMostMove - 1 ||
12683         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12684
12685         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12686
12687         if (strchr(cpThinkOutput, '\n')) {
12688             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12689         }
12690     } else {
12691         *cpThinkOutput = NULLCHAR;
12692     }
12693
12694     /* [AS] Hide thinking from human user */
12695     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12696         *cpThinkOutput = NULLCHAR;
12697         if( thinkOutput[0] != NULLCHAR ) {
12698             int i;
12699
12700             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12701                 cpThinkOutput[i] = '.';
12702             }
12703             cpThinkOutput[i] = NULLCHAR;
12704             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12705         }
12706     }
12707
12708     if (moveNumber == forwardMostMove - 1 &&
12709         gameInfo.resultDetails != NULL) {
12710         if (gameInfo.resultDetails[0] == NULLCHAR) {
12711             sprintf(res, " %s", PGNResult(gameInfo.result));
12712         } else {
12713             sprintf(res, " {%s} %s",
12714                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12715         }
12716     } else {
12717         res[0] = NULLCHAR;
12718     }
12719
12720     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12721         DisplayMessage(res, cpThinkOutput);
12722     } else {
12723         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12724                 WhiteOnMove(moveNumber) ? " " : ".. ",
12725                 parseList[moveNumber], res);
12726         DisplayMessage(message, cpThinkOutput);
12727     }
12728 }
12729
12730 void
12731 DisplayAnalysisText(text)
12732      char *text;
12733 {
12734     char buf[MSG_SIZ];
12735
12736     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12737                || appData.icsEngineAnalyze) {
12738         sprintf(buf, "Analysis (%s)", first.tidy);
12739         AnalysisPopUp(buf, text);
12740     }
12741 }
12742
12743 static int
12744 only_one_move(str)
12745      char *str;
12746 {
12747     while (*str && isspace(*str)) ++str;
12748     while (*str && !isspace(*str)) ++str;
12749     if (!*str) return 1;
12750     while (*str && isspace(*str)) ++str;
12751     if (!*str) return 1;
12752     return 0;
12753 }
12754
12755 void
12756 DisplayAnalysis()
12757 {
12758     char buf[MSG_SIZ];
12759     char lst[MSG_SIZ / 2];
12760     double nps;
12761     static char *xtra[] = { "", " (--)", " (++)" };
12762     int h, m, s, cs;
12763
12764     if (programStats.time == 0) {
12765         programStats.time = 1;
12766     }
12767
12768     if (programStats.got_only_move) {
12769         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12770     } else {
12771         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12772
12773         nps = (u64ToDouble(programStats.nodes) /
12774              ((double)programStats.time /100.0));
12775
12776         cs = programStats.time % 100;
12777         s = programStats.time / 100;
12778         h = (s / (60*60));
12779         s = s - h*60*60;
12780         m = (s/60);
12781         s = s - m*60;
12782
12783         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12784           if (programStats.move_name[0] != NULLCHAR) {
12785             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12786                     programStats.depth,
12787                     programStats.nr_moves-programStats.moves_left,
12788                     programStats.nr_moves, programStats.move_name,
12789                     ((float)programStats.score)/100.0, lst,
12790                     only_one_move(lst)?
12791                     xtra[programStats.got_fail] : "",
12792                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12793           } else {
12794             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12795                     programStats.depth,
12796                     programStats.nr_moves-programStats.moves_left,
12797                     programStats.nr_moves, ((float)programStats.score)/100.0,
12798                     lst,
12799                     only_one_move(lst)?
12800                     xtra[programStats.got_fail] : "",
12801                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12802           }
12803         } else {
12804             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12805                     programStats.depth,
12806                     ((float)programStats.score)/100.0,
12807                     lst,
12808                     only_one_move(lst)?
12809                     xtra[programStats.got_fail] : "",
12810                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12811         }
12812     }
12813     DisplayAnalysisText(buf);
12814 }
12815
12816 void
12817 DisplayComment(moveNumber, text)
12818      int moveNumber;
12819      char *text;
12820 {
12821     char title[MSG_SIZ];
12822     char buf[8000]; // comment can be long!
12823     int score, depth;
12824
12825     if( appData.autoDisplayComment ) {
12826         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12827             strcpy(title, "Comment");
12828         } else {
12829             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12830                     WhiteOnMove(moveNumber) ? " " : ".. ",
12831                     parseList[moveNumber]);
12832         }
12833         // [HGM] PV info: display PV info together with (or as) comment
12834         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12835             if(text == NULL) text = "";
12836             score = pvInfoList[moveNumber].score;
12837             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12838                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12839             text = buf;
12840         }
12841     } else title[0] = 0;
12842
12843     if (text != NULL)
12844         CommentPopUp(title, text);
12845 }
12846
12847 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12848  * might be busy thinking or pondering.  It can be omitted if your
12849  * gnuchess is configured to stop thinking immediately on any user
12850  * input.  However, that gnuchess feature depends on the FIONREAD
12851  * ioctl, which does not work properly on some flavors of Unix.
12852  */
12853 void
12854 Attention(cps)
12855      ChessProgramState *cps;
12856 {
12857 #if ATTENTION
12858     if (!cps->useSigint) return;
12859     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12860     switch (gameMode) {
12861       case MachinePlaysWhite:
12862       case MachinePlaysBlack:
12863       case TwoMachinesPlay:
12864       case IcsPlayingWhite:
12865       case IcsPlayingBlack:
12866       case AnalyzeMode:
12867       case AnalyzeFile:
12868         /* Skip if we know it isn't thinking */
12869         if (!cps->maybeThinking) return;
12870         if (appData.debugMode)
12871           fprintf(debugFP, "Interrupting %s\n", cps->which);
12872         InterruptChildProcess(cps->pr);
12873         cps->maybeThinking = FALSE;
12874         break;
12875       default:
12876         break;
12877     }
12878 #endif /*ATTENTION*/
12879 }
12880
12881 int
12882 CheckFlags()
12883 {
12884     if (whiteTimeRemaining <= 0) {
12885         if (!whiteFlag) {
12886             whiteFlag = TRUE;
12887             if (appData.icsActive) {
12888                 if (appData.autoCallFlag &&
12889                     gameMode == IcsPlayingBlack && !blackFlag) {
12890                   SendToICS(ics_prefix);
12891                   SendToICS("flag\n");
12892                 }
12893             } else {
12894                 if (blackFlag) {
12895                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12896                 } else {
12897                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12898                     if (appData.autoCallFlag) {
12899                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12900                         return TRUE;
12901                     }
12902                 }
12903             }
12904         }
12905     }
12906     if (blackTimeRemaining <= 0) {
12907         if (!blackFlag) {
12908             blackFlag = TRUE;
12909             if (appData.icsActive) {
12910                 if (appData.autoCallFlag &&
12911                     gameMode == IcsPlayingWhite && !whiteFlag) {
12912                   SendToICS(ics_prefix);
12913                   SendToICS("flag\n");
12914                 }
12915             } else {
12916                 if (whiteFlag) {
12917                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12918                 } else {
12919                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12920                     if (appData.autoCallFlag) {
12921                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12922                         return TRUE;
12923                     }
12924                 }
12925             }
12926         }
12927     }
12928     return FALSE;
12929 }
12930
12931 void
12932 CheckTimeControl()
12933 {
12934     if (!appData.clockMode || appData.icsActive ||
12935         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12936
12937     /*
12938      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12939      */
12940     if ( !WhiteOnMove(forwardMostMove) )
12941         /* White made time control */
12942         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12943         /* [HGM] time odds: correct new time quota for time odds! */
12944                                             / WhitePlayer()->timeOdds;
12945       else
12946         /* Black made time control */
12947         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12948                                             / WhitePlayer()->other->timeOdds;
12949 }
12950
12951 void
12952 DisplayBothClocks()
12953 {
12954     int wom = gameMode == EditPosition ?
12955       !blackPlaysFirst : WhiteOnMove(currentMove);
12956     DisplayWhiteClock(whiteTimeRemaining, wom);
12957     DisplayBlackClock(blackTimeRemaining, !wom);
12958 }
12959
12960
12961 /* Timekeeping seems to be a portability nightmare.  I think everyone
12962    has ftime(), but I'm really not sure, so I'm including some ifdefs
12963    to use other calls if you don't.  Clocks will be less accurate if
12964    you have neither ftime nor gettimeofday.
12965 */
12966
12967 /* VS 2008 requires the #include outside of the function */
12968 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12969 #include <sys/timeb.h>
12970 #endif
12971
12972 /* Get the current time as a TimeMark */
12973 void
12974 GetTimeMark(tm)
12975      TimeMark *tm;
12976 {
12977 #if HAVE_GETTIMEOFDAY
12978
12979     struct timeval timeVal;
12980     struct timezone timeZone;
12981
12982     gettimeofday(&timeVal, &timeZone);
12983     tm->sec = (long) timeVal.tv_sec;
12984     tm->ms = (int) (timeVal.tv_usec / 1000L);
12985
12986 #else /*!HAVE_GETTIMEOFDAY*/
12987 #if HAVE_FTIME
12988
12989 // include <sys/timeb.h> / moved to just above start of function
12990     struct timeb timeB;
12991
12992     ftime(&timeB);
12993     tm->sec = (long) timeB.time;
12994     tm->ms = (int) timeB.millitm;
12995
12996 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12997     tm->sec = (long) time(NULL);
12998     tm->ms = 0;
12999 #endif
13000 #endif
13001 }
13002
13003 /* Return the difference in milliseconds between two
13004    time marks.  We assume the difference will fit in a long!
13005 */
13006 long
13007 SubtractTimeMarks(tm2, tm1)
13008      TimeMark *tm2, *tm1;
13009 {
13010     return 1000L*(tm2->sec - tm1->sec) +
13011            (long) (tm2->ms - tm1->ms);
13012 }
13013
13014
13015 /*
13016  * Code to manage the game clocks.
13017  *
13018  * In tournament play, black starts the clock and then white makes a move.
13019  * We give the human user a slight advantage if he is playing white---the
13020  * clocks don't run until he makes his first move, so it takes zero time.
13021  * Also, we don't account for network lag, so we could get out of sync
13022  * with GNU Chess's clock -- but then, referees are always right.
13023  */
13024
13025 static TimeMark tickStartTM;
13026 static long intendedTickLength;
13027
13028 long
13029 NextTickLength(timeRemaining)
13030      long timeRemaining;
13031 {
13032     long nominalTickLength, nextTickLength;
13033
13034     if (timeRemaining > 0L && timeRemaining <= 10000L)
13035       nominalTickLength = 100L;
13036     else
13037       nominalTickLength = 1000L;
13038     nextTickLength = timeRemaining % nominalTickLength;
13039     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13040
13041     return nextTickLength;
13042 }
13043
13044 /* Adjust clock one minute up or down */
13045 void
13046 AdjustClock(Boolean which, int dir)
13047 {
13048     if(which) blackTimeRemaining += 60000*dir;
13049     else      whiteTimeRemaining += 60000*dir;
13050     DisplayBothClocks();
13051 }
13052
13053 /* Stop clocks and reset to a fresh time control */
13054 void
13055 ResetClocks()
13056 {
13057     (void) StopClockTimer();
13058     if (appData.icsActive) {
13059         whiteTimeRemaining = blackTimeRemaining = 0;
13060     } else { /* [HGM] correct new time quote for time odds */
13061         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13062         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13063     }
13064     if (whiteFlag || blackFlag) {
13065         DisplayTitle("");
13066         whiteFlag = blackFlag = FALSE;
13067     }
13068     DisplayBothClocks();
13069 }
13070
13071 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13072
13073 /* Decrement running clock by amount of time that has passed */
13074 void
13075 DecrementClocks()
13076 {
13077     long timeRemaining;
13078     long lastTickLength, fudge;
13079     TimeMark now;
13080
13081     if (!appData.clockMode) return;
13082     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13083
13084     GetTimeMark(&now);
13085
13086     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13087
13088     /* Fudge if we woke up a little too soon */
13089     fudge = intendedTickLength - lastTickLength;
13090     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13091
13092     if (WhiteOnMove(forwardMostMove)) {
13093         if(whiteNPS >= 0) lastTickLength = 0;
13094         timeRemaining = whiteTimeRemaining -= lastTickLength;
13095         DisplayWhiteClock(whiteTimeRemaining - fudge,
13096                           WhiteOnMove(currentMove));
13097     } else {
13098         if(blackNPS >= 0) lastTickLength = 0;
13099         timeRemaining = blackTimeRemaining -= lastTickLength;
13100         DisplayBlackClock(blackTimeRemaining - fudge,
13101                           !WhiteOnMove(currentMove));
13102     }
13103
13104     if (CheckFlags()) return;
13105
13106     tickStartTM = now;
13107     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13108     StartClockTimer(intendedTickLength);
13109
13110     /* if the time remaining has fallen below the alarm threshold, sound the
13111      * alarm. if the alarm has sounded and (due to a takeback or time control
13112      * with increment) the time remaining has increased to a level above the
13113      * threshold, reset the alarm so it can sound again.
13114      */
13115
13116     if (appData.icsActive && appData.icsAlarm) {
13117
13118         /* make sure we are dealing with the user's clock */
13119         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13120                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13121            )) return;
13122
13123         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13124             alarmSounded = FALSE;
13125         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13126             PlayAlarmSound();
13127             alarmSounded = TRUE;
13128         }
13129     }
13130 }
13131
13132
13133 /* A player has just moved, so stop the previously running
13134    clock and (if in clock mode) start the other one.
13135    We redisplay both clocks in case we're in ICS mode, because
13136    ICS gives us an update to both clocks after every move.
13137    Note that this routine is called *after* forwardMostMove
13138    is updated, so the last fractional tick must be subtracted
13139    from the color that is *not* on move now.
13140 */
13141 void
13142 SwitchClocks()
13143 {
13144     long lastTickLength;
13145     TimeMark now;
13146     int flagged = FALSE;
13147
13148     GetTimeMark(&now);
13149
13150     if (StopClockTimer() && appData.clockMode) {
13151         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13152         if (WhiteOnMove(forwardMostMove)) {
13153             if(blackNPS >= 0) lastTickLength = 0;
13154             blackTimeRemaining -= lastTickLength;
13155            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13156 //         if(pvInfoList[forwardMostMove-1].time == -1)
13157                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13158                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13159         } else {
13160            if(whiteNPS >= 0) lastTickLength = 0;
13161            whiteTimeRemaining -= lastTickLength;
13162            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13163 //         if(pvInfoList[forwardMostMove-1].time == -1)
13164                  pvInfoList[forwardMostMove-1].time =
13165                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13166         }
13167         flagged = CheckFlags();
13168     }
13169     CheckTimeControl();
13170
13171     if (flagged || !appData.clockMode) return;
13172
13173     switch (gameMode) {
13174       case MachinePlaysBlack:
13175       case MachinePlaysWhite:
13176       case BeginningOfGame:
13177         if (pausing) return;
13178         break;
13179
13180       case EditGame:
13181       case PlayFromGameFile:
13182       case IcsExamining:
13183         return;
13184
13185       default:
13186         break;
13187     }
13188
13189     tickStartTM = now;
13190     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13191       whiteTimeRemaining : blackTimeRemaining);
13192     StartClockTimer(intendedTickLength);
13193 }
13194
13195
13196 /* Stop both clocks */
13197 void
13198 StopClocks()
13199 {
13200     long lastTickLength;
13201     TimeMark now;
13202
13203     if (!StopClockTimer()) return;
13204     if (!appData.clockMode) return;
13205
13206     printf("Debug: in stop clocks\n");
13207
13208     GetTimeMark(&now);
13209
13210     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13211     if (WhiteOnMove(forwardMostMove)) {
13212         if(whiteNPS >= 0) lastTickLength = 0;
13213         whiteTimeRemaining -= lastTickLength;
13214         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13215     } else {
13216         if(blackNPS >= 0) lastTickLength = 0;
13217         blackTimeRemaining -= lastTickLength;
13218         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13219     }
13220     CheckFlags();
13221     printf("Debug: end stop clocks\n");
13222 }
13223
13224 /* Start clock of player on move.  Time may have been reset, so
13225    if clock is already running, stop and restart it. */
13226 void
13227 StartClocks()
13228 {
13229     (void) StopClockTimer(); /* in case it was running already */
13230     DisplayBothClocks();
13231     if (CheckFlags()) return;
13232
13233     if (!appData.clockMode) return;
13234     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13235
13236     GetTimeMark(&tickStartTM);
13237     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13238       whiteTimeRemaining : blackTimeRemaining);
13239
13240    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13241     whiteNPS = blackNPS = -1;
13242     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13243        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13244         whiteNPS = first.nps;
13245     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13246        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13247         blackNPS = first.nps;
13248     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13249         whiteNPS = second.nps;
13250     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13251         blackNPS = second.nps;
13252     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13253
13254     StartClockTimer(intendedTickLength);
13255 }
13256
13257 char *
13258 TimeString(ms)
13259      long ms;
13260 {
13261     long second, minute, hour, day;
13262     char *sign = "";
13263     static char buf[32];
13264
13265     if (ms > 0 && ms <= 9900) {
13266       /* convert milliseconds to tenths, rounding up */
13267       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13268
13269       sprintf(buf, " %03.1f ", tenths/10.0);
13270       return buf;
13271     }
13272
13273     /* convert milliseconds to seconds, rounding up */
13274     /* use floating point to avoid strangeness of integer division
13275        with negative dividends on many machines */
13276     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13277
13278     if (second < 0) {
13279         sign = "-";
13280         second = -second;
13281     }
13282
13283     day = second / (60 * 60 * 24);
13284     second = second % (60 * 60 * 24);
13285     hour = second / (60 * 60);
13286     second = second % (60 * 60);
13287     minute = second / 60;
13288     second = second % 60;
13289
13290     if (day > 0)
13291       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13292               sign, day, hour, minute, second);
13293     else if (hour > 0)
13294       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13295     else
13296       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13297
13298     return buf;
13299 }
13300
13301
13302 /*
13303  * This is necessary because some C libraries aren't ANSI C compliant yet.
13304  */
13305 char *
13306 StrStr(string, match)
13307      char *string, *match;
13308 {
13309     int i, length;
13310
13311     length = strlen(match);
13312
13313     for (i = strlen(string) - length; i >= 0; i--, string++)
13314       if (!strncmp(match, string, length))
13315         return string;
13316
13317     return NULL;
13318 }
13319
13320 char *
13321 StrCaseStr(string, match)
13322      char *string, *match;
13323 {
13324     int i, j, length;
13325
13326     length = strlen(match);
13327
13328     for (i = strlen(string) - length; i >= 0; i--, string++) {
13329         for (j = 0; j < length; j++) {
13330             if (ToLower(match[j]) != ToLower(string[j]))
13331               break;
13332         }
13333         if (j == length) return string;
13334     }
13335
13336     return NULL;
13337 }
13338
13339 #ifndef _amigados
13340 int
13341 StrCaseCmp(s1, s2)
13342      char *s1, *s2;
13343 {
13344     char c1, c2;
13345
13346     for (;;) {
13347         c1 = ToLower(*s1++);
13348         c2 = ToLower(*s2++);
13349         if (c1 > c2) return 1;
13350         if (c1 < c2) return -1;
13351         if (c1 == NULLCHAR) return 0;
13352     }
13353 }
13354
13355
13356 int
13357 ToLower(c)
13358      int c;
13359 {
13360     return isupper(c) ? tolower(c) : c;
13361 }
13362
13363
13364 int
13365 ToUpper(c)
13366      int c;
13367 {
13368     return islower(c) ? toupper(c) : c;
13369 }
13370 #endif /* !_amigados    */
13371
13372 char *
13373 StrSave(s)
13374      char *s;
13375 {
13376     char *ret;
13377
13378     if ((ret = (char *) malloc(strlen(s) + 1))) {
13379         strcpy(ret, s);
13380     }
13381     return ret;
13382 }
13383
13384 char *
13385 StrSavePtr(s, savePtr)
13386      char *s, **savePtr;
13387 {
13388     if (*savePtr) {
13389         free(*savePtr);
13390     }
13391     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13392         strcpy(*savePtr, s);
13393     }
13394     return(*savePtr);
13395 }
13396
13397 char *
13398 PGNDate()
13399 {
13400     time_t clock;
13401     struct tm *tm;
13402     char buf[MSG_SIZ];
13403
13404     clock = time((time_t *)NULL);
13405     tm = localtime(&clock);
13406     sprintf(buf, "%04d.%02d.%02d",
13407             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13408     return StrSave(buf);
13409 }
13410
13411
13412 char *
13413 PositionToFEN(move, overrideCastling)
13414      int move;
13415      char *overrideCastling;
13416 {
13417     int i, j, fromX, fromY, toX, toY;
13418     int whiteToPlay;
13419     char buf[128];
13420     char *p, *q;
13421     int emptycount;
13422     ChessSquare piece;
13423
13424     whiteToPlay = (gameMode == EditPosition) ?
13425       !blackPlaysFirst : (move % 2 == 0);
13426     p = buf;
13427
13428     /* Piece placement data */
13429     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13430         emptycount = 0;
13431         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13432             if (boards[move][i][j] == EmptySquare) {
13433                 emptycount++;
13434             } else { ChessSquare piece = boards[move][i][j];
13435                 if (emptycount > 0) {
13436                     if(emptycount<10) /* [HGM] can be >= 10 */
13437                         *p++ = '0' + emptycount;
13438                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13439                     emptycount = 0;
13440                 }
13441                 if(PieceToChar(piece) == '+') {
13442                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13443                     *p++ = '+';
13444                     piece = (ChessSquare)(DEMOTED piece);
13445                 }
13446                 *p++ = PieceToChar(piece);
13447                 if(p[-1] == '~') {
13448                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13449                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13450                     *p++ = '~';
13451                 }
13452             }
13453         }
13454         if (emptycount > 0) {
13455             if(emptycount<10) /* [HGM] can be >= 10 */
13456                 *p++ = '0' + emptycount;
13457             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13458             emptycount = 0;
13459         }
13460         *p++ = '/';
13461     }
13462     *(p - 1) = ' ';
13463
13464     /* [HGM] print Crazyhouse or Shogi holdings */
13465     if( gameInfo.holdingsWidth ) {
13466         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13467         q = p;
13468         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13469             piece = boards[move][i][BOARD_WIDTH-1];
13470             if( piece != EmptySquare )
13471               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13472                   *p++ = PieceToChar(piece);
13473         }
13474         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13475             piece = boards[move][BOARD_HEIGHT-i-1][0];
13476             if( piece != EmptySquare )
13477               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13478                   *p++ = PieceToChar(piece);
13479         }
13480
13481         if( q == p ) *p++ = '-';
13482         *p++ = ']';
13483         *p++ = ' ';
13484     }
13485
13486     /* Active color */
13487     *p++ = whiteToPlay ? 'w' : 'b';
13488     *p++ = ' ';
13489
13490   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13491     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13492   } else {
13493   if(nrCastlingRights) {
13494      q = p;
13495      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13496        /* [HGM] write directly from rights */
13497            if(castlingRights[move][2] >= 0 &&
13498               castlingRights[move][0] >= 0   )
13499                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13500            if(castlingRights[move][2] >= 0 &&
13501               castlingRights[move][1] >= 0   )
13502                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13503            if(castlingRights[move][5] >= 0 &&
13504               castlingRights[move][3] >= 0   )
13505                 *p++ = castlingRights[move][3] + AAA;
13506            if(castlingRights[move][5] >= 0 &&
13507               castlingRights[move][4] >= 0   )
13508                 *p++ = castlingRights[move][4] + AAA;
13509      } else {
13510
13511         /* [HGM] write true castling rights */
13512         if( nrCastlingRights == 6 ) {
13513             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13514                castlingRights[move][2] >= 0  ) *p++ = 'K';
13515             if(castlingRights[move][1] == BOARD_LEFT &&
13516                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13517             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13518                castlingRights[move][5] >= 0  ) *p++ = 'k';
13519             if(castlingRights[move][4] == BOARD_LEFT &&
13520                castlingRights[move][5] >= 0  ) *p++ = 'q';
13521         }
13522      }
13523      if (q == p) *p++ = '-'; /* No castling rights */
13524      *p++ = ' ';
13525   }
13526
13527   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13528      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13529     /* En passant target square */
13530     if (move > backwardMostMove) {
13531         fromX = moveList[move - 1][0] - AAA;
13532         fromY = moveList[move - 1][1] - ONE;
13533         toX = moveList[move - 1][2] - AAA;
13534         toY = moveList[move - 1][3] - ONE;
13535         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13536             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13537             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13538             fromX == toX) {
13539             /* 2-square pawn move just happened */
13540             *p++ = toX + AAA;
13541             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13542         } else {
13543             *p++ = '-';
13544         }
13545     } else {
13546         *p++ = '-';
13547     }
13548     *p++ = ' ';
13549   }
13550   }
13551
13552     /* [HGM] find reversible plies */
13553     {   int i = 0, j=move;
13554
13555         if (appData.debugMode) { int k;
13556             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13557             for(k=backwardMostMove; k<=forwardMostMove; k++)
13558                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13559
13560         }
13561
13562         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13563         if( j == backwardMostMove ) i += initialRulePlies;
13564         sprintf(p, "%d ", i);
13565         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13566     }
13567     /* Fullmove number */
13568     sprintf(p, "%d", (move / 2) + 1);
13569
13570     return StrSave(buf);
13571 }
13572
13573 Boolean
13574 ParseFEN(board, blackPlaysFirst, fen)
13575     Board board;
13576      int *blackPlaysFirst;
13577      char *fen;
13578 {
13579     int i, j;
13580     char *p;
13581     int emptycount;
13582     ChessSquare piece;
13583
13584     p = fen;
13585
13586     /* [HGM] by default clear Crazyhouse holdings, if present */
13587     if(gameInfo.holdingsWidth) {
13588        for(i=0; i<BOARD_HEIGHT; i++) {
13589            board[i][0]             = EmptySquare; /* black holdings */
13590            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13591            board[i][1]             = (ChessSquare) 0; /* black counts */
13592            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13593        }
13594     }
13595
13596     /* Piece placement data */
13597     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13598         j = 0;
13599         for (;;) {
13600             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13601                 if (*p == '/') p++;
13602                 emptycount = gameInfo.boardWidth - j;
13603                 while (emptycount--)
13604                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13605                 break;
13606 #if(BOARD_SIZE >= 10)
13607             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13608                 p++; emptycount=10;
13609                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13610                 while (emptycount--)
13611                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13612 #endif
13613             } else if (isdigit(*p)) {
13614                 emptycount = *p++ - '0';
13615                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13616                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13617                 while (emptycount--)
13618                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13619             } else if (*p == '+' || isalpha(*p)) {
13620                 if (j >= gameInfo.boardWidth) return FALSE;
13621                 if(*p=='+') {
13622                     piece = CharToPiece(*++p);
13623                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13624                     piece = (ChessSquare) (PROMOTED piece ); p++;
13625                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13626                 } else piece = CharToPiece(*p++);
13627
13628                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13629                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13630                     piece = (ChessSquare) (PROMOTED piece);
13631                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13632                     p++;
13633                 }
13634                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13635             } else {
13636                 return FALSE;
13637             }
13638         }
13639     }
13640     while (*p == '/' || *p == ' ') p++;
13641
13642     /* [HGM] look for Crazyhouse holdings here */
13643     while(*p==' ') p++;
13644     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13645         if(*p == '[') p++;
13646         if(*p == '-' ) *p++; /* empty holdings */ else {
13647             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13648             /* if we would allow FEN reading to set board size, we would   */
13649             /* have to add holdings and shift the board read so far here   */
13650             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13651                 *p++;
13652                 if((int) piece >= (int) BlackPawn ) {
13653                     i = (int)piece - (int)BlackPawn;
13654                     i = PieceToNumber((ChessSquare)i);
13655                     if( i >= gameInfo.holdingsSize ) return FALSE;
13656                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13657                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13658                 } else {
13659                     i = (int)piece - (int)WhitePawn;
13660                     i = PieceToNumber((ChessSquare)i);
13661                     if( i >= gameInfo.holdingsSize ) return FALSE;
13662                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13663                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13664                 }
13665             }
13666         }
13667         if(*p == ']') *p++;
13668     }
13669
13670     while(*p == ' ') p++;
13671
13672     /* Active color */
13673     switch (*p++) {
13674       case 'w':
13675         *blackPlaysFirst = FALSE;
13676         break;
13677       case 'b':
13678         *blackPlaysFirst = TRUE;
13679         break;
13680       default:
13681         return FALSE;
13682     }
13683
13684     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13685     /* return the extra info in global variiables             */
13686
13687     /* set defaults in case FEN is incomplete */
13688     FENepStatus = EP_UNKNOWN;
13689     for(i=0; i<nrCastlingRights; i++ ) {
13690         FENcastlingRights[i] =
13691             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13692     }   /* assume possible unless obviously impossible */
13693     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13694     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13695     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13696     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13697     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13698     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13699     FENrulePlies = 0;
13700
13701     while(*p==' ') p++;
13702     if(nrCastlingRights) {
13703       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13704           /* castling indicator present, so default becomes no castlings */
13705           for(i=0; i<nrCastlingRights; i++ ) {
13706                  FENcastlingRights[i] = -1;
13707           }
13708       }
13709       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13710              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13711              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13712              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13713         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13714
13715         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13716             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13717             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13718         }
13719         switch(c) {
13720           case'K':
13721               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13722               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13723               FENcastlingRights[2] = whiteKingFile;
13724               break;
13725           case'Q':
13726               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13727               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13728               FENcastlingRights[2] = whiteKingFile;
13729               break;
13730           case'k':
13731               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13732               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13733               FENcastlingRights[5] = blackKingFile;
13734               break;
13735           case'q':
13736               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13737               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13738               FENcastlingRights[5] = blackKingFile;
13739           case '-':
13740               break;
13741           default: /* FRC castlings */
13742               if(c >= 'a') { /* black rights */
13743                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13744                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13745                   if(i == BOARD_RGHT) break;
13746                   FENcastlingRights[5] = i;
13747                   c -= AAA;
13748                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13749                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13750                   if(c > i)
13751                       FENcastlingRights[3] = c;
13752                   else
13753                       FENcastlingRights[4] = c;
13754               } else { /* white rights */
13755                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13756                     if(board[0][i] == WhiteKing) break;
13757                   if(i == BOARD_RGHT) break;
13758                   FENcastlingRights[2] = i;
13759                   c -= AAA - 'a' + 'A';
13760                   if(board[0][c] >= WhiteKing) break;
13761                   if(c > i)
13762                       FENcastlingRights[0] = c;
13763                   else
13764                       FENcastlingRights[1] = c;
13765               }
13766         }
13767       }
13768     if (appData.debugMode) {
13769         fprintf(debugFP, "FEN castling rights:");
13770         for(i=0; i<nrCastlingRights; i++)
13771         fprintf(debugFP, " %d", FENcastlingRights[i]);
13772         fprintf(debugFP, "\n");
13773     }
13774
13775       while(*p==' ') p++;
13776     }
13777
13778     /* read e.p. field in games that know e.p. capture */
13779     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13780        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13781       if(*p=='-') {
13782         p++; FENepStatus = EP_NONE;
13783       } else {
13784          char c = *p++ - AAA;
13785
13786          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13787          if(*p >= '0' && *p <='9') *p++;
13788          FENepStatus = c;
13789       }
13790     }
13791
13792
13793     if(sscanf(p, "%d", &i) == 1) {
13794         FENrulePlies = i; /* 50-move ply counter */
13795         /* (The move number is still ignored)    */
13796     }
13797
13798     return TRUE;
13799 }
13800
13801 void
13802 EditPositionPasteFEN(char *fen)
13803 {
13804   if (fen != NULL) {
13805     Board initial_position;
13806
13807     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13808       DisplayError(_("Bad FEN position in clipboard"), 0);
13809       return ;
13810     } else {
13811       int savedBlackPlaysFirst = blackPlaysFirst;
13812       EditPositionEvent();
13813       blackPlaysFirst = savedBlackPlaysFirst;
13814       CopyBoard(boards[0], initial_position);
13815           /* [HGM] copy FEN attributes as well */
13816           {   int i;
13817               initialRulePlies = FENrulePlies;
13818               epStatus[0] = FENepStatus;
13819               for( i=0; i<nrCastlingRights; i++ )
13820                   castlingRights[0][i] = FENcastlingRights[i];
13821           }
13822       EditPositionDone();
13823       DisplayBothClocks();
13824       DrawPosition(FALSE, boards[currentMove]);
13825     }
13826   }
13827 }