6c18a361c03f78c5f292ab68095bd81e8772e1d2
[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, 2010 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 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270
271 /* States for ics_getting_history */
272 #define H_FALSE 0
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
278
279 /* whosays values for GameEnds */
280 #define GE_ICS 0
281 #define GE_ENGINE 1
282 #define GE_PLAYER 2
283 #define GE_FILE 3
284 #define GE_XBOARD 4
285 #define GE_ENGINE1 5
286 #define GE_ENGINE2 6
287
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
290
291 /* Different types of move when calling RegisterMove */
292 #define CMAIL_MOVE   0
293 #define CMAIL_RESIGN 1
294 #define CMAIL_DRAW   2
295 #define CMAIL_ACCEPT 3
296
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
301
302 /* Telnet protocol constants */
303 #define TN_WILL 0373
304 #define TN_WONT 0374
305 #define TN_DO   0375
306 #define TN_DONT 0376
307 #define TN_IAC  0377
308 #define TN_ECHO 0001
309 #define TN_SGA  0003
310 #define TN_PORT 23
311
312 char*
313 safeStrCpy( char *dst, const char *src, size_t count )
314 {
315   /* see for example: https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/854-BSI.html
316    *
317    * usage:   safeStrCpy( stringA, stringB, sizeof(stringA)/sizeof(stringA[0]);
318    */
319
320   assert( dst != NULL );
321   assert( src != NULL );
322   assert( count > 0 );
323
324   strncpy( dst, src, count );
325   if(  dst[ count-1 ] != '\0' )
326     {
327       if(appData.debugMode)
328       printf("safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
329     }
330   dst[ count-1 ] = '\0';
331
332   return dst;
333 }
334
335 /* Some compiler can't cast u64 to double
336  * This function do the job for us:
337
338  * We use the highest bit for cast, this only
339  * works if the highest bit is not
340  * in use (This should not happen)
341  *
342  * We used this for all compiler
343  */
344 double
345 u64ToDouble(u64 value)
346 {
347   double r;
348   u64 tmp = value & u64Const(0x7fffffffffffffff);
349   r = (double)(s64)tmp;
350   if (value & u64Const(0x8000000000000000))
351        r +=  9.2233720368547758080e18; /* 2^63 */
352  return r;
353 }
354
355 /* Fake up flags for now, as we aren't keeping track of castling
356    availability yet. [HGM] Change of logic: the flag now only
357    indicates the type of castlings allowed by the rule of the game.
358    The actual rights themselves are maintained in the array
359    castlingRights, as part of the game history, and are not probed
360    by this function.
361  */
362 int
363 PosFlags(index)
364 {
365   int flags = F_ALL_CASTLE_OK;
366   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
367   switch (gameInfo.variant) {
368   case VariantSuicide:
369     flags &= ~F_ALL_CASTLE_OK;
370   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
371     flags |= F_IGNORE_CHECK;
372   case VariantLosers:
373     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374     break;
375   case VariantAtomic:
376     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377     break;
378   case VariantKriegspiel:
379     flags |= F_KRIEGSPIEL_CAPTURE;
380     break;
381   case VariantCapaRandom:
382   case VariantFischeRandom:
383     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
384   case VariantNoCastle:
385   case VariantShatranj:
386   case VariantCourier:
387   case VariantMakruk:
388     flags &= ~F_ALL_CASTLE_OK;
389     break;
390   default:
391     break;
392   }
393   return flags;
394 }
395
396 FILE *gameFileFP, *debugFP;
397
398 /*
399     [AS] Note: sometimes, the sscanf() function is used to parse the input
400     into a fixed-size buffer. Because of this, we must be prepared to
401     receive strings as long as the size of the input buffer, which is currently
402     set to 4K for Windows and 8K for the rest.
403     So, we must either allocate sufficiently large buffers here, or
404     reduce the size of the input buffer in the input reading part.
405 */
406
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
410
411 ChessProgramState first, second;
412
413 /* premove variables */
414 int premoveToX = 0;
415 int premoveToY = 0;
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
419 int gotPremove = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
422
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey; // [HGM] set by mouse handler
453
454 int have_sent_ICS_logon = 0;
455 int sending_ICS_login    = 0;
456 int sending_ICS_password = 0;
457
458 int movesPerSession;
459 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
460 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
461 long timeControl_2; /* [AS] Allow separate time controls */
462 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
463 long timeRemaining[2][MAX_MOVES];
464 int matchGame = 0;
465 TimeMark programStartTime;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
468
469 /* animateTraining preserves the state of appData.animate
470  * when Training mode is activated. This allows the
471  * response to be animated when appData.animate == TRUE and
472  * appData.animateDragging == TRUE.
473  */
474 Boolean animateTraining;
475
476 GameInfo gameInfo;
477
478 AppData appData;
479
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char  initialRights[BOARD_FILES];
484 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int   initialRulePlies, FENrulePlies;
486 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 int loadFlag = 0;
488 int shuffleOpenings;
489 int mute; // mute all sounds
490
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int storedGames = 0;
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
500
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void CleanupTail P((void));
504
505 ChessSquare  FIDEArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackBishop, BlackKnight, BlackRook }
510 };
511
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackKing, BlackKnight, BlackRook }
517 };
518
519 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522     { BlackRook, BlackMan, BlackBishop, BlackQueen,
523         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
524 };
525
526 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
530         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
531 };
532
533 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
534     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
535         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
536     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
537         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
538 };
539
540 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
542         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackMan, BlackFerz,
544         BlackKing, BlackMan, BlackKnight, BlackRook }
545 };
546
547
548 #if (BOARD_FILES>=10)
549 ChessSquare ShogiArray[2][BOARD_FILES] = {
550     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
551         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
552     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
553         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
554 };
555
556 ChessSquare XiangqiArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
558         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
560         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
561 };
562
563 ChessSquare CapablancaArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
567         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
568 };
569
570 ChessSquare GreatArray[2][BOARD_FILES] = {
571     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
572         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
573     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
574         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
575 };
576
577 ChessSquare JanusArray[2][BOARD_FILES] = {
578     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
579         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
580     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
581         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
582 };
583
584 #ifdef GOTHIC
585 ChessSquare GothicArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
587         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
589         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
590 };
591 #else // !GOTHIC
592 #define GothicArray CapablancaArray
593 #endif // !GOTHIC
594
595 #ifdef FALCON
596 ChessSquare FalconArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
598         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
600         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
601 };
602 #else // !FALCON
603 #define FalconArray CapablancaArray
604 #endif // !FALCON
605
606 #else // !(BOARD_FILES>=10)
607 #define XiangqiPosition FIDEArray
608 #define CapablancaArray FIDEArray
609 #define GothicArray FIDEArray
610 #define GreatArray FIDEArray
611 #endif // !(BOARD_FILES>=10)
612
613 #if (BOARD_FILES>=12)
614 ChessSquare CourierArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
616         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
618         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
619 };
620 #else // !(BOARD_FILES>=12)
621 #define CourierArray CapablancaArray
622 #endif // !(BOARD_FILES>=12)
623
624
625 Board initialPosition;
626
627
628 /* Convert str to a rating. Checks for special cases of "----",
629
630    "++++", etc. Also strips ()'s */
631 int
632 string_to_rating(str)
633   char *str;
634 {
635   while(*str && !isdigit(*str)) ++str;
636   if (!*str)
637     return 0;   /* One of the special "no rating" cases */
638   else
639     return atoi(str);
640 }
641
642 void
643 ClearProgramStats()
644 {
645     /* Init programStats */
646     programStats.movelist[0] = 0;
647     programStats.depth = 0;
648     programStats.nr_moves = 0;
649     programStats.moves_left = 0;
650     programStats.nodes = 0;
651     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
652     programStats.score = 0;
653     programStats.got_only_move = 0;
654     programStats.got_fail = 0;
655     programStats.line_is_book = 0;
656 }
657
658 void
659 InitBackEnd1()
660 {
661     int matched, min, sec;
662
663     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
664     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
665
666     GetTimeMark(&programStartTime);
667     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
668
669     ClearProgramStats();
670     programStats.ok_to_send = 1;
671     programStats.seen_stat = 0;
672
673     /*
674      * Initialize game list
675      */
676     ListNew(&gameList);
677
678
679     /*
680      * Internet chess server status
681      */
682     if (appData.icsActive) {
683         appData.matchMode = FALSE;
684         appData.matchGames = 0;
685 #if ZIPPY
686         appData.noChessProgram = !appData.zippyPlay;
687 #else
688         appData.zippyPlay = FALSE;
689         appData.zippyTalk = FALSE;
690         appData.noChessProgram = TRUE;
691 #endif
692         if (*appData.icsHelper != NULLCHAR) {
693             appData.useTelnet = TRUE;
694             appData.telnetProgram = appData.icsHelper;
695         }
696     } else {
697         appData.zippyTalk = appData.zippyPlay = FALSE;
698     }
699
700     /* [AS] Initialize pv info list [HGM] and game state */
701     {
702         int i, j;
703
704         for( i=0; i<=framePtr; i++ ) {
705             pvInfoList[i].depth = -1;
706             boards[i][EP_STATUS] = EP_NONE;
707             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
708         }
709     }
710
711     /*
712      * Parse timeControl resource
713      */
714     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
715                           appData.movesPerSession)) {
716         char buf[MSG_SIZ];
717         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
718         DisplayFatalError(buf, 0, 2);
719     }
720
721     /*
722      * Parse searchTime resource
723      */
724     if (*appData.searchTime != NULLCHAR) {
725         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
726         if (matched == 1) {
727             searchTime = min * 60;
728         } else if (matched == 2) {
729             searchTime = min * 60 + sec;
730         } else {
731             char buf[MSG_SIZ];
732             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
733             DisplayFatalError(buf, 0, 2);
734         }
735     }
736
737     /* [AS] Adjudication threshold */
738     adjudicateLossThreshold = appData.adjudicateLossThreshold;
739
740     first.which = _("first");
741     second.which = _("second");
742     first.maybeThinking = second.maybeThinking = FALSE;
743     first.pr = second.pr = NoProc;
744     first.isr = second.isr = NULL;
745     first.sendTime = second.sendTime = 2;
746     first.sendDrawOffers = 1;
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754     first.program = appData.firstChessProgram;
755     second.program = appData.secondChessProgram;
756     first.host = appData.firstHost;
757     second.host = appData.secondHost;
758     first.dir = appData.firstDirectory;
759     second.dir = appData.secondDirectory;
760     first.other = &second;
761     second.other = &first;
762     first.initString = appData.initString;
763     second.initString = appData.secondInitString;
764     first.computerString = appData.firstComputerString;
765     second.computerString = appData.secondComputerString;
766     first.useSigint = second.useSigint = TRUE;
767     first.useSigterm = second.useSigterm = TRUE;
768     first.reuse = appData.reuseFirst;
769     second.reuse = appData.reuseSecond;
770     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
771     second.nps = appData.secondNPS;
772     first.useSetboard = second.useSetboard = FALSE;
773     first.useSAN = second.useSAN = FALSE;
774     first.usePing = second.usePing = FALSE;
775     first.lastPing = second.lastPing = 0;
776     first.lastPong = second.lastPong = 0;
777     first.usePlayother = second.usePlayother = FALSE;
778     first.useColors = second.useColors = TRUE;
779     first.useUsermove = second.useUsermove = FALSE;
780     first.sendICS = second.sendICS = FALSE;
781     first.sendName = second.sendName = appData.icsActive;
782     first.sdKludge = second.sdKludge = FALSE;
783     first.stKludge = second.stKludge = FALSE;
784     TidyProgramName(first.program, first.host, first.tidy);
785     TidyProgramName(second.program, second.host, second.tidy);
786     first.matchWins = second.matchWins = 0;
787     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
788     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
789     first.analysisSupport = second.analysisSupport = 2; /* detect */
790     first.analyzing = second.analyzing = FALSE;
791     first.initDone = second.initDone = FALSE;
792
793     /* New features added by Tord: */
794     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
795     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
796     /* End of new features added by Tord. */
797     first.fenOverride  = appData.fenOverride1;
798     second.fenOverride = appData.fenOverride2;
799
800     /* [HGM] time odds: set factor for each machine */
801     first.timeOdds  = appData.firstTimeOdds;
802     second.timeOdds = appData.secondTimeOdds;
803     { float norm = 1;
804         if(appData.timeOddsMode) {
805             norm = first.timeOdds;
806             if(norm > second.timeOdds) norm = second.timeOdds;
807         }
808         first.timeOdds /= norm;
809         second.timeOdds /= norm;
810     }
811
812     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
813     first.accumulateTC = appData.firstAccumulateTC;
814     second.accumulateTC = appData.secondAccumulateTC;
815     first.maxNrOfSessions = second.maxNrOfSessions = 1;
816
817     /* [HGM] debug */
818     first.debug = second.debug = FALSE;
819     first.supportsNPS = second.supportsNPS = UNKNOWN;
820
821     /* [HGM] options */
822     first.optionSettings  = appData.firstOptions;
823     second.optionSettings = appData.secondOptions;
824
825     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
826     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
827     first.isUCI = appData.firstIsUCI; /* [AS] */
828     second.isUCI = appData.secondIsUCI; /* [AS] */
829     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
830     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
831
832     if (appData.firstProtocolVersion > PROTOVER
833         || appData.firstProtocolVersion < 1)
834       {
835         char buf[MSG_SIZ];
836         int len;
837
838         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
839                        appData.firstProtocolVersion);
840         if( (len > MSG_SIZ) && appData.debugMode )
841           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
842
843         DisplayFatalError(buf, 0, 2);
844       }
845     else
846       {
847         first.protocolVersion = appData.firstProtocolVersion;
848       }
849
850     if (appData.secondProtocolVersion > PROTOVER
851         || appData.secondProtocolVersion < 1)
852       {
853         char buf[MSG_SIZ];
854         int len;
855
856         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
857                        appData.secondProtocolVersion);
858         if( (len > MSG_SIZ) && appData.debugMode )
859           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
860
861         DisplayFatalError(buf, 0, 2);
862       }
863     else
864       {
865         second.protocolVersion = appData.secondProtocolVersion;
866       }
867
868     if (appData.icsActive) {
869         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
870 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
871     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
872         appData.clockMode = FALSE;
873         first.sendTime = second.sendTime = 0;
874     }
875
876 #if ZIPPY
877     /* Override some settings from environment variables, for backward
878        compatibility.  Unfortunately it's not feasible to have the env
879        vars just set defaults, at least in xboard.  Ugh.
880     */
881     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
882       ZippyInit();
883     }
884 #endif
885
886     if (appData.noChessProgram) {
887         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
888         sprintf(programVersion, "%s", PACKAGE_STRING);
889     } else {
890       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
891       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
892       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
893     }
894
895     if (!appData.icsActive) {
896       char buf[MSG_SIZ];
897       int len;
898
899       /* Check for variants that are supported only in ICS mode,
900          or not at all.  Some that are accepted here nevertheless
901          have bugs; see comments below.
902       */
903       VariantClass variant = StringToVariant(appData.variant);
904       switch (variant) {
905       case VariantBughouse:     /* need four players and two boards */
906       case VariantKriegspiel:   /* need to hide pieces and move details */
907         /* case VariantFischeRandom: (Fabien: moved below) */
908         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
909         if( (len > MSG_SIZ) && appData.debugMode )
910           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
911
912         DisplayFatalError(buf, 0, 2);
913         return;
914
915       case VariantUnknown:
916       case VariantLoadable:
917       case Variant29:
918       case Variant30:
919       case Variant31:
920       case Variant32:
921       case Variant33:
922       case Variant34:
923       case Variant35:
924       case Variant36:
925       default:
926         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
927         if( (len > MSG_SIZ) && appData.debugMode )
928           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
929
930         DisplayFatalError(buf, 0, 2);
931         return;
932
933       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
934       case VariantFairy:      /* [HGM] TestLegality definitely off! */
935       case VariantGothic:     /* [HGM] should work */
936       case VariantCapablanca: /* [HGM] should work */
937       case VariantCourier:    /* [HGM] initial forced moves not implemented */
938       case VariantShogi:      /* [HGM] could still mate with pawn drop */
939       case VariantKnightmate: /* [HGM] should work */
940       case VariantCylinder:   /* [HGM] untested */
941       case VariantFalcon:     /* [HGM] untested */
942       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
943                                  offboard interposition not understood */
944       case VariantNormal:     /* definitely works! */
945       case VariantWildCastle: /* pieces not automatically shuffled */
946       case VariantNoCastle:   /* pieces not automatically shuffled */
947       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
948       case VariantLosers:     /* should work except for win condition,
949                                  and doesn't know captures are mandatory */
950       case VariantSuicide:    /* should work except for win condition,
951                                  and doesn't know captures are mandatory */
952       case VariantGiveaway:   /* should work except for win condition,
953                                  and doesn't know captures are mandatory */
954       case VariantTwoKings:   /* should work */
955       case VariantAtomic:     /* should work except for win condition */
956       case Variant3Check:     /* should work except for win condition */
957       case VariantShatranj:   /* should work except for all win conditions */
958       case VariantMakruk:     /* should work except for daw countdown */
959       case VariantBerolina:   /* might work if TestLegality is off */
960       case VariantCapaRandom: /* should work */
961       case VariantJanus:      /* should work */
962       case VariantSuper:      /* experimental */
963       case VariantGreat:      /* experimental, requires legality testing to be off */
964         break;
965       }
966     }
967
968     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
969     InitEngineUCI( installDir, &second );
970 }
971
972 int NextIntegerFromString( char ** str, long * value )
973 {
974     int result = -1;
975     char * s = *str;
976
977     while( *s == ' ' || *s == '\t' ) {
978         s++;
979     }
980
981     *value = 0;
982
983     if( *s >= '0' && *s <= '9' ) {
984         while( *s >= '0' && *s <= '9' ) {
985             *value = *value * 10 + (*s - '0');
986             s++;
987         }
988
989         result = 0;
990     }
991
992     *str = s;
993
994     return result;
995 }
996
997 int NextTimeControlFromString( char ** str, long * value )
998 {
999     long temp;
1000     int result = NextIntegerFromString( str, &temp );
1001
1002     if( result == 0 ) {
1003         *value = temp * 60; /* Minutes */
1004         if( **str == ':' ) {
1005             (*str)++;
1006             result = NextIntegerFromString( str, &temp );
1007             *value += temp; /* Seconds */
1008         }
1009     }
1010
1011     return result;
1012 }
1013
1014 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1015 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1016     int result = -1, type = 0; long temp, temp2;
1017
1018     if(**str != ':') return -1; // old params remain in force!
1019     (*str)++;
1020     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1021     if( NextIntegerFromString( str, &temp ) ) return -1;
1022     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1023
1024     if(**str != '/') {
1025         /* time only: incremental or sudden-death time control */
1026         if(**str == '+') { /* increment follows; read it */
1027             (*str)++;
1028             if(**str == '!') type = *(*str)++; // Bronstein TC
1029             if(result = NextIntegerFromString( str, &temp2)) return -1;
1030             *inc = temp2 * 1000;
1031             if(**str == '.') { // read fraction of increment
1032                 char *start = ++(*str);
1033                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1034                 temp2 *= 1000;
1035                 while(start++ < *str) temp2 /= 10;
1036                 *inc += temp2;
1037             }
1038         } else *inc = 0;
1039         *moves = 0; *tc = temp * 1000; *incType = type;
1040         return 0;
1041     }
1042
1043     (*str)++; /* classical time control */
1044     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1045
1046     if(result == 0) {
1047         *moves = temp;
1048         *tc    = temp2 * 1000;
1049         *inc   = 0;
1050         *incType = type;
1051     }
1052     return result;
1053 }
1054
1055 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1056 {   /* [HGM] get time to add from the multi-session time-control string */
1057     int incType, moves=1; /* kludge to force reading of first session */
1058     long time, increment;
1059     char *s = tcString;
1060
1061     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1062     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1063     do {
1064         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1065         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1066         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1067         if(movenr == -1) return time;    /* last move before new session     */
1068         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1069         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1070         if(!moves) return increment;     /* current session is incremental   */
1071         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1072     } while(movenr >= -1);               /* try again for next session       */
1073
1074     return 0; // no new time quota on this move
1075 }
1076
1077 int
1078 ParseTimeControl(tc, ti, mps)
1079      char *tc;
1080      float ti;
1081      int mps;
1082 {
1083   long tc1;
1084   long tc2;
1085   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1086   int min, sec=0;
1087
1088   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1089   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1090       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1091   if(ti > 0) {
1092
1093     if(mps)
1094       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1095     else
1096       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1097   } else {
1098     if(mps)
1099       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1100     else
1101       snprintf(buf, MSG_SIZ, ":%s", mytc);
1102   }
1103   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1104
1105   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1106     return FALSE;
1107   }
1108
1109   if( *tc == '/' ) {
1110     /* Parse second time control */
1111     tc++;
1112
1113     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1114       return FALSE;
1115     }
1116
1117     if( tc2 == 0 ) {
1118       return FALSE;
1119     }
1120
1121     timeControl_2 = tc2 * 1000;
1122   }
1123   else {
1124     timeControl_2 = 0;
1125   }
1126
1127   if( tc1 == 0 ) {
1128     return FALSE;
1129   }
1130
1131   timeControl = tc1 * 1000;
1132
1133   if (ti >= 0) {
1134     timeIncrement = ti * 1000;  /* convert to ms */
1135     movesPerSession = 0;
1136   } else {
1137     timeIncrement = 0;
1138     movesPerSession = mps;
1139   }
1140   return TRUE;
1141 }
1142
1143 void
1144 InitBackEnd2()
1145 {
1146     if (appData.debugMode) {
1147         fprintf(debugFP, "%s\n", programVersion);
1148     }
1149
1150     set_cont_sequence(appData.wrapContSeq);
1151     if (appData.matchGames > 0) {
1152         appData.matchMode = TRUE;
1153     } else if (appData.matchMode) {
1154         appData.matchGames = 1;
1155     }
1156     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1157         appData.matchGames = appData.sameColorGames;
1158     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1159         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1160         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1161     }
1162     Reset(TRUE, FALSE);
1163     if (appData.noChessProgram || first.protocolVersion == 1) {
1164       InitBackEnd3();
1165     } else {
1166       /* kludge: allow timeout for initial "feature" commands */
1167       FreezeUI();
1168       DisplayMessage("", _("Starting chess program"));
1169       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1170     }
1171 }
1172
1173 void
1174 InitBackEnd3 P((void))
1175 {
1176     GameMode initialMode;
1177     char buf[MSG_SIZ];
1178     int err, len;
1179
1180     InitChessProgram(&first, startedFromSetupPosition);
1181
1182     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1183         free(programVersion);
1184         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1185         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1186     }
1187
1188     if (appData.icsActive) {
1189 #ifdef WIN32
1190         /* [DM] Make a console window if needed [HGM] merged ifs */
1191         ConsoleCreate();
1192 #endif
1193         err = establish();
1194         if (err != 0)
1195           {
1196             if (*appData.icsCommPort != NULLCHAR)
1197               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1198                              appData.icsCommPort);
1199             else
1200               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1201                         appData.icsHost, appData.icsPort);
1202
1203             if( (len > MSG_SIZ) && appData.debugMode )
1204               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1205
1206             DisplayFatalError(buf, err, 1);
1207             return;
1208         }
1209         SetICSMode();
1210         telnetISR =
1211           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1212         fromUserISR =
1213           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1214         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1215             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1216     } else if (appData.noChessProgram) {
1217         SetNCPMode();
1218     } else {
1219         SetGNUMode();
1220     }
1221
1222     if (*appData.cmailGameName != NULLCHAR) {
1223         SetCmailMode();
1224         OpenLoopback(&cmailPR);
1225         cmailISR =
1226           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1227     }
1228
1229     ThawUI();
1230     DisplayMessage("", "");
1231     if (StrCaseCmp(appData.initialMode, "") == 0) {
1232       initialMode = BeginningOfGame;
1233     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1234       initialMode = TwoMachinesPlay;
1235     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1236       initialMode = AnalyzeFile;
1237     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1238       initialMode = AnalyzeMode;
1239     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1240       initialMode = MachinePlaysWhite;
1241     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1242       initialMode = MachinePlaysBlack;
1243     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1244       initialMode = EditGame;
1245     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1246       initialMode = EditPosition;
1247     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1248       initialMode = Training;
1249     } else {
1250       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1251       if( (len > MSG_SIZ) && appData.debugMode )
1252         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1253
1254       DisplayFatalError(buf, 0, 2);
1255       return;
1256     }
1257
1258     if (appData.matchMode) {
1259         /* Set up machine vs. machine match */
1260         if (appData.noChessProgram) {
1261             DisplayFatalError(_("Can't have a match with no chess programs"),
1262                               0, 2);
1263             return;
1264         }
1265         matchMode = TRUE;
1266         matchGame = 1;
1267         if (*appData.loadGameFile != NULLCHAR) {
1268             int index = appData.loadGameIndex; // [HGM] autoinc
1269             if(index<0) lastIndex = index = 1;
1270             if (!LoadGameFromFile(appData.loadGameFile,
1271                                   index,
1272                                   appData.loadGameFile, FALSE)) {
1273                 DisplayFatalError(_("Bad game file"), 0, 1);
1274                 return;
1275             }
1276         } else if (*appData.loadPositionFile != NULLCHAR) {
1277             int index = appData.loadPositionIndex; // [HGM] autoinc
1278             if(index<0) lastIndex = index = 1;
1279             if (!LoadPositionFromFile(appData.loadPositionFile,
1280                                       index,
1281                                       appData.loadPositionFile)) {
1282                 DisplayFatalError(_("Bad position file"), 0, 1);
1283                 return;
1284             }
1285         }
1286         TwoMachinesEvent();
1287     } else if (*appData.cmailGameName != NULLCHAR) {
1288         /* Set up cmail mode */
1289         ReloadCmailMsgEvent(TRUE);
1290     } else {
1291         /* Set up other modes */
1292         if (initialMode == AnalyzeFile) {
1293           if (*appData.loadGameFile == NULLCHAR) {
1294             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1295             return;
1296           }
1297         }
1298         if (*appData.loadGameFile != NULLCHAR) {
1299             (void) LoadGameFromFile(appData.loadGameFile,
1300                                     appData.loadGameIndex,
1301                                     appData.loadGameFile, TRUE);
1302         } else if (*appData.loadPositionFile != NULLCHAR) {
1303             (void) LoadPositionFromFile(appData.loadPositionFile,
1304                                         appData.loadPositionIndex,
1305                                         appData.loadPositionFile);
1306             /* [HGM] try to make self-starting even after FEN load */
1307             /* to allow automatic setup of fairy variants with wtm */
1308             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1309                 gameMode = BeginningOfGame;
1310                 setboardSpoiledMachineBlack = 1;
1311             }
1312             /* [HGM] loadPos: make that every new game uses the setup */
1313             /* from file as long as we do not switch variant          */
1314             if(!blackPlaysFirst) {
1315                 startedFromPositionFile = TRUE;
1316                 CopyBoard(filePosition, boards[0]);
1317             }
1318         }
1319         if (initialMode == AnalyzeMode) {
1320           if (appData.noChessProgram) {
1321             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1322             return;
1323           }
1324           if (appData.icsActive) {
1325             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1326             return;
1327           }
1328           AnalyzeModeEvent();
1329         } else if (initialMode == AnalyzeFile) {
1330           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1331           ShowThinkingEvent();
1332           AnalyzeFileEvent();
1333           AnalysisPeriodicEvent(1);
1334         } else if (initialMode == MachinePlaysWhite) {
1335           if (appData.noChessProgram) {
1336             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1337                               0, 2);
1338             return;
1339           }
1340           if (appData.icsActive) {
1341             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1342                               0, 2);
1343             return;
1344           }
1345           MachineWhiteEvent();
1346         } else if (initialMode == MachinePlaysBlack) {
1347           if (appData.noChessProgram) {
1348             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1349                               0, 2);
1350             return;
1351           }
1352           if (appData.icsActive) {
1353             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1354                               0, 2);
1355             return;
1356           }
1357           MachineBlackEvent();
1358         } else if (initialMode == TwoMachinesPlay) {
1359           if (appData.noChessProgram) {
1360             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1361                               0, 2);
1362             return;
1363           }
1364           if (appData.icsActive) {
1365             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1366                               0, 2);
1367             return;
1368           }
1369           TwoMachinesEvent();
1370         } else if (initialMode == EditGame) {
1371           EditGameEvent();
1372         } else if (initialMode == EditPosition) {
1373           EditPositionEvent();
1374         } else if (initialMode == Training) {
1375           if (*appData.loadGameFile == NULLCHAR) {
1376             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1377             return;
1378           }
1379           TrainingEvent();
1380         }
1381     }
1382 }
1383
1384 /*
1385  * Establish will establish a contact to a remote host.port.
1386  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1387  *  used to talk to the host.
1388  * Returns 0 if okay, error code if not.
1389  */
1390 int
1391 establish()
1392 {
1393     char buf[MSG_SIZ];
1394
1395     if (*appData.icsCommPort != NULLCHAR) {
1396         /* Talk to the host through a serial comm port */
1397         return OpenCommPort(appData.icsCommPort, &icsPR);
1398
1399     } else if (*appData.gateway != NULLCHAR) {
1400         if (*appData.remoteShell == NULLCHAR) {
1401             /* Use the rcmd protocol to run telnet program on a gateway host */
1402             snprintf(buf, sizeof(buf), "%s %s %s",
1403                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1404             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1405
1406         } else {
1407             /* Use the rsh program to run telnet program on a gateway host */
1408             if (*appData.remoteUser == NULLCHAR) {
1409                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1410                         appData.gateway, appData.telnetProgram,
1411                         appData.icsHost, appData.icsPort);
1412             } else {
1413                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1414                         appData.remoteShell, appData.gateway,
1415                         appData.remoteUser, appData.telnetProgram,
1416                         appData.icsHost, appData.icsPort);
1417             }
1418             return StartChildProcess(buf, "", &icsPR);
1419
1420         }
1421     } else if (appData.useTelnet) {
1422         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1423
1424     } else {
1425         /* TCP socket interface differs somewhat between
1426            Unix and NT; handle details in the front end.
1427            */
1428         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1429     }
1430 }
1431
1432 void EscapeExpand(char *p, char *q)
1433 {       // [HGM] initstring: routine to shape up string arguments
1434         while(*p++ = *q++) if(p[-1] == '\\')
1435             switch(*q++) {
1436                 case 'n': p[-1] = '\n'; break;
1437                 case 'r': p[-1] = '\r'; break;
1438                 case 't': p[-1] = '\t'; break;
1439                 case '\\': p[-1] = '\\'; break;
1440                 case 0: *p = 0; return;
1441                 default: p[-1] = q[-1]; break;
1442             }
1443 }
1444
1445 void
1446 show_bytes(fp, buf, count)
1447      FILE *fp;
1448      char *buf;
1449      int count;
1450 {
1451     while (count--) {
1452         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1453             fprintf(fp, "\\%03o", *buf & 0xff);
1454         } else {
1455             putc(*buf, fp);
1456         }
1457         buf++;
1458     }
1459     fflush(fp);
1460 }
1461
1462 /* Returns an errno value */
1463 int
1464 OutputMaybeTelnet(pr, message, count, outError)
1465      ProcRef pr;
1466      char *message;
1467      int count;
1468      int *outError;
1469 {
1470     char buf[8192], *p, *q, *buflim;
1471     int left, newcount, outcount;
1472
1473     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1474         *appData.gateway != NULLCHAR) {
1475         if (appData.debugMode) {
1476             fprintf(debugFP, ">ICS: ");
1477             show_bytes(debugFP, message, count);
1478             fprintf(debugFP, "\n");
1479         }
1480         return OutputToProcess(pr, message, count, outError);
1481     }
1482
1483     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1484     p = message;
1485     q = buf;
1486     left = count;
1487     newcount = 0;
1488     while (left) {
1489         if (q >= buflim) {
1490             if (appData.debugMode) {
1491                 fprintf(debugFP, ">ICS: ");
1492                 show_bytes(debugFP, buf, newcount);
1493                 fprintf(debugFP, "\n");
1494             }
1495             outcount = OutputToProcess(pr, buf, newcount, outError);
1496             if (outcount < newcount) return -1; /* to be sure */
1497             q = buf;
1498             newcount = 0;
1499         }
1500         if (*p == '\n') {
1501             *q++ = '\r';
1502             newcount++;
1503         } else if (((unsigned char) *p) == TN_IAC) {
1504             *q++ = (char) TN_IAC;
1505             newcount ++;
1506         }
1507         *q++ = *p++;
1508         newcount++;
1509         left--;
1510     }
1511     if (appData.debugMode) {
1512         fprintf(debugFP, ">ICS: ");
1513         show_bytes(debugFP, buf, newcount);
1514         fprintf(debugFP, "\n");
1515     }
1516     outcount = OutputToProcess(pr, buf, newcount, outError);
1517     if (outcount < newcount) return -1; /* to be sure */
1518     return count;
1519 }
1520
1521 void
1522 read_from_player(isr, closure, message, count, error)
1523      InputSourceRef isr;
1524      VOIDSTAR closure;
1525      char *message;
1526      int count;
1527      int error;
1528 {
1529     int outError, outCount;
1530     static int gotEof = 0;
1531
1532     /* Pass data read from player on to ICS */
1533     if (count > 0) {
1534         gotEof = 0;
1535         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1536         if (outCount < count) {
1537             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1538         }
1539     } else if (count < 0) {
1540         RemoveInputSource(isr);
1541         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1542     } else if (gotEof++ > 0) {
1543         RemoveInputSource(isr);
1544         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1545     }
1546 }
1547
1548 void
1549 KeepAlive()
1550 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1551     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1552     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1553     SendToICS("date\n");
1554     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1555 }
1556
1557 /* added routine for printf style output to ics */
1558 void ics_printf(char *format, ...)
1559 {
1560     char buffer[MSG_SIZ];
1561     va_list args;
1562
1563     va_start(args, format);
1564     vsnprintf(buffer, sizeof(buffer), format, args);
1565     buffer[sizeof(buffer)-1] = '\0';
1566     SendToICS(buffer);
1567     va_end(args);
1568 }
1569
1570 void
1571 SendToICS(s)
1572      char *s;
1573 {
1574     int count, outCount, outError;
1575
1576     if (icsPR == NULL) return;
1577
1578     count = strlen(s);
1579     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1580     if (outCount < count) {
1581         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1582     }
1583 }
1584
1585 /* This is used for sending logon scripts to the ICS. Sending
1586    without a delay causes problems when using timestamp on ICC
1587    (at least on my machine). */
1588 void
1589 SendToICSDelayed(s,msdelay)
1590      char *s;
1591      long msdelay;
1592 {
1593     int count, outCount, outError;
1594
1595     if (icsPR == NULL) return;
1596
1597     count = strlen(s);
1598     if (appData.debugMode) {
1599         fprintf(debugFP, ">ICS: ");
1600         show_bytes(debugFP, s, count);
1601         fprintf(debugFP, "\n");
1602     }
1603     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1604                                       msdelay);
1605     if (outCount < count) {
1606         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1607     }
1608 }
1609
1610
1611 /* Remove all highlighting escape sequences in s
1612    Also deletes any suffix starting with '('
1613    */
1614 char *
1615 StripHighlightAndTitle(s)
1616      char *s;
1617 {
1618     static char retbuf[MSG_SIZ];
1619     char *p = retbuf;
1620
1621     while (*s != NULLCHAR) {
1622         while (*s == '\033') {
1623             while (*s != NULLCHAR && !isalpha(*s)) s++;
1624             if (*s != NULLCHAR) s++;
1625         }
1626         while (*s != NULLCHAR && *s != '\033') {
1627             if (*s == '(' || *s == '[') {
1628                 *p = NULLCHAR;
1629                 return retbuf;
1630             }
1631             *p++ = *s++;
1632         }
1633     }
1634     *p = NULLCHAR;
1635     return retbuf;
1636 }
1637
1638 /* Remove all highlighting escape sequences in s */
1639 char *
1640 StripHighlight(s)
1641      char *s;
1642 {
1643     static char retbuf[MSG_SIZ];
1644     char *p = retbuf;
1645
1646     while (*s != NULLCHAR) {
1647         while (*s == '\033') {
1648             while (*s != NULLCHAR && !isalpha(*s)) s++;
1649             if (*s != NULLCHAR) s++;
1650         }
1651         while (*s != NULLCHAR && *s != '\033') {
1652             *p++ = *s++;
1653         }
1654     }
1655     *p = NULLCHAR;
1656     return retbuf;
1657 }
1658
1659 char *variantNames[] = VARIANT_NAMES;
1660 char *
1661 VariantName(v)
1662      VariantClass v;
1663 {
1664     return variantNames[v];
1665 }
1666
1667
1668 /* Identify a variant from the strings the chess servers use or the
1669    PGN Variant tag names we use. */
1670 VariantClass
1671 StringToVariant(e)
1672      char *e;
1673 {
1674     char *p;
1675     int wnum = -1;
1676     VariantClass v = VariantNormal;
1677     int i, found = FALSE;
1678     char buf[MSG_SIZ];
1679     int len;
1680
1681     if (!e) return v;
1682
1683     /* [HGM] skip over optional board-size prefixes */
1684     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1685         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1686         while( *e++ != '_');
1687     }
1688
1689     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1690         v = VariantNormal;
1691         found = TRUE;
1692     } else
1693     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1694       if (StrCaseStr(e, variantNames[i])) {
1695         v = (VariantClass) i;
1696         found = TRUE;
1697         break;
1698       }
1699     }
1700
1701     if (!found) {
1702       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1703           || StrCaseStr(e, "wild/fr")
1704           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1705         v = VariantFischeRandom;
1706       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1707                  (i = 1, p = StrCaseStr(e, "w"))) {
1708         p += i;
1709         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1710         if (isdigit(*p)) {
1711           wnum = atoi(p);
1712         } else {
1713           wnum = -1;
1714         }
1715         switch (wnum) {
1716         case 0: /* FICS only, actually */
1717         case 1:
1718           /* Castling legal even if K starts on d-file */
1719           v = VariantWildCastle;
1720           break;
1721         case 2:
1722         case 3:
1723         case 4:
1724           /* Castling illegal even if K & R happen to start in
1725              normal positions. */
1726           v = VariantNoCastle;
1727           break;
1728         case 5:
1729         case 7:
1730         case 8:
1731         case 10:
1732         case 11:
1733         case 12:
1734         case 13:
1735         case 14:
1736         case 15:
1737         case 18:
1738         case 19:
1739           /* Castling legal iff K & R start in normal positions */
1740           v = VariantNormal;
1741           break;
1742         case 6:
1743         case 20:
1744         case 21:
1745           /* Special wilds for position setup; unclear what to do here */
1746           v = VariantLoadable;
1747           break;
1748         case 9:
1749           /* Bizarre ICC game */
1750           v = VariantTwoKings;
1751           break;
1752         case 16:
1753           v = VariantKriegspiel;
1754           break;
1755         case 17:
1756           v = VariantLosers;
1757           break;
1758         case 22:
1759           v = VariantFischeRandom;
1760           break;
1761         case 23:
1762           v = VariantCrazyhouse;
1763           break;
1764         case 24:
1765           v = VariantBughouse;
1766           break;
1767         case 25:
1768           v = Variant3Check;
1769           break;
1770         case 26:
1771           /* Not quite the same as FICS suicide! */
1772           v = VariantGiveaway;
1773           break;
1774         case 27:
1775           v = VariantAtomic;
1776           break;
1777         case 28:
1778           v = VariantShatranj;
1779           break;
1780
1781         /* Temporary names for future ICC types.  The name *will* change in
1782            the next xboard/WinBoard release after ICC defines it. */
1783         case 29:
1784           v = Variant29;
1785           break;
1786         case 30:
1787           v = Variant30;
1788           break;
1789         case 31:
1790           v = Variant31;
1791           break;
1792         case 32:
1793           v = Variant32;
1794           break;
1795         case 33:
1796           v = Variant33;
1797           break;
1798         case 34:
1799           v = Variant34;
1800           break;
1801         case 35:
1802           v = Variant35;
1803           break;
1804         case 36:
1805           v = Variant36;
1806           break;
1807         case 37:
1808           v = VariantShogi;
1809           break;
1810         case 38:
1811           v = VariantXiangqi;
1812           break;
1813         case 39:
1814           v = VariantCourier;
1815           break;
1816         case 40:
1817           v = VariantGothic;
1818           break;
1819         case 41:
1820           v = VariantCapablanca;
1821           break;
1822         case 42:
1823           v = VariantKnightmate;
1824           break;
1825         case 43:
1826           v = VariantFairy;
1827           break;
1828         case 44:
1829           v = VariantCylinder;
1830           break;
1831         case 45:
1832           v = VariantFalcon;
1833           break;
1834         case 46:
1835           v = VariantCapaRandom;
1836           break;
1837         case 47:
1838           v = VariantBerolina;
1839           break;
1840         case 48:
1841           v = VariantJanus;
1842           break;
1843         case 49:
1844           v = VariantSuper;
1845           break;
1846         case 50:
1847           v = VariantGreat;
1848           break;
1849         case -1:
1850           /* Found "wild" or "w" in the string but no number;
1851              must assume it's normal chess. */
1852           v = VariantNormal;
1853           break;
1854         default:
1855           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1856           if( (len > MSG_SIZ) && appData.debugMode )
1857             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1858
1859           DisplayError(buf, 0);
1860           v = VariantUnknown;
1861           break;
1862         }
1863       }
1864     }
1865     if (appData.debugMode) {
1866       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1867               e, wnum, VariantName(v));
1868     }
1869     return v;
1870 }
1871
1872 static int leftover_start = 0, leftover_len = 0;
1873 char star_match[STAR_MATCH_N][MSG_SIZ];
1874
1875 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1876    advance *index beyond it, and set leftover_start to the new value of
1877    *index; else return FALSE.  If pattern contains the character '*', it
1878    matches any sequence of characters not containing '\r', '\n', or the
1879    character following the '*' (if any), and the matched sequence(s) are
1880    copied into star_match.
1881    */
1882 int
1883 looking_at(buf, index, pattern)
1884      char *buf;
1885      int *index;
1886      char *pattern;
1887 {
1888     char *bufp = &buf[*index], *patternp = pattern;
1889     int star_count = 0;
1890     char *matchp = star_match[0];
1891
1892     for (;;) {
1893         if (*patternp == NULLCHAR) {
1894             *index = leftover_start = bufp - buf;
1895             *matchp = NULLCHAR;
1896             return TRUE;
1897         }
1898         if (*bufp == NULLCHAR) return FALSE;
1899         if (*patternp == '*') {
1900             if (*bufp == *(patternp + 1)) {
1901                 *matchp = NULLCHAR;
1902                 matchp = star_match[++star_count];
1903                 patternp += 2;
1904                 bufp++;
1905                 continue;
1906             } else if (*bufp == '\n' || *bufp == '\r') {
1907                 patternp++;
1908                 if (*patternp == NULLCHAR)
1909                   continue;
1910                 else
1911                   return FALSE;
1912             } else {
1913                 *matchp++ = *bufp++;
1914                 continue;
1915             }
1916         }
1917         if (*patternp != *bufp) return FALSE;
1918         patternp++;
1919         bufp++;
1920     }
1921 }
1922
1923 void
1924 SendToPlayer(data, length)
1925      char *data;
1926      int length;
1927 {
1928     int error, outCount;
1929     outCount = OutputToProcess(NoProc, data, length, &error);
1930     if (outCount < length) {
1931         DisplayFatalError(_("Error writing to display"), error, 1);
1932     }
1933 }
1934
1935 void
1936 PackHolding(packed, holding)
1937      char packed[];
1938      char *holding;
1939 {
1940     char *p = holding;
1941     char *q = packed;
1942     int runlength = 0;
1943     int curr = 9999;
1944     do {
1945         if (*p == curr) {
1946             runlength++;
1947         } else {
1948             switch (runlength) {
1949               case 0:
1950                 break;
1951               case 1:
1952                 *q++ = curr;
1953                 break;
1954               case 2:
1955                 *q++ = curr;
1956                 *q++ = curr;
1957                 break;
1958               default:
1959                 sprintf(q, "%d", runlength);
1960                 while (*q) q++;
1961                 *q++ = curr;
1962                 break;
1963             }
1964             runlength = 1;
1965             curr = *p;
1966         }
1967     } while (*p++);
1968     *q = NULLCHAR;
1969 }
1970
1971 /* Telnet protocol requests from the front end */
1972 void
1973 TelnetRequest(ddww, option)
1974      unsigned char ddww, option;
1975 {
1976     unsigned char msg[3];
1977     int outCount, outError;
1978
1979     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1980
1981     if (appData.debugMode) {
1982         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1983         switch (ddww) {
1984           case TN_DO:
1985             ddwwStr = "DO";
1986             break;
1987           case TN_DONT:
1988             ddwwStr = "DONT";
1989             break;
1990           case TN_WILL:
1991             ddwwStr = "WILL";
1992             break;
1993           case TN_WONT:
1994             ddwwStr = "WONT";
1995             break;
1996           default:
1997             ddwwStr = buf1;
1998             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
1999             break;
2000         }
2001         switch (option) {
2002           case TN_ECHO:
2003             optionStr = "ECHO";
2004             break;
2005           default:
2006             optionStr = buf2;
2007             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2008             break;
2009         }
2010         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2011     }
2012     msg[0] = TN_IAC;
2013     msg[1] = ddww;
2014     msg[2] = option;
2015     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2016     if (outCount < 3) {
2017         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2018     }
2019 }
2020
2021 void
2022 DoEcho()
2023 {
2024     if (!appData.icsActive) return;
2025     TelnetRequest(TN_DO, TN_ECHO);
2026 }
2027
2028 void
2029 DontEcho()
2030 {
2031     if (!appData.icsActive) return;
2032     TelnetRequest(TN_DONT, TN_ECHO);
2033 }
2034
2035 void
2036 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2037 {
2038     /* put the holdings sent to us by the server on the board holdings area */
2039     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2040     char p;
2041     ChessSquare piece;
2042
2043     if(gameInfo.holdingsWidth < 2)  return;
2044     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2045         return; // prevent overwriting by pre-board holdings
2046
2047     if( (int)lowestPiece >= BlackPawn ) {
2048         holdingsColumn = 0;
2049         countsColumn = 1;
2050         holdingsStartRow = BOARD_HEIGHT-1;
2051         direction = -1;
2052     } else {
2053         holdingsColumn = BOARD_WIDTH-1;
2054         countsColumn = BOARD_WIDTH-2;
2055         holdingsStartRow = 0;
2056         direction = 1;
2057     }
2058
2059     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2060         board[i][holdingsColumn] = EmptySquare;
2061         board[i][countsColumn]   = (ChessSquare) 0;
2062     }
2063     while( (p=*holdings++) != NULLCHAR ) {
2064         piece = CharToPiece( ToUpper(p) );
2065         if(piece == EmptySquare) continue;
2066         /*j = (int) piece - (int) WhitePawn;*/
2067         j = PieceToNumber(piece);
2068         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2069         if(j < 0) continue;               /* should not happen */
2070         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2071         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2072         board[holdingsStartRow+j*direction][countsColumn]++;
2073     }
2074 }
2075
2076
2077 void
2078 VariantSwitch(Board board, VariantClass newVariant)
2079 {
2080    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2081    static Board oldBoard;
2082
2083    startedFromPositionFile = FALSE;
2084    if(gameInfo.variant == newVariant) return;
2085
2086    /* [HGM] This routine is called each time an assignment is made to
2087     * gameInfo.variant during a game, to make sure the board sizes
2088     * are set to match the new variant. If that means adding or deleting
2089     * holdings, we shift the playing board accordingly
2090     * This kludge is needed because in ICS observe mode, we get boards
2091     * of an ongoing game without knowing the variant, and learn about the
2092     * latter only later. This can be because of the move list we requested,
2093     * in which case the game history is refilled from the beginning anyway,
2094     * but also when receiving holdings of a crazyhouse game. In the latter
2095     * case we want to add those holdings to the already received position.
2096     */
2097
2098
2099    if (appData.debugMode) {
2100      fprintf(debugFP, "Switch board from %s to %s\n",
2101              VariantName(gameInfo.variant), VariantName(newVariant));
2102      setbuf(debugFP, NULL);
2103    }
2104    shuffleOpenings = 0;       /* [HGM] shuffle */
2105    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2106    switch(newVariant)
2107      {
2108      case VariantShogi:
2109        newWidth = 9;  newHeight = 9;
2110        gameInfo.holdingsSize = 7;
2111      case VariantBughouse:
2112      case VariantCrazyhouse:
2113        newHoldingsWidth = 2; break;
2114      case VariantGreat:
2115        newWidth = 10;
2116      case VariantSuper:
2117        newHoldingsWidth = 2;
2118        gameInfo.holdingsSize = 8;
2119        break;
2120      case VariantGothic:
2121      case VariantCapablanca:
2122      case VariantCapaRandom:
2123        newWidth = 10;
2124      default:
2125        newHoldingsWidth = gameInfo.holdingsSize = 0;
2126      };
2127
2128    if(newWidth  != gameInfo.boardWidth  ||
2129       newHeight != gameInfo.boardHeight ||
2130       newHoldingsWidth != gameInfo.holdingsWidth ) {
2131
2132      /* shift position to new playing area, if needed */
2133      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2134        for(i=0; i<BOARD_HEIGHT; i++)
2135          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2136            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2137              board[i][j];
2138        for(i=0; i<newHeight; i++) {
2139          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2140          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2141        }
2142      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2143        for(i=0; i<BOARD_HEIGHT; i++)
2144          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2145            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2146              board[i][j];
2147      }
2148      gameInfo.boardWidth  = newWidth;
2149      gameInfo.boardHeight = newHeight;
2150      gameInfo.holdingsWidth = newHoldingsWidth;
2151      gameInfo.variant = newVariant;
2152      InitDrawingSizes(-2, 0);
2153    } else gameInfo.variant = newVariant;
2154    CopyBoard(oldBoard, board);   // remember correctly formatted board
2155      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2156    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2157 }
2158
2159 static int loggedOn = FALSE;
2160
2161 /*-- Game start info cache: --*/
2162 int gs_gamenum;
2163 char gs_kind[MSG_SIZ];
2164 static char player1Name[128] = "";
2165 static char player2Name[128] = "";
2166 static char cont_seq[] = "\n\\   ";
2167 static int player1Rating = -1;
2168 static int player2Rating = -1;
2169 /*----------------------------*/
2170
2171 ColorClass curColor = ColorNormal;
2172 int suppressKibitz = 0;
2173
2174 // [HGM] seekgraph
2175 Boolean soughtPending = FALSE;
2176 Boolean seekGraphUp;
2177 #define MAX_SEEK_ADS 200
2178 #define SQUARE 0x80
2179 char *seekAdList[MAX_SEEK_ADS];
2180 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2181 float tcList[MAX_SEEK_ADS];
2182 char colorList[MAX_SEEK_ADS];
2183 int nrOfSeekAds = 0;
2184 int minRating = 1010, maxRating = 2800;
2185 int hMargin = 10, vMargin = 20, h, w;
2186 extern int squareSize, lineGap;
2187
2188 void
2189 PlotSeekAd(int i)
2190 {
2191         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2192         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2193         if(r < minRating+100 && r >=0 ) r = minRating+100;
2194         if(r > maxRating) r = maxRating;
2195         if(tc < 1.) tc = 1.;
2196         if(tc > 95.) tc = 95.;
2197         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2198         y = ((double)r - minRating)/(maxRating - minRating)
2199             * (h-vMargin-squareSize/8-1) + vMargin;
2200         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2201         if(strstr(seekAdList[i], " u ")) color = 1;
2202         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2203            !strstr(seekAdList[i], "bullet") &&
2204            !strstr(seekAdList[i], "blitz") &&
2205            !strstr(seekAdList[i], "standard") ) color = 2;
2206         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2207         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2208 }
2209
2210 void
2211 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2212 {
2213         char buf[MSG_SIZ], *ext = "";
2214         VariantClass v = StringToVariant(type);
2215         if(strstr(type, "wild")) {
2216             ext = type + 4; // append wild number
2217             if(v == VariantFischeRandom) type = "chess960"; else
2218             if(v == VariantLoadable) type = "setup"; else
2219             type = VariantName(v);
2220         }
2221         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2222         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2223             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2224             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2225             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2226             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2227             seekNrList[nrOfSeekAds] = nr;
2228             zList[nrOfSeekAds] = 0;
2229             seekAdList[nrOfSeekAds++] = StrSave(buf);
2230             if(plot) PlotSeekAd(nrOfSeekAds-1);
2231         }
2232 }
2233
2234 void
2235 EraseSeekDot(int i)
2236 {
2237     int x = xList[i], y = yList[i], d=squareSize/4, k;
2238     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2239     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2240     // now replot every dot that overlapped
2241     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2242         int xx = xList[k], yy = yList[k];
2243         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2244             DrawSeekDot(xx, yy, colorList[k]);
2245     }
2246 }
2247
2248 void
2249 RemoveSeekAd(int nr)
2250 {
2251         int i;
2252         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2253             EraseSeekDot(i);
2254             if(seekAdList[i]) free(seekAdList[i]);
2255             seekAdList[i] = seekAdList[--nrOfSeekAds];
2256             seekNrList[i] = seekNrList[nrOfSeekAds];
2257             ratingList[i] = ratingList[nrOfSeekAds];
2258             colorList[i]  = colorList[nrOfSeekAds];
2259             tcList[i] = tcList[nrOfSeekAds];
2260             xList[i]  = xList[nrOfSeekAds];
2261             yList[i]  = yList[nrOfSeekAds];
2262             zList[i]  = zList[nrOfSeekAds];
2263             seekAdList[nrOfSeekAds] = NULL;
2264             break;
2265         }
2266 }
2267
2268 Boolean
2269 MatchSoughtLine(char *line)
2270 {
2271     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2272     int nr, base, inc, u=0; char dummy;
2273
2274     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2275        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2276        (u=1) &&
2277        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2278         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2279         // match: compact and save the line
2280         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2281         return TRUE;
2282     }
2283     return FALSE;
2284 }
2285
2286 int
2287 DrawSeekGraph()
2288 {
2289     int i;
2290     if(!seekGraphUp) return FALSE;
2291     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2292     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2293
2294     DrawSeekBackground(0, 0, w, h);
2295     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2296     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2297     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2298         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2299         yy = h-1-yy;
2300         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2301         if(i%500 == 0) {
2302             char buf[MSG_SIZ];
2303             snprintf(buf, MSG_SIZ, "%d", i);
2304             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2305         }
2306     }
2307     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2308     for(i=1; i<100; i+=(i<10?1:5)) {
2309         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2310         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2311         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2312             char buf[MSG_SIZ];
2313             snprintf(buf, MSG_SIZ, "%d", i);
2314             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2315         }
2316     }
2317     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2318     return TRUE;
2319 }
2320
2321 int SeekGraphClick(ClickType click, int x, int y, int moving)
2322 {
2323     static int lastDown = 0, displayed = 0, lastSecond;
2324     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2325         if(click == Release || moving) return FALSE;
2326         nrOfSeekAds = 0;
2327         soughtPending = TRUE;
2328         SendToICS(ics_prefix);
2329         SendToICS("sought\n"); // should this be "sought all"?
2330     } else { // issue challenge based on clicked ad
2331         int dist = 10000; int i, closest = 0, second = 0;
2332         for(i=0; i<nrOfSeekAds; i++) {
2333             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2334             if(d < dist) { dist = d; closest = i; }
2335             second += (d - zList[i] < 120); // count in-range ads
2336             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2337         }
2338         if(dist < 120) {
2339             char buf[MSG_SIZ];
2340             second = (second > 1);
2341             if(displayed != closest || second != lastSecond) {
2342                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2343                 lastSecond = second; displayed = closest;
2344             }
2345             if(click == Press) {
2346                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2347                 lastDown = closest;
2348                 return TRUE;
2349             } // on press 'hit', only show info
2350             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2351             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2352             SendToICS(ics_prefix);
2353             SendToICS(buf);
2354             return TRUE; // let incoming board of started game pop down the graph
2355         } else if(click == Release) { // release 'miss' is ignored
2356             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2357             if(moving == 2) { // right up-click
2358                 nrOfSeekAds = 0; // refresh graph
2359                 soughtPending = TRUE;
2360                 SendToICS(ics_prefix);
2361                 SendToICS("sought\n"); // should this be "sought all"?
2362             }
2363             return TRUE;
2364         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2365         // press miss or release hit 'pop down' seek graph
2366         seekGraphUp = FALSE;
2367         DrawPosition(TRUE, NULL);
2368     }
2369     return TRUE;
2370 }
2371
2372 void
2373 read_from_ics(isr, closure, data, count, error)
2374      InputSourceRef isr;
2375      VOIDSTAR closure;
2376      char *data;
2377      int count;
2378      int error;
2379 {
2380 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2381 #define STARTED_NONE 0
2382 #define STARTED_MOVES 1
2383 #define STARTED_BOARD 2
2384 #define STARTED_OBSERVE 3
2385 #define STARTED_HOLDINGS 4
2386 #define STARTED_CHATTER 5
2387 #define STARTED_COMMENT 6
2388 #define STARTED_MOVES_NOHIDE 7
2389
2390     static int started = STARTED_NONE;
2391     static char parse[20000];
2392     static int parse_pos = 0;
2393     static char buf[BUF_SIZE + 1];
2394     static int firstTime = TRUE, intfSet = FALSE;
2395     static ColorClass prevColor = ColorNormal;
2396     static int savingComment = FALSE;
2397     static int cmatch = 0; // continuation sequence match
2398     char *bp;
2399     char str[MSG_SIZ];
2400     int i, oldi;
2401     int buf_len;
2402     int next_out;
2403     int tkind;
2404     int backup;    /* [DM] For zippy color lines */
2405     char *p;
2406     char talker[MSG_SIZ]; // [HGM] chat
2407     int channel;
2408
2409     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2410
2411     if (appData.debugMode) {
2412       if (!error) {
2413         fprintf(debugFP, "<ICS: ");
2414         show_bytes(debugFP, data, count);
2415         fprintf(debugFP, "\n");
2416       }
2417     }
2418
2419     if (appData.debugMode) { int f = forwardMostMove;
2420         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2421                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2422                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2423     }
2424     if (count > 0) {
2425         /* If last read ended with a partial line that we couldn't parse,
2426            prepend it to the new read and try again. */
2427         if (leftover_len > 0) {
2428             for (i=0; i<leftover_len; i++)
2429               buf[i] = buf[leftover_start + i];
2430         }
2431
2432     /* copy new characters into the buffer */
2433     bp = buf + leftover_len;
2434     buf_len=leftover_len;
2435     for (i=0; i<count; i++)
2436     {
2437         // ignore these
2438         if (data[i] == '\r')
2439             continue;
2440
2441         // join lines split by ICS?
2442         if (!appData.noJoin)
2443         {
2444             /*
2445                 Joining just consists of finding matches against the
2446                 continuation sequence, and discarding that sequence
2447                 if found instead of copying it.  So, until a match
2448                 fails, there's nothing to do since it might be the
2449                 complete sequence, and thus, something we don't want
2450                 copied.
2451             */
2452             if (data[i] == cont_seq[cmatch])
2453             {
2454                 cmatch++;
2455                 if (cmatch == strlen(cont_seq))
2456                 {
2457                     cmatch = 0; // complete match.  just reset the counter
2458
2459                     /*
2460                         it's possible for the ICS to not include the space
2461                         at the end of the last word, making our [correct]
2462                         join operation fuse two separate words.  the server
2463                         does this when the space occurs at the width setting.
2464                     */
2465                     if (!buf_len || buf[buf_len-1] != ' ')
2466                     {
2467                         *bp++ = ' ';
2468                         buf_len++;
2469                     }
2470                 }
2471                 continue;
2472             }
2473             else if (cmatch)
2474             {
2475                 /*
2476                     match failed, so we have to copy what matched before
2477                     falling through and copying this character.  In reality,
2478                     this will only ever be just the newline character, but
2479                     it doesn't hurt to be precise.
2480                 */
2481                 strncpy(bp, cont_seq, cmatch);
2482                 bp += cmatch;
2483                 buf_len += cmatch;
2484                 cmatch = 0;
2485             }
2486         }
2487
2488         // copy this char
2489         *bp++ = data[i];
2490         buf_len++;
2491     }
2492
2493         buf[buf_len] = NULLCHAR;
2494 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2495         next_out = 0;
2496         leftover_start = 0;
2497
2498         i = 0;
2499         while (i < buf_len) {
2500             /* Deal with part of the TELNET option negotiation
2501                protocol.  We refuse to do anything beyond the
2502                defaults, except that we allow the WILL ECHO option,
2503                which ICS uses to turn off password echoing when we are
2504                directly connected to it.  We reject this option
2505                if localLineEditing mode is on (always on in xboard)
2506                and we are talking to port 23, which might be a real
2507                telnet server that will try to keep WILL ECHO on permanently.
2508              */
2509             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2510                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2511                 unsigned char option;
2512                 oldi = i;
2513                 switch ((unsigned char) buf[++i]) {
2514                   case TN_WILL:
2515                     if (appData.debugMode)
2516                       fprintf(debugFP, "\n<WILL ");
2517                     switch (option = (unsigned char) buf[++i]) {
2518                       case TN_ECHO:
2519                         if (appData.debugMode)
2520                           fprintf(debugFP, "ECHO ");
2521                         /* Reply only if this is a change, according
2522                            to the protocol rules. */
2523                         if (remoteEchoOption) break;
2524                         if (appData.localLineEditing &&
2525                             atoi(appData.icsPort) == TN_PORT) {
2526                             TelnetRequest(TN_DONT, TN_ECHO);
2527                         } else {
2528                             EchoOff();
2529                             TelnetRequest(TN_DO, TN_ECHO);
2530                             remoteEchoOption = TRUE;
2531                         }
2532                         break;
2533                       default:
2534                         if (appData.debugMode)
2535                           fprintf(debugFP, "%d ", option);
2536                         /* Whatever this is, we don't want it. */
2537                         TelnetRequest(TN_DONT, option);
2538                         break;
2539                     }
2540                     break;
2541                   case TN_WONT:
2542                     if (appData.debugMode)
2543                       fprintf(debugFP, "\n<WONT ");
2544                     switch (option = (unsigned char) buf[++i]) {
2545                       case TN_ECHO:
2546                         if (appData.debugMode)
2547                           fprintf(debugFP, "ECHO ");
2548                         /* Reply only if this is a change, according
2549                            to the protocol rules. */
2550                         if (!remoteEchoOption) break;
2551                         EchoOn();
2552                         TelnetRequest(TN_DONT, TN_ECHO);
2553                         remoteEchoOption = FALSE;
2554                         break;
2555                       default:
2556                         if (appData.debugMode)
2557                           fprintf(debugFP, "%d ", (unsigned char) option);
2558                         /* Whatever this is, it must already be turned
2559                            off, because we never agree to turn on
2560                            anything non-default, so according to the
2561                            protocol rules, we don't reply. */
2562                         break;
2563                     }
2564                     break;
2565                   case TN_DO:
2566                     if (appData.debugMode)
2567                       fprintf(debugFP, "\n<DO ");
2568                     switch (option = (unsigned char) buf[++i]) {
2569                       default:
2570                         /* Whatever this is, we refuse to do it. */
2571                         if (appData.debugMode)
2572                           fprintf(debugFP, "%d ", option);
2573                         TelnetRequest(TN_WONT, option);
2574                         break;
2575                     }
2576                     break;
2577                   case TN_DONT:
2578                     if (appData.debugMode)
2579                       fprintf(debugFP, "\n<DONT ");
2580                     switch (option = (unsigned char) buf[++i]) {
2581                       default:
2582                         if (appData.debugMode)
2583                           fprintf(debugFP, "%d ", option);
2584                         /* Whatever this is, we are already not doing
2585                            it, because we never agree to do anything
2586                            non-default, so according to the protocol
2587                            rules, we don't reply. */
2588                         break;
2589                     }
2590                     break;
2591                   case TN_IAC:
2592                     if (appData.debugMode)
2593                       fprintf(debugFP, "\n<IAC ");
2594                     /* Doubled IAC; pass it through */
2595                     i--;
2596                     break;
2597                   default:
2598                     if (appData.debugMode)
2599                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2600                     /* Drop all other telnet commands on the floor */
2601                     break;
2602                 }
2603                 if (oldi > next_out)
2604                   SendToPlayer(&buf[next_out], oldi - next_out);
2605                 if (++i > next_out)
2606                   next_out = i;
2607                 continue;
2608             }
2609
2610             /* OK, this at least will *usually* work */
2611             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2612                 loggedOn = TRUE;
2613             }
2614
2615             if (loggedOn && !intfSet) {
2616                 if (ics_type == ICS_ICC) {
2617                   snprintf(str, MSG_SIZ,
2618                           "/set-quietly interface %s\n/set-quietly style 12\n",
2619                           programVersion);
2620                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2621                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2622                 } else if (ics_type == ICS_CHESSNET) {
2623                   snprintf(str, MSG_SIZ, "/style 12\n");
2624                 } else {
2625                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2626                   strcat(str, programVersion);
2627                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2628                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2629                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2630 #ifdef WIN32
2631                   strcat(str, "$iset nohighlight 1\n");
2632 #endif
2633                   strcat(str, "$iset lock 1\n$style 12\n");
2634                 }
2635                 SendToICS(str);
2636                 NotifyFrontendLogin();
2637                 intfSet = TRUE;
2638             }
2639
2640             if (started == STARTED_COMMENT) {
2641                 /* Accumulate characters in comment */
2642                 parse[parse_pos++] = buf[i];
2643                 if (buf[i] == '\n') {
2644                     parse[parse_pos] = NULLCHAR;
2645                     if(chattingPartner>=0) {
2646                         char mess[MSG_SIZ];
2647                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2648                         OutputChatMessage(chattingPartner, mess);
2649                         chattingPartner = -1;
2650                         next_out = i+1; // [HGM] suppress printing in ICS window
2651                     } else
2652                     if(!suppressKibitz) // [HGM] kibitz
2653                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2654                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2655                         int nrDigit = 0, nrAlph = 0, j;
2656                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2657                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2658                         parse[parse_pos] = NULLCHAR;
2659                         // try to be smart: if it does not look like search info, it should go to
2660                         // ICS interaction window after all, not to engine-output window.
2661                         for(j=0; j<parse_pos; j++) { // count letters and digits
2662                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2663                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2664                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2665                         }
2666                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2667                             int depth=0; float score;
2668                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2669                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2670                                 pvInfoList[forwardMostMove-1].depth = depth;
2671                                 pvInfoList[forwardMostMove-1].score = 100*score;
2672                             }
2673                             OutputKibitz(suppressKibitz, parse);
2674                         } else {
2675                             char tmp[MSG_SIZ];
2676                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2677                             SendToPlayer(tmp, strlen(tmp));
2678                         }
2679                         next_out = i+1; // [HGM] suppress printing in ICS window
2680                     }
2681                     started = STARTED_NONE;
2682                 } else {
2683                     /* Don't match patterns against characters in comment */
2684                     i++;
2685                     continue;
2686                 }
2687             }
2688             if (started == STARTED_CHATTER) {
2689                 if (buf[i] != '\n') {
2690                     /* Don't match patterns against characters in chatter */
2691                     i++;
2692                     continue;
2693                 }
2694                 started = STARTED_NONE;
2695                 if(suppressKibitz) next_out = i+1;
2696             }
2697
2698             /* Kludge to deal with rcmd protocol */
2699             if (firstTime && looking_at(buf, &i, "\001*")) {
2700                 DisplayFatalError(&buf[1], 0, 1);
2701                 continue;
2702             } else {
2703                 firstTime = FALSE;
2704             }
2705
2706             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2707                 ics_type = ICS_ICC;
2708                 ics_prefix = "/";
2709                 if (appData.debugMode)
2710                   fprintf(debugFP, "ics_type %d\n", ics_type);
2711                 continue;
2712             }
2713             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2714                 ics_type = ICS_FICS;
2715                 ics_prefix = "$";
2716                 if (appData.debugMode)
2717                   fprintf(debugFP, "ics_type %d\n", ics_type);
2718                 continue;
2719             }
2720             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2721                 ics_type = ICS_CHESSNET;
2722                 ics_prefix = "/";
2723                 if (appData.debugMode)
2724                   fprintf(debugFP, "ics_type %d\n", ics_type);
2725                 continue;
2726             }
2727
2728             if (!loggedOn &&
2729                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2730                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2731                  looking_at(buf, &i, "will be \"*\""))) {
2732               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2733               continue;
2734             }
2735
2736             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2737               char buf[MSG_SIZ];
2738               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2739               DisplayIcsInteractionTitle(buf);
2740               have_set_title = TRUE;
2741             }
2742
2743             /* skip finger notes */
2744             if (started == STARTED_NONE &&
2745                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2746                  (buf[i] == '1' && buf[i+1] == '0')) &&
2747                 buf[i+2] == ':' && buf[i+3] == ' ') {
2748               started = STARTED_CHATTER;
2749               i += 3;
2750               continue;
2751             }
2752
2753             oldi = i;
2754             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2755             if(appData.seekGraph) {
2756                 if(soughtPending && MatchSoughtLine(buf+i)) {
2757                     i = strstr(buf+i, "rated") - buf;
2758                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2759                     next_out = leftover_start = i;
2760                     started = STARTED_CHATTER;
2761                     suppressKibitz = TRUE;
2762                     continue;
2763                 }
2764                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2765                         && looking_at(buf, &i, "* ads displayed")) {
2766                     soughtPending = FALSE;
2767                     seekGraphUp = TRUE;
2768                     DrawSeekGraph();
2769                     continue;
2770                 }
2771                 if(appData.autoRefresh) {
2772                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2773                         int s = (ics_type == ICS_ICC); // ICC format differs
2774                         if(seekGraphUp)
2775                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2776                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2777                         looking_at(buf, &i, "*% "); // eat prompt
2778                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2779                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2780                         next_out = i; // suppress
2781                         continue;
2782                     }
2783                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2784                         char *p = star_match[0];
2785                         while(*p) {
2786                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2787                             while(*p && *p++ != ' '); // next
2788                         }
2789                         looking_at(buf, &i, "*% "); // eat prompt
2790                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2791                         next_out = i;
2792                         continue;
2793                     }
2794                 }
2795             }
2796
2797             /* skip formula vars */
2798             if (started == STARTED_NONE &&
2799                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2800               started = STARTED_CHATTER;
2801               i += 3;
2802               continue;
2803             }
2804
2805             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2806             if (appData.autoKibitz && started == STARTED_NONE &&
2807                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2808                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2809                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2810                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2811                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2812                         suppressKibitz = TRUE;
2813                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2814                         next_out = i;
2815                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2816                                 && (gameMode == IcsPlayingWhite)) ||
2817                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2818                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2819                             started = STARTED_CHATTER; // own kibitz we simply discard
2820                         else {
2821                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2822                             parse_pos = 0; parse[0] = NULLCHAR;
2823                             savingComment = TRUE;
2824                             suppressKibitz = gameMode != IcsObserving ? 2 :
2825                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2826                         }
2827                         continue;
2828                 } else
2829                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2830                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2831                          && atoi(star_match[0])) {
2832                     // suppress the acknowledgements of our own autoKibitz
2833                     char *p;
2834                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2835                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2836                     SendToPlayer(star_match[0], strlen(star_match[0]));
2837                     if(looking_at(buf, &i, "*% ")) // eat prompt
2838                         suppressKibitz = FALSE;
2839                     next_out = i;
2840                     continue;
2841                 }
2842             } // [HGM] kibitz: end of patch
2843
2844             // [HGM] chat: intercept tells by users for which we have an open chat window
2845             channel = -1;
2846             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2847                                            looking_at(buf, &i, "* whispers:") ||
2848                                            looking_at(buf, &i, "* kibitzes:") ||
2849                                            looking_at(buf, &i, "* shouts:") ||
2850                                            looking_at(buf, &i, "* c-shouts:") ||
2851                                            looking_at(buf, &i, "--> * ") ||
2852                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2853                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2854                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2855                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2856                 int p;
2857                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2858                 chattingPartner = -1;
2859
2860                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2861                 for(p=0; p<MAX_CHAT; p++) {
2862                     if(channel == atoi(chatPartner[p])) {
2863                     talker[0] = '['; strcat(talker, "] ");
2864                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2865                     chattingPartner = p; break;
2866                     }
2867                 } else
2868                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2869                 for(p=0; p<MAX_CHAT; p++) {
2870                     if(!strcmp("kibitzes", chatPartner[p])) {
2871                         talker[0] = '['; strcat(talker, "] ");
2872                         chattingPartner = p; break;
2873                     }
2874                 } else
2875                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2876                 for(p=0; p<MAX_CHAT; p++) {
2877                     if(!strcmp("whispers", chatPartner[p])) {
2878                         talker[0] = '['; strcat(talker, "] ");
2879                         chattingPartner = p; break;
2880                     }
2881                 } else
2882                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2883                   if(buf[i-8] == '-' && buf[i-3] == 't')
2884                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2885                     if(!strcmp("c-shouts", chatPartner[p])) {
2886                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2887                         chattingPartner = p; break;
2888                     }
2889                   }
2890                   if(chattingPartner < 0)
2891                   for(p=0; p<MAX_CHAT; p++) {
2892                     if(!strcmp("shouts", chatPartner[p])) {
2893                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2894                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2895                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2896                         chattingPartner = p; break;
2897                     }
2898                   }
2899                 }
2900                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2901                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2902                     talker[0] = 0; Colorize(ColorTell, FALSE);
2903                     chattingPartner = p; break;
2904                 }
2905                 if(chattingPartner<0) i = oldi; else {
2906                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2907                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2908                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2909                     started = STARTED_COMMENT;
2910                     parse_pos = 0; parse[0] = NULLCHAR;
2911                     savingComment = 3 + chattingPartner; // counts as TRUE
2912                     suppressKibitz = TRUE;
2913                     continue;
2914                 }
2915             } // [HGM] chat: end of patch
2916
2917             if (appData.zippyTalk || appData.zippyPlay) {
2918                 /* [DM] Backup address for color zippy lines */
2919                 backup = i;
2920 #if ZIPPY
2921                if (loggedOn == TRUE)
2922                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2923                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2924 #endif
2925             } // [DM] 'else { ' deleted
2926                 if (
2927                     /* Regular tells and says */
2928                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2929                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2930                     looking_at(buf, &i, "* says: ") ||
2931                     /* Don't color "message" or "messages" output */
2932                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2933                     looking_at(buf, &i, "*. * at *:*: ") ||
2934                     looking_at(buf, &i, "--* (*:*): ") ||
2935                     /* Message notifications (same color as tells) */
2936                     looking_at(buf, &i, "* has left a message ") ||
2937                     looking_at(buf, &i, "* just sent you a message:\n") ||
2938                     /* Whispers and kibitzes */
2939                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2940                     looking_at(buf, &i, "* kibitzes: ") ||
2941                     /* Channel tells */
2942                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2943
2944                   if (tkind == 1 && strchr(star_match[0], ':')) {
2945                       /* Avoid "tells you:" spoofs in channels */
2946                      tkind = 3;
2947                   }
2948                   if (star_match[0][0] == NULLCHAR ||
2949                       strchr(star_match[0], ' ') ||
2950                       (tkind == 3 && strchr(star_match[1], ' '))) {
2951                     /* Reject bogus matches */
2952                     i = oldi;
2953                   } else {
2954                     if (appData.colorize) {
2955                       if (oldi > next_out) {
2956                         SendToPlayer(&buf[next_out], oldi - next_out);
2957                         next_out = oldi;
2958                       }
2959                       switch (tkind) {
2960                       case 1:
2961                         Colorize(ColorTell, FALSE);
2962                         curColor = ColorTell;
2963                         break;
2964                       case 2:
2965                         Colorize(ColorKibitz, FALSE);
2966                         curColor = ColorKibitz;
2967                         break;
2968                       case 3:
2969                         p = strrchr(star_match[1], '(');
2970                         if (p == NULL) {
2971                           p = star_match[1];
2972                         } else {
2973                           p++;
2974                         }
2975                         if (atoi(p) == 1) {
2976                           Colorize(ColorChannel1, FALSE);
2977                           curColor = ColorChannel1;
2978                         } else {
2979                           Colorize(ColorChannel, FALSE);
2980                           curColor = ColorChannel;
2981                         }
2982                         break;
2983                       case 5:
2984                         curColor = ColorNormal;
2985                         break;
2986                       }
2987                     }
2988                     if (started == STARTED_NONE && appData.autoComment &&
2989                         (gameMode == IcsObserving ||
2990                          gameMode == IcsPlayingWhite ||
2991                          gameMode == IcsPlayingBlack)) {
2992                       parse_pos = i - oldi;
2993                       memcpy(parse, &buf[oldi], parse_pos);
2994                       parse[parse_pos] = NULLCHAR;
2995                       started = STARTED_COMMENT;
2996                       savingComment = TRUE;
2997                     } else {
2998                       started = STARTED_CHATTER;
2999                       savingComment = FALSE;
3000                     }
3001                     loggedOn = TRUE;
3002                     continue;
3003                   }
3004                 }
3005
3006                 if (looking_at(buf, &i, "* s-shouts: ") ||
3007                     looking_at(buf, &i, "* c-shouts: ")) {
3008                     if (appData.colorize) {
3009                         if (oldi > next_out) {
3010                             SendToPlayer(&buf[next_out], oldi - next_out);
3011                             next_out = oldi;
3012                         }
3013                         Colorize(ColorSShout, FALSE);
3014                         curColor = ColorSShout;
3015                     }
3016                     loggedOn = TRUE;
3017                     started = STARTED_CHATTER;
3018                     continue;
3019                 }
3020
3021                 if (looking_at(buf, &i, "--->")) {
3022                     loggedOn = TRUE;
3023                     continue;
3024                 }
3025
3026                 if (looking_at(buf, &i, "* shouts: ") ||
3027                     looking_at(buf, &i, "--> ")) {
3028                     if (appData.colorize) {
3029                         if (oldi > next_out) {
3030                             SendToPlayer(&buf[next_out], oldi - next_out);
3031                             next_out = oldi;
3032                         }
3033                         Colorize(ColorShout, FALSE);
3034                         curColor = ColorShout;
3035                     }
3036                     loggedOn = TRUE;
3037                     started = STARTED_CHATTER;
3038                     continue;
3039                 }
3040
3041                 if (looking_at( buf, &i, "Challenge:")) {
3042                     if (appData.colorize) {
3043                         if (oldi > next_out) {
3044                             SendToPlayer(&buf[next_out], oldi - next_out);
3045                             next_out = oldi;
3046                         }
3047                         Colorize(ColorChallenge, FALSE);
3048                         curColor = ColorChallenge;
3049                     }
3050                     loggedOn = TRUE;
3051                     continue;
3052                 }
3053
3054                 if (looking_at(buf, &i, "* offers you") ||
3055                     looking_at(buf, &i, "* offers to be") ||
3056                     looking_at(buf, &i, "* would like to") ||
3057                     looking_at(buf, &i, "* requests to") ||
3058                     looking_at(buf, &i, "Your opponent offers") ||
3059                     looking_at(buf, &i, "Your opponent requests")) {
3060
3061                     if (appData.colorize) {
3062                         if (oldi > next_out) {
3063                             SendToPlayer(&buf[next_out], oldi - next_out);
3064                             next_out = oldi;
3065                         }
3066                         Colorize(ColorRequest, FALSE);
3067                         curColor = ColorRequest;
3068                     }
3069                     continue;
3070                 }
3071
3072                 if (looking_at(buf, &i, "* (*) seeking")) {
3073                     if (appData.colorize) {
3074                         if (oldi > next_out) {
3075                             SendToPlayer(&buf[next_out], oldi - next_out);
3076                             next_out = oldi;
3077                         }
3078                         Colorize(ColorSeek, FALSE);
3079                         curColor = ColorSeek;
3080                     }
3081                     continue;
3082             }
3083
3084             if (looking_at(buf, &i, "\\   ")) {
3085                 if (prevColor != ColorNormal) {
3086                     if (oldi > next_out) {
3087                         SendToPlayer(&buf[next_out], oldi - next_out);
3088                         next_out = oldi;
3089                     }
3090                     Colorize(prevColor, TRUE);
3091                     curColor = prevColor;
3092                 }
3093                 if (savingComment) {
3094                     parse_pos = i - oldi;
3095                     memcpy(parse, &buf[oldi], parse_pos);
3096                     parse[parse_pos] = NULLCHAR;
3097                     started = STARTED_COMMENT;
3098                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3099                         chattingPartner = savingComment - 3; // kludge to remember the box
3100                 } else {
3101                     started = STARTED_CHATTER;
3102                 }
3103                 continue;
3104             }
3105
3106             if (looking_at(buf, &i, "Black Strength :") ||
3107                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3108                 looking_at(buf, &i, "<10>") ||
3109                 looking_at(buf, &i, "#@#")) {
3110                 /* Wrong board style */
3111                 loggedOn = TRUE;
3112                 SendToICS(ics_prefix);
3113                 SendToICS("set style 12\n");
3114                 SendToICS(ics_prefix);
3115                 SendToICS("refresh\n");
3116                 continue;
3117             }
3118
3119             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3120                 ICSInitScript();
3121                 have_sent_ICS_logon = 1;
3122                 /* if we don't send the login/password via icsLogon, use special readline
3123                    code for it */
3124                 if (strlen(appData.icsLogon)==0)
3125                   {
3126                     sending_ICS_password = 0; // in case we come back to login
3127                     sending_ICS_login = 1;
3128                   };
3129                 continue;
3130             }
3131             /* need to shadow the password */
3132             if (!sending_ICS_password && looking_at(buf, &i, "password:")) {
3133               /* if we don't send the login/password via icsLogon, use special readline
3134                  code for it */
3135               if (strlen(appData.icsLogon)==0)
3136                 sending_ICS_password = 1;
3137               continue;
3138             }
3139
3140             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3141                 (looking_at(buf, &i, "\n<12> ") ||
3142                  looking_at(buf, &i, "<12> "))) {
3143                 loggedOn = TRUE;
3144                 if (oldi > next_out) {
3145                     SendToPlayer(&buf[next_out], oldi - next_out);
3146                 }
3147                 next_out = i;
3148                 started = STARTED_BOARD;
3149                 parse_pos = 0;
3150                 continue;
3151             }
3152
3153             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3154                 looking_at(buf, &i, "<b1> ")) {
3155                 if (oldi > next_out) {
3156                     SendToPlayer(&buf[next_out], oldi - next_out);
3157                 }
3158                 next_out = i;
3159                 started = STARTED_HOLDINGS;
3160                 parse_pos = 0;
3161                 continue;
3162             }
3163
3164             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3165                 loggedOn = TRUE;
3166                 /* Header for a move list -- first line */
3167
3168                 switch (ics_getting_history) {
3169                   case H_FALSE:
3170                     switch (gameMode) {
3171                       case IcsIdle:
3172                       case BeginningOfGame:
3173                         /* User typed "moves" or "oldmoves" while we
3174                            were idle.  Pretend we asked for these
3175                            moves and soak them up so user can step
3176                            through them and/or save them.
3177                            */
3178                         Reset(FALSE, TRUE);
3179                         gameMode = IcsObserving;
3180                         ModeHighlight();
3181                         ics_gamenum = -1;
3182                         ics_getting_history = H_GOT_UNREQ_HEADER;
3183                         break;
3184                       case EditGame: /*?*/
3185                       case EditPosition: /*?*/
3186                         /* Should above feature work in these modes too? */
3187                         /* For now it doesn't */
3188                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3189                         break;
3190                       default:
3191                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3192                         break;
3193                     }
3194                     break;
3195                   case H_REQUESTED:
3196                     /* Is this the right one? */
3197                     if (gameInfo.white && gameInfo.black &&
3198                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3199                         strcmp(gameInfo.black, star_match[2]) == 0) {
3200                         /* All is well */
3201                         ics_getting_history = H_GOT_REQ_HEADER;
3202                     }
3203                     break;
3204                   case H_GOT_REQ_HEADER:
3205                   case H_GOT_UNREQ_HEADER:
3206                   case H_GOT_UNWANTED_HEADER:
3207                   case H_GETTING_MOVES:
3208                     /* Should not happen */
3209                     DisplayError(_("Error gathering move list: two headers"), 0);
3210                     ics_getting_history = H_FALSE;
3211                     break;
3212                 }
3213
3214                 /* Save player ratings into gameInfo if needed */
3215                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3216                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3217                     (gameInfo.whiteRating == -1 ||
3218                      gameInfo.blackRating == -1)) {
3219
3220                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3221                     gameInfo.blackRating = string_to_rating(star_match[3]);
3222                     if (appData.debugMode)
3223                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3224                               gameInfo.whiteRating, gameInfo.blackRating);
3225                 }
3226                 continue;
3227             }
3228
3229             if (looking_at(buf, &i,
3230               "* * match, initial time: * minute*, increment: * second")) {
3231                 /* Header for a move list -- second line */
3232                 /* Initial board will follow if this is a wild game */
3233                 if (gameInfo.event != NULL) free(gameInfo.event);
3234                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3235                 gameInfo.event = StrSave(str);
3236                 /* [HGM] we switched variant. Translate boards if needed. */
3237                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3238                 continue;
3239             }
3240
3241             if (looking_at(buf, &i, "Move  ")) {
3242                 /* Beginning of a move list */
3243                 switch (ics_getting_history) {
3244                   case H_FALSE:
3245                     /* Normally should not happen */
3246                     /* Maybe user hit reset while we were parsing */
3247                     break;
3248                   case H_REQUESTED:
3249                     /* Happens if we are ignoring a move list that is not
3250                      * the one we just requested.  Common if the user
3251                      * tries to observe two games without turning off
3252                      * getMoveList */
3253                     break;
3254                   case H_GETTING_MOVES:
3255                     /* Should not happen */
3256                     DisplayError(_("Error gathering move list: nested"), 0);
3257                     ics_getting_history = H_FALSE;
3258                     break;
3259                   case H_GOT_REQ_HEADER:
3260                     ics_getting_history = H_GETTING_MOVES;
3261                     started = STARTED_MOVES;
3262                     parse_pos = 0;
3263                     if (oldi > next_out) {
3264                         SendToPlayer(&buf[next_out], oldi - next_out);
3265                     }
3266                     break;
3267                   case H_GOT_UNREQ_HEADER:
3268                     ics_getting_history = H_GETTING_MOVES;
3269                     started = STARTED_MOVES_NOHIDE;
3270                     parse_pos = 0;
3271                     break;
3272                   case H_GOT_UNWANTED_HEADER:
3273                     ics_getting_history = H_FALSE;
3274                     break;
3275                 }
3276                 continue;
3277             }
3278
3279             if (looking_at(buf, &i, "% ") ||
3280                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3281                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3282                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3283                     soughtPending = FALSE;
3284                     seekGraphUp = TRUE;
3285                     DrawSeekGraph();
3286                 }
3287                 if(suppressKibitz) next_out = i;
3288                 savingComment = FALSE;
3289                 suppressKibitz = 0;
3290                 switch (started) {
3291                   case STARTED_MOVES:
3292                   case STARTED_MOVES_NOHIDE:
3293                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3294                     parse[parse_pos + i - oldi] = NULLCHAR;
3295                     ParseGameHistory(parse);
3296 #if ZIPPY
3297                     if (appData.zippyPlay && first.initDone) {
3298                         FeedMovesToProgram(&first, forwardMostMove);
3299                         if (gameMode == IcsPlayingWhite) {
3300                             if (WhiteOnMove(forwardMostMove)) {
3301                                 if (first.sendTime) {
3302                                   if (first.useColors) {
3303                                     SendToProgram("black\n", &first);
3304                                   }
3305                                   SendTimeRemaining(&first, TRUE);
3306                                 }
3307                                 if (first.useColors) {
3308                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3309                                 }
3310                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3311                                 first.maybeThinking = TRUE;
3312                             } else {
3313                                 if (first.usePlayother) {
3314                                   if (first.sendTime) {
3315                                     SendTimeRemaining(&first, TRUE);
3316                                   }
3317                                   SendToProgram("playother\n", &first);
3318                                   firstMove = FALSE;
3319                                 } else {
3320                                   firstMove = TRUE;
3321                                 }
3322                             }
3323                         } else if (gameMode == IcsPlayingBlack) {
3324                             if (!WhiteOnMove(forwardMostMove)) {
3325                                 if (first.sendTime) {
3326                                   if (first.useColors) {
3327                                     SendToProgram("white\n", &first);
3328                                   }
3329                                   SendTimeRemaining(&first, FALSE);
3330                                 }
3331                                 if (first.useColors) {
3332                                   SendToProgram("black\n", &first);
3333                                 }
3334                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3335                                 first.maybeThinking = TRUE;
3336                             } else {
3337                                 if (first.usePlayother) {
3338                                   if (first.sendTime) {
3339                                     SendTimeRemaining(&first, FALSE);
3340                                   }
3341                                   SendToProgram("playother\n", &first);
3342                                   firstMove = FALSE;
3343                                 } else {
3344                                   firstMove = TRUE;
3345                                 }
3346                             }
3347                         }
3348                     }
3349 #endif
3350                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3351                         /* Moves came from oldmoves or moves command
3352                            while we weren't doing anything else.
3353                            */
3354                         currentMove = forwardMostMove;
3355                         ClearHighlights();/*!!could figure this out*/
3356                         flipView = appData.flipView;
3357                         DrawPosition(TRUE, boards[currentMove]);
3358                         DisplayBothClocks();
3359                         snprintf(str, MSG_SIZ, "%s vs. %s",
3360                                 gameInfo.white, gameInfo.black);
3361                         DisplayTitle(str);
3362                         gameMode = IcsIdle;
3363                     } else {
3364                         /* Moves were history of an active game */
3365                         if (gameInfo.resultDetails != NULL) {
3366                             free(gameInfo.resultDetails);
3367                             gameInfo.resultDetails = NULL;
3368                         }
3369                     }
3370                     HistorySet(parseList, backwardMostMove,
3371                                forwardMostMove, currentMove-1);
3372                     DisplayMove(currentMove - 1);
3373                     if (started == STARTED_MOVES) next_out = i;
3374                     started = STARTED_NONE;
3375                     ics_getting_history = H_FALSE;
3376                     break;
3377
3378                   case STARTED_OBSERVE:
3379                     started = STARTED_NONE;
3380                     SendToICS(ics_prefix);
3381                     SendToICS("refresh\n");
3382                     break;
3383
3384                   default:
3385                     break;
3386                 }
3387                 if(bookHit) { // [HGM] book: simulate book reply
3388                     static char bookMove[MSG_SIZ]; // a bit generous?
3389
3390                     programStats.nodes = programStats.depth = programStats.time =
3391                     programStats.score = programStats.got_only_move = 0;
3392                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3393
3394                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3395                     strcat(bookMove, bookHit);
3396                     HandleMachineMove(bookMove, &first);
3397                 }
3398                 continue;
3399             }
3400
3401             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3402                  started == STARTED_HOLDINGS ||
3403                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3404                 /* Accumulate characters in move list or board */
3405                 parse[parse_pos++] = buf[i];
3406             }
3407
3408             /* Start of game messages.  Mostly we detect start of game
3409                when the first board image arrives.  On some versions
3410                of the ICS, though, we need to do a "refresh" after starting
3411                to observe in order to get the current board right away. */
3412             if (looking_at(buf, &i, "Adding game * to observation list")) {
3413                 started = STARTED_OBSERVE;
3414                 continue;
3415             }
3416
3417             /* Handle auto-observe */
3418             if (appData.autoObserve &&
3419                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3420                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3421                 char *player;
3422                 /* Choose the player that was highlighted, if any. */
3423                 if (star_match[0][0] == '\033' ||
3424                     star_match[1][0] != '\033') {
3425                     player = star_match[0];
3426                 } else {
3427                     player = star_match[2];
3428                 }
3429                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3430                         ics_prefix, StripHighlightAndTitle(player));
3431                 SendToICS(str);
3432
3433                 /* Save ratings from notify string */
3434                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3435                 player1Rating = string_to_rating(star_match[1]);
3436                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3437                 player2Rating = string_to_rating(star_match[3]);
3438
3439                 if (appData.debugMode)
3440                   fprintf(debugFP,
3441                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3442                           player1Name, player1Rating,
3443                           player2Name, player2Rating);
3444
3445                 continue;
3446             }
3447
3448             /* Deal with automatic examine mode after a game,
3449                and with IcsObserving -> IcsExamining transition */
3450             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3451                 looking_at(buf, &i, "has made you an examiner of game *")) {
3452
3453                 int gamenum = atoi(star_match[0]);
3454                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3455                     gamenum == ics_gamenum) {
3456                     /* We were already playing or observing this game;
3457                        no need to refetch history */
3458                     gameMode = IcsExamining;
3459                     if (pausing) {
3460                         pauseExamForwardMostMove = forwardMostMove;
3461                     } else if (currentMove < forwardMostMove) {
3462                         ForwardInner(forwardMostMove);
3463                     }
3464                 } else {
3465                     /* I don't think this case really can happen */
3466                     SendToICS(ics_prefix);
3467                     SendToICS("refresh\n");
3468                 }
3469                 continue;
3470             }
3471
3472             /* Error messages */
3473 //          if (ics_user_moved) {
3474             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3475                 if (looking_at(buf, &i, "Illegal move") ||
3476                     looking_at(buf, &i, "Not a legal move") ||
3477                     looking_at(buf, &i, "Your king is in check") ||
3478                     looking_at(buf, &i, "It isn't your turn") ||
3479                     looking_at(buf, &i, "It is not your move")) {
3480                     /* Illegal move */
3481                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3482                         currentMove = forwardMostMove-1;
3483                         DisplayMove(currentMove - 1); /* before DMError */
3484                         DrawPosition(FALSE, boards[currentMove]);
3485                         SwitchClocks(forwardMostMove-1); // [HGM] race
3486                         DisplayBothClocks();
3487                     }
3488                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3489                     ics_user_moved = 0;
3490                     continue;
3491                 }
3492             }
3493
3494             if (looking_at(buf, &i, "still have time") ||
3495                 looking_at(buf, &i, "not out of time") ||
3496                 looking_at(buf, &i, "either player is out of time") ||
3497                 looking_at(buf, &i, "has timeseal; checking")) {
3498                 /* We must have called his flag a little too soon */
3499                 whiteFlag = blackFlag = FALSE;
3500                 continue;
3501             }
3502
3503             if (looking_at(buf, &i, "added * seconds to") ||
3504                 looking_at(buf, &i, "seconds were added to")) {
3505                 /* Update the clocks */
3506                 SendToICS(ics_prefix);
3507                 SendToICS("refresh\n");
3508                 continue;
3509             }
3510
3511             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3512                 ics_clock_paused = TRUE;
3513                 StopClocks();
3514                 continue;
3515             }
3516
3517             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3518                 ics_clock_paused = FALSE;
3519                 StartClocks();
3520                 continue;
3521             }
3522
3523             /* Grab player ratings from the Creating: message.
3524                Note we have to check for the special case when
3525                the ICS inserts things like [white] or [black]. */
3526             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3527                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3528                 /* star_matches:
3529                    0    player 1 name (not necessarily white)
3530                    1    player 1 rating
3531                    2    empty, white, or black (IGNORED)
3532                    3    player 2 name (not necessarily black)
3533                    4    player 2 rating
3534
3535                    The names/ratings are sorted out when the game
3536                    actually starts (below).
3537                 */
3538                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3539                 player1Rating = string_to_rating(star_match[1]);
3540                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3541                 player2Rating = string_to_rating(star_match[4]);
3542
3543                 if (appData.debugMode)
3544                   fprintf(debugFP,
3545                           "Ratings from 'Creating:' %s %d, %s %d\n",
3546                           player1Name, player1Rating,
3547                           player2Name, player2Rating);
3548
3549                 continue;
3550             }
3551
3552             /* Improved generic start/end-of-game messages */
3553             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3554                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3555                 /* If tkind == 0: */
3556                 /* star_match[0] is the game number */
3557                 /*           [1] is the white player's name */
3558                 /*           [2] is the black player's name */
3559                 /* For end-of-game: */
3560                 /*           [3] is the reason for the game end */
3561                 /*           [4] is a PGN end game-token, preceded by " " */
3562                 /* For start-of-game: */
3563                 /*           [3] begins with "Creating" or "Continuing" */
3564                 /*           [4] is " *" or empty (don't care). */
3565                 int gamenum = atoi(star_match[0]);
3566                 char *whitename, *blackname, *why, *endtoken;
3567                 ChessMove endtype = EndOfFile;
3568
3569                 if (tkind == 0) {
3570                   whitename = star_match[1];
3571                   blackname = star_match[2];
3572                   why = star_match[3];
3573                   endtoken = star_match[4];
3574                 } else {
3575                   whitename = star_match[1];
3576                   blackname = star_match[3];
3577                   why = star_match[5];
3578                   endtoken = star_match[6];
3579                 }
3580
3581                 /* Game start messages */
3582                 if (strncmp(why, "Creating ", 9) == 0 ||
3583                     strncmp(why, "Continuing ", 11) == 0) {
3584                     gs_gamenum = gamenum;
3585                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3586                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3587 #if ZIPPY
3588                     if (appData.zippyPlay) {
3589                         ZippyGameStart(whitename, blackname);
3590                     }
3591 #endif /*ZIPPY*/
3592                     partnerBoardValid = FALSE; // [HGM] bughouse
3593                     continue;
3594                 }
3595
3596                 /* Game end messages */
3597                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3598                     ics_gamenum != gamenum) {
3599                     continue;
3600                 }
3601                 while (endtoken[0] == ' ') endtoken++;
3602                 switch (endtoken[0]) {
3603                   case '*':
3604                   default:
3605                     endtype = GameUnfinished;
3606                     break;
3607                   case '0':
3608                     endtype = BlackWins;
3609                     break;
3610                   case '1':
3611                     if (endtoken[1] == '/')
3612                       endtype = GameIsDrawn;
3613                     else
3614                       endtype = WhiteWins;
3615                     break;
3616                 }
3617                 GameEnds(endtype, why, GE_ICS);
3618 #if ZIPPY
3619                 if (appData.zippyPlay && first.initDone) {
3620                     ZippyGameEnd(endtype, why);
3621                     if (first.pr == NULL) {
3622                       /* Start the next process early so that we'll
3623                          be ready for the next challenge */
3624                       StartChessProgram(&first);
3625                     }
3626                     /* Send "new" early, in case this command takes
3627                        a long time to finish, so that we'll be ready
3628                        for the next challenge. */
3629                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3630                     Reset(TRUE, TRUE);
3631                 }
3632 #endif /*ZIPPY*/
3633                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3634                 continue;
3635             }
3636
3637             if (looking_at(buf, &i, "Removing game * from observation") ||
3638                 looking_at(buf, &i, "no longer observing game *") ||
3639                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3640                 if (gameMode == IcsObserving &&
3641                     atoi(star_match[0]) == ics_gamenum)
3642                   {
3643                       /* icsEngineAnalyze */
3644                       if (appData.icsEngineAnalyze) {
3645                             ExitAnalyzeMode();
3646                             ModeHighlight();
3647                       }
3648                       StopClocks();
3649                       gameMode = IcsIdle;
3650                       ics_gamenum = -1;
3651                       ics_user_moved = FALSE;
3652                   }
3653                 continue;
3654             }
3655
3656             if (looking_at(buf, &i, "no longer examining game *")) {
3657                 if (gameMode == IcsExamining &&
3658                     atoi(star_match[0]) == ics_gamenum)
3659                   {
3660                       gameMode = IcsIdle;
3661                       ics_gamenum = -1;
3662                       ics_user_moved = FALSE;
3663                   }
3664                 continue;
3665             }
3666
3667             /* Advance leftover_start past any newlines we find,
3668                so only partial lines can get reparsed */
3669             if (looking_at(buf, &i, "\n")) {
3670                 prevColor = curColor;
3671                 if (curColor != ColorNormal) {
3672                     if (oldi > next_out) {
3673                         SendToPlayer(&buf[next_out], oldi - next_out);
3674                         next_out = oldi;
3675                     }
3676                     Colorize(ColorNormal, FALSE);
3677                     curColor = ColorNormal;
3678                 }
3679                 if (started == STARTED_BOARD) {
3680                     started = STARTED_NONE;
3681                     parse[parse_pos] = NULLCHAR;
3682                     ParseBoard12(parse);
3683                     ics_user_moved = 0;
3684
3685                     /* Send premove here */
3686                     if (appData.premove) {
3687                       char str[MSG_SIZ];
3688                       if (currentMove == 0 &&
3689                           gameMode == IcsPlayingWhite &&
3690                           appData.premoveWhite) {
3691                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3692                         if (appData.debugMode)
3693                           fprintf(debugFP, "Sending premove:\n");
3694                         SendToICS(str);
3695                       } else if (currentMove == 1 &&
3696                                  gameMode == IcsPlayingBlack &&
3697                                  appData.premoveBlack) {
3698                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3699                         if (appData.debugMode)
3700                           fprintf(debugFP, "Sending premove:\n");
3701                         SendToICS(str);
3702                       } else if (gotPremove) {
3703                         gotPremove = 0;
3704                         ClearPremoveHighlights();
3705                         if (appData.debugMode)
3706                           fprintf(debugFP, "Sending premove:\n");
3707                           UserMoveEvent(premoveFromX, premoveFromY,
3708                                         premoveToX, premoveToY,
3709                                         premovePromoChar);
3710                       }
3711                     }
3712
3713                     /* Usually suppress following prompt */
3714                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3715                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3716                         if (looking_at(buf, &i, "*% ")) {
3717                             savingComment = FALSE;
3718                             suppressKibitz = 0;
3719                         }
3720                     }
3721                     next_out = i;
3722                 } else if (started == STARTED_HOLDINGS) {
3723                     int gamenum;
3724                     char new_piece[MSG_SIZ];
3725                     started = STARTED_NONE;
3726                     parse[parse_pos] = NULLCHAR;
3727                     if (appData.debugMode)
3728                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3729                                                         parse, currentMove);
3730                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3731                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3732                         if (gameInfo.variant == VariantNormal) {
3733                           /* [HGM] We seem to switch variant during a game!
3734                            * Presumably no holdings were displayed, so we have
3735                            * to move the position two files to the right to
3736                            * create room for them!
3737                            */
3738                           VariantClass newVariant;
3739                           switch(gameInfo.boardWidth) { // base guess on board width
3740                                 case 9:  newVariant = VariantShogi; break;
3741                                 case 10: newVariant = VariantGreat; break;
3742                                 default: newVariant = VariantCrazyhouse; break;
3743                           }
3744                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3745                           /* Get a move list just to see the header, which
3746                              will tell us whether this is really bug or zh */
3747                           if (ics_getting_history == H_FALSE) {
3748                             ics_getting_history = H_REQUESTED;
3749                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3750                             SendToICS(str);
3751                           }
3752                         }
3753                         new_piece[0] = NULLCHAR;
3754                         sscanf(parse, "game %d white [%s black [%s <- %s",
3755                                &gamenum, white_holding, black_holding,
3756                                new_piece);
3757                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3758                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3759                         /* [HGM] copy holdings to board holdings area */
3760                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3761                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3762                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3763 #if ZIPPY
3764                         if (appData.zippyPlay && first.initDone) {
3765                             ZippyHoldings(white_holding, black_holding,
3766                                           new_piece);
3767                         }
3768 #endif /*ZIPPY*/
3769                         if (tinyLayout || smallLayout) {
3770                             char wh[16], bh[16];
3771                             PackHolding(wh, white_holding);
3772                             PackHolding(bh, black_holding);
3773                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3774                                     gameInfo.white, gameInfo.black);
3775                         } else {
3776                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3777                                     gameInfo.white, white_holding,
3778                                     gameInfo.black, black_holding);
3779                         }
3780                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3781                         DrawPosition(FALSE, boards[currentMove]);
3782                         DisplayTitle(str);
3783                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3784                         sscanf(parse, "game %d white [%s black [%s <- %s",
3785                                &gamenum, white_holding, black_holding,
3786                                new_piece);
3787                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3788                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3789                         /* [HGM] copy holdings to partner-board holdings area */
3790                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3791                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3792                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3793                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3794                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3795                       }
3796                     }
3797                     /* Suppress following prompt */
3798                     if (looking_at(buf, &i, "*% ")) {
3799                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3800                         savingComment = FALSE;
3801                         suppressKibitz = 0;
3802                     }
3803                     next_out = i;
3804                 }
3805                 continue;
3806             }
3807
3808             i++;                /* skip unparsed character and loop back */
3809         }
3810
3811         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3812 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3813 //          SendToPlayer(&buf[next_out], i - next_out);
3814             started != STARTED_HOLDINGS && leftover_start > next_out) {
3815             SendToPlayer(&buf[next_out], leftover_start - next_out);
3816             next_out = i;
3817         }
3818
3819         leftover_len = buf_len - leftover_start;
3820         /* if buffer ends with something we couldn't parse,
3821            reparse it after appending the next read */
3822
3823     } else if (count == 0) {
3824         RemoveInputSource(isr);
3825         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3826     } else {
3827         DisplayFatalError(_("Error reading from ICS"), error, 1);
3828     }
3829 }
3830
3831
3832 /* Board style 12 looks like this:
3833
3834    <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
3835
3836  * The "<12> " is stripped before it gets to this routine.  The two
3837  * trailing 0's (flip state and clock ticking) are later addition, and
3838  * some chess servers may not have them, or may have only the first.
3839  * Additional trailing fields may be added in the future.
3840  */
3841
3842 #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"
3843
3844 #define RELATION_OBSERVING_PLAYED    0
3845 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3846 #define RELATION_PLAYING_MYMOVE      1
3847 #define RELATION_PLAYING_NOTMYMOVE  -1
3848 #define RELATION_EXAMINING           2
3849 #define RELATION_ISOLATED_BOARD     -3
3850 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3851
3852 void
3853 ParseBoard12(string)
3854      char *string;
3855 {
3856     GameMode newGameMode;
3857     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3858     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3859     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3860     char to_play, board_chars[200];
3861     char move_str[500], str[500], elapsed_time[500];
3862     char black[32], white[32];
3863     Board board;
3864     int prevMove = currentMove;
3865     int ticking = 2;
3866     ChessMove moveType;
3867     int fromX, fromY, toX, toY;
3868     char promoChar;
3869     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3870     char *bookHit = NULL; // [HGM] book
3871     Boolean weird = FALSE, reqFlag = FALSE;
3872
3873     fromX = fromY = toX = toY = -1;
3874
3875     newGame = FALSE;
3876
3877     if (appData.debugMode)
3878       fprintf(debugFP, _("Parsing board: %s\n"), string);
3879
3880     move_str[0] = NULLCHAR;
3881     elapsed_time[0] = NULLCHAR;
3882     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3883         int  i = 0, j;
3884         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3885             if(string[i] == ' ') { ranks++; files = 0; }
3886             else files++;
3887             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3888             i++;
3889         }
3890         for(j = 0; j <i; j++) board_chars[j] = string[j];
3891         board_chars[i] = '\0';
3892         string += i + 1;
3893     }
3894     n = sscanf(string, PATTERN, &to_play, &double_push,
3895                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3896                &gamenum, white, black, &relation, &basetime, &increment,
3897                &white_stren, &black_stren, &white_time, &black_time,
3898                &moveNum, str, elapsed_time, move_str, &ics_flip,
3899                &ticking);
3900
3901     if (n < 21) {
3902         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3903         DisplayError(str, 0);
3904         return;
3905     }
3906
3907     /* Convert the move number to internal form */
3908     moveNum = (moveNum - 1) * 2;
3909     if (to_play == 'B') moveNum++;
3910     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3911       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3912                         0, 1);
3913       return;
3914     }
3915
3916     switch (relation) {
3917       case RELATION_OBSERVING_PLAYED:
3918       case RELATION_OBSERVING_STATIC:
3919         if (gamenum == -1) {
3920             /* Old ICC buglet */
3921             relation = RELATION_OBSERVING_STATIC;
3922         }
3923         newGameMode = IcsObserving;
3924         break;
3925       case RELATION_PLAYING_MYMOVE:
3926       case RELATION_PLAYING_NOTMYMOVE:
3927         newGameMode =
3928           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3929             IcsPlayingWhite : IcsPlayingBlack;
3930         break;
3931       case RELATION_EXAMINING:
3932         newGameMode = IcsExamining;
3933         break;
3934       case RELATION_ISOLATED_BOARD:
3935       default:
3936         /* Just display this board.  If user was doing something else,
3937            we will forget about it until the next board comes. */
3938         newGameMode = IcsIdle;
3939         break;
3940       case RELATION_STARTING_POSITION:
3941         newGameMode = gameMode;
3942         break;
3943     }
3944
3945     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3946          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3947       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3948       char *toSqr;
3949       for (k = 0; k < ranks; k++) {
3950         for (j = 0; j < files; j++)
3951           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3952         if(gameInfo.holdingsWidth > 1) {
3953              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3954              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3955         }
3956       }
3957       CopyBoard(partnerBoard, board);
3958       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3959         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3960         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3961       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3962       if(toSqr = strchr(str, '-')) {
3963         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3964         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3965       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3966       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3967       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3968       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3969       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3970       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3971                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3972       DisplayMessage(partnerStatus, "");
3973         partnerBoardValid = TRUE;
3974       return;
3975     }
3976
3977     /* Modify behavior for initial board display on move listing
3978        of wild games.
3979        */
3980     switch (ics_getting_history) {
3981       case H_FALSE:
3982       case H_REQUESTED:
3983         break;
3984       case H_GOT_REQ_HEADER:
3985       case H_GOT_UNREQ_HEADER:
3986         /* This is the initial position of the current game */
3987         gamenum = ics_gamenum;
3988         moveNum = 0;            /* old ICS bug workaround */
3989         if (to_play == 'B') {
3990           startedFromSetupPosition = TRUE;
3991           blackPlaysFirst = TRUE;
3992           moveNum = 1;
3993           if (forwardMostMove == 0) forwardMostMove = 1;
3994           if (backwardMostMove == 0) backwardMostMove = 1;
3995           if (currentMove == 0) currentMove = 1;
3996         }
3997         newGameMode = gameMode;
3998         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3999         break;
4000       case H_GOT_UNWANTED_HEADER:
4001         /* This is an initial board that we don't want */
4002         return;
4003       case H_GETTING_MOVES:
4004         /* Should not happen */
4005         DisplayError(_("Error gathering move list: extra board"), 0);
4006         ics_getting_history = H_FALSE;
4007         return;
4008     }
4009
4010    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4011                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4012      /* [HGM] We seem to have switched variant unexpectedly
4013       * Try to guess new variant from board size
4014       */
4015           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4016           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4017           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4018           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4019           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4020           if(!weird) newVariant = VariantNormal;
4021           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4022           /* Get a move list just to see the header, which
4023              will tell us whether this is really bug or zh */
4024           if (ics_getting_history == H_FALSE) {
4025             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4026             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4027             SendToICS(str);
4028           }
4029     }
4030
4031     /* Take action if this is the first board of a new game, or of a
4032        different game than is currently being displayed.  */
4033     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4034         relation == RELATION_ISOLATED_BOARD) {
4035
4036         /* Forget the old game and get the history (if any) of the new one */
4037         if (gameMode != BeginningOfGame) {
4038           Reset(TRUE, TRUE);
4039         }
4040         newGame = TRUE;
4041         if (appData.autoRaiseBoard) BoardToTop();
4042         prevMove = -3;
4043         if (gamenum == -1) {
4044             newGameMode = IcsIdle;
4045         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4046                    appData.getMoveList && !reqFlag) {
4047             /* Need to get game history */
4048             ics_getting_history = H_REQUESTED;
4049             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4050             SendToICS(str);
4051         }
4052
4053         /* Initially flip the board to have black on the bottom if playing
4054            black or if the ICS flip flag is set, but let the user change
4055            it with the Flip View button. */
4056         flipView = appData.autoFlipView ?
4057           (newGameMode == IcsPlayingBlack) || ics_flip :
4058           appData.flipView;
4059
4060         /* Done with values from previous mode; copy in new ones */
4061         gameMode = newGameMode;
4062         ModeHighlight();
4063         ics_gamenum = gamenum;
4064         if (gamenum == gs_gamenum) {
4065             int klen = strlen(gs_kind);
4066             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4067             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4068             gameInfo.event = StrSave(str);
4069         } else {
4070             gameInfo.event = StrSave("ICS game");
4071         }
4072         gameInfo.site = StrSave(appData.icsHost);
4073         gameInfo.date = PGNDate();
4074         gameInfo.round = StrSave("-");
4075         gameInfo.white = StrSave(white);
4076         gameInfo.black = StrSave(black);
4077         timeControl = basetime * 60 * 1000;
4078         timeControl_2 = 0;
4079         timeIncrement = increment * 1000;
4080         movesPerSession = 0;
4081         gameInfo.timeControl = TimeControlTagValue();
4082         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4083   if (appData.debugMode) {
4084     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4085     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4086     setbuf(debugFP, NULL);
4087   }
4088
4089         gameInfo.outOfBook = NULL;
4090
4091         /* Do we have the ratings? */
4092         if (strcmp(player1Name, white) == 0 &&
4093             strcmp(player2Name, black) == 0) {
4094             if (appData.debugMode)
4095               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4096                       player1Rating, player2Rating);
4097             gameInfo.whiteRating = player1Rating;
4098             gameInfo.blackRating = player2Rating;
4099         } else if (strcmp(player2Name, white) == 0 &&
4100                    strcmp(player1Name, black) == 0) {
4101             if (appData.debugMode)
4102               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4103                       player2Rating, player1Rating);
4104             gameInfo.whiteRating = player2Rating;
4105             gameInfo.blackRating = player1Rating;
4106         }
4107         player1Name[0] = player2Name[0] = NULLCHAR;
4108
4109         /* Silence shouts if requested */
4110         if (appData.quietPlay &&
4111             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4112             SendToICS(ics_prefix);
4113             SendToICS("set shout 0\n");
4114         }
4115     }
4116
4117     /* Deal with midgame name changes */
4118     if (!newGame) {
4119         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4120             if (gameInfo.white) free(gameInfo.white);
4121             gameInfo.white = StrSave(white);
4122         }
4123         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4124             if (gameInfo.black) free(gameInfo.black);
4125             gameInfo.black = StrSave(black);
4126         }
4127     }
4128
4129     /* Throw away game result if anything actually changes in examine mode */
4130     if (gameMode == IcsExamining && !newGame) {
4131         gameInfo.result = GameUnfinished;
4132         if (gameInfo.resultDetails != NULL) {
4133             free(gameInfo.resultDetails);
4134             gameInfo.resultDetails = NULL;
4135         }
4136     }
4137
4138     /* In pausing && IcsExamining mode, we ignore boards coming
4139        in if they are in a different variation than we are. */
4140     if (pauseExamInvalid) return;
4141     if (pausing && gameMode == IcsExamining) {
4142         if (moveNum <= pauseExamForwardMostMove) {
4143             pauseExamInvalid = TRUE;
4144             forwardMostMove = pauseExamForwardMostMove;
4145             return;
4146         }
4147     }
4148
4149   if (appData.debugMode) {
4150     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4151   }
4152     /* Parse the board */
4153     for (k = 0; k < ranks; k++) {
4154       for (j = 0; j < files; j++)
4155         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4156       if(gameInfo.holdingsWidth > 1) {
4157            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4158            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4159       }
4160     }
4161     CopyBoard(boards[moveNum], board);
4162     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4163     if (moveNum == 0) {
4164         startedFromSetupPosition =
4165           !CompareBoards(board, initialPosition);
4166         if(startedFromSetupPosition)
4167             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4168     }
4169
4170     /* [HGM] Set castling rights. Take the outermost Rooks,
4171        to make it also work for FRC opening positions. Note that board12
4172        is really defective for later FRC positions, as it has no way to
4173        indicate which Rook can castle if they are on the same side of King.
4174        For the initial position we grant rights to the outermost Rooks,
4175        and remember thos rights, and we then copy them on positions
4176        later in an FRC game. This means WB might not recognize castlings with
4177        Rooks that have moved back to their original position as illegal,
4178        but in ICS mode that is not its job anyway.
4179     */
4180     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4181     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4182
4183         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4184             if(board[0][i] == WhiteRook) j = i;
4185         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4186         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4187             if(board[0][i] == WhiteRook) j = i;
4188         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4189         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4190             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4191         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4192         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4193             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4194         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4195
4196         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4197         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4198             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4199         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4200             if(board[BOARD_HEIGHT-1][k] == bKing)
4201                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4202         if(gameInfo.variant == VariantTwoKings) {
4203             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4204             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4205             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4206         }
4207     } else { int r;
4208         r = boards[moveNum][CASTLING][0] = initialRights[0];
4209         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4210         r = boards[moveNum][CASTLING][1] = initialRights[1];
4211         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4212         r = boards[moveNum][CASTLING][3] = initialRights[3];
4213         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4214         r = boards[moveNum][CASTLING][4] = initialRights[4];
4215         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4216         /* wildcastle kludge: always assume King has rights */
4217         r = boards[moveNum][CASTLING][2] = initialRights[2];
4218         r = boards[moveNum][CASTLING][5] = initialRights[5];
4219     }
4220     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4221     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4222
4223
4224     if (ics_getting_history == H_GOT_REQ_HEADER ||
4225         ics_getting_history == H_GOT_UNREQ_HEADER) {
4226         /* This was an initial position from a move list, not
4227            the current position */
4228         return;
4229     }
4230
4231     /* Update currentMove and known move number limits */
4232     newMove = newGame || moveNum > forwardMostMove;
4233
4234     if (newGame) {
4235         forwardMostMove = backwardMostMove = currentMove = moveNum;
4236         if (gameMode == IcsExamining && moveNum == 0) {
4237           /* Workaround for ICS limitation: we are not told the wild
4238              type when starting to examine a game.  But if we ask for
4239              the move list, the move list header will tell us */
4240             ics_getting_history = H_REQUESTED;
4241             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4242             SendToICS(str);
4243         }
4244     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4245                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4246 #if ZIPPY
4247         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4248         /* [HGM] applied this also to an engine that is silently watching        */
4249         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4250             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4251             gameInfo.variant == currentlyInitializedVariant) {
4252           takeback = forwardMostMove - moveNum;
4253           for (i = 0; i < takeback; i++) {
4254             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4255             SendToProgram("undo\n", &first);
4256           }
4257         }
4258 #endif
4259
4260         forwardMostMove = moveNum;
4261         if (!pausing || currentMove > forwardMostMove)
4262           currentMove = forwardMostMove;
4263     } else {
4264         /* New part of history that is not contiguous with old part */
4265         if (pausing && gameMode == IcsExamining) {
4266             pauseExamInvalid = TRUE;
4267             forwardMostMove = pauseExamForwardMostMove;
4268             return;
4269         }
4270         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4271 #if ZIPPY
4272             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4273                 // [HGM] when we will receive the move list we now request, it will be
4274                 // fed to the engine from the first move on. So if the engine is not
4275                 // in the initial position now, bring it there.
4276                 InitChessProgram(&first, 0);
4277             }
4278 #endif
4279             ics_getting_history = H_REQUESTED;
4280             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4281             SendToICS(str);
4282         }
4283         forwardMostMove = backwardMostMove = currentMove = moveNum;
4284     }
4285
4286     /* Update the clocks */
4287     if (strchr(elapsed_time, '.')) {
4288       /* Time is in ms */
4289       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4290       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4291     } else {
4292       /* Time is in seconds */
4293       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4294       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4295     }
4296
4297
4298 #if ZIPPY
4299     if (appData.zippyPlay && newGame &&
4300         gameMode != IcsObserving && gameMode != IcsIdle &&
4301         gameMode != IcsExamining)
4302       ZippyFirstBoard(moveNum, basetime, increment);
4303 #endif
4304
4305     /* Put the move on the move list, first converting
4306        to canonical algebraic form. */
4307     if (moveNum > 0) {
4308   if (appData.debugMode) {
4309     if (appData.debugMode) { int f = forwardMostMove;
4310         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4311                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4312                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4313     }
4314     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4315     fprintf(debugFP, "moveNum = %d\n", moveNum);
4316     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4317     setbuf(debugFP, NULL);
4318   }
4319         if (moveNum <= backwardMostMove) {
4320             /* We don't know what the board looked like before
4321                this move.  Punt. */
4322           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4323             strcat(parseList[moveNum - 1], " ");
4324             strcat(parseList[moveNum - 1], elapsed_time);
4325             moveList[moveNum - 1][0] = NULLCHAR;
4326         } else if (strcmp(move_str, "none") == 0) {
4327             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4328             /* Again, we don't know what the board looked like;
4329                this is really the start of the game. */
4330             parseList[moveNum - 1][0] = NULLCHAR;
4331             moveList[moveNum - 1][0] = NULLCHAR;
4332             backwardMostMove = moveNum;
4333             startedFromSetupPosition = TRUE;
4334             fromX = fromY = toX = toY = -1;
4335         } else {
4336           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4337           //                 So we parse the long-algebraic move string in stead of the SAN move
4338           int valid; char buf[MSG_SIZ], *prom;
4339
4340           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4341                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4342           // str looks something like "Q/a1-a2"; kill the slash
4343           if(str[1] == '/')
4344             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4345           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4346           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4347                 strcat(buf, prom); // long move lacks promo specification!
4348           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4349                 if(appData.debugMode)
4350                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4351                 safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0]));
4352           }
4353           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4354                                 &fromX, &fromY, &toX, &toY, &promoChar)
4355                || ParseOneMove(buf, moveNum - 1, &moveType,
4356                                 &fromX, &fromY, &toX, &toY, &promoChar);
4357           // end of long SAN patch
4358           if (valid) {
4359             (void) CoordsToAlgebraic(boards[moveNum - 1],
4360                                      PosFlags(moveNum - 1),
4361                                      fromY, fromX, toY, toX, promoChar,
4362                                      parseList[moveNum-1]);
4363             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4364               case MT_NONE:
4365               case MT_STALEMATE:
4366               default:
4367                 break;
4368               case MT_CHECK:
4369                 if(gameInfo.variant != VariantShogi)
4370                     strcat(parseList[moveNum - 1], "+");
4371                 break;
4372               case MT_CHECKMATE:
4373               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4374                 strcat(parseList[moveNum - 1], "#");
4375                 break;
4376             }
4377             strcat(parseList[moveNum - 1], " ");
4378             strcat(parseList[moveNum - 1], elapsed_time);
4379             /* currentMoveString is set as a side-effect of ParseOneMove */
4380             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '+';
4381             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4382             strcat(moveList[moveNum - 1], "\n");
4383
4384             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4385               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4386                 ChessSquare old, new = boards[moveNum][k][j];
4387                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4388                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4389                   if(old == new) continue;
4390                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4391                   else if(new == WhiteWazir || new == BlackWazir) {
4392                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4393                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4394                       else boards[moveNum][k][j] = old; // preserve type of Gold
4395                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4396                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4397               }
4398           } else {
4399             /* Move from ICS was illegal!?  Punt. */
4400             if (appData.debugMode) {
4401               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4402               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4403             }
4404             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4405             strcat(parseList[moveNum - 1], " ");
4406             strcat(parseList[moveNum - 1], elapsed_time);
4407             moveList[moveNum - 1][0] = NULLCHAR;
4408             fromX = fromY = toX = toY = -1;
4409           }
4410         }
4411   if (appData.debugMode) {
4412     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4413     setbuf(debugFP, NULL);
4414   }
4415
4416 #if ZIPPY
4417         /* Send move to chess program (BEFORE animating it). */
4418         if (appData.zippyPlay && !newGame && newMove &&
4419            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4420
4421             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4422                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4423                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4424                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4425                             move_str);
4426                     DisplayError(str, 0);
4427                 } else {
4428                     if (first.sendTime) {
4429                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4430                     }
4431                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4432                     if (firstMove && !bookHit) {
4433                         firstMove = FALSE;
4434                         if (first.useColors) {
4435                           SendToProgram(gameMode == IcsPlayingWhite ?
4436                                         "white\ngo\n" :
4437                                         "black\ngo\n", &first);
4438                         } else {
4439                           SendToProgram("go\n", &first);
4440                         }
4441                         first.maybeThinking = TRUE;
4442                     }
4443                 }
4444             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4445               if (moveList[moveNum - 1][0] == NULLCHAR) {
4446                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4447                 DisplayError(str, 0);
4448               } else {
4449                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4450                 SendMoveToProgram(moveNum - 1, &first);
4451               }
4452             }
4453         }
4454 #endif
4455     }
4456
4457     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4458         /* If move comes from a remote source, animate it.  If it
4459            isn't remote, it will have already been animated. */
4460         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4461             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4462         }
4463         if (!pausing && appData.highlightLastMove) {
4464             SetHighlights(fromX, fromY, toX, toY);
4465         }
4466     }
4467
4468     /* Start the clocks */
4469     whiteFlag = blackFlag = FALSE;
4470     appData.clockMode = !(basetime == 0 && increment == 0);
4471     if (ticking == 0) {
4472       ics_clock_paused = TRUE;
4473       StopClocks();
4474     } else if (ticking == 1) {
4475       ics_clock_paused = FALSE;
4476     }
4477     if (gameMode == IcsIdle ||
4478         relation == RELATION_OBSERVING_STATIC ||
4479         relation == RELATION_EXAMINING ||
4480         ics_clock_paused)
4481       DisplayBothClocks();
4482     else
4483       StartClocks();
4484
4485     /* Display opponents and material strengths */
4486     if (gameInfo.variant != VariantBughouse &&
4487         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4488         if (tinyLayout || smallLayout) {
4489             if(gameInfo.variant == VariantNormal)
4490               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4491                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4492                     basetime, increment);
4493             else
4494               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4495                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4496                     basetime, increment, (int) gameInfo.variant);
4497         } else {
4498             if(gameInfo.variant == VariantNormal)
4499               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4500                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4501                     basetime, increment);
4502             else
4503               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4504                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4505                     basetime, increment, VariantName(gameInfo.variant));
4506         }
4507         DisplayTitle(str);
4508   if (appData.debugMode) {
4509     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4510   }
4511     }
4512
4513
4514     /* Display the board */
4515     if (!pausing && !appData.noGUI) {
4516
4517       if (appData.premove)
4518           if (!gotPremove ||
4519              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4520              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4521               ClearPremoveHighlights();
4522
4523       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4524         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4525       DrawPosition(j, boards[currentMove]);
4526
4527       DisplayMove(moveNum - 1);
4528       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4529             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4530               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4531         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4532       }
4533     }
4534
4535     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4536 #if ZIPPY
4537     if(bookHit) { // [HGM] book: simulate book reply
4538         static char bookMove[MSG_SIZ]; // a bit generous?
4539
4540         programStats.nodes = programStats.depth = programStats.time =
4541         programStats.score = programStats.got_only_move = 0;
4542         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4543
4544         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4545         strcat(bookMove, bookHit);
4546         HandleMachineMove(bookMove, &first);
4547     }
4548 #endif
4549 }
4550
4551 void
4552 GetMoveListEvent()
4553 {
4554     char buf[MSG_SIZ];
4555     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4556         ics_getting_history = H_REQUESTED;
4557         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4558         SendToICS(buf);
4559     }
4560 }
4561
4562 void
4563 AnalysisPeriodicEvent(force)
4564      int force;
4565 {
4566     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4567          && !force) || !appData.periodicUpdates)
4568       return;
4569
4570     /* Send . command to Crafty to collect stats */
4571     SendToProgram(".\n", &first);
4572
4573     /* Don't send another until we get a response (this makes
4574        us stop sending to old Crafty's which don't understand
4575        the "." command (sending illegal cmds resets node count & time,
4576        which looks bad)) */
4577     programStats.ok_to_send = 0;
4578 }
4579
4580 void ics_update_width(new_width)
4581         int new_width;
4582 {
4583         ics_printf("set width %d\n", new_width);
4584 }
4585
4586 void
4587 SendMoveToProgram(moveNum, cps)
4588      int moveNum;
4589      ChessProgramState *cps;
4590 {
4591     char buf[MSG_SIZ];
4592
4593     if (cps->useUsermove) {
4594       SendToProgram("usermove ", cps);
4595     }
4596     if (cps->useSAN) {
4597       char *space;
4598       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4599         int len = space - parseList[moveNum];
4600         memcpy(buf, parseList[moveNum], len);
4601         buf[len++] = '\n';
4602         buf[len] = NULLCHAR;
4603       } else {
4604         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4605       }
4606       SendToProgram(buf, cps);
4607     } else {
4608       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4609         AlphaRank(moveList[moveNum], 4);
4610         SendToProgram(moveList[moveNum], cps);
4611         AlphaRank(moveList[moveNum], 4); // and back
4612       } else
4613       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4614        * the engine. It would be nice to have a better way to identify castle
4615        * moves here. */
4616       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4617                                                                          && cps->useOOCastle) {
4618         int fromX = moveList[moveNum][0] - AAA;
4619         int fromY = moveList[moveNum][1] - ONE;
4620         int toX = moveList[moveNum][2] - AAA;
4621         int toY = moveList[moveNum][3] - ONE;
4622         if((boards[moveNum][fromY][fromX] == WhiteKing
4623             && boards[moveNum][toY][toX] == WhiteRook)
4624            || (boards[moveNum][fromY][fromX] == BlackKing
4625                && boards[moveNum][toY][toX] == BlackRook)) {
4626           if(toX > fromX) SendToProgram("O-O\n", cps);
4627           else SendToProgram("O-O-O\n", cps);
4628         }
4629         else SendToProgram(moveList[moveNum], cps);
4630       }
4631       else SendToProgram(moveList[moveNum], cps);
4632       /* End of additions by Tord */
4633     }
4634
4635     /* [HGM] setting up the opening has brought engine in force mode! */
4636     /*       Send 'go' if we are in a mode where machine should play. */
4637     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4638         (gameMode == TwoMachinesPlay   ||
4639 #if ZIPPY
4640          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4641 #endif
4642          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4643         SendToProgram("go\n", cps);
4644   if (appData.debugMode) {
4645     fprintf(debugFP, "(extra)\n");
4646   }
4647     }
4648     setboardSpoiledMachineBlack = 0;
4649 }
4650
4651 void
4652 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4653      ChessMove moveType;
4654      int fromX, fromY, toX, toY;
4655      char promoChar;
4656 {
4657     char user_move[MSG_SIZ];
4658
4659     switch (moveType) {
4660       default:
4661         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4662                 (int)moveType, fromX, fromY, toX, toY);
4663         DisplayError(user_move + strlen("say "), 0);
4664         break;
4665       case WhiteKingSideCastle:
4666       case BlackKingSideCastle:
4667       case WhiteQueenSideCastleWild:
4668       case BlackQueenSideCastleWild:
4669       /* PUSH Fabien */
4670       case WhiteHSideCastleFR:
4671       case BlackHSideCastleFR:
4672       /* POP Fabien */
4673         snprintf(user_move, MSG_SIZ, "o-o\n");
4674         break;
4675       case WhiteQueenSideCastle:
4676       case BlackQueenSideCastle:
4677       case WhiteKingSideCastleWild:
4678       case BlackKingSideCastleWild:
4679       /* PUSH Fabien */
4680       case WhiteASideCastleFR:
4681       case BlackASideCastleFR:
4682       /* POP Fabien */
4683         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4684         break;
4685       case WhiteNonPromotion:
4686       case BlackNonPromotion:
4687         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4688         break;
4689       case WhitePromotion:
4690       case BlackPromotion:
4691         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4692           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4693                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4694                 PieceToChar(WhiteFerz));
4695         else if(gameInfo.variant == VariantGreat)
4696           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4697                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4698                 PieceToChar(WhiteMan));
4699         else
4700           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4701                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4702                 promoChar);
4703         break;
4704       case WhiteDrop:
4705       case BlackDrop:
4706       drop:
4707         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4708                  ToUpper(PieceToChar((ChessSquare) fromX)),
4709                  AAA + toX, ONE + toY);
4710         break;
4711       case IllegalMove:  /* could be a variant we don't quite understand */
4712         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4713       case NormalMove:
4714       case WhiteCapturesEnPassant:
4715       case BlackCapturesEnPassant:
4716         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4717                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4718         break;
4719     }
4720     SendToICS(user_move);
4721     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4722         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4723 }
4724
4725 void
4726 UploadGameEvent()
4727 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4728     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4729     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4730     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4731         DisplayError("You cannot do this while you are playing or observing", 0);
4732         return;
4733     }
4734     if(gameMode != IcsExamining) { // is this ever not the case?
4735         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4736
4737         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4738           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4739         } else { // on FICS we must first go to general examine mode
4740           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4741         }
4742         if(gameInfo.variant != VariantNormal) {
4743             // try figure out wild number, as xboard names are not always valid on ICS
4744             for(i=1; i<=36; i++) {
4745               snprintf(buf, MSG_SIZ, "wild/%d", i);
4746                 if(StringToVariant(buf) == gameInfo.variant) break;
4747             }
4748             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4749             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4750             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4751         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4752         SendToICS(ics_prefix);
4753         SendToICS(buf);
4754         if(startedFromSetupPosition || backwardMostMove != 0) {
4755           fen = PositionToFEN(backwardMostMove, NULL);
4756           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4757             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4758             SendToICS(buf);
4759           } else { // FICS: everything has to set by separate bsetup commands
4760             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4761             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4762             SendToICS(buf);
4763             if(!WhiteOnMove(backwardMostMove)) {
4764                 SendToICS("bsetup tomove black\n");
4765             }
4766             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4767             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4768             SendToICS(buf);
4769             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4770             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4771             SendToICS(buf);
4772             i = boards[backwardMostMove][EP_STATUS];
4773             if(i >= 0) { // set e.p.
4774               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4775                 SendToICS(buf);
4776             }
4777             bsetup++;
4778           }
4779         }
4780       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4781             SendToICS("bsetup done\n"); // switch to normal examining.
4782     }
4783     for(i = backwardMostMove; i<last; i++) {
4784         char buf[20];
4785         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4786         SendToICS(buf);
4787     }
4788     SendToICS(ics_prefix);
4789     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4790 }
4791
4792 void
4793 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4794      int rf, ff, rt, ft;
4795      char promoChar;
4796      char move[7];
4797 {
4798     if (rf == DROP_RANK) {
4799       sprintf(move, "%c@%c%c\n",
4800                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4801     } else {
4802         if (promoChar == 'x' || promoChar == NULLCHAR) {
4803           sprintf(move, "%c%c%c%c\n",
4804                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4805         } else {
4806             sprintf(move, "%c%c%c%c%c\n",
4807                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4808         }
4809     }
4810 }
4811
4812 void
4813 ProcessICSInitScript(f)
4814      FILE *f;
4815 {
4816     char buf[MSG_SIZ];
4817
4818     while (fgets(buf, MSG_SIZ, f)) {
4819         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4820     }
4821
4822     fclose(f);
4823 }
4824
4825
4826 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4827 void
4828 AlphaRank(char *move, int n)
4829 {
4830 //    char *p = move, c; int x, y;
4831
4832     if (appData.debugMode) {
4833         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4834     }
4835
4836     if(move[1]=='*' &&
4837        move[2]>='0' && move[2]<='9' &&
4838        move[3]>='a' && move[3]<='x'    ) {
4839         move[1] = '@';
4840         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4841         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4842     } else
4843     if(move[0]>='0' && move[0]<='9' &&
4844        move[1]>='a' && move[1]<='x' &&
4845        move[2]>='0' && move[2]<='9' &&
4846        move[3]>='a' && move[3]<='x'    ) {
4847         /* input move, Shogi -> normal */
4848         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4849         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4850         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4851         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4852     } else
4853     if(move[1]=='@' &&
4854        move[3]>='0' && move[3]<='9' &&
4855        move[2]>='a' && move[2]<='x'    ) {
4856         move[1] = '*';
4857         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4858         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4859     } else
4860     if(
4861        move[0]>='a' && move[0]<='x' &&
4862        move[3]>='0' && move[3]<='9' &&
4863        move[2]>='a' && move[2]<='x'    ) {
4864          /* output move, normal -> Shogi */
4865         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4866         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4867         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4868         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4869         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4870     }
4871     if (appData.debugMode) {
4872         fprintf(debugFP, "   out = '%s'\n", move);
4873     }
4874 }
4875
4876 char yy_textstr[8000];
4877
4878 /* Parser for moves from gnuchess, ICS, or user typein box */
4879 Boolean
4880 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4881      char *move;
4882      int moveNum;
4883      ChessMove *moveType;
4884      int *fromX, *fromY, *toX, *toY;
4885      char *promoChar;
4886 {
4887     if (appData.debugMode) {
4888         fprintf(debugFP, "move to parse: %s\n", move);
4889     }
4890     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4891
4892     switch (*moveType) {
4893       case WhitePromotion:
4894       case BlackPromotion:
4895       case WhiteNonPromotion:
4896       case BlackNonPromotion:
4897       case NormalMove:
4898       case WhiteCapturesEnPassant:
4899       case BlackCapturesEnPassant:
4900       case WhiteKingSideCastle:
4901       case WhiteQueenSideCastle:
4902       case BlackKingSideCastle:
4903       case BlackQueenSideCastle:
4904       case WhiteKingSideCastleWild:
4905       case WhiteQueenSideCastleWild:
4906       case BlackKingSideCastleWild:
4907       case BlackQueenSideCastleWild:
4908       /* Code added by Tord: */
4909       case WhiteHSideCastleFR:
4910       case WhiteASideCastleFR:
4911       case BlackHSideCastleFR:
4912       case BlackASideCastleFR:
4913       /* End of code added by Tord */
4914       case IllegalMove:         /* bug or odd chess variant */
4915         *fromX = currentMoveString[0] - AAA;
4916         *fromY = currentMoveString[1] - ONE;
4917         *toX = currentMoveString[2] - AAA;
4918         *toY = currentMoveString[3] - ONE;
4919         *promoChar = currentMoveString[4];
4920         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4921             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4922     if (appData.debugMode) {
4923         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4924     }
4925             *fromX = *fromY = *toX = *toY = 0;
4926             return FALSE;
4927         }
4928         if (appData.testLegality) {
4929           return (*moveType != IllegalMove);
4930         } else {
4931           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4932                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4933         }
4934
4935       case WhiteDrop:
4936       case BlackDrop:
4937         *fromX = *moveType == WhiteDrop ?
4938           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4939           (int) CharToPiece(ToLower(currentMoveString[0]));
4940         *fromY = DROP_RANK;
4941         *toX = currentMoveString[2] - AAA;
4942         *toY = currentMoveString[3] - ONE;
4943         *promoChar = NULLCHAR;
4944         return TRUE;
4945
4946       case AmbiguousMove:
4947       case ImpossibleMove:
4948       case EndOfFile:
4949       case ElapsedTime:
4950       case Comment:
4951       case PGNTag:
4952       case NAG:
4953       case WhiteWins:
4954       case BlackWins:
4955       case GameIsDrawn:
4956       default:
4957     if (appData.debugMode) {
4958         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4959     }
4960         /* bug? */
4961         *fromX = *fromY = *toX = *toY = 0;
4962         *promoChar = NULLCHAR;
4963         return FALSE;
4964     }
4965 }
4966
4967
4968 void
4969 ParsePV(char *pv, Boolean storeComments)
4970 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4971   int fromX, fromY, toX, toY; char promoChar;
4972   ChessMove moveType;
4973   Boolean valid;
4974   int nr = 0;
4975
4976   endPV = forwardMostMove;
4977   do {
4978     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4979     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4980     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4981 if(appData.debugMode){
4982 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
4983 }
4984     if(!valid && nr == 0 &&
4985        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4986         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4987         // Hande case where played move is different from leading PV move
4988         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4989         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4990         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4991         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4992           endPV += 2; // if position different, keep this
4993           moveList[endPV-1][0] = fromX + AAA;
4994           moveList[endPV-1][1] = fromY + ONE;
4995           moveList[endPV-1][2] = toX + AAA;
4996           moveList[endPV-1][3] = toY + ONE;
4997           parseList[endPV-1][0] = NULLCHAR;
4998           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4999         }
5000       }
5001     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5002     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5003     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5004     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5005         valid++; // allow comments in PV
5006         continue;
5007     }
5008     nr++;
5009     if(endPV+1 > framePtr) break; // no space, truncate
5010     if(!valid) break;
5011     endPV++;
5012     CopyBoard(boards[endPV], boards[endPV-1]);
5013     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5014     moveList[endPV-1][0] = fromX + AAA;
5015     moveList[endPV-1][1] = fromY + ONE;
5016     moveList[endPV-1][2] = toX + AAA;
5017     moveList[endPV-1][3] = toY + ONE;
5018     if(storeComments)
5019         CoordsToAlgebraic(boards[endPV - 1],
5020                              PosFlags(endPV - 1),
5021                              fromY, fromX, toY, toX, promoChar,
5022                              parseList[endPV - 1]);
5023     else
5024         parseList[endPV-1][0] = NULLCHAR;
5025   } while(valid);
5026   currentMove = endPV;
5027   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5028   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5029                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5030   DrawPosition(TRUE, boards[currentMove]);
5031 }
5032
5033 static int lastX, lastY;
5034
5035 Boolean
5036 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5037 {
5038         int startPV;
5039         char *p;
5040
5041         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5042         lastX = x; lastY = y;
5043         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5044         startPV = index;
5045         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5046         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5047         index = startPV;
5048         do{ while(buf[index] && buf[index] != '\n') index++;
5049         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5050         buf[index] = 0;
5051         ParsePV(buf+startPV, FALSE);
5052         *start = startPV; *end = index-1;
5053         return TRUE;
5054 }
5055
5056 Boolean
5057 LoadPV(int x, int y)
5058 { // called on right mouse click to load PV
5059   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5060   lastX = x; lastY = y;
5061   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5062   return TRUE;
5063 }
5064
5065 void
5066 UnLoadPV()
5067 {
5068   if(endPV < 0) return;
5069   endPV = -1;
5070   currentMove = forwardMostMove;
5071   ClearPremoveHighlights();
5072   DrawPosition(TRUE, boards[currentMove]);
5073 }
5074
5075 void
5076 MovePV(int x, int y, int h)
5077 { // step through PV based on mouse coordinates (called on mouse move)
5078   int margin = h>>3, step = 0;
5079
5080   if(endPV < 0) return;
5081   // we must somehow check if right button is still down (might be released off board!)
5082   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5083   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5084   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5085   if(!step) return;
5086   lastX = x; lastY = y;
5087   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5088   currentMove += step;
5089   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5090   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5091                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5092   DrawPosition(FALSE, boards[currentMove]);
5093 }
5094
5095
5096 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5097 // All positions will have equal probability, but the current method will not provide a unique
5098 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5099 #define DARK 1
5100 #define LITE 2
5101 #define ANY 3
5102
5103 int squaresLeft[4];
5104 int piecesLeft[(int)BlackPawn];
5105 int seed, nrOfShuffles;
5106
5107 void GetPositionNumber()
5108 {       // sets global variable seed
5109         int i;
5110
5111         seed = appData.defaultFrcPosition;
5112         if(seed < 0) { // randomize based on time for negative FRC position numbers
5113                 for(i=0; i<50; i++) seed += random();
5114                 seed = random() ^ random() >> 8 ^ random() << 8;
5115                 if(seed<0) seed = -seed;
5116         }
5117 }
5118
5119 int put(Board board, int pieceType, int rank, int n, int shade)
5120 // put the piece on the (n-1)-th empty squares of the given shade
5121 {
5122         int i;
5123
5124         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5125                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5126                         board[rank][i] = (ChessSquare) pieceType;
5127                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5128                         squaresLeft[ANY]--;
5129                         piecesLeft[pieceType]--;
5130                         return i;
5131                 }
5132         }
5133         return -1;
5134 }
5135
5136
5137 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5138 // calculate where the next piece goes, (any empty square), and put it there
5139 {
5140         int i;
5141
5142         i = seed % squaresLeft[shade];
5143         nrOfShuffles *= squaresLeft[shade];
5144         seed /= squaresLeft[shade];
5145         put(board, pieceType, rank, i, shade);
5146 }
5147
5148 void AddTwoPieces(Board board, int pieceType, int rank)
5149 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5150 {
5151         int i, n=squaresLeft[ANY], j=n-1, k;
5152
5153         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5154         i = seed % k;  // pick one
5155         nrOfShuffles *= k;
5156         seed /= k;
5157         while(i >= j) i -= j--;
5158         j = n - 1 - j; i += j;
5159         put(board, pieceType, rank, j, ANY);
5160         put(board, pieceType, rank, i, ANY);
5161 }
5162
5163 void SetUpShuffle(Board board, int number)
5164 {
5165         int i, p, first=1;
5166
5167         GetPositionNumber(); nrOfShuffles = 1;
5168
5169         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5170         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5171         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5172
5173         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5174
5175         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5176             p = (int) board[0][i];
5177             if(p < (int) BlackPawn) piecesLeft[p] ++;
5178             board[0][i] = EmptySquare;
5179         }
5180
5181         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5182             // shuffles restricted to allow normal castling put KRR first
5183             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5184                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5185             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5186                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5187             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5188                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5189             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5190                 put(board, WhiteRook, 0, 0, ANY);
5191             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5192         }
5193
5194         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5195             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5196             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5197                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5198                 while(piecesLeft[p] >= 2) {
5199                     AddOnePiece(board, p, 0, LITE);
5200                     AddOnePiece(board, p, 0, DARK);
5201                 }
5202                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5203             }
5204
5205         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5206             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5207             // but we leave King and Rooks for last, to possibly obey FRC restriction
5208             if(p == (int)WhiteRook) continue;
5209             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5210             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5211         }
5212
5213         // now everything is placed, except perhaps King (Unicorn) and Rooks
5214
5215         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5216             // Last King gets castling rights
5217             while(piecesLeft[(int)WhiteUnicorn]) {
5218                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5219                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5220             }
5221
5222             while(piecesLeft[(int)WhiteKing]) {
5223                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5224                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5225             }
5226
5227
5228         } else {
5229             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5230             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5231         }
5232
5233         // Only Rooks can be left; simply place them all
5234         while(piecesLeft[(int)WhiteRook]) {
5235                 i = put(board, WhiteRook, 0, 0, ANY);
5236                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5237                         if(first) {
5238                                 first=0;
5239                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5240                         }
5241                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5242                 }
5243         }
5244         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5245             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5246         }
5247
5248         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5249 }
5250
5251 int SetCharTable( char *table, const char * map )
5252 /* [HGM] moved here from winboard.c because of its general usefulness */
5253 /*       Basically a safe strcpy that uses the last character as King */
5254 {
5255     int result = FALSE; int NrPieces;
5256
5257     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5258                     && NrPieces >= 12 && !(NrPieces&1)) {
5259         int i; /* [HGM] Accept even length from 12 to 34 */
5260
5261         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5262         for( i=0; i<NrPieces/2-1; i++ ) {
5263             table[i] = map[i];
5264             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5265         }
5266         table[(int) WhiteKing]  = map[NrPieces/2-1];
5267         table[(int) BlackKing]  = map[NrPieces-1];
5268
5269         result = TRUE;
5270     }
5271
5272     return result;
5273 }
5274
5275 void Prelude(Board board)
5276 {       // [HGM] superchess: random selection of exo-pieces
5277         int i, j, k; ChessSquare p;
5278         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5279
5280         GetPositionNumber(); // use FRC position number
5281
5282         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5283             SetCharTable(pieceToChar, appData.pieceToCharTable);
5284             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5285                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5286         }
5287
5288         j = seed%4;                 seed /= 4;
5289         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5290         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5291         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5292         j = seed%3 + (seed%3 >= j); seed /= 3;
5293         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5294         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5295         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5296         j = seed%3;                 seed /= 3;
5297         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5298         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5299         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5300         j = seed%2 + (seed%2 >= j); seed /= 2;
5301         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5302         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5303         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5304         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5305         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5306         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5307         put(board, exoPieces[0],    0, 0, ANY);
5308         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5309 }
5310
5311 void
5312 InitPosition(redraw)
5313      int redraw;
5314 {
5315     ChessSquare (* pieces)[BOARD_FILES];
5316     int i, j, pawnRow, overrule,
5317     oldx = gameInfo.boardWidth,
5318     oldy = gameInfo.boardHeight,
5319     oldh = gameInfo.holdingsWidth,
5320     oldv = gameInfo.variant;
5321
5322     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5323
5324     /* [AS] Initialize pv info list [HGM] and game status */
5325     {
5326         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5327             pvInfoList[i].depth = 0;
5328             boards[i][EP_STATUS] = EP_NONE;
5329             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5330         }
5331
5332         initialRulePlies = 0; /* 50-move counter start */
5333
5334         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5335         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5336     }
5337
5338
5339     /* [HGM] logic here is completely changed. In stead of full positions */
5340     /* the initialized data only consist of the two backranks. The switch */
5341     /* selects which one we will use, which is than copied to the Board   */
5342     /* initialPosition, which for the rest is initialized by Pawns and    */
5343     /* empty squares. This initial position is then copied to boards[0],  */
5344     /* possibly after shuffling, so that it remains available.            */
5345
5346     gameInfo.holdingsWidth = 0; /* default board sizes */
5347     gameInfo.boardWidth    = 8;
5348     gameInfo.boardHeight   = 8;
5349     gameInfo.holdingsSize  = 0;
5350     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5351     for(i=0; i<BOARD_FILES-2; i++)
5352       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5353     initialPosition[EP_STATUS] = EP_NONE;
5354     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5355     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5356          SetCharTable(pieceNickName, appData.pieceNickNames);
5357     else SetCharTable(pieceNickName, "............");
5358
5359     switch (gameInfo.variant) {
5360     case VariantFischeRandom:
5361       shuffleOpenings = TRUE;
5362     default:
5363       pieces = FIDEArray;
5364       break;
5365     case VariantShatranj:
5366       pieces = ShatranjArray;
5367       nrCastlingRights = 0;
5368       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5369       break;
5370     case VariantMakruk:
5371       pieces = makrukArray;
5372       nrCastlingRights = 0;
5373       startedFromSetupPosition = TRUE;
5374       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5375       break;
5376     case VariantTwoKings:
5377       pieces = twoKingsArray;
5378       break;
5379     case VariantCapaRandom:
5380       shuffleOpenings = TRUE;
5381     case VariantCapablanca:
5382       pieces = CapablancaArray;
5383       gameInfo.boardWidth = 10;
5384       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5385       break;
5386     case VariantGothic:
5387       pieces = GothicArray;
5388       gameInfo.boardWidth = 10;
5389       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5390       break;
5391     case VariantJanus:
5392       pieces = JanusArray;
5393       gameInfo.boardWidth = 10;
5394       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5395       nrCastlingRights = 6;
5396         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5397         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5398         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5399         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5400         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5401         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5402       break;
5403     case VariantFalcon:
5404       pieces = FalconArray;
5405       gameInfo.boardWidth = 10;
5406       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5407       break;
5408     case VariantXiangqi:
5409       pieces = XiangqiArray;
5410       gameInfo.boardWidth  = 9;
5411       gameInfo.boardHeight = 10;
5412       nrCastlingRights = 0;
5413       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5414       break;
5415     case VariantShogi:
5416       pieces = ShogiArray;
5417       gameInfo.boardWidth  = 9;
5418       gameInfo.boardHeight = 9;
5419       gameInfo.holdingsSize = 7;
5420       nrCastlingRights = 0;
5421       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5422       break;
5423     case VariantCourier:
5424       pieces = CourierArray;
5425       gameInfo.boardWidth  = 12;
5426       nrCastlingRights = 0;
5427       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5428       break;
5429     case VariantKnightmate:
5430       pieces = KnightmateArray;
5431       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5432       break;
5433     case VariantFairy:
5434       pieces = fairyArray;
5435       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5436       break;
5437     case VariantGreat:
5438       pieces = GreatArray;
5439       gameInfo.boardWidth = 10;
5440       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5441       gameInfo.holdingsSize = 8;
5442       break;
5443     case VariantSuper:
5444       pieces = FIDEArray;
5445       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5446       gameInfo.holdingsSize = 8;
5447       startedFromSetupPosition = TRUE;
5448       break;
5449     case VariantCrazyhouse:
5450     case VariantBughouse:
5451       pieces = FIDEArray;
5452       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5453       gameInfo.holdingsSize = 5;
5454       break;
5455     case VariantWildCastle:
5456       pieces = FIDEArray;
5457       /* !!?shuffle with kings guaranteed to be on d or e file */
5458       shuffleOpenings = 1;
5459       break;
5460     case VariantNoCastle:
5461       pieces = FIDEArray;
5462       nrCastlingRights = 0;
5463       /* !!?unconstrained back-rank shuffle */
5464       shuffleOpenings = 1;
5465       break;
5466     }
5467
5468     overrule = 0;
5469     if(appData.NrFiles >= 0) {
5470         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5471         gameInfo.boardWidth = appData.NrFiles;
5472     }
5473     if(appData.NrRanks >= 0) {
5474         gameInfo.boardHeight = appData.NrRanks;
5475     }
5476     if(appData.holdingsSize >= 0) {
5477         i = appData.holdingsSize;
5478         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5479         gameInfo.holdingsSize = i;
5480     }
5481     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5482     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5483         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5484
5485     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5486     if(pawnRow < 1) pawnRow = 1;
5487     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5488
5489     /* User pieceToChar list overrules defaults */
5490     if(appData.pieceToCharTable != NULL)
5491         SetCharTable(pieceToChar, appData.pieceToCharTable);
5492
5493     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5494
5495         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5496             s = (ChessSquare) 0; /* account holding counts in guard band */
5497         for( i=0; i<BOARD_HEIGHT; i++ )
5498             initialPosition[i][j] = s;
5499
5500         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5501         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5502         initialPosition[pawnRow][j] = WhitePawn;
5503         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5504         if(gameInfo.variant == VariantXiangqi) {
5505             if(j&1) {
5506                 initialPosition[pawnRow][j] =
5507                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5508                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5509                    initialPosition[2][j] = WhiteCannon;
5510                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5511                 }
5512             }
5513         }
5514         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5515     }
5516     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5517
5518             j=BOARD_LEFT+1;
5519             initialPosition[1][j] = WhiteBishop;
5520             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5521             j=BOARD_RGHT-2;
5522             initialPosition[1][j] = WhiteRook;
5523             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5524     }
5525
5526     if( nrCastlingRights == -1) {
5527         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5528         /*       This sets default castling rights from none to normal corners   */
5529         /* Variants with other castling rights must set them themselves above    */
5530         nrCastlingRights = 6;
5531
5532         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5533         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5534         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5535         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5536         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5537         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5538      }
5539
5540      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5541      if(gameInfo.variant == VariantGreat) { // promotion commoners
5542         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5543         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5544         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5545         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5546      }
5547   if (appData.debugMode) {
5548     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5549   }
5550     if(shuffleOpenings) {
5551         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5552         startedFromSetupPosition = TRUE;
5553     }
5554     if(startedFromPositionFile) {
5555       /* [HGM] loadPos: use PositionFile for every new game */
5556       CopyBoard(initialPosition, filePosition);
5557       for(i=0; i<nrCastlingRights; i++)
5558           initialRights[i] = filePosition[CASTLING][i];
5559       startedFromSetupPosition = TRUE;
5560     }
5561
5562     CopyBoard(boards[0], initialPosition);
5563
5564     if(oldx != gameInfo.boardWidth ||
5565        oldy != gameInfo.boardHeight ||
5566        oldh != gameInfo.holdingsWidth
5567 #ifdef GOTHIC
5568        || oldv == VariantGothic ||        // For licensing popups
5569        gameInfo.variant == VariantGothic
5570 #endif
5571 #ifdef FALCON
5572        || oldv == VariantFalcon ||
5573        gameInfo.variant == VariantFalcon
5574 #endif
5575                                          )
5576             InitDrawingSizes(-2 ,0);
5577
5578     if (redraw)
5579       DrawPosition(TRUE, boards[currentMove]);
5580 }
5581
5582 void
5583 SendBoard(cps, moveNum)
5584      ChessProgramState *cps;
5585      int moveNum;
5586 {
5587     char message[MSG_SIZ];
5588
5589     if (cps->useSetboard) {
5590       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5591       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5592       SendToProgram(message, cps);
5593       free(fen);
5594
5595     } else {
5596       ChessSquare *bp;
5597       int i, j;
5598       /* Kludge to set black to move, avoiding the troublesome and now
5599        * deprecated "black" command.
5600        */
5601       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5602
5603       SendToProgram("edit\n", cps);
5604       SendToProgram("#\n", cps);
5605       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5606         bp = &boards[moveNum][i][BOARD_LEFT];
5607         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5608           if ((int) *bp < (int) BlackPawn) {
5609             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5610                     AAA + j, ONE + i);
5611             if(message[0] == '+' || message[0] == '~') {
5612               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5613                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5614                         AAA + j, ONE + i);
5615             }
5616             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5617                 message[1] = BOARD_RGHT   - 1 - j + '1';
5618                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5619             }
5620             SendToProgram(message, cps);
5621           }
5622         }
5623       }
5624
5625       SendToProgram("c\n", cps);
5626       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5627         bp = &boards[moveNum][i][BOARD_LEFT];
5628         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5629           if (((int) *bp != (int) EmptySquare)
5630               && ((int) *bp >= (int) BlackPawn)) {
5631             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5632                     AAA + j, ONE + i);
5633             if(message[0] == '+' || message[0] == '~') {
5634               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5635                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5636                         AAA + j, ONE + i);
5637             }
5638             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5639                 message[1] = BOARD_RGHT   - 1 - j + '1';
5640                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5641             }
5642             SendToProgram(message, cps);
5643           }
5644         }
5645       }
5646
5647       SendToProgram(".\n", cps);
5648     }
5649     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5650 }
5651
5652 static int autoQueen; // [HGM] oneclick
5653
5654 int
5655 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5656 {
5657     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5658     /* [HGM] add Shogi promotions */
5659     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5660     ChessSquare piece;
5661     ChessMove moveType;
5662     Boolean premove;
5663
5664     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5665     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5666
5667     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5668       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5669         return FALSE;
5670
5671     piece = boards[currentMove][fromY][fromX];
5672     if(gameInfo.variant == VariantShogi) {
5673         promotionZoneSize = BOARD_HEIGHT/3;
5674         highestPromotingPiece = (int)WhiteFerz;
5675     } else if(gameInfo.variant == VariantMakruk) {
5676         promotionZoneSize = 3;
5677     }
5678
5679     // next weed out all moves that do not touch the promotion zone at all
5680     if((int)piece >= BlackPawn) {
5681         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5682              return FALSE;
5683         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5684     } else {
5685         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5686            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5687     }
5688
5689     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5690
5691     // weed out mandatory Shogi promotions
5692     if(gameInfo.variant == VariantShogi) {
5693         if(piece >= BlackPawn) {
5694             if(toY == 0 && piece == BlackPawn ||
5695                toY == 0 && piece == BlackQueen ||
5696                toY <= 1 && piece == BlackKnight) {
5697                 *promoChoice = '+';
5698                 return FALSE;
5699             }
5700         } else {
5701             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5702                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5703                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5704                 *promoChoice = '+';
5705                 return FALSE;
5706             }
5707         }
5708     }
5709
5710     // weed out obviously illegal Pawn moves
5711     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5712         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5713         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5714         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5715         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5716         // note we are not allowed to test for valid (non-)capture, due to premove
5717     }
5718
5719     // we either have a choice what to promote to, or (in Shogi) whether to promote
5720     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5721         *promoChoice = PieceToChar(BlackFerz);  // no choice
5722         return FALSE;
5723     }
5724     // no sense asking what we must promote to if it is going to explode...
5725     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5726         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5727         return FALSE;
5728     }
5729     if(autoQueen) { // predetermined
5730         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5731              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5732         else *promoChoice = PieceToChar(BlackQueen);
5733         return FALSE;
5734     }
5735
5736     // suppress promotion popup on illegal moves that are not premoves
5737     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5738               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5739     if(appData.testLegality && !premove) {
5740         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5741                         fromY, fromX, toY, toX, NULLCHAR);
5742         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5743             return FALSE;
5744     }
5745
5746     return TRUE;
5747 }
5748
5749 int
5750 InPalace(row, column)
5751      int row, column;
5752 {   /* [HGM] for Xiangqi */
5753     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5754          column < (BOARD_WIDTH + 4)/2 &&
5755          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5756     return FALSE;
5757 }
5758
5759 int
5760 PieceForSquare (x, y)
5761      int x;
5762      int y;
5763 {
5764   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5765      return -1;
5766   else
5767      return boards[currentMove][y][x];
5768 }
5769
5770 int
5771 OKToStartUserMove(x, y)
5772      int x, y;
5773 {
5774     ChessSquare from_piece;
5775     int white_piece;
5776
5777     if (matchMode) return FALSE;
5778     if (gameMode == EditPosition) return TRUE;
5779
5780     if (x >= 0 && y >= 0)
5781       from_piece = boards[currentMove][y][x];
5782     else
5783       from_piece = EmptySquare;
5784
5785     if (from_piece == EmptySquare) return FALSE;
5786
5787     white_piece = (int)from_piece >= (int)WhitePawn &&
5788       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5789
5790     switch (gameMode) {
5791       case PlayFromGameFile:
5792       case AnalyzeFile:
5793       case TwoMachinesPlay:
5794       case EndOfGame:
5795         return FALSE;
5796
5797       case IcsObserving:
5798       case IcsIdle:
5799         return FALSE;
5800
5801       case MachinePlaysWhite:
5802       case IcsPlayingBlack:
5803         if (appData.zippyPlay) return FALSE;
5804         if (white_piece) {
5805             DisplayMoveError(_("You are playing Black"));
5806             return FALSE;
5807         }
5808         break;
5809
5810       case MachinePlaysBlack:
5811       case IcsPlayingWhite:
5812         if (appData.zippyPlay) return FALSE;
5813         if (!white_piece) {
5814             DisplayMoveError(_("You are playing White"));
5815             return FALSE;
5816         }
5817         break;
5818
5819       case EditGame:
5820         if (!white_piece && WhiteOnMove(currentMove)) {
5821             DisplayMoveError(_("It is White's turn"));
5822             return FALSE;
5823         }
5824         if (white_piece && !WhiteOnMove(currentMove)) {
5825             DisplayMoveError(_("It is Black's turn"));
5826             return FALSE;
5827         }
5828         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5829             /* Editing correspondence game history */
5830             /* Could disallow this or prompt for confirmation */
5831             cmailOldMove = -1;
5832         }
5833         break;
5834
5835       case BeginningOfGame:
5836         if (appData.icsActive) return FALSE;
5837         if (!appData.noChessProgram) {
5838             if (!white_piece) {
5839                 DisplayMoveError(_("You are playing White"));
5840                 return FALSE;
5841             }
5842         }
5843         break;
5844
5845       case Training:
5846         if (!white_piece && WhiteOnMove(currentMove)) {
5847             DisplayMoveError(_("It is White's turn"));
5848             return FALSE;
5849         }
5850         if (white_piece && !WhiteOnMove(currentMove)) {
5851             DisplayMoveError(_("It is Black's turn"));
5852             return FALSE;
5853         }
5854         break;
5855
5856       default:
5857       case IcsExamining:
5858         break;
5859     }
5860     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5861         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5862         && gameMode != AnalyzeFile && gameMode != Training) {
5863         DisplayMoveError(_("Displayed position is not current"));
5864         return FALSE;
5865     }
5866     return TRUE;
5867 }
5868
5869 Boolean
5870 OnlyMove(int *x, int *y, Boolean captures) {
5871     DisambiguateClosure cl;
5872     if (appData.zippyPlay) return FALSE;
5873     switch(gameMode) {
5874       case MachinePlaysBlack:
5875       case IcsPlayingWhite:
5876       case BeginningOfGame:
5877         if(!WhiteOnMove(currentMove)) return FALSE;
5878         break;
5879       case MachinePlaysWhite:
5880       case IcsPlayingBlack:
5881         if(WhiteOnMove(currentMove)) return FALSE;
5882         break;
5883       case EditGame:
5884         break;
5885       default:
5886         return FALSE;
5887     }
5888     cl.pieceIn = EmptySquare;
5889     cl.rfIn = *y;
5890     cl.ffIn = *x;
5891     cl.rtIn = -1;
5892     cl.ftIn = -1;
5893     cl.promoCharIn = NULLCHAR;
5894     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5895     if( cl.kind == NormalMove ||
5896         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5897         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5898         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5899       fromX = cl.ff;
5900       fromY = cl.rf;
5901       *x = cl.ft;
5902       *y = cl.rt;
5903       return TRUE;
5904     }
5905     if(cl.kind != ImpossibleMove) return FALSE;
5906     cl.pieceIn = EmptySquare;
5907     cl.rfIn = -1;
5908     cl.ffIn = -1;
5909     cl.rtIn = *y;
5910     cl.ftIn = *x;
5911     cl.promoCharIn = NULLCHAR;
5912     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5913     if( cl.kind == NormalMove ||
5914         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5915         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5916         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5917       fromX = cl.ff;
5918       fromY = cl.rf;
5919       *x = cl.ft;
5920       *y = cl.rt;
5921       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5922       return TRUE;
5923     }
5924     return FALSE;
5925 }
5926
5927 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5928 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5929 int lastLoadGameUseList = FALSE;
5930 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5931 ChessMove lastLoadGameStart = EndOfFile;
5932
5933 void
5934 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5935      int fromX, fromY, toX, toY;
5936      int promoChar;
5937 {
5938     ChessMove moveType;
5939     ChessSquare pdown, pup;
5940
5941     /* Check if the user is playing in turn.  This is complicated because we
5942        let the user "pick up" a piece before it is his turn.  So the piece he
5943        tried to pick up may have been captured by the time he puts it down!
5944        Therefore we use the color the user is supposed to be playing in this
5945        test, not the color of the piece that is currently on the starting
5946        square---except in EditGame mode, where the user is playing both
5947        sides; fortunately there the capture race can't happen.  (It can
5948        now happen in IcsExamining mode, but that's just too bad.  The user
5949        will get a somewhat confusing message in that case.)
5950        */
5951
5952     switch (gameMode) {
5953       case PlayFromGameFile:
5954       case AnalyzeFile:
5955       case TwoMachinesPlay:
5956       case EndOfGame:
5957       case IcsObserving:
5958       case IcsIdle:
5959         /* We switched into a game mode where moves are not accepted,
5960            perhaps while the mouse button was down. */
5961         return;
5962
5963       case MachinePlaysWhite:
5964         /* User is moving for Black */
5965         if (WhiteOnMove(currentMove)) {
5966             DisplayMoveError(_("It is White's turn"));
5967             return;
5968         }
5969         break;
5970
5971       case MachinePlaysBlack:
5972         /* User is moving for White */
5973         if (!WhiteOnMove(currentMove)) {
5974             DisplayMoveError(_("It is Black's turn"));
5975             return;
5976         }
5977         break;
5978
5979       case EditGame:
5980       case IcsExamining:
5981       case BeginningOfGame:
5982       case AnalyzeMode:
5983       case Training:
5984         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5985             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5986             /* User is moving for Black */
5987             if (WhiteOnMove(currentMove)) {
5988                 DisplayMoveError(_("It is White's turn"));
5989                 return;
5990             }
5991         } else {
5992             /* User is moving for White */
5993             if (!WhiteOnMove(currentMove)) {
5994                 DisplayMoveError(_("It is Black's turn"));
5995                 return;
5996             }
5997         }
5998         break;
5999
6000       case IcsPlayingBlack:
6001         /* User is moving for Black */
6002         if (WhiteOnMove(currentMove)) {
6003             if (!appData.premove) {
6004                 DisplayMoveError(_("It is White's turn"));
6005             } else if (toX >= 0 && toY >= 0) {
6006                 premoveToX = toX;
6007                 premoveToY = toY;
6008                 premoveFromX = fromX;
6009                 premoveFromY = fromY;
6010                 premovePromoChar = promoChar;
6011                 gotPremove = 1;
6012                 if (appData.debugMode)
6013                     fprintf(debugFP, "Got premove: fromX %d,"
6014                             "fromY %d, toX %d, toY %d\n",
6015                             fromX, fromY, toX, toY);
6016             }
6017             return;
6018         }
6019         break;
6020
6021       case IcsPlayingWhite:
6022         /* User is moving for White */
6023         if (!WhiteOnMove(currentMove)) {
6024             if (!appData.premove) {
6025                 DisplayMoveError(_("It is Black's turn"));
6026             } else if (toX >= 0 && toY >= 0) {
6027                 premoveToX = toX;
6028                 premoveToY = toY;
6029                 premoveFromX = fromX;
6030                 premoveFromY = fromY;
6031                 premovePromoChar = promoChar;
6032                 gotPremove = 1;
6033                 if (appData.debugMode)
6034                     fprintf(debugFP, "Got premove: fromX %d,"
6035                             "fromY %d, toX %d, toY %d\n",
6036                             fromX, fromY, toX, toY);
6037             }
6038             return;
6039         }
6040         break;
6041
6042       default:
6043         break;
6044
6045       case EditPosition:
6046         /* EditPosition, empty square, or different color piece;
6047            click-click move is possible */
6048         if (toX == -2 || toY == -2) {
6049             boards[0][fromY][fromX] = EmptySquare;
6050             DrawPosition(FALSE, boards[currentMove]);
6051             return;
6052         } else if (toX >= 0 && toY >= 0) {
6053             boards[0][toY][toX] = boards[0][fromY][fromX];
6054             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6055                 if(boards[0][fromY][0] != EmptySquare) {
6056                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6057                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6058                 }
6059             } else
6060             if(fromX == BOARD_RGHT+1) {
6061                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6062                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6063                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6064                 }
6065             } else
6066             boards[0][fromY][fromX] = EmptySquare;
6067             DrawPosition(FALSE, boards[currentMove]);
6068             return;
6069         }
6070         return;
6071     }
6072
6073     if(toX < 0 || toY < 0) return;
6074     pdown = boards[currentMove][fromY][fromX];
6075     pup = boards[currentMove][toY][toX];
6076
6077     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6078     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
6079          if( pup != EmptySquare ) return;
6080          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6081            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6082                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6083            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6084            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6085            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6086            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6087          fromY = DROP_RANK;
6088     }
6089
6090     /* [HGM] always test for legality, to get promotion info */
6091     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6092                                          fromY, fromX, toY, toX, promoChar);
6093     /* [HGM] but possibly ignore an IllegalMove result */
6094     if (appData.testLegality) {
6095         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6096             DisplayMoveError(_("Illegal move"));
6097             return;
6098         }
6099     }
6100
6101     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6102 }
6103
6104 /* Common tail of UserMoveEvent and DropMenuEvent */
6105 int
6106 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6107      ChessMove moveType;
6108      int fromX, fromY, toX, toY;
6109      /*char*/int promoChar;
6110 {
6111     char *bookHit = 0;
6112
6113     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6114         // [HGM] superchess: suppress promotions to non-available piece
6115         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6116         if(WhiteOnMove(currentMove)) {
6117             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6118         } else {
6119             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6120         }
6121     }
6122
6123     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6124        move type in caller when we know the move is a legal promotion */
6125     if(moveType == NormalMove && promoChar)
6126         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6127
6128     /* [HGM] <popupFix> The following if has been moved here from
6129        UserMoveEvent(). Because it seemed to belong here (why not allow
6130        piece drops in training games?), and because it can only be
6131        performed after it is known to what we promote. */
6132     if (gameMode == Training) {
6133       /* compare the move played on the board to the next move in the
6134        * game. If they match, display the move and the opponent's response.
6135        * If they don't match, display an error message.
6136        */
6137       int saveAnimate;
6138       Board testBoard;
6139       CopyBoard(testBoard, boards[currentMove]);
6140       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6141
6142       if (CompareBoards(testBoard, boards[currentMove+1])) {
6143         ForwardInner(currentMove+1);
6144
6145         /* Autoplay the opponent's response.
6146          * if appData.animate was TRUE when Training mode was entered,
6147          * the response will be animated.
6148          */
6149         saveAnimate = appData.animate;
6150         appData.animate = animateTraining;
6151         ForwardInner(currentMove+1);
6152         appData.animate = saveAnimate;
6153
6154         /* check for the end of the game */
6155         if (currentMove >= forwardMostMove) {
6156           gameMode = PlayFromGameFile;
6157           ModeHighlight();
6158           SetTrainingModeOff();
6159           DisplayInformation(_("End of game"));
6160         }
6161       } else {
6162         DisplayError(_("Incorrect move"), 0);
6163       }
6164       return 1;
6165     }
6166
6167   /* Ok, now we know that the move is good, so we can kill
6168      the previous line in Analysis Mode */
6169   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6170                                 && currentMove < forwardMostMove) {
6171     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6172     else forwardMostMove = currentMove;
6173   }
6174
6175   /* If we need the chess program but it's dead, restart it */
6176   ResurrectChessProgram();
6177
6178   /* A user move restarts a paused game*/
6179   if (pausing)
6180     PauseEvent();
6181
6182   thinkOutput[0] = NULLCHAR;
6183
6184   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6185
6186   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6187
6188   if (gameMode == BeginningOfGame) {
6189     if (appData.noChessProgram) {
6190       gameMode = EditGame;
6191       SetGameInfo();
6192     } else {
6193       char buf[MSG_SIZ];
6194       gameMode = MachinePlaysBlack;
6195       StartClocks();
6196       SetGameInfo();
6197       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6198       DisplayTitle(buf);
6199       if (first.sendName) {
6200         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6201         SendToProgram(buf, &first);
6202       }
6203       StartClocks();
6204     }
6205     ModeHighlight();
6206   }
6207
6208   /* Relay move to ICS or chess engine */
6209   if (appData.icsActive) {
6210     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6211         gameMode == IcsExamining) {
6212       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6213         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6214         SendToICS("draw ");
6215         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6216       }
6217       // also send plain move, in case ICS does not understand atomic claims
6218       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6219       ics_user_moved = 1;
6220     }
6221   } else {
6222     if (first.sendTime && (gameMode == BeginningOfGame ||
6223                            gameMode == MachinePlaysWhite ||
6224                            gameMode == MachinePlaysBlack)) {
6225       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6226     }
6227     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6228          // [HGM] book: if program might be playing, let it use book
6229         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6230         first.maybeThinking = TRUE;
6231     } else SendMoveToProgram(forwardMostMove-1, &first);
6232     if (currentMove == cmailOldMove + 1) {
6233       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6234     }
6235   }
6236
6237   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6238
6239   switch (gameMode) {
6240   case EditGame:
6241     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6242     case MT_NONE:
6243     case MT_CHECK:
6244       break;
6245     case MT_CHECKMATE:
6246     case MT_STAINMATE:
6247       if (WhiteOnMove(currentMove)) {
6248         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6249       } else {
6250         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6251       }
6252       break;
6253     case MT_STALEMATE:
6254       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6255       break;
6256     }
6257     break;
6258
6259   case MachinePlaysBlack:
6260   case MachinePlaysWhite:
6261     /* disable certain menu options while machine is thinking */
6262     SetMachineThinkingEnables();
6263     break;
6264
6265   default:
6266     break;
6267   }
6268
6269   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6270
6271   if(bookHit) { // [HGM] book: simulate book reply
6272         static char bookMove[MSG_SIZ]; // a bit generous?
6273
6274         programStats.nodes = programStats.depth = programStats.time =
6275         programStats.score = programStats.got_only_move = 0;
6276         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6277
6278         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6279         strcat(bookMove, bookHit);
6280         HandleMachineMove(bookMove, &first);
6281   }
6282   return 1;
6283 }
6284
6285 void
6286 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6287      Board board;
6288      int flags;
6289      ChessMove kind;
6290      int rf, ff, rt, ft;
6291      VOIDSTAR closure;
6292 {
6293     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6294     Markers *m = (Markers *) closure;
6295     if(rf == fromY && ff == fromX)
6296         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6297                          || kind == WhiteCapturesEnPassant
6298                          || kind == BlackCapturesEnPassant);
6299     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6300 }
6301
6302 void
6303 MarkTargetSquares(int clear)
6304 {
6305   int x, y;
6306   if(!appData.markers || !appData.highlightDragging ||
6307      !appData.testLegality || gameMode == EditPosition) return;
6308   if(clear) {
6309     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6310   } else {
6311     int capt = 0;
6312     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6313     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6314       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6315       if(capt)
6316       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6317     }
6318   }
6319   DrawPosition(TRUE, NULL);
6320 }
6321
6322 void LeftClick(ClickType clickType, int xPix, int yPix)
6323 {
6324     int x, y;
6325     Boolean saveAnimate;
6326     static int second = 0, promotionChoice = 0, dragging = 0;
6327     char promoChoice = NULLCHAR;
6328
6329     if(appData.seekGraph && appData.icsActive && loggedOn &&
6330         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6331         SeekGraphClick(clickType, xPix, yPix, 0);
6332         return;
6333     }
6334
6335     if (clickType == Press) ErrorPopDown();
6336     MarkTargetSquares(1);
6337
6338     x = EventToSquare(xPix, BOARD_WIDTH);
6339     y = EventToSquare(yPix, BOARD_HEIGHT);
6340     if (!flipView && y >= 0) {
6341         y = BOARD_HEIGHT - 1 - y;
6342     }
6343     if (flipView && x >= 0) {
6344         x = BOARD_WIDTH - 1 - x;
6345     }
6346
6347     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6348         if(clickType == Release) return; // ignore upclick of click-click destination
6349         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6350         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6351         if(gameInfo.holdingsWidth &&
6352                 (WhiteOnMove(currentMove)
6353                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6354                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6355             // click in right holdings, for determining promotion piece
6356             ChessSquare p = boards[currentMove][y][x];
6357             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6358             if(p != EmptySquare) {
6359                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6360                 fromX = fromY = -1;
6361                 return;
6362             }
6363         }
6364         DrawPosition(FALSE, boards[currentMove]);
6365         return;
6366     }
6367
6368     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6369     if(clickType == Press
6370             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6371               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6372               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6373         return;
6374
6375     autoQueen = appData.alwaysPromoteToQueen;
6376
6377     if (fromX == -1) {
6378       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6379         if (clickType == Press) {
6380             /* First square */
6381             if (OKToStartUserMove(x, y)) {
6382                 fromX = x;
6383                 fromY = y;
6384                 second = 0;
6385                 MarkTargetSquares(0);
6386                 DragPieceBegin(xPix, yPix); dragging = 1;
6387                 if (appData.highlightDragging) {
6388                     SetHighlights(x, y, -1, -1);
6389                 }
6390             }
6391         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6392             DragPieceEnd(xPix, yPix); dragging = 0;
6393             DrawPosition(FALSE, NULL);
6394         }
6395         return;
6396       }
6397     }
6398
6399     /* fromX != -1 */
6400     if (clickType == Press && gameMode != EditPosition) {
6401         ChessSquare fromP;
6402         ChessSquare toP;
6403         int frc;
6404
6405         // ignore off-board to clicks
6406         if(y < 0 || x < 0) return;
6407
6408         /* Check if clicking again on the same color piece */
6409         fromP = boards[currentMove][fromY][fromX];
6410         toP = boards[currentMove][y][x];
6411         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6412         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6413              WhitePawn <= toP && toP <= WhiteKing &&
6414              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6415              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6416             (BlackPawn <= fromP && fromP <= BlackKing &&
6417              BlackPawn <= toP && toP <= BlackKing &&
6418              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6419              !(fromP == BlackKing && toP == BlackRook && frc))) {
6420             /* Clicked again on same color piece -- changed his mind */
6421             second = (x == fromX && y == fromY);
6422            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6423             if (appData.highlightDragging) {
6424                 SetHighlights(x, y, -1, -1);
6425             } else {
6426                 ClearHighlights();
6427             }
6428             if (OKToStartUserMove(x, y)) {
6429                 fromX = x;
6430                 fromY = y; dragging = 1;
6431                 MarkTargetSquares(0);
6432                 DragPieceBegin(xPix, yPix);
6433             }
6434             return;
6435            }
6436         }
6437         // ignore clicks on holdings
6438         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6439     }
6440
6441     if (clickType == Release && x == fromX && y == fromY) {
6442         DragPieceEnd(xPix, yPix); dragging = 0;
6443         if (appData.animateDragging) {
6444             /* Undo animation damage if any */
6445             DrawPosition(FALSE, NULL);
6446         }
6447         if (second) {
6448             /* Second up/down in same square; just abort move */
6449             second = 0;
6450             fromX = fromY = -1;
6451             ClearHighlights();
6452             gotPremove = 0;
6453             ClearPremoveHighlights();
6454         } else {
6455             /* First upclick in same square; start click-click mode */
6456             SetHighlights(x, y, -1, -1);
6457         }
6458         return;
6459     }
6460
6461     /* we now have a different from- and (possibly off-board) to-square */
6462     /* Completed move */
6463     toX = x;
6464     toY = y;
6465     saveAnimate = appData.animate;
6466     if (clickType == Press) {
6467         /* Finish clickclick move */
6468         if (appData.animate || appData.highlightLastMove) {
6469             SetHighlights(fromX, fromY, toX, toY);
6470         } else {
6471             ClearHighlights();
6472         }
6473     } else {
6474         /* Finish drag move */
6475         if (appData.highlightLastMove) {
6476             SetHighlights(fromX, fromY, toX, toY);
6477         } else {
6478             ClearHighlights();
6479         }
6480         DragPieceEnd(xPix, yPix); dragging = 0;
6481         /* Don't animate move and drag both */
6482         appData.animate = FALSE;
6483     }
6484
6485     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6486     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6487         ChessSquare piece = boards[currentMove][fromY][fromX];
6488         if(gameMode == EditPosition && piece != EmptySquare &&
6489            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6490             int n;
6491
6492             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6493                 n = PieceToNumber(piece - (int)BlackPawn);
6494                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6495                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6496                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6497             } else
6498             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6499                 n = PieceToNumber(piece);
6500                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6501                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6502                 boards[currentMove][n][BOARD_WIDTH-2]++;
6503             }
6504             boards[currentMove][fromY][fromX] = EmptySquare;
6505         }
6506         ClearHighlights();
6507         fromX = fromY = -1;
6508         DrawPosition(TRUE, boards[currentMove]);
6509         return;
6510     }
6511
6512     // off-board moves should not be highlighted
6513     if(x < 0 || x < 0) ClearHighlights();
6514
6515     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6516         SetHighlights(fromX, fromY, toX, toY);
6517         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6518             // [HGM] super: promotion to captured piece selected from holdings
6519             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6520             promotionChoice = TRUE;
6521             // kludge follows to temporarily execute move on display, without promoting yet
6522             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6523             boards[currentMove][toY][toX] = p;
6524             DrawPosition(FALSE, boards[currentMove]);
6525             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6526             boards[currentMove][toY][toX] = q;
6527             DisplayMessage("Click in holdings to choose piece", "");
6528             return;
6529         }
6530         PromotionPopUp();
6531     } else {
6532         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6533         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6534         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6535         fromX = fromY = -1;
6536     }
6537     appData.animate = saveAnimate;
6538     if (appData.animate || appData.animateDragging) {
6539         /* Undo animation damage if needed */
6540         DrawPosition(FALSE, NULL);
6541     }
6542 }
6543
6544 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6545 {   // front-end-free part taken out of PieceMenuPopup
6546     int whichMenu; int xSqr, ySqr;
6547
6548     if(seekGraphUp) { // [HGM] seekgraph
6549         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6550         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6551         return -2;
6552     }
6553
6554     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6555          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6556         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6557         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6558         if(action == Press)   {
6559             originalFlip = flipView;
6560             flipView = !flipView; // temporarily flip board to see game from partners perspective
6561             DrawPosition(TRUE, partnerBoard);
6562             DisplayMessage(partnerStatus, "");
6563             partnerUp = TRUE;
6564         } else if(action == Release) {
6565             flipView = originalFlip;
6566             DrawPosition(TRUE, boards[currentMove]);
6567             partnerUp = FALSE;
6568         }
6569         return -2;
6570     }
6571
6572     xSqr = EventToSquare(x, BOARD_WIDTH);
6573     ySqr = EventToSquare(y, BOARD_HEIGHT);
6574     if (action == Release) UnLoadPV(); // [HGM] pv
6575     if (action != Press) return -2; // return code to be ignored
6576     switch (gameMode) {
6577       case IcsExamining:
6578         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6579       case EditPosition:
6580         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6581         if (xSqr < 0 || ySqr < 0) return -1;\r
6582         whichMenu = 0; // edit-position menu
6583         break;
6584       case IcsObserving:
6585         if(!appData.icsEngineAnalyze) return -1;
6586       case IcsPlayingWhite:
6587       case IcsPlayingBlack:
6588         if(!appData.zippyPlay) goto noZip;
6589       case AnalyzeMode:
6590       case AnalyzeFile:
6591       case MachinePlaysWhite:
6592       case MachinePlaysBlack:
6593       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6594         if (!appData.dropMenu) {
6595           LoadPV(x, y);
6596           return 2; // flag front-end to grab mouse events
6597         }
6598         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6599            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6600       case EditGame:
6601       noZip:
6602         if (xSqr < 0 || ySqr < 0) return -1;
6603         if (!appData.dropMenu || appData.testLegality &&
6604             gameInfo.variant != VariantBughouse &&
6605             gameInfo.variant != VariantCrazyhouse) return -1;
6606         whichMenu = 1; // drop menu
6607         break;
6608       default:
6609         return -1;
6610     }
6611
6612     if (((*fromX = xSqr) < 0) ||
6613         ((*fromY = ySqr) < 0)) {
6614         *fromX = *fromY = -1;
6615         return -1;
6616     }
6617     if (flipView)
6618       *fromX = BOARD_WIDTH - 1 - *fromX;
6619     else
6620       *fromY = BOARD_HEIGHT - 1 - *fromY;
6621
6622     return whichMenu;
6623 }
6624
6625 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6626 {
6627 //    char * hint = lastHint;
6628     FrontEndProgramStats stats;
6629
6630     stats.which = cps == &first ? 0 : 1;
6631     stats.depth = cpstats->depth;
6632     stats.nodes = cpstats->nodes;
6633     stats.score = cpstats->score;
6634     stats.time = cpstats->time;
6635     stats.pv = cpstats->movelist;
6636     stats.hint = lastHint;
6637     stats.an_move_index = 0;
6638     stats.an_move_count = 0;
6639
6640     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6641         stats.hint = cpstats->move_name;
6642         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6643         stats.an_move_count = cpstats->nr_moves;
6644     }
6645
6646     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
6647
6648     SetProgramStats( &stats );
6649 }
6650
6651 void
6652 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6653 {       // count all piece types
6654         int p, f, r;
6655         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6656         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6657         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6658                 p = board[r][f];
6659                 pCnt[p]++;
6660                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6661                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6662                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6663                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6664                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6665                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6666         }
6667 }
6668
6669 int
6670 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6671 {
6672         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6673         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6674
6675         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6676         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6677         if(myPawns == 2 && nMine == 3) // KPP
6678             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6679         if(myPawns == 1 && nMine == 2) // KP
6680             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6681         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6682             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6683         if(myPawns) return FALSE;
6684         if(pCnt[WhiteRook+side])
6685             return pCnt[BlackRook-side] ||
6686                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6687                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6688                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6689         if(pCnt[WhiteCannon+side]) {
6690             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6691             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6692         }
6693         if(pCnt[WhiteKnight+side])
6694             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6695         return FALSE;
6696 }
6697
6698 int
6699 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6700 {
6701         VariantClass v = gameInfo.variant;
6702
6703         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6704         if(v == VariantShatranj) return TRUE; // always winnable through baring
6705         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6706         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6707
6708         if(v == VariantXiangqi) {
6709                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6710
6711                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6712                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6713                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6714                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6715                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6716                 if(stale) // we have at least one last-rank P plus perhaps C
6717                     return majors // KPKX
6718                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6719                 else // KCA*E*
6720                     return pCnt[WhiteFerz+side] // KCAK
6721                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6722                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6723                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6724
6725         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6726                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6727
6728                 if(nMine == 1) return FALSE; // bare King
6729                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
6730                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6731                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6732                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6733                 if(pCnt[WhiteKnight+side])
6734                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6735                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6736                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6737                 if(nBishops)
6738                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6739                 if(pCnt[WhiteAlfil+side])
6740                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6741                 if(pCnt[WhiteWazir+side])
6742                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6743         }
6744
6745         return TRUE;
6746 }
6747
6748 int
6749 Adjudicate(ChessProgramState *cps)
6750 {       // [HGM] some adjudications useful with buggy engines
6751         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6752         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6753         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6754         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6755         int k, count = 0; static int bare = 1;
6756         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6757         Boolean canAdjudicate = !appData.icsActive;
6758
6759         // most tests only when we understand the game, i.e. legality-checking on
6760             if( appData.testLegality )
6761             {   /* [HGM] Some more adjudications for obstinate engines */
6762                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6763                 static int moveCount = 6;
6764                 ChessMove result;
6765                 char *reason = NULL;
6766
6767                 /* Count what is on board. */
6768                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6769
6770                 /* Some material-based adjudications that have to be made before stalemate test */
6771                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6772                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6773                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6774                      if(canAdjudicate && appData.checkMates) {
6775                          if(engineOpponent)
6776                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6777                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6778                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6779                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6780                          return 1;
6781                      }
6782                 }
6783
6784                 /* Bare King in Shatranj (loses) or Losers (wins) */
6785                 if( nrW == 1 || nrB == 1) {
6786                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6787                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6788                      if(canAdjudicate && appData.checkMates) {
6789                          if(engineOpponent)
6790                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6791                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6792                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6793                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6794                          return 1;
6795                      }
6796                   } else
6797                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6798                   {    /* bare King */
6799                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6800                         if(canAdjudicate && appData.checkMates) {
6801                             /* but only adjudicate if adjudication enabled */
6802                             if(engineOpponent)
6803                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6804                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6805                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6806                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6807                             return 1;
6808                         }
6809                   }
6810                 } else bare = 1;
6811
6812
6813             // don't wait for engine to announce game end if we can judge ourselves
6814             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6815               case MT_CHECK:
6816                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6817                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6818                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6819                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6820                             checkCnt++;
6821                         if(checkCnt >= 2) {
6822                             reason = "Xboard adjudication: 3rd check";
6823                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6824                             break;
6825                         }
6826                     }
6827                 }
6828               case MT_NONE:
6829               default:
6830                 break;
6831               case MT_STALEMATE:
6832               case MT_STAINMATE:
6833                 reason = "Xboard adjudication: Stalemate";
6834                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6835                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6836                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6837                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6838                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6839                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6840                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6841                                                                         EP_CHECKMATE : EP_WINS);
6842                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6843                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6844                 }
6845                 break;
6846               case MT_CHECKMATE:
6847                 reason = "Xboard adjudication: Checkmate";
6848                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6849                 break;
6850             }
6851
6852                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6853                     case EP_STALEMATE:
6854                         result = GameIsDrawn; break;
6855                     case EP_CHECKMATE:
6856                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6857                     case EP_WINS:
6858                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6859                     default:
6860                         result = EndOfFile;
6861                 }
6862                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6863                     if(engineOpponent)
6864                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6865                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6866                     GameEnds( result, reason, GE_XBOARD );
6867                     return 1;
6868                 }
6869
6870                 /* Next absolutely insufficient mating material. */
6871                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6872                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6873                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6874
6875                      /* always flag draws, for judging claims */
6876                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6877
6878                      if(canAdjudicate && appData.materialDraws) {
6879                          /* but only adjudicate them if adjudication enabled */
6880                          if(engineOpponent) {
6881                            SendToProgram("force\n", engineOpponent); // suppress reply
6882                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6883                          }
6884                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6885                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6886                          return 1;
6887                      }
6888                 }
6889
6890                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6891                 if(gameInfo.variant == VariantXiangqi ?
6892                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6893                  : nrW + nrB == 4 &&
6894                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6895                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6896                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6897                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6898                    ) ) {
6899                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6900                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6901                           if(engineOpponent) {
6902                             SendToProgram("force\n", engineOpponent); // suppress reply
6903                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6904                           }
6905                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6906                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6907                           return 1;
6908                      }
6909                 } else moveCount = 6;
6910             }
6911         if (appData.debugMode) { int i;
6912             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6913                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6914                     appData.drawRepeats);
6915             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6916               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6917
6918         }
6919
6920         // Repetition draws and 50-move rule can be applied independently of legality testing
6921
6922                 /* Check for rep-draws */
6923                 count = 0;
6924                 for(k = forwardMostMove-2;
6925                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6926                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6927                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6928                     k-=2)
6929                 {   int rights=0;
6930                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6931                         /* compare castling rights */
6932                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6933                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6934                                 rights++; /* King lost rights, while rook still had them */
6935                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6936                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6937                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6938                                    rights++; /* but at least one rook lost them */
6939                         }
6940                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6941                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6942                                 rights++;
6943                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6944                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6945                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6946                                    rights++;
6947                         }
6948                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6949                             && appData.drawRepeats > 1) {
6950                              /* adjudicate after user-specified nr of repeats */
6951                              int result = GameIsDrawn;
6952                              char *details = "XBoard adjudication: repetition draw";
6953                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6954                                 // [HGM] xiangqi: check for forbidden perpetuals
6955                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6956                                 for(m=forwardMostMove; m>k; m-=2) {
6957                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6958                                         ourPerpetual = 0; // the current mover did not always check
6959                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6960                                         hisPerpetual = 0; // the opponent did not always check
6961                                 }
6962                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6963                                                                         ourPerpetual, hisPerpetual);
6964                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6965                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6966                                     details = "Xboard adjudication: perpetual checking";
6967                                 } else
6968                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6969                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6970                                 } else
6971                                 // Now check for perpetual chases
6972                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6973                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6974                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6975                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6976                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6977                                         details = "Xboard adjudication: perpetual chasing";
6978                                     } else
6979                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6980                                         break; // Abort repetition-checking loop.
6981                                 }
6982                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6983                              }
6984                              if(engineOpponent) {
6985                                SendToProgram("force\n", engineOpponent); // suppress reply
6986                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6987                              }
6988                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6989                              GameEnds( result, details, GE_XBOARD );
6990                              return 1;
6991                         }
6992                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6993                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6994                     }
6995                 }
6996
6997                 /* Now we test for 50-move draws. Determine ply count */
6998                 count = forwardMostMove;
6999                 /* look for last irreversble move */
7000                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7001                     count--;
7002                 /* if we hit starting position, add initial plies */
7003                 if( count == backwardMostMove )
7004                     count -= initialRulePlies;
7005                 count = forwardMostMove - count;
7006                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7007                         // adjust reversible move counter for checks in Xiangqi
7008                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7009                         if(i < backwardMostMove) i = backwardMostMove;
7010                         while(i <= forwardMostMove) {
7011                                 lastCheck = inCheck; // check evasion does not count
7012                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7013                                 if(inCheck || lastCheck) count--; // check does not count
7014                                 i++;
7015                         }
7016                 }
7017                 if( count >= 100)
7018                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7019                          /* this is used to judge if draw claims are legal */
7020                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7021                          if(engineOpponent) {
7022                            SendToProgram("force\n", engineOpponent); // suppress reply
7023                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7024                          }
7025                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7026                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7027                          return 1;
7028                 }
7029
7030                 /* if draw offer is pending, treat it as a draw claim
7031                  * when draw condition present, to allow engines a way to
7032                  * claim draws before making their move to avoid a race
7033                  * condition occurring after their move
7034                  */
7035                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7036                          char *p = NULL;
7037                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7038                              p = "Draw claim: 50-move rule";
7039                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7040                              p = "Draw claim: 3-fold repetition";
7041                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7042                              p = "Draw claim: insufficient mating material";
7043                          if( p != NULL && canAdjudicate) {
7044                              if(engineOpponent) {
7045                                SendToProgram("force\n", engineOpponent); // suppress reply
7046                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7047                              }
7048                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7049                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7050                              return 1;
7051                          }
7052                 }
7053
7054                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7055                     if(engineOpponent) {
7056                       SendToProgram("force\n", engineOpponent); // suppress reply
7057                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7058                     }
7059                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7060                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7061                     return 1;
7062                 }
7063         return 0;
7064 }
7065
7066 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7067 {   // [HGM] book: this routine intercepts moves to simulate book replies
7068     char *bookHit = NULL;
7069
7070     //first determine if the incoming move brings opponent into his book
7071     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7072         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7073     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7074     if(bookHit != NULL && !cps->bookSuspend) {
7075         // make sure opponent is not going to reply after receiving move to book position
7076         SendToProgram("force\n", cps);
7077         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7078     }
7079     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7080     // now arrange restart after book miss
7081     if(bookHit) {
7082         // after a book hit we never send 'go', and the code after the call to this routine
7083         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7084         char buf[MSG_SIZ];
7085         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7086         SendToProgram(buf, cps);
7087         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7088     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7089         SendToProgram("go\n", cps);
7090         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7091     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7092         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7093             SendToProgram("go\n", cps);
7094         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7095     }
7096     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7097 }
7098
7099 char *savedMessage;
7100 ChessProgramState *savedState;
7101 void DeferredBookMove(void)
7102 {
7103         if(savedState->lastPing != savedState->lastPong)
7104                     ScheduleDelayedEvent(DeferredBookMove, 10);
7105         else
7106         HandleMachineMove(savedMessage, savedState);
7107 }
7108
7109 void
7110 HandleMachineMove(message, cps)
7111      char *message;
7112      ChessProgramState *cps;
7113 {
7114     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7115     char realname[MSG_SIZ];
7116     int fromX, fromY, toX, toY;
7117     ChessMove moveType;
7118     char promoChar;
7119     char *p;
7120     int machineWhite;
7121     char *bookHit;
7122
7123     cps->userError = 0;
7124
7125 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7126     /*
7127      * Kludge to ignore BEL characters
7128      */
7129     while (*message == '\007') message++;
7130
7131     /*
7132      * [HGM] engine debug message: ignore lines starting with '#' character
7133      */
7134     if(cps->debug && *message == '#') return;
7135
7136     /*
7137      * Look for book output
7138      */
7139     if (cps == &first && bookRequested) {
7140         if (message[0] == '\t' || message[0] == ' ') {
7141             /* Part of the book output is here; append it */
7142             strcat(bookOutput, message);
7143             strcat(bookOutput, "  \n");
7144             return;
7145         } else if (bookOutput[0] != NULLCHAR) {
7146             /* All of book output has arrived; display it */
7147             char *p = bookOutput;
7148             while (*p != NULLCHAR) {
7149                 if (*p == '\t') *p = ' ';
7150                 p++;
7151             }
7152             DisplayInformation(bookOutput);
7153             bookRequested = FALSE;
7154             /* Fall through to parse the current output */
7155         }
7156     }
7157
7158     /*
7159      * Look for machine move.
7160      */
7161     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7162         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7163     {
7164         /* This method is only useful on engines that support ping */
7165         if (cps->lastPing != cps->lastPong) {
7166           if (gameMode == BeginningOfGame) {
7167             /* Extra move from before last new; ignore */
7168             if (appData.debugMode) {
7169                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7170             }
7171           } else {
7172             if (appData.debugMode) {
7173                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7174                         cps->which, gameMode);
7175             }
7176
7177             SendToProgram("undo\n", cps);
7178           }
7179           return;
7180         }
7181
7182         switch (gameMode) {
7183           case BeginningOfGame:
7184             /* Extra move from before last reset; ignore */
7185             if (appData.debugMode) {
7186                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7187             }
7188             return;
7189
7190           case EndOfGame:
7191           case IcsIdle:
7192           default:
7193             /* Extra move after we tried to stop.  The mode test is
7194                not a reliable way of detecting this problem, but it's
7195                the best we can do on engines that don't support ping.
7196             */
7197             if (appData.debugMode) {
7198                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7199                         cps->which, gameMode);
7200             }
7201             SendToProgram("undo\n", cps);
7202             return;
7203
7204           case MachinePlaysWhite:
7205           case IcsPlayingWhite:
7206             machineWhite = TRUE;
7207             break;
7208
7209           case MachinePlaysBlack:
7210           case IcsPlayingBlack:
7211             machineWhite = FALSE;
7212             break;
7213
7214           case TwoMachinesPlay:
7215             machineWhite = (cps->twoMachinesColor[0] == 'w');
7216             break;
7217         }
7218         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7219             if (appData.debugMode) {
7220                 fprintf(debugFP,
7221                         "Ignoring move out of turn by %s, gameMode %d"
7222                         ", forwardMost %d\n",
7223                         cps->which, gameMode, forwardMostMove);
7224             }
7225             return;
7226         }
7227
7228     if (appData.debugMode) { int f = forwardMostMove;
7229         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7230                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7231                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7232     }
7233         if(cps->alphaRank) AlphaRank(machineMove, 4);
7234         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7235                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7236             /* Machine move could not be parsed; ignore it. */
7237           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7238                     machineMove, cps->which);
7239             DisplayError(buf1, 0);
7240             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7241                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7242             if (gameMode == TwoMachinesPlay) {
7243               GameEnds(machineWhite ? BlackWins : WhiteWins,
7244                        buf1, GE_XBOARD);
7245             }
7246             return;
7247         }
7248
7249         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7250         /* So we have to redo legality test with true e.p. status here,  */
7251         /* to make sure an illegal e.p. capture does not slip through,   */
7252         /* to cause a forfeit on a justified illegal-move complaint      */
7253         /* of the opponent.                                              */
7254         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7255            ChessMove moveType;
7256            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7257                              fromY, fromX, toY, toX, promoChar);
7258             if (appData.debugMode) {
7259                 int i;
7260                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7261                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7262                 fprintf(debugFP, "castling rights\n");
7263             }
7264             if(moveType == IllegalMove) {
7265               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7266                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7267                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7268                            buf1, GE_XBOARD);
7269                 return;
7270            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7271            /* [HGM] Kludge to handle engines that send FRC-style castling
7272               when they shouldn't (like TSCP-Gothic) */
7273            switch(moveType) {
7274              case WhiteASideCastleFR:
7275              case BlackASideCastleFR:
7276                toX+=2;
7277                currentMoveString[2]++;
7278                break;
7279              case WhiteHSideCastleFR:
7280              case BlackHSideCastleFR:
7281                toX--;
7282                currentMoveString[2]--;
7283                break;
7284              default: ; // nothing to do, but suppresses warning of pedantic compilers
7285            }
7286         }
7287         hintRequested = FALSE;
7288         lastHint[0] = NULLCHAR;
7289         bookRequested = FALSE;
7290         /* Program may be pondering now */
7291         cps->maybeThinking = TRUE;
7292         if (cps->sendTime == 2) cps->sendTime = 1;
7293         if (cps->offeredDraw) cps->offeredDraw--;
7294
7295         /* currentMoveString is set as a side-effect of ParseOneMove */
7296         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7297         strcat(machineMove, "\n");
7298         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7299
7300         /* [AS] Save move info*/
7301         pvInfoList[ forwardMostMove ].score = programStats.score;
7302         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7303         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7304
7305         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7306
7307         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7308         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7309             int count = 0;
7310
7311             while( count < adjudicateLossPlies ) {
7312                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7313
7314                 if( count & 1 ) {
7315                     score = -score; /* Flip score for winning side */
7316                 }
7317
7318                 if( score > adjudicateLossThreshold ) {
7319                     break;
7320                 }
7321
7322                 count++;
7323             }
7324
7325             if( count >= adjudicateLossPlies ) {
7326                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7327
7328                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7329                     "Xboard adjudication",
7330                     GE_XBOARD );
7331
7332                 return;
7333             }
7334         }
7335
7336         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7337
7338 #if ZIPPY
7339         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7340             first.initDone) {
7341           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7342                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7343                 SendToICS("draw ");
7344                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7345           }
7346           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7347           ics_user_moved = 1;
7348           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7349                 char buf[3*MSG_SIZ];
7350
7351                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7352                         programStats.score / 100.,
7353                         programStats.depth,
7354                         programStats.time / 100.,
7355                         (unsigned int)programStats.nodes,
7356                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7357                         programStats.movelist);
7358                 SendToICS(buf);
7359 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7360           }
7361         }
7362 #endif
7363
7364         /* [AS] Clear stats for next move */
7365         ClearProgramStats();
7366         thinkOutput[0] = NULLCHAR;
7367         hiddenThinkOutputState = 0;
7368
7369         bookHit = NULL;
7370         if (gameMode == TwoMachinesPlay) {
7371             /* [HGM] relaying draw offers moved to after reception of move */
7372             /* and interpreting offer as claim if it brings draw condition */
7373             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7374                 SendToProgram("draw\n", cps->other);
7375             }
7376             if (cps->other->sendTime) {
7377                 SendTimeRemaining(cps->other,
7378                                   cps->other->twoMachinesColor[0] == 'w');
7379             }
7380             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7381             if (firstMove && !bookHit) {
7382                 firstMove = FALSE;
7383                 if (cps->other->useColors) {
7384                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7385                 }
7386                 SendToProgram("go\n", cps->other);
7387             }
7388             cps->other->maybeThinking = TRUE;
7389         }
7390
7391         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7392
7393         if (!pausing && appData.ringBellAfterMoves) {
7394             RingBell();
7395         }
7396
7397         /*
7398          * Reenable menu items that were disabled while
7399          * machine was thinking
7400          */
7401         if (gameMode != TwoMachinesPlay)
7402             SetUserThinkingEnables();
7403
7404         // [HGM] book: after book hit opponent has received move and is now in force mode
7405         // force the book reply into it, and then fake that it outputted this move by jumping
7406         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7407         if(bookHit) {
7408                 static char bookMove[MSG_SIZ]; // a bit generous?
7409
7410                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7411                 strcat(bookMove, bookHit);
7412                 message = bookMove;
7413                 cps = cps->other;
7414                 programStats.nodes = programStats.depth = programStats.time =
7415                 programStats.score = programStats.got_only_move = 0;
7416                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7417
7418                 if(cps->lastPing != cps->lastPong) {
7419                     savedMessage = message; // args for deferred call
7420                     savedState = cps;
7421                     ScheduleDelayedEvent(DeferredBookMove, 10);
7422                     return;
7423                 }
7424                 goto FakeBookMove;
7425         }
7426
7427         return;
7428     }
7429
7430     /* Set special modes for chess engines.  Later something general
7431      *  could be added here; for now there is just one kludge feature,
7432      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7433      *  when "xboard" is given as an interactive command.
7434      */
7435     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7436         cps->useSigint = FALSE;
7437         cps->useSigterm = FALSE;
7438     }
7439     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7440       ParseFeatures(message+8, cps);
7441       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7442     }
7443
7444     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7445      * want this, I was asked to put it in, and obliged.
7446      */
7447     if (!strncmp(message, "setboard ", 9)) {
7448         Board initial_position;
7449
7450         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7451
7452         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7453             DisplayError(_("Bad FEN received from engine"), 0);
7454             return ;
7455         } else {
7456            Reset(TRUE, FALSE);
7457            CopyBoard(boards[0], initial_position);
7458            initialRulePlies = FENrulePlies;
7459            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7460            else gameMode = MachinePlaysBlack;
7461            DrawPosition(FALSE, boards[currentMove]);
7462         }
7463         return;
7464     }
7465
7466     /*
7467      * Look for communication commands
7468      */
7469     if (!strncmp(message, "telluser ", 9)) {
7470         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7471         DisplayNote(message + 9);
7472         return;
7473     }
7474     if (!strncmp(message, "tellusererror ", 14)) {
7475         cps->userError = 1;
7476         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7477         DisplayError(message + 14, 0);
7478         return;
7479     }
7480     if (!strncmp(message, "tellopponent ", 13)) {
7481       if (appData.icsActive) {
7482         if (loggedOn) {
7483           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7484           SendToICS(buf1);
7485         }
7486       } else {
7487         DisplayNote(message + 13);
7488       }
7489       return;
7490     }
7491     if (!strncmp(message, "tellothers ", 11)) {
7492       if (appData.icsActive) {
7493         if (loggedOn) {
7494           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7495           SendToICS(buf1);
7496         }
7497       }
7498       return;
7499     }
7500     if (!strncmp(message, "tellall ", 8)) {
7501       if (appData.icsActive) {
7502         if (loggedOn) {
7503           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7504           SendToICS(buf1);
7505         }
7506       } else {
7507         DisplayNote(message + 8);
7508       }
7509       return;
7510     }
7511     if (strncmp(message, "warning", 7) == 0) {
7512         /* Undocumented feature, use tellusererror in new code */
7513         DisplayError(message, 0);
7514         return;
7515     }
7516     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7517         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7518         strcat(realname, " query");
7519         AskQuestion(realname, buf2, buf1, cps->pr);
7520         return;
7521     }
7522     /* Commands from the engine directly to ICS.  We don't allow these to be
7523      *  sent until we are logged on. Crafty kibitzes have been known to
7524      *  interfere with the login process.
7525      */
7526     if (loggedOn) {
7527         if (!strncmp(message, "tellics ", 8)) {
7528             SendToICS(message + 8);
7529             SendToICS("\n");
7530             return;
7531         }
7532         if (!strncmp(message, "tellicsnoalias ", 15)) {
7533             SendToICS(ics_prefix);
7534             SendToICS(message + 15);
7535             SendToICS("\n");
7536             return;
7537         }
7538         /* The following are for backward compatibility only */
7539         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7540             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7541             SendToICS(ics_prefix);
7542             SendToICS(message);
7543             SendToICS("\n");
7544             return;
7545         }
7546     }
7547     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7548         return;
7549     }
7550     /*
7551      * If the move is illegal, cancel it and redraw the board.
7552      * Also deal with other error cases.  Matching is rather loose
7553      * here to accommodate engines written before the spec.
7554      */
7555     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7556         strncmp(message, "Error", 5) == 0) {
7557         if (StrStr(message, "name") ||
7558             StrStr(message, "rating") || StrStr(message, "?") ||
7559             StrStr(message, "result") || StrStr(message, "board") ||
7560             StrStr(message, "bk") || StrStr(message, "computer") ||
7561             StrStr(message, "variant") || StrStr(message, "hint") ||
7562             StrStr(message, "random") || StrStr(message, "depth") ||
7563             StrStr(message, "accepted")) {
7564             return;
7565         }
7566         if (StrStr(message, "protover")) {
7567           /* Program is responding to input, so it's apparently done
7568              initializing, and this error message indicates it is
7569              protocol version 1.  So we don't need to wait any longer
7570              for it to initialize and send feature commands. */
7571           FeatureDone(cps, 1);
7572           cps->protocolVersion = 1;
7573           return;
7574         }
7575         cps->maybeThinking = FALSE;
7576
7577         if (StrStr(message, "draw")) {
7578             /* Program doesn't have "draw" command */
7579             cps->sendDrawOffers = 0;
7580             return;
7581         }
7582         if (cps->sendTime != 1 &&
7583             (StrStr(message, "time") || StrStr(message, "otim"))) {
7584           /* Program apparently doesn't have "time" or "otim" command */
7585           cps->sendTime = 0;
7586           return;
7587         }
7588         if (StrStr(message, "analyze")) {
7589             cps->analysisSupport = FALSE;
7590             cps->analyzing = FALSE;
7591             Reset(FALSE, TRUE);
7592             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7593             DisplayError(buf2, 0);
7594             return;
7595         }
7596         if (StrStr(message, "(no matching move)st")) {
7597           /* Special kludge for GNU Chess 4 only */
7598           cps->stKludge = TRUE;
7599           SendTimeControl(cps, movesPerSession, timeControl,
7600                           timeIncrement, appData.searchDepth,
7601                           searchTime);
7602           return;
7603         }
7604         if (StrStr(message, "(no matching move)sd")) {
7605           /* Special kludge for GNU Chess 4 only */
7606           cps->sdKludge = TRUE;
7607           SendTimeControl(cps, movesPerSession, timeControl,
7608                           timeIncrement, appData.searchDepth,
7609                           searchTime);
7610           return;
7611         }
7612         if (!StrStr(message, "llegal")) {
7613             return;
7614         }
7615         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7616             gameMode == IcsIdle) return;
7617         if (forwardMostMove <= backwardMostMove) return;
7618         if (pausing) PauseEvent();
7619       if(appData.forceIllegal) {
7620             // [HGM] illegal: machine refused move; force position after move into it
7621           SendToProgram("force\n", cps);
7622           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7623                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7624                 // when black is to move, while there might be nothing on a2 or black
7625                 // might already have the move. So send the board as if white has the move.
7626                 // But first we must change the stm of the engine, as it refused the last move
7627                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7628                 if(WhiteOnMove(forwardMostMove)) {
7629                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7630                     SendBoard(cps, forwardMostMove); // kludgeless board
7631                 } else {
7632                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7633                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7634                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7635                 }
7636           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7637             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7638                  gameMode == TwoMachinesPlay)
7639               SendToProgram("go\n", cps);
7640             return;
7641       } else
7642         if (gameMode == PlayFromGameFile) {
7643             /* Stop reading this game file */
7644             gameMode = EditGame;
7645             ModeHighlight();
7646         }
7647         currentMove = forwardMostMove-1;
7648         DisplayMove(currentMove-1); /* before DisplayMoveError */
7649         SwitchClocks(forwardMostMove-1); // [HGM] race
7650         DisplayBothClocks();
7651         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7652                 parseList[currentMove], cps->which);
7653         DisplayMoveError(buf1);
7654         DrawPosition(FALSE, boards[currentMove]);
7655
7656         /* [HGM] illegal-move claim should forfeit game when Xboard */
7657         /* only passes fully legal moves                            */
7658         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7659             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7660                                 "False illegal-move claim", GE_XBOARD );
7661         }
7662         return;
7663     }
7664     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7665         /* Program has a broken "time" command that
7666            outputs a string not ending in newline.
7667            Don't use it. */
7668         cps->sendTime = 0;
7669     }
7670
7671     /*
7672      * If chess program startup fails, exit with an error message.
7673      * Attempts to recover here are futile.
7674      */
7675     if ((StrStr(message, "unknown host") != NULL)
7676         || (StrStr(message, "No remote directory") != NULL)
7677         || (StrStr(message, "not found") != NULL)
7678         || (StrStr(message, "No such file") != NULL)
7679         || (StrStr(message, "can't alloc") != NULL)
7680         || (StrStr(message, "Permission denied") != NULL)) {
7681
7682         cps->maybeThinking = FALSE;
7683         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7684                 cps->which, cps->program, cps->host, message);
7685         RemoveInputSource(cps->isr);
7686         DisplayFatalError(buf1, 0, 1);
7687         return;
7688     }
7689
7690     /*
7691      * Look for hint output
7692      */
7693     if (sscanf(message, "Hint: %s", buf1) == 1) {
7694         if (cps == &first && hintRequested) {
7695             hintRequested = FALSE;
7696             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7697                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7698                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7699                                     PosFlags(forwardMostMove),
7700                                     fromY, fromX, toY, toX, promoChar, buf1);
7701                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7702                 DisplayInformation(buf2);
7703             } else {
7704                 /* Hint move could not be parsed!? */
7705               snprintf(buf2, sizeof(buf2),
7706                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7707                         buf1, cps->which);
7708                 DisplayError(buf2, 0);
7709             }
7710         } else {
7711           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7712         }
7713         return;
7714     }
7715
7716     /*
7717      * Ignore other messages if game is not in progress
7718      */
7719     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7720         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7721
7722     /*
7723      * look for win, lose, draw, or draw offer
7724      */
7725     if (strncmp(message, "1-0", 3) == 0) {
7726         char *p, *q, *r = "";
7727         p = strchr(message, '{');
7728         if (p) {
7729             q = strchr(p, '}');
7730             if (q) {
7731                 *q = NULLCHAR;
7732                 r = p + 1;
7733             }
7734         }
7735         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7736         return;
7737     } else if (strncmp(message, "0-1", 3) == 0) {
7738         char *p, *q, *r = "";
7739         p = strchr(message, '{');
7740         if (p) {
7741             q = strchr(p, '}');
7742             if (q) {
7743                 *q = NULLCHAR;
7744                 r = p + 1;
7745             }
7746         }
7747         /* Kludge for Arasan 4.1 bug */
7748         if (strcmp(r, "Black resigns") == 0) {
7749             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7750             return;
7751         }
7752         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7753         return;
7754     } else if (strncmp(message, "1/2", 3) == 0) {
7755         char *p, *q, *r = "";
7756         p = strchr(message, '{');
7757         if (p) {
7758             q = strchr(p, '}');
7759             if (q) {
7760                 *q = NULLCHAR;
7761                 r = p + 1;
7762             }
7763         }
7764
7765         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7766         return;
7767
7768     } else if (strncmp(message, "White resign", 12) == 0) {
7769         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7770         return;
7771     } else if (strncmp(message, "Black resign", 12) == 0) {
7772         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7773         return;
7774     } else if (strncmp(message, "White matches", 13) == 0 ||
7775                strncmp(message, "Black matches", 13) == 0   ) {
7776         /* [HGM] ignore GNUShogi noises */
7777         return;
7778     } else if (strncmp(message, "White", 5) == 0 &&
7779                message[5] != '(' &&
7780                StrStr(message, "Black") == NULL) {
7781         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7782         return;
7783     } else if (strncmp(message, "Black", 5) == 0 &&
7784                message[5] != '(') {
7785         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7786         return;
7787     } else if (strcmp(message, "resign") == 0 ||
7788                strcmp(message, "computer resigns") == 0) {
7789         switch (gameMode) {
7790           case MachinePlaysBlack:
7791           case IcsPlayingBlack:
7792             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7793             break;
7794           case MachinePlaysWhite:
7795           case IcsPlayingWhite:
7796             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7797             break;
7798           case TwoMachinesPlay:
7799             if (cps->twoMachinesColor[0] == 'w')
7800               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7801             else
7802               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7803             break;
7804           default:
7805             /* can't happen */
7806             break;
7807         }
7808         return;
7809     } else if (strncmp(message, "opponent mates", 14) == 0) {
7810         switch (gameMode) {
7811           case MachinePlaysBlack:
7812           case IcsPlayingBlack:
7813             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7814             break;
7815           case MachinePlaysWhite:
7816           case IcsPlayingWhite:
7817             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7818             break;
7819           case TwoMachinesPlay:
7820             if (cps->twoMachinesColor[0] == 'w')
7821               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7822             else
7823               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7824             break;
7825           default:
7826             /* can't happen */
7827             break;
7828         }
7829         return;
7830     } else if (strncmp(message, "computer mates", 14) == 0) {
7831         switch (gameMode) {
7832           case MachinePlaysBlack:
7833           case IcsPlayingBlack:
7834             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7835             break;
7836           case MachinePlaysWhite:
7837           case IcsPlayingWhite:
7838             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7839             break;
7840           case TwoMachinesPlay:
7841             if (cps->twoMachinesColor[0] == 'w')
7842               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7843             else
7844               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7845             break;
7846           default:
7847             /* can't happen */
7848             break;
7849         }
7850         return;
7851     } else if (strncmp(message, "checkmate", 9) == 0) {
7852         if (WhiteOnMove(forwardMostMove)) {
7853             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7854         } else {
7855             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7856         }
7857         return;
7858     } else if (strstr(message, "Draw") != NULL ||
7859                strstr(message, "game is a draw") != NULL) {
7860         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7861         return;
7862     } else if (strstr(message, "offer") != NULL &&
7863                strstr(message, "draw") != NULL) {
7864 #if ZIPPY
7865         if (appData.zippyPlay && first.initDone) {
7866             /* Relay offer to ICS */
7867             SendToICS(ics_prefix);
7868             SendToICS("draw\n");
7869         }
7870 #endif
7871         cps->offeredDraw = 2; /* valid until this engine moves twice */
7872         if (gameMode == TwoMachinesPlay) {
7873             if (cps->other->offeredDraw) {
7874                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7875             /* [HGM] in two-machine mode we delay relaying draw offer      */
7876             /* until after we also have move, to see if it is really claim */
7877             }
7878         } else if (gameMode == MachinePlaysWhite ||
7879                    gameMode == MachinePlaysBlack) {
7880           if (userOfferedDraw) {
7881             DisplayInformation(_("Machine accepts your draw offer"));
7882             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7883           } else {
7884             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7885           }
7886         }
7887     }
7888
7889
7890     /*
7891      * Look for thinking output
7892      */
7893     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7894           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7895                                 ) {
7896         int plylev, mvleft, mvtot, curscore, time;
7897         char mvname[MOVE_LEN];
7898         u64 nodes; // [DM]
7899         char plyext;
7900         int ignore = FALSE;
7901         int prefixHint = FALSE;
7902         mvname[0] = NULLCHAR;
7903
7904         switch (gameMode) {
7905           case MachinePlaysBlack:
7906           case IcsPlayingBlack:
7907             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7908             break;
7909           case MachinePlaysWhite:
7910           case IcsPlayingWhite:
7911             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7912             break;
7913           case AnalyzeMode:
7914           case AnalyzeFile:
7915             break;
7916           case IcsObserving: /* [DM] icsEngineAnalyze */
7917             if (!appData.icsEngineAnalyze) ignore = TRUE;
7918             break;
7919           case TwoMachinesPlay:
7920             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7921                 ignore = TRUE;
7922             }
7923             break;
7924           default:
7925             ignore = TRUE;
7926             break;
7927         }
7928
7929         if (!ignore) {
7930             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7931             buf1[0] = NULLCHAR;
7932             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7933                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7934
7935                 if (plyext != ' ' && plyext != '\t') {
7936                     time *= 100;
7937                 }
7938
7939                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7940                 if( cps->scoreIsAbsolute &&
7941                     ( gameMode == MachinePlaysBlack ||
7942                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7943                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7944                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7945                      !WhiteOnMove(currentMove)
7946                     ) )
7947                 {
7948                     curscore = -curscore;
7949                 }
7950
7951
7952                 tempStats.depth = plylev;
7953                 tempStats.nodes = nodes;
7954                 tempStats.time = time;
7955                 tempStats.score = curscore;
7956                 tempStats.got_only_move = 0;
7957
7958                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7959                         int ticklen;
7960
7961                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7962                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7963                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7964                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7965                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7966                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7967                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7968                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7969                 }
7970
7971                 /* Buffer overflow protection */
7972                 if (buf1[0] != NULLCHAR) {
7973                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7974                         && appData.debugMode) {
7975                         fprintf(debugFP,
7976                                 "PV is too long; using the first %u bytes.\n",
7977                                 (unsigned) sizeof(tempStats.movelist) - 1);
7978                     }
7979
7980                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
7981                 } else {
7982                     sprintf(tempStats.movelist, " no PV\n");
7983                 }
7984
7985                 if (tempStats.seen_stat) {
7986                     tempStats.ok_to_send = 1;
7987                 }
7988
7989                 if (strchr(tempStats.movelist, '(') != NULL) {
7990                     tempStats.line_is_book = 1;
7991                     tempStats.nr_moves = 0;
7992                     tempStats.moves_left = 0;
7993                 } else {
7994                     tempStats.line_is_book = 0;
7995                 }
7996
7997                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7998                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7999
8000                 SendProgramStatsToFrontend( cps, &tempStats );
8001
8002                 /*
8003                     [AS] Protect the thinkOutput buffer from overflow... this
8004                     is only useful if buf1 hasn't overflowed first!
8005                 */
8006                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8007                          plylev,
8008                          (gameMode == TwoMachinesPlay ?
8009                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8010                          ((double) curscore) / 100.0,
8011                          prefixHint ? lastHint : "",
8012                          prefixHint ? " " : "" );
8013
8014                 if( buf1[0] != NULLCHAR ) {
8015                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8016
8017                     if( strlen(buf1) > max_len ) {
8018                         if( appData.debugMode) {
8019                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8020                         }
8021                         buf1[max_len+1] = '\0';
8022                     }
8023
8024                     strcat( thinkOutput, buf1 );
8025                 }
8026
8027                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8028                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8029                     DisplayMove(currentMove - 1);
8030                 }
8031                 return;
8032
8033             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8034                 /* crafty (9.25+) says "(only move) <move>"
8035                  * if there is only 1 legal move
8036                  */
8037                 sscanf(p, "(only move) %s", buf1);
8038                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8039                 sprintf(programStats.movelist, "%s (only move)", buf1);
8040                 programStats.depth = 1;
8041                 programStats.nr_moves = 1;
8042                 programStats.moves_left = 1;
8043                 programStats.nodes = 1;
8044                 programStats.time = 1;
8045                 programStats.got_only_move = 1;
8046
8047                 /* Not really, but we also use this member to
8048                    mean "line isn't going to change" (Crafty
8049                    isn't searching, so stats won't change) */
8050                 programStats.line_is_book = 1;
8051
8052                 SendProgramStatsToFrontend( cps, &programStats );
8053
8054                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8055                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8056                     DisplayMove(currentMove - 1);
8057                 }
8058                 return;
8059             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8060                               &time, &nodes, &plylev, &mvleft,
8061                               &mvtot, mvname) >= 5) {
8062                 /* The stat01: line is from Crafty (9.29+) in response
8063                    to the "." command */
8064                 programStats.seen_stat = 1;
8065                 cps->maybeThinking = TRUE;
8066
8067                 if (programStats.got_only_move || !appData.periodicUpdates)
8068                   return;
8069
8070                 programStats.depth = plylev;
8071                 programStats.time = time;
8072                 programStats.nodes = nodes;
8073                 programStats.moves_left = mvleft;
8074                 programStats.nr_moves = mvtot;
8075                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8076                 programStats.ok_to_send = 1;
8077                 programStats.movelist[0] = '\0';
8078
8079                 SendProgramStatsToFrontend( cps, &programStats );
8080
8081                 return;
8082
8083             } else if (strncmp(message,"++",2) == 0) {
8084                 /* Crafty 9.29+ outputs this */
8085                 programStats.got_fail = 2;
8086                 return;
8087
8088             } else if (strncmp(message,"--",2) == 0) {
8089                 /* Crafty 9.29+ outputs this */
8090                 programStats.got_fail = 1;
8091                 return;
8092
8093             } else if (thinkOutput[0] != NULLCHAR &&
8094                        strncmp(message, "    ", 4) == 0) {
8095                 unsigned message_len;
8096
8097                 p = message;
8098                 while (*p && *p == ' ') p++;
8099
8100                 message_len = strlen( p );
8101
8102                 /* [AS] Avoid buffer overflow */
8103                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8104                     strcat(thinkOutput, " ");
8105                     strcat(thinkOutput, p);
8106                 }
8107
8108                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8109                     strcat(programStats.movelist, " ");
8110                     strcat(programStats.movelist, p);
8111                 }
8112
8113                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8114                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8115                     DisplayMove(currentMove - 1);
8116                 }
8117                 return;
8118             }
8119         }
8120         else {
8121             buf1[0] = NULLCHAR;
8122
8123             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8124                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8125             {
8126                 ChessProgramStats cpstats;
8127
8128                 if (plyext != ' ' && plyext != '\t') {
8129                     time *= 100;
8130                 }
8131
8132                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8133                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8134                     curscore = -curscore;
8135                 }
8136
8137                 cpstats.depth = plylev;
8138                 cpstats.nodes = nodes;
8139                 cpstats.time = time;
8140                 cpstats.score = curscore;
8141                 cpstats.got_only_move = 0;
8142                 cpstats.movelist[0] = '\0';
8143
8144                 if (buf1[0] != NULLCHAR) {
8145                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8146                 }
8147
8148                 cpstats.ok_to_send = 0;
8149                 cpstats.line_is_book = 0;
8150                 cpstats.nr_moves = 0;
8151                 cpstats.moves_left = 0;
8152
8153                 SendProgramStatsToFrontend( cps, &cpstats );
8154             }
8155         }
8156     }
8157 }
8158
8159
8160 /* Parse a game score from the character string "game", and
8161    record it as the history of the current game.  The game
8162    score is NOT assumed to start from the standard position.
8163    The display is not updated in any way.
8164    */
8165 void
8166 ParseGameHistory(game)
8167      char *game;
8168 {
8169     ChessMove moveType;
8170     int fromX, fromY, toX, toY, boardIndex;
8171     char promoChar;
8172     char *p, *q;
8173     char buf[MSG_SIZ];
8174
8175     if (appData.debugMode)
8176       fprintf(debugFP, "Parsing game history: %s\n", game);
8177
8178     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8179     gameInfo.site = StrSave(appData.icsHost);
8180     gameInfo.date = PGNDate();
8181     gameInfo.round = StrSave("-");
8182
8183     /* Parse out names of players */
8184     while (*game == ' ') game++;
8185     p = buf;
8186     while (*game != ' ') *p++ = *game++;
8187     *p = NULLCHAR;
8188     gameInfo.white = StrSave(buf);
8189     while (*game == ' ') game++;
8190     p = buf;
8191     while (*game != ' ' && *game != '\n') *p++ = *game++;
8192     *p = NULLCHAR;
8193     gameInfo.black = StrSave(buf);
8194
8195     /* Parse moves */
8196     boardIndex = blackPlaysFirst ? 1 : 0;
8197     yynewstr(game);
8198     for (;;) {
8199         yyboardindex = boardIndex;
8200         moveType = (ChessMove) yylex();
8201         switch (moveType) {
8202           case IllegalMove:             /* maybe suicide chess, etc. */
8203   if (appData.debugMode) {
8204     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8205     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8206     setbuf(debugFP, NULL);
8207   }
8208           case WhitePromotion:
8209           case BlackPromotion:
8210           case WhiteNonPromotion:
8211           case BlackNonPromotion:
8212           case NormalMove:
8213           case WhiteCapturesEnPassant:
8214           case BlackCapturesEnPassant:
8215           case WhiteKingSideCastle:
8216           case WhiteQueenSideCastle:
8217           case BlackKingSideCastle:
8218           case BlackQueenSideCastle:
8219           case WhiteKingSideCastleWild:
8220           case WhiteQueenSideCastleWild:
8221           case BlackKingSideCastleWild:
8222           case BlackQueenSideCastleWild:
8223           /* PUSH Fabien */
8224           case WhiteHSideCastleFR:
8225           case WhiteASideCastleFR:
8226           case BlackHSideCastleFR:
8227           case BlackASideCastleFR:
8228           /* POP Fabien */
8229             fromX = currentMoveString[0] - AAA;
8230             fromY = currentMoveString[1] - ONE;
8231             toX = currentMoveString[2] - AAA;
8232             toY = currentMoveString[3] - ONE;
8233             promoChar = currentMoveString[4];
8234             break;
8235           case WhiteDrop:
8236           case BlackDrop:
8237             fromX = moveType == WhiteDrop ?
8238               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8239             (int) CharToPiece(ToLower(currentMoveString[0]));
8240             fromY = DROP_RANK;
8241             toX = currentMoveString[2] - AAA;
8242             toY = currentMoveString[3] - ONE;
8243             promoChar = NULLCHAR;
8244             break;
8245           case AmbiguousMove:
8246             /* bug? */
8247             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8248   if (appData.debugMode) {
8249     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8250     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8251     setbuf(debugFP, NULL);
8252   }
8253             DisplayError(buf, 0);
8254             return;
8255           case ImpossibleMove:
8256             /* bug? */
8257             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8258   if (appData.debugMode) {
8259     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8260     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8261     setbuf(debugFP, NULL);
8262   }
8263             DisplayError(buf, 0);
8264             return;
8265           case EndOfFile:
8266             if (boardIndex < backwardMostMove) {
8267                 /* Oops, gap.  How did that happen? */
8268                 DisplayError(_("Gap in move list"), 0);
8269                 return;
8270             }
8271             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8272             if (boardIndex > forwardMostMove) {
8273                 forwardMostMove = boardIndex;
8274             }
8275             return;
8276           case ElapsedTime:
8277             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8278                 strcat(parseList[boardIndex-1], " ");
8279                 strcat(parseList[boardIndex-1], yy_text);
8280             }
8281             continue;
8282           case Comment:
8283           case PGNTag:
8284           case NAG:
8285           default:
8286             /* ignore */
8287             continue;
8288           case WhiteWins:
8289           case BlackWins:
8290           case GameIsDrawn:
8291           case GameUnfinished:
8292             if (gameMode == IcsExamining) {
8293                 if (boardIndex < backwardMostMove) {
8294                     /* Oops, gap.  How did that happen? */
8295                     return;
8296                 }
8297                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8298                 return;
8299             }
8300             gameInfo.result = moveType;
8301             p = strchr(yy_text, '{');
8302             if (p == NULL) p = strchr(yy_text, '(');
8303             if (p == NULL) {
8304                 p = yy_text;
8305                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8306             } else {
8307                 q = strchr(p, *p == '{' ? '}' : ')');
8308                 if (q != NULL) *q = NULLCHAR;
8309                 p++;
8310             }
8311             gameInfo.resultDetails = StrSave(p);
8312             continue;
8313         }
8314         if (boardIndex >= forwardMostMove &&
8315             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8316             backwardMostMove = blackPlaysFirst ? 1 : 0;
8317             return;
8318         }
8319         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8320                                  fromY, fromX, toY, toX, promoChar,
8321                                  parseList[boardIndex]);
8322         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8323         /* currentMoveString is set as a side-effect of yylex */
8324         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8325         strcat(moveList[boardIndex], "\n");
8326         boardIndex++;
8327         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8328         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8329           case MT_NONE:
8330           case MT_STALEMATE:
8331           default:
8332             break;
8333           case MT_CHECK:
8334             if(gameInfo.variant != VariantShogi)
8335                 strcat(parseList[boardIndex - 1], "+");
8336             break;
8337           case MT_CHECKMATE:
8338           case MT_STAINMATE:
8339             strcat(parseList[boardIndex - 1], "#");
8340             break;
8341         }
8342     }
8343 }
8344
8345
8346 /* Apply a move to the given board  */
8347 void
8348 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8349      int fromX, fromY, toX, toY;
8350      int promoChar;
8351      Board board;
8352 {
8353   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8354   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8355
8356     /* [HGM] compute & store e.p. status and castling rights for new position */
8357     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8358
8359       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8360       oldEP = (signed char)board[EP_STATUS];
8361       board[EP_STATUS] = EP_NONE;
8362
8363       if( board[toY][toX] != EmptySquare )
8364            board[EP_STATUS] = EP_CAPTURE;
8365
8366   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8367   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8368        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8369
8370   if (fromY == DROP_RANK) {
8371         /* must be first */
8372         piece = board[toY][toX] = (ChessSquare) fromX;
8373   } else {
8374       int i;
8375
8376       if( board[fromY][fromX] == WhitePawn ) {
8377            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8378                board[EP_STATUS] = EP_PAWN_MOVE;
8379            if( toY-fromY==2) {
8380                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8381                         gameInfo.variant != VariantBerolina || toX < fromX)
8382                       board[EP_STATUS] = toX | berolina;
8383                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8384                         gameInfo.variant != VariantBerolina || toX > fromX)
8385                       board[EP_STATUS] = toX;
8386            }
8387       } else
8388       if( board[fromY][fromX] == BlackPawn ) {
8389            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8390                board[EP_STATUS] = EP_PAWN_MOVE;
8391            if( toY-fromY== -2) {
8392                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8393                         gameInfo.variant != VariantBerolina || toX < fromX)
8394                       board[EP_STATUS] = toX | berolina;
8395                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8396                         gameInfo.variant != VariantBerolina || toX > fromX)
8397                       board[EP_STATUS] = toX;
8398            }
8399        }
8400
8401        for(i=0; i<nrCastlingRights; i++) {
8402            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8403               board[CASTLING][i] == toX   && castlingRank[i] == toY
8404              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8405        }
8406
8407      if (fromX == toX && fromY == toY) return;
8408
8409      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8410      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8411      if(gameInfo.variant == VariantKnightmate)
8412          king += (int) WhiteUnicorn - (int) WhiteKing;
8413
8414     /* Code added by Tord: */
8415     /* FRC castling assumed when king captures friendly rook. */
8416     if (board[fromY][fromX] == WhiteKing &&
8417              board[toY][toX] == WhiteRook) {
8418       board[fromY][fromX] = EmptySquare;
8419       board[toY][toX] = EmptySquare;
8420       if(toX > fromX) {
8421         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8422       } else {
8423         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8424       }
8425     } else if (board[fromY][fromX] == BlackKing &&
8426                board[toY][toX] == BlackRook) {
8427       board[fromY][fromX] = EmptySquare;
8428       board[toY][toX] = EmptySquare;
8429       if(toX > fromX) {
8430         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8431       } else {
8432         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8433       }
8434     /* End of code added by Tord */
8435
8436     } else if (board[fromY][fromX] == king
8437         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8438         && toY == fromY && toX > fromX+1) {
8439         board[fromY][fromX] = EmptySquare;
8440         board[toY][toX] = king;
8441         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8442         board[fromY][BOARD_RGHT-1] = EmptySquare;
8443     } else if (board[fromY][fromX] == king
8444         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8445                && toY == fromY && toX < fromX-1) {
8446         board[fromY][fromX] = EmptySquare;
8447         board[toY][toX] = king;
8448         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8449         board[fromY][BOARD_LEFT] = EmptySquare;
8450     } else if (board[fromY][fromX] == WhitePawn
8451                && toY >= BOARD_HEIGHT-promoRank
8452                && gameInfo.variant != VariantXiangqi
8453                ) {
8454         /* white pawn promotion */
8455         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8456         if (board[toY][toX] == EmptySquare) {
8457             board[toY][toX] = WhiteQueen;
8458         }
8459         if(gameInfo.variant==VariantBughouse ||
8460            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8461             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8462         board[fromY][fromX] = EmptySquare;
8463     } else if ((fromY == BOARD_HEIGHT-4)
8464                && (toX != fromX)
8465                && gameInfo.variant != VariantXiangqi
8466                && gameInfo.variant != VariantBerolina
8467                && (board[fromY][fromX] == WhitePawn)
8468                && (board[toY][toX] == EmptySquare)) {
8469         board[fromY][fromX] = EmptySquare;
8470         board[toY][toX] = WhitePawn;
8471         captured = board[toY - 1][toX];
8472         board[toY - 1][toX] = EmptySquare;
8473     } else if ((fromY == BOARD_HEIGHT-4)
8474                && (toX == fromX)
8475                && gameInfo.variant == VariantBerolina
8476                && (board[fromY][fromX] == WhitePawn)
8477                && (board[toY][toX] == EmptySquare)) {
8478         board[fromY][fromX] = EmptySquare;
8479         board[toY][toX] = WhitePawn;
8480         if(oldEP & EP_BEROLIN_A) {
8481                 captured = board[fromY][fromX-1];
8482                 board[fromY][fromX-1] = EmptySquare;
8483         }else{  captured = board[fromY][fromX+1];
8484                 board[fromY][fromX+1] = EmptySquare;
8485         }
8486     } else if (board[fromY][fromX] == king
8487         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8488                && toY == fromY && toX > fromX+1) {
8489         board[fromY][fromX] = EmptySquare;
8490         board[toY][toX] = king;
8491         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8492         board[fromY][BOARD_RGHT-1] = EmptySquare;
8493     } else if (board[fromY][fromX] == king
8494         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8495                && toY == fromY && toX < fromX-1) {
8496         board[fromY][fromX] = EmptySquare;
8497         board[toY][toX] = king;
8498         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8499         board[fromY][BOARD_LEFT] = EmptySquare;
8500     } else if (fromY == 7 && fromX == 3
8501                && board[fromY][fromX] == BlackKing
8502                && toY == 7 && toX == 5) {
8503         board[fromY][fromX] = EmptySquare;
8504         board[toY][toX] = BlackKing;
8505         board[fromY][7] = EmptySquare;
8506         board[toY][4] = BlackRook;
8507     } else if (fromY == 7 && fromX == 3
8508                && board[fromY][fromX] == BlackKing
8509                && toY == 7 && toX == 1) {
8510         board[fromY][fromX] = EmptySquare;
8511         board[toY][toX] = BlackKing;
8512         board[fromY][0] = EmptySquare;
8513         board[toY][2] = BlackRook;
8514     } else if (board[fromY][fromX] == BlackPawn
8515                && toY < promoRank
8516                && gameInfo.variant != VariantXiangqi
8517                ) {
8518         /* black pawn promotion */
8519         board[toY][toX] = CharToPiece(ToLower(promoChar));
8520         if (board[toY][toX] == EmptySquare) {
8521             board[toY][toX] = BlackQueen;
8522         }
8523         if(gameInfo.variant==VariantBughouse ||
8524            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8525             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8526         board[fromY][fromX] = EmptySquare;
8527     } else if ((fromY == 3)
8528                && (toX != fromX)
8529                && gameInfo.variant != VariantXiangqi
8530                && gameInfo.variant != VariantBerolina
8531                && (board[fromY][fromX] == BlackPawn)
8532                && (board[toY][toX] == EmptySquare)) {
8533         board[fromY][fromX] = EmptySquare;
8534         board[toY][toX] = BlackPawn;
8535         captured = board[toY + 1][toX];
8536         board[toY + 1][toX] = EmptySquare;
8537     } else if ((fromY == 3)
8538                && (toX == fromX)
8539                && gameInfo.variant == VariantBerolina
8540                && (board[fromY][fromX] == BlackPawn)
8541                && (board[toY][toX] == EmptySquare)) {
8542         board[fromY][fromX] = EmptySquare;
8543         board[toY][toX] = BlackPawn;
8544         if(oldEP & EP_BEROLIN_A) {
8545                 captured = board[fromY][fromX-1];
8546                 board[fromY][fromX-1] = EmptySquare;
8547         }else{  captured = board[fromY][fromX+1];
8548                 board[fromY][fromX+1] = EmptySquare;
8549         }
8550     } else {
8551         board[toY][toX] = board[fromY][fromX];
8552         board[fromY][fromX] = EmptySquare;
8553     }
8554   }
8555
8556     if (gameInfo.holdingsWidth != 0) {
8557
8558       /* !!A lot more code needs to be written to support holdings  */
8559       /* [HGM] OK, so I have written it. Holdings are stored in the */
8560       /* penultimate board files, so they are automaticlly stored   */
8561       /* in the game history.                                       */
8562       if (fromY == DROP_RANK) {
8563         /* Delete from holdings, by decreasing count */
8564         /* and erasing image if necessary            */
8565         p = (int) fromX;
8566         if(p < (int) BlackPawn) { /* white drop */
8567              p -= (int)WhitePawn;
8568                  p = PieceToNumber((ChessSquare)p);
8569              if(p >= gameInfo.holdingsSize) p = 0;
8570              if(--board[p][BOARD_WIDTH-2] <= 0)
8571                   board[p][BOARD_WIDTH-1] = EmptySquare;
8572              if((int)board[p][BOARD_WIDTH-2] < 0)
8573                         board[p][BOARD_WIDTH-2] = 0;
8574         } else {                  /* black drop */
8575              p -= (int)BlackPawn;
8576                  p = PieceToNumber((ChessSquare)p);
8577              if(p >= gameInfo.holdingsSize) p = 0;
8578              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8579                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8580              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8581                         board[BOARD_HEIGHT-1-p][1] = 0;
8582         }
8583       }
8584       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8585           && gameInfo.variant != VariantBughouse        ) {
8586         /* [HGM] holdings: Add to holdings, if holdings exist */
8587         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8588                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8589                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8590         }
8591         p = (int) captured;
8592         if (p >= (int) BlackPawn) {
8593           p -= (int)BlackPawn;
8594           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8595                   /* in Shogi restore piece to its original  first */
8596                   captured = (ChessSquare) (DEMOTED captured);
8597                   p = DEMOTED p;
8598           }
8599           p = PieceToNumber((ChessSquare)p);
8600           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8601           board[p][BOARD_WIDTH-2]++;
8602           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8603         } else {
8604           p -= (int)WhitePawn;
8605           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8606                   captured = (ChessSquare) (DEMOTED captured);
8607                   p = DEMOTED p;
8608           }
8609           p = PieceToNumber((ChessSquare)p);
8610           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8611           board[BOARD_HEIGHT-1-p][1]++;
8612           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8613         }
8614       }
8615     } else if (gameInfo.variant == VariantAtomic) {
8616       if (captured != EmptySquare) {
8617         int y, x;
8618         for (y = toY-1; y <= toY+1; y++) {
8619           for (x = toX-1; x <= toX+1; x++) {
8620             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8621                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8622               board[y][x] = EmptySquare;
8623             }
8624           }
8625         }
8626         board[toY][toX] = EmptySquare;
8627       }
8628     }
8629     if(promoChar == '+') {
8630         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8631         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8632     } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar
8633         board[toY][toX] = CharToPiece(promoChar);
8634     }
8635     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8636                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8637         // [HGM] superchess: take promotion piece out of holdings
8638         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8639         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8640             if(!--board[k][BOARD_WIDTH-2])
8641                 board[k][BOARD_WIDTH-1] = EmptySquare;
8642         } else {
8643             if(!--board[BOARD_HEIGHT-1-k][1])
8644                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8645         }
8646     }
8647
8648 }
8649
8650 /* Updates forwardMostMove */
8651 void
8652 MakeMove(fromX, fromY, toX, toY, promoChar)
8653      int fromX, fromY, toX, toY;
8654      int promoChar;
8655 {
8656 //    forwardMostMove++; // [HGM] bare: moved downstream
8657
8658     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8659         int timeLeft; static int lastLoadFlag=0; int king, piece;
8660         piece = boards[forwardMostMove][fromY][fromX];
8661         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8662         if(gameInfo.variant == VariantKnightmate)
8663             king += (int) WhiteUnicorn - (int) WhiteKing;
8664         if(forwardMostMove == 0) {
8665             if(blackPlaysFirst)
8666                 fprintf(serverMoves, "%s;", second.tidy);
8667             fprintf(serverMoves, "%s;", first.tidy);
8668             if(!blackPlaysFirst)
8669                 fprintf(serverMoves, "%s;", second.tidy);
8670         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8671         lastLoadFlag = loadFlag;
8672         // print base move
8673         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8674         // print castling suffix
8675         if( toY == fromY && piece == king ) {
8676             if(toX-fromX > 1)
8677                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8678             if(fromX-toX >1)
8679                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8680         }
8681         // e.p. suffix
8682         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8683              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8684              boards[forwardMostMove][toY][toX] == EmptySquare
8685              && fromX != toX && fromY != toY)
8686                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8687         // promotion suffix
8688         if(promoChar != NULLCHAR)
8689                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8690         if(!loadFlag) {
8691             fprintf(serverMoves, "/%d/%d",
8692                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8693             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8694             else                      timeLeft = blackTimeRemaining/1000;
8695             fprintf(serverMoves, "/%d", timeLeft);
8696         }
8697         fflush(serverMoves);
8698     }
8699
8700     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8701       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8702                         0, 1);
8703       return;
8704     }
8705     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8706     if (commentList[forwardMostMove+1] != NULL) {
8707         free(commentList[forwardMostMove+1]);
8708         commentList[forwardMostMove+1] = NULL;
8709     }
8710     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8711     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8712     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8713     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8714     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8715     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8716     gameInfo.result = GameUnfinished;
8717     if (gameInfo.resultDetails != NULL) {
8718         free(gameInfo.resultDetails);
8719         gameInfo.resultDetails = NULL;
8720     }
8721     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8722                               moveList[forwardMostMove - 1]);
8723     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8724                              PosFlags(forwardMostMove - 1),
8725                              fromY, fromX, toY, toX, promoChar,
8726                              parseList[forwardMostMove - 1]);
8727     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8728       case MT_NONE:
8729       case MT_STALEMATE:
8730       default:
8731         break;
8732       case MT_CHECK:
8733         if(gameInfo.variant != VariantShogi)
8734             strcat(parseList[forwardMostMove - 1], "+");
8735         break;
8736       case MT_CHECKMATE:
8737       case MT_STAINMATE:
8738         strcat(parseList[forwardMostMove - 1], "#");
8739         break;
8740     }
8741     if (appData.debugMode) {
8742         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8743     }
8744
8745 }
8746
8747 /* Updates currentMove if not pausing */
8748 void
8749 ShowMove(fromX, fromY, toX, toY)
8750 {
8751     int instant = (gameMode == PlayFromGameFile) ?
8752         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8753     if(appData.noGUI) return;
8754     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8755         if (!instant) {
8756             if (forwardMostMove == currentMove + 1) {
8757                 AnimateMove(boards[forwardMostMove - 1],
8758                             fromX, fromY, toX, toY);
8759             }
8760             if (appData.highlightLastMove) {
8761                 SetHighlights(fromX, fromY, toX, toY);
8762             }
8763         }
8764         currentMove = forwardMostMove;
8765     }
8766
8767     if (instant) return;
8768
8769     DisplayMove(currentMove - 1);
8770     DrawPosition(FALSE, boards[currentMove]);
8771     DisplayBothClocks();
8772     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8773 }
8774
8775 void SendEgtPath(ChessProgramState *cps)
8776 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8777         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8778
8779         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8780
8781         while(*p) {
8782             char c, *q = name+1, *r, *s;
8783
8784             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8785             while(*p && *p != ',') *q++ = *p++;
8786             *q++ = ':'; *q = 0;
8787             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8788                 strcmp(name, ",nalimov:") == 0 ) {
8789                 // take nalimov path from the menu-changeable option first, if it is defined
8790               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8791                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8792             } else
8793             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8794                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8795                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8796                 s = r = StrStr(s, ":") + 1; // beginning of path info
8797                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8798                 c = *r; *r = 0;             // temporarily null-terminate path info
8799                     *--q = 0;               // strip of trailig ':' from name
8800                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8801                 *r = c;
8802                 SendToProgram(buf,cps);     // send egtbpath command for this format
8803             }
8804             if(*p == ',') p++; // read away comma to position for next format name
8805         }
8806 }
8807
8808 void
8809 InitChessProgram(cps, setup)
8810      ChessProgramState *cps;
8811      int setup; /* [HGM] needed to setup FRC opening position */
8812 {
8813     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8814     if (appData.noChessProgram) return;
8815     hintRequested = FALSE;
8816     bookRequested = FALSE;
8817
8818     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8819     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8820     if(cps->memSize) { /* [HGM] memory */
8821       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8822         SendToProgram(buf, cps);
8823     }
8824     SendEgtPath(cps); /* [HGM] EGT */
8825     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8826       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8827         SendToProgram(buf, cps);
8828     }
8829
8830     SendToProgram(cps->initString, cps);
8831     if (gameInfo.variant != VariantNormal &&
8832         gameInfo.variant != VariantLoadable
8833         /* [HGM] also send variant if board size non-standard */
8834         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8835                                             ) {
8836       char *v = VariantName(gameInfo.variant);
8837       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8838         /* [HGM] in protocol 1 we have to assume all variants valid */
8839         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8840         DisplayFatalError(buf, 0, 1);
8841         return;
8842       }
8843
8844       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8845       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8846       if( gameInfo.variant == VariantXiangqi )
8847            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8848       if( gameInfo.variant == VariantShogi )
8849            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8850       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8851            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8852       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8853                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8854            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8855       if( gameInfo.variant == VariantCourier )
8856            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8857       if( gameInfo.variant == VariantSuper )
8858            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8859       if( gameInfo.variant == VariantGreat )
8860            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8861
8862       if(overruled) {
8863         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8864                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8865            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8866            if(StrStr(cps->variants, b) == NULL) {
8867                // specific sized variant not known, check if general sizing allowed
8868                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8869                    if(StrStr(cps->variants, "boardsize") == NULL) {
8870                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8871                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8872                        DisplayFatalError(buf, 0, 1);
8873                        return;
8874                    }
8875                    /* [HGM] here we really should compare with the maximum supported board size */
8876                }
8877            }
8878       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8879       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8880       SendToProgram(buf, cps);
8881     }
8882     currentlyInitializedVariant = gameInfo.variant;
8883
8884     /* [HGM] send opening position in FRC to first engine */
8885     if(setup) {
8886           SendToProgram("force\n", cps);
8887           SendBoard(cps, 0);
8888           /* engine is now in force mode! Set flag to wake it up after first move. */
8889           setboardSpoiledMachineBlack = 1;
8890     }
8891
8892     if (cps->sendICS) {
8893       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8894       SendToProgram(buf, cps);
8895     }
8896     cps->maybeThinking = FALSE;
8897     cps->offeredDraw = 0;
8898     if (!appData.icsActive) {
8899         SendTimeControl(cps, movesPerSession, timeControl,
8900                         timeIncrement, appData.searchDepth,
8901                         searchTime);
8902     }
8903     if (appData.showThinking
8904         // [HGM] thinking: four options require thinking output to be sent
8905         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8906                                 ) {
8907         SendToProgram("post\n", cps);
8908     }
8909     SendToProgram("hard\n", cps);
8910     if (!appData.ponderNextMove) {
8911         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8912            it without being sure what state we are in first.  "hard"
8913            is not a toggle, so that one is OK.
8914          */
8915         SendToProgram("easy\n", cps);
8916     }
8917     if (cps->usePing) {
8918       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8919       SendToProgram(buf, cps);
8920     }
8921     cps->initDone = TRUE;
8922 }
8923
8924
8925 void
8926 StartChessProgram(cps)
8927      ChessProgramState *cps;
8928 {
8929     char buf[MSG_SIZ];
8930     int err;
8931
8932     if (appData.noChessProgram) return;
8933     cps->initDone = FALSE;
8934
8935     if (strcmp(cps->host, "localhost") == 0) {
8936         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8937     } else if (*appData.remoteShell == NULLCHAR) {
8938         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8939     } else {
8940         if (*appData.remoteUser == NULLCHAR) {
8941           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8942                     cps->program);
8943         } else {
8944           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8945                     cps->host, appData.remoteUser, cps->program);
8946         }
8947         err = StartChildProcess(buf, "", &cps->pr);
8948     }
8949
8950     if (err != 0) {
8951       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8952         DisplayFatalError(buf, err, 1);
8953         cps->pr = NoProc;
8954         cps->isr = NULL;
8955         return;
8956     }
8957
8958     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8959     if (cps->protocolVersion > 1) {
8960       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
8961       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8962       cps->comboCnt = 0;  //                and values of combo boxes
8963       SendToProgram(buf, cps);
8964     } else {
8965       SendToProgram("xboard\n", cps);
8966     }
8967 }
8968
8969
8970 void
8971 TwoMachinesEventIfReady P((void))
8972 {
8973   if (first.lastPing != first.lastPong) {
8974     DisplayMessage("", _("Waiting for first chess program"));
8975     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8976     return;
8977   }
8978   if (second.lastPing != second.lastPong) {
8979     DisplayMessage("", _("Waiting for second chess program"));
8980     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8981     return;
8982   }
8983   ThawUI();
8984   TwoMachinesEvent();
8985 }
8986
8987 void
8988 NextMatchGame P((void))
8989 {
8990     int index; /* [HGM] autoinc: step load index during match */
8991     Reset(FALSE, TRUE);
8992     if (*appData.loadGameFile != NULLCHAR) {
8993         index = appData.loadGameIndex;
8994         if(index < 0) { // [HGM] autoinc
8995             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8996             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8997         }
8998         LoadGameFromFile(appData.loadGameFile,
8999                          index,
9000                          appData.loadGameFile, FALSE);
9001     } else if (*appData.loadPositionFile != NULLCHAR) {
9002         index = appData.loadPositionIndex;
9003         if(index < 0) { // [HGM] autoinc
9004             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9005             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9006         }
9007         LoadPositionFromFile(appData.loadPositionFile,
9008                              index,
9009                              appData.loadPositionFile);
9010     }
9011     TwoMachinesEventIfReady();
9012 }
9013
9014 void UserAdjudicationEvent( int result )
9015 {
9016     ChessMove gameResult = GameIsDrawn;
9017
9018     if( result > 0 ) {
9019         gameResult = WhiteWins;
9020     }
9021     else if( result < 0 ) {
9022         gameResult = BlackWins;
9023     }
9024
9025     if( gameMode == TwoMachinesPlay ) {
9026         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9027     }
9028 }
9029
9030
9031 // [HGM] save: calculate checksum of game to make games easily identifiable
9032 int StringCheckSum(char *s)
9033 {
9034         int i = 0;
9035         if(s==NULL) return 0;
9036         while(*s) i = i*259 + *s++;
9037         return i;
9038 }
9039
9040 int GameCheckSum()
9041 {
9042         int i, sum=0;
9043         for(i=backwardMostMove; i<forwardMostMove; i++) {
9044                 sum += pvInfoList[i].depth;
9045                 sum += StringCheckSum(parseList[i]);
9046                 sum += StringCheckSum(commentList[i]);
9047                 sum *= 261;
9048         }
9049         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9050         return sum + StringCheckSum(commentList[i]);
9051 } // end of save patch
9052
9053 void
9054 GameEnds(result, resultDetails, whosays)
9055      ChessMove result;
9056      char *resultDetails;
9057      int whosays;
9058 {
9059     GameMode nextGameMode;
9060     int isIcsGame;
9061     char buf[MSG_SIZ], popupRequested = 0;
9062
9063     if(endingGame) return; /* [HGM] crash: forbid recursion */
9064     endingGame = 1;
9065     if(twoBoards) { // [HGM] dual: switch back to one board
9066         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9067         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9068     }
9069     if (appData.debugMode) {
9070       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9071               result, resultDetails ? resultDetails : "(null)", whosays);
9072     }
9073
9074     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9075
9076     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9077         /* If we are playing on ICS, the server decides when the
9078            game is over, but the engine can offer to draw, claim
9079            a draw, or resign.
9080          */
9081 #if ZIPPY
9082         if (appData.zippyPlay && first.initDone) {
9083             if (result == GameIsDrawn) {
9084                 /* In case draw still needs to be claimed */
9085                 SendToICS(ics_prefix);
9086                 SendToICS("draw\n");
9087             } else if (StrCaseStr(resultDetails, "resign")) {
9088                 SendToICS(ics_prefix);
9089                 SendToICS("resign\n");
9090             }
9091         }
9092 #endif
9093         endingGame = 0; /* [HGM] crash */
9094         return;
9095     }
9096
9097     /* If we're loading the game from a file, stop */
9098     if (whosays == GE_FILE) {
9099       (void) StopLoadGameTimer();
9100       gameFileFP = NULL;
9101     }
9102
9103     /* Cancel draw offers */
9104     first.offeredDraw = second.offeredDraw = 0;
9105
9106     /* If this is an ICS game, only ICS can really say it's done;
9107        if not, anyone can. */
9108     isIcsGame = (gameMode == IcsPlayingWhite ||
9109                  gameMode == IcsPlayingBlack ||
9110                  gameMode == IcsObserving    ||
9111                  gameMode == IcsExamining);
9112
9113     if (!isIcsGame || whosays == GE_ICS) {
9114         /* OK -- not an ICS game, or ICS said it was done */
9115         StopClocks();
9116         if (!isIcsGame && !appData.noChessProgram)
9117           SetUserThinkingEnables();
9118
9119         /* [HGM] if a machine claims the game end we verify this claim */
9120         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9121             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9122                 char claimer;
9123                 ChessMove trueResult = (ChessMove) -1;
9124
9125                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9126                                             first.twoMachinesColor[0] :
9127                                             second.twoMachinesColor[0] ;
9128
9129                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9130                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9131                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9132                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9133                 } else
9134                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9135                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9136                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9137                 } else
9138                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9139                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9140                 }
9141
9142                 // now verify win claims, but not in drop games, as we don't understand those yet
9143                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9144                                                  || gameInfo.variant == VariantGreat) &&
9145                     (result == WhiteWins && claimer == 'w' ||
9146                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9147                       if (appData.debugMode) {
9148                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9149                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9150                       }
9151                       if(result != trueResult) {
9152                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9153                               result = claimer == 'w' ? BlackWins : WhiteWins;
9154                               resultDetails = buf;
9155                       }
9156                 } else
9157                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9158                     && (forwardMostMove <= backwardMostMove ||
9159                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9160                         (claimer=='b')==(forwardMostMove&1))
9161                                                                                   ) {
9162                       /* [HGM] verify: draws that were not flagged are false claims */
9163                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9164                       result = claimer == 'w' ? BlackWins : WhiteWins;
9165                       resultDetails = buf;
9166                 }
9167                 /* (Claiming a loss is accepted no questions asked!) */
9168             }
9169             /* [HGM] bare: don't allow bare King to win */
9170             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9171                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9172                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9173                && result != GameIsDrawn)
9174             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9175                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9176                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9177                         if(p >= 0 && p <= (int)WhiteKing) k++;
9178                 }
9179                 if (appData.debugMode) {
9180                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9181                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9182                 }
9183                 if(k <= 1) {
9184                         result = GameIsDrawn;
9185                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9186                         resultDetails = buf;
9187                 }
9188             }
9189         }
9190
9191
9192         if(serverMoves != NULL && !loadFlag) { char c = '=';
9193             if(result==WhiteWins) c = '+';
9194             if(result==BlackWins) c = '-';
9195             if(resultDetails != NULL)
9196                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9197         }
9198         if (resultDetails != NULL) {
9199             gameInfo.result = result;
9200             gameInfo.resultDetails = StrSave(resultDetails);
9201
9202             /* display last move only if game was not loaded from file */
9203             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9204                 DisplayMove(currentMove - 1);
9205
9206             if (forwardMostMove != 0) {
9207                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9208                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9209                                                                 ) {
9210                     if (*appData.saveGameFile != NULLCHAR) {
9211                         SaveGameToFile(appData.saveGameFile, TRUE);
9212                     } else if (appData.autoSaveGames) {
9213                         AutoSaveGame();
9214                     }
9215                     if (*appData.savePositionFile != NULLCHAR) {
9216                         SavePositionToFile(appData.savePositionFile);
9217                     }
9218                 }
9219             }
9220
9221             /* Tell program how game ended in case it is learning */
9222             /* [HGM] Moved this to after saving the PGN, just in case */
9223             /* engine died and we got here through time loss. In that */
9224             /* case we will get a fatal error writing the pipe, which */
9225             /* would otherwise lose us the PGN.                       */
9226             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9227             /* output during GameEnds should never be fatal anymore   */
9228             if (gameMode == MachinePlaysWhite ||
9229                 gameMode == MachinePlaysBlack ||
9230                 gameMode == TwoMachinesPlay ||
9231                 gameMode == IcsPlayingWhite ||
9232                 gameMode == IcsPlayingBlack ||
9233                 gameMode == BeginningOfGame) {
9234                 char buf[MSG_SIZ];
9235                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9236                         resultDetails);
9237                 if (first.pr != NoProc) {
9238                     SendToProgram(buf, &first);
9239                 }
9240                 if (second.pr != NoProc &&
9241                     gameMode == TwoMachinesPlay) {
9242                     SendToProgram(buf, &second);
9243                 }
9244             }
9245         }
9246
9247         if (appData.icsActive) {
9248             if (appData.quietPlay &&
9249                 (gameMode == IcsPlayingWhite ||
9250                  gameMode == IcsPlayingBlack)) {
9251                 SendToICS(ics_prefix);
9252                 SendToICS("set shout 1\n");
9253             }
9254             nextGameMode = IcsIdle;
9255             ics_user_moved = FALSE;
9256             /* clean up premove.  It's ugly when the game has ended and the
9257              * premove highlights are still on the board.
9258              */
9259             if (gotPremove) {
9260               gotPremove = FALSE;
9261               ClearPremoveHighlights();
9262               DrawPosition(FALSE, boards[currentMove]);
9263             }
9264             if (whosays == GE_ICS) {
9265                 switch (result) {
9266                 case WhiteWins:
9267                     if (gameMode == IcsPlayingWhite)
9268                         PlayIcsWinSound();
9269                     else if(gameMode == IcsPlayingBlack)
9270                         PlayIcsLossSound();
9271                     break;
9272                 case BlackWins:
9273                     if (gameMode == IcsPlayingBlack)
9274                         PlayIcsWinSound();
9275                     else if(gameMode == IcsPlayingWhite)
9276                         PlayIcsLossSound();
9277                     break;
9278                 case GameIsDrawn:
9279                     PlayIcsDrawSound();
9280                     break;
9281                 default:
9282                     PlayIcsUnfinishedSound();
9283                 }
9284             }
9285         } else if (gameMode == EditGame ||
9286                    gameMode == PlayFromGameFile ||
9287                    gameMode == AnalyzeMode ||
9288                    gameMode == AnalyzeFile) {
9289             nextGameMode = gameMode;
9290         } else {
9291             nextGameMode = EndOfGame;
9292         }
9293         pausing = FALSE;
9294         ModeHighlight();
9295     } else {
9296         nextGameMode = gameMode;
9297     }
9298
9299     if (appData.noChessProgram) {
9300         gameMode = nextGameMode;
9301         ModeHighlight();
9302         endingGame = 0; /* [HGM] crash */
9303         return;
9304     }
9305
9306     if (first.reuse) {
9307         /* Put first chess program into idle state */
9308         if (first.pr != NoProc &&
9309             (gameMode == MachinePlaysWhite ||
9310              gameMode == MachinePlaysBlack ||
9311              gameMode == TwoMachinesPlay ||
9312              gameMode == IcsPlayingWhite ||
9313              gameMode == IcsPlayingBlack ||
9314              gameMode == BeginningOfGame)) {
9315             SendToProgram("force\n", &first);
9316             if (first.usePing) {
9317               char buf[MSG_SIZ];
9318               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9319               SendToProgram(buf, &first);
9320             }
9321         }
9322     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9323         /* Kill off first chess program */
9324         if (first.isr != NULL)
9325           RemoveInputSource(first.isr);
9326         first.isr = NULL;
9327
9328         if (first.pr != NoProc) {
9329             ExitAnalyzeMode();
9330             DoSleep( appData.delayBeforeQuit );
9331             SendToProgram("quit\n", &first);
9332             DoSleep( appData.delayAfterQuit );
9333             DestroyChildProcess(first.pr, first.useSigterm);
9334         }
9335         first.pr = NoProc;
9336     }
9337     if (second.reuse) {
9338         /* Put second chess program into idle state */
9339         if (second.pr != NoProc &&
9340             gameMode == TwoMachinesPlay) {
9341             SendToProgram("force\n", &second);
9342             if (second.usePing) {
9343               char buf[MSG_SIZ];
9344               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9345               SendToProgram(buf, &second);
9346             }
9347         }
9348     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9349         /* Kill off second chess program */
9350         if (second.isr != NULL)
9351           RemoveInputSource(second.isr);
9352         second.isr = NULL;
9353
9354         if (second.pr != NoProc) {
9355             DoSleep( appData.delayBeforeQuit );
9356             SendToProgram("quit\n", &second);
9357             DoSleep( appData.delayAfterQuit );
9358             DestroyChildProcess(second.pr, second.useSigterm);
9359         }
9360         second.pr = NoProc;
9361     }
9362
9363     if (matchMode && gameMode == TwoMachinesPlay) {
9364         switch (result) {
9365         case WhiteWins:
9366           if (first.twoMachinesColor[0] == 'w') {
9367             first.matchWins++;
9368           } else {
9369             second.matchWins++;
9370           }
9371           break;
9372         case BlackWins:
9373           if (first.twoMachinesColor[0] == 'b') {
9374             first.matchWins++;
9375           } else {
9376             second.matchWins++;
9377           }
9378           break;
9379         default:
9380           break;
9381         }
9382         if (matchGame < appData.matchGames) {
9383             char *tmp;
9384             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9385                 tmp = first.twoMachinesColor;
9386                 first.twoMachinesColor = second.twoMachinesColor;
9387                 second.twoMachinesColor = tmp;
9388             }
9389             gameMode = nextGameMode;
9390             matchGame++;
9391             if(appData.matchPause>10000 || appData.matchPause<10)
9392                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9393             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9394             endingGame = 0; /* [HGM] crash */
9395             return;
9396         } else {
9397             gameMode = nextGameMode;
9398             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9399                      first.tidy, second.tidy,
9400                      first.matchWins, second.matchWins,
9401                      appData.matchGames - (first.matchWins + second.matchWins));
9402             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9403         }
9404     }
9405     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9406         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9407       ExitAnalyzeMode();
9408     gameMode = nextGameMode;
9409     ModeHighlight();
9410     endingGame = 0;  /* [HGM] crash */
9411     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9412       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9413         matchMode = FALSE; appData.matchGames = matchGame = 0;
9414         DisplayNote(buf);
9415       }
9416     }
9417 }
9418
9419 /* Assumes program was just initialized (initString sent).
9420    Leaves program in force mode. */
9421 void
9422 FeedMovesToProgram(cps, upto)
9423      ChessProgramState *cps;
9424      int upto;
9425 {
9426     int i;
9427
9428     if (appData.debugMode)
9429       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9430               startedFromSetupPosition ? "position and " : "",
9431               backwardMostMove, upto, cps->which);
9432     if(currentlyInitializedVariant != gameInfo.variant) {
9433       char buf[MSG_SIZ];
9434         // [HGM] variantswitch: make engine aware of new variant
9435         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9436                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9437         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9438         SendToProgram(buf, cps);
9439         currentlyInitializedVariant = gameInfo.variant;
9440     }
9441     SendToProgram("force\n", cps);
9442     if (startedFromSetupPosition) {
9443         SendBoard(cps, backwardMostMove);
9444     if (appData.debugMode) {
9445         fprintf(debugFP, "feedMoves\n");
9446     }
9447     }
9448     for (i = backwardMostMove; i < upto; i++) {
9449         SendMoveToProgram(i, cps);
9450     }
9451 }
9452
9453
9454 void
9455 ResurrectChessProgram()
9456 {
9457      /* The chess program may have exited.
9458         If so, restart it and feed it all the moves made so far. */
9459
9460     if (appData.noChessProgram || first.pr != NoProc) return;
9461
9462     StartChessProgram(&first);
9463     InitChessProgram(&first, FALSE);
9464     FeedMovesToProgram(&first, currentMove);
9465
9466     if (!first.sendTime) {
9467         /* can't tell gnuchess what its clock should read,
9468            so we bow to its notion. */
9469         ResetClocks();
9470         timeRemaining[0][currentMove] = whiteTimeRemaining;
9471         timeRemaining[1][currentMove] = blackTimeRemaining;
9472     }
9473
9474     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9475                 appData.icsEngineAnalyze) && first.analysisSupport) {
9476       SendToProgram("analyze\n", &first);
9477       first.analyzing = TRUE;
9478     }
9479 }
9480
9481 /*
9482  * Button procedures
9483  */
9484 void
9485 Reset(redraw, init)
9486      int redraw, init;
9487 {
9488     int i;
9489
9490     if (appData.debugMode) {
9491         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9492                 redraw, init, gameMode);
9493     }
9494     CleanupTail(); // [HGM] vari: delete any stored variations
9495     pausing = pauseExamInvalid = FALSE;
9496     startedFromSetupPosition = blackPlaysFirst = FALSE;
9497     firstMove = TRUE;
9498     whiteFlag = blackFlag = FALSE;
9499     userOfferedDraw = FALSE;
9500     hintRequested = bookRequested = FALSE;
9501     first.maybeThinking = FALSE;
9502     second.maybeThinking = FALSE;
9503     first.bookSuspend = FALSE; // [HGM] book
9504     second.bookSuspend = FALSE;
9505     thinkOutput[0] = NULLCHAR;
9506     lastHint[0] = NULLCHAR;
9507     ClearGameInfo(&gameInfo);
9508     gameInfo.variant = StringToVariant(appData.variant);
9509     ics_user_moved = ics_clock_paused = FALSE;
9510     ics_getting_history = H_FALSE;
9511     ics_gamenum = -1;
9512     white_holding[0] = black_holding[0] = NULLCHAR;
9513     ClearProgramStats();
9514     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9515
9516     ResetFrontEnd();
9517     ClearHighlights();
9518     flipView = appData.flipView;
9519     ClearPremoveHighlights();
9520     gotPremove = FALSE;
9521     alarmSounded = FALSE;
9522
9523     GameEnds(EndOfFile, NULL, GE_PLAYER);
9524     if(appData.serverMovesName != NULL) {
9525         /* [HGM] prepare to make moves file for broadcasting */
9526         clock_t t = clock();
9527         if(serverMoves != NULL) fclose(serverMoves);
9528         serverMoves = fopen(appData.serverMovesName, "r");
9529         if(serverMoves != NULL) {
9530             fclose(serverMoves);
9531             /* delay 15 sec before overwriting, so all clients can see end */
9532             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9533         }
9534         serverMoves = fopen(appData.serverMovesName, "w");
9535     }
9536
9537     ExitAnalyzeMode();
9538     gameMode = BeginningOfGame;
9539     ModeHighlight();
9540     if(appData.icsActive) gameInfo.variant = VariantNormal;
9541     currentMove = forwardMostMove = backwardMostMove = 0;
9542     InitPosition(redraw);
9543     for (i = 0; i < MAX_MOVES; i++) {
9544         if (commentList[i] != NULL) {
9545             free(commentList[i]);
9546             commentList[i] = NULL;
9547         }
9548     }
9549     ResetClocks();
9550     timeRemaining[0][0] = whiteTimeRemaining;
9551     timeRemaining[1][0] = blackTimeRemaining;
9552     if (first.pr == NULL) {
9553         StartChessProgram(&first);
9554     }
9555     if (init) {
9556             InitChessProgram(&first, startedFromSetupPosition);
9557     }
9558     DisplayTitle("");
9559     DisplayMessage("", "");
9560     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9561     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9562 }
9563
9564 void
9565 AutoPlayGameLoop()
9566 {
9567     for (;;) {
9568         if (!AutoPlayOneMove())
9569           return;
9570         if (matchMode || appData.timeDelay == 0)
9571           continue;
9572         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9573           return;
9574         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9575         break;
9576     }
9577 }
9578
9579
9580 int
9581 AutoPlayOneMove()
9582 {
9583     int fromX, fromY, toX, toY;
9584
9585     if (appData.debugMode) {
9586       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9587     }
9588
9589     if (gameMode != PlayFromGameFile)
9590       return FALSE;
9591
9592     if (currentMove >= forwardMostMove) {
9593       gameMode = EditGame;
9594       ModeHighlight();
9595
9596       /* [AS] Clear current move marker at the end of a game */
9597       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9598
9599       return FALSE;
9600     }
9601
9602     toX = moveList[currentMove][2] - AAA;
9603     toY = moveList[currentMove][3] - ONE;
9604
9605     if (moveList[currentMove][1] == '@') {
9606         if (appData.highlightLastMove) {
9607             SetHighlights(-1, -1, toX, toY);
9608         }
9609     } else {
9610         fromX = moveList[currentMove][0] - AAA;
9611         fromY = moveList[currentMove][1] - ONE;
9612
9613         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9614
9615         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9616
9617         if (appData.highlightLastMove) {
9618             SetHighlights(fromX, fromY, toX, toY);
9619         }
9620     }
9621     DisplayMove(currentMove);
9622     SendMoveToProgram(currentMove++, &first);
9623     DisplayBothClocks();
9624     DrawPosition(FALSE, boards[currentMove]);
9625     // [HGM] PV info: always display, routine tests if empty
9626     DisplayComment(currentMove - 1, commentList[currentMove]);
9627     return TRUE;
9628 }
9629
9630
9631 int
9632 LoadGameOneMove(readAhead)
9633      ChessMove readAhead;
9634 {
9635     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9636     char promoChar = NULLCHAR;
9637     ChessMove moveType;
9638     char move[MSG_SIZ];
9639     char *p, *q;
9640
9641     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9642         gameMode != AnalyzeMode && gameMode != Training) {
9643         gameFileFP = NULL;
9644         return FALSE;
9645     }
9646
9647     yyboardindex = forwardMostMove;
9648     if (readAhead != EndOfFile) {
9649       moveType = readAhead;
9650     } else {
9651       if (gameFileFP == NULL)
9652           return FALSE;
9653       moveType = (ChessMove) yylex();
9654     }
9655
9656     done = FALSE;
9657     switch (moveType) {
9658       case Comment:
9659         if (appData.debugMode)
9660           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9661         p = yy_text;
9662
9663         /* append the comment but don't display it */
9664         AppendComment(currentMove, p, FALSE);
9665         return TRUE;
9666
9667       case WhiteCapturesEnPassant:
9668       case BlackCapturesEnPassant:
9669       case WhitePromotion:
9670       case BlackPromotion:
9671       case WhiteNonPromotion:
9672       case BlackNonPromotion:
9673       case NormalMove:
9674       case WhiteKingSideCastle:
9675       case WhiteQueenSideCastle:
9676       case BlackKingSideCastle:
9677       case BlackQueenSideCastle:
9678       case WhiteKingSideCastleWild:
9679       case WhiteQueenSideCastleWild:
9680       case BlackKingSideCastleWild:
9681       case BlackQueenSideCastleWild:
9682       /* PUSH Fabien */
9683       case WhiteHSideCastleFR:
9684       case WhiteASideCastleFR:
9685       case BlackHSideCastleFR:
9686       case BlackASideCastleFR:
9687       /* POP Fabien */
9688         if (appData.debugMode)
9689           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9690         fromX = currentMoveString[0] - AAA;
9691         fromY = currentMoveString[1] - ONE;
9692         toX = currentMoveString[2] - AAA;
9693         toY = currentMoveString[3] - ONE;
9694         promoChar = currentMoveString[4];
9695         break;
9696
9697       case WhiteDrop:
9698       case BlackDrop:
9699         if (appData.debugMode)
9700           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9701         fromX = moveType == WhiteDrop ?
9702           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9703         (int) CharToPiece(ToLower(currentMoveString[0]));
9704         fromY = DROP_RANK;
9705         toX = currentMoveString[2] - AAA;
9706         toY = currentMoveString[3] - ONE;
9707         break;
9708
9709       case WhiteWins:
9710       case BlackWins:
9711       case GameIsDrawn:
9712       case GameUnfinished:
9713         if (appData.debugMode)
9714           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9715         p = strchr(yy_text, '{');
9716         if (p == NULL) p = strchr(yy_text, '(');
9717         if (p == NULL) {
9718             p = yy_text;
9719             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9720         } else {
9721             q = strchr(p, *p == '{' ? '}' : ')');
9722             if (q != NULL) *q = NULLCHAR;
9723             p++;
9724         }
9725         GameEnds(moveType, p, GE_FILE);
9726         done = TRUE;
9727         if (cmailMsgLoaded) {
9728             ClearHighlights();
9729             flipView = WhiteOnMove(currentMove);
9730             if (moveType == GameUnfinished) flipView = !flipView;
9731             if (appData.debugMode)
9732               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9733         }
9734         break;
9735
9736       case EndOfFile:
9737         if (appData.debugMode)
9738           fprintf(debugFP, "Parser hit end of file\n");
9739         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9740           case MT_NONE:
9741           case MT_CHECK:
9742             break;
9743           case MT_CHECKMATE:
9744           case MT_STAINMATE:
9745             if (WhiteOnMove(currentMove)) {
9746                 GameEnds(BlackWins, "Black mates", GE_FILE);
9747             } else {
9748                 GameEnds(WhiteWins, "White mates", GE_FILE);
9749             }
9750             break;
9751           case MT_STALEMATE:
9752             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9753             break;
9754         }
9755         done = TRUE;
9756         break;
9757
9758       case MoveNumberOne:
9759         if (lastLoadGameStart == GNUChessGame) {
9760             /* GNUChessGames have numbers, but they aren't move numbers */
9761             if (appData.debugMode)
9762               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9763                       yy_text, (int) moveType);
9764             return LoadGameOneMove(EndOfFile); /* tail recursion */
9765         }
9766         /* else fall thru */
9767
9768       case XBoardGame:
9769       case GNUChessGame:
9770       case PGNTag:
9771         /* Reached start of next game in file */
9772         if (appData.debugMode)
9773           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9774         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9775           case MT_NONE:
9776           case MT_CHECK:
9777             break;
9778           case MT_CHECKMATE:
9779           case MT_STAINMATE:
9780             if (WhiteOnMove(currentMove)) {
9781                 GameEnds(BlackWins, "Black mates", GE_FILE);
9782             } else {
9783                 GameEnds(WhiteWins, "White mates", GE_FILE);
9784             }
9785             break;
9786           case MT_STALEMATE:
9787             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9788             break;
9789         }
9790         done = TRUE;
9791         break;
9792
9793       case PositionDiagram:     /* should not happen; ignore */
9794       case ElapsedTime:         /* ignore */
9795       case NAG:                 /* ignore */
9796         if (appData.debugMode)
9797           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9798                   yy_text, (int) moveType);
9799         return LoadGameOneMove(EndOfFile); /* tail recursion */
9800
9801       case IllegalMove:
9802         if (appData.testLegality) {
9803             if (appData.debugMode)
9804               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9805             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9806                     (forwardMostMove / 2) + 1,
9807                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9808             DisplayError(move, 0);
9809             done = TRUE;
9810         } else {
9811             if (appData.debugMode)
9812               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9813                       yy_text, currentMoveString);
9814             fromX = currentMoveString[0] - AAA;
9815             fromY = currentMoveString[1] - ONE;
9816             toX = currentMoveString[2] - AAA;
9817             toY = currentMoveString[3] - ONE;
9818             promoChar = currentMoveString[4];
9819         }
9820         break;
9821
9822       case AmbiguousMove:
9823         if (appData.debugMode)
9824           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9825         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9826                 (forwardMostMove / 2) + 1,
9827                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9828         DisplayError(move, 0);
9829         done = TRUE;
9830         break;
9831
9832       default:
9833       case ImpossibleMove:
9834         if (appData.debugMode)
9835           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9836         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9837                 (forwardMostMove / 2) + 1,
9838                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9839         DisplayError(move, 0);
9840         done = TRUE;
9841         break;
9842     }
9843
9844     if (done) {
9845         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9846             DrawPosition(FALSE, boards[currentMove]);
9847             DisplayBothClocks();
9848             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9849               DisplayComment(currentMove - 1, commentList[currentMove]);
9850         }
9851         (void) StopLoadGameTimer();
9852         gameFileFP = NULL;
9853         cmailOldMove = forwardMostMove;
9854         return FALSE;
9855     } else {
9856         /* currentMoveString is set as a side-effect of yylex */
9857         strcat(currentMoveString, "\n");
9858         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9859
9860         thinkOutput[0] = NULLCHAR;
9861         MakeMove(fromX, fromY, toX, toY, promoChar);
9862         currentMove = forwardMostMove;
9863         return TRUE;
9864     }
9865 }
9866
9867 /* Load the nth game from the given file */
9868 int
9869 LoadGameFromFile(filename, n, title, useList)
9870      char *filename;
9871      int n;
9872      char *title;
9873      /*Boolean*/ int useList;
9874 {
9875     FILE *f;
9876     char buf[MSG_SIZ];
9877
9878     if (strcmp(filename, "-") == 0) {
9879         f = stdin;
9880         title = "stdin";
9881     } else {
9882         f = fopen(filename, "rb");
9883         if (f == NULL) {
9884           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9885             DisplayError(buf, errno);
9886             return FALSE;
9887         }
9888     }
9889     if (fseek(f, 0, 0) == -1) {
9890         /* f is not seekable; probably a pipe */
9891         useList = FALSE;
9892     }
9893     if (useList && n == 0) {
9894         int error = GameListBuild(f);
9895         if (error) {
9896             DisplayError(_("Cannot build game list"), error);
9897         } else if (!ListEmpty(&gameList) &&
9898                    ((ListGame *) gameList.tailPred)->number > 1) {
9899             GameListPopUp(f, title);
9900             return TRUE;
9901         }
9902         GameListDestroy();
9903         n = 1;
9904     }
9905     if (n == 0) n = 1;
9906     return LoadGame(f, n, title, FALSE);
9907 }
9908
9909
9910 void
9911 MakeRegisteredMove()
9912 {
9913     int fromX, fromY, toX, toY;
9914     char promoChar;
9915     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9916         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9917           case CMAIL_MOVE:
9918           case CMAIL_DRAW:
9919             if (appData.debugMode)
9920               fprintf(debugFP, "Restoring %s for game %d\n",
9921                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9922
9923             thinkOutput[0] = NULLCHAR;
9924             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9925             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9926             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9927             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9928             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9929             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9930             MakeMove(fromX, fromY, toX, toY, promoChar);
9931             ShowMove(fromX, fromY, toX, toY);
9932
9933             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9934               case MT_NONE:
9935               case MT_CHECK:
9936                 break;
9937
9938               case MT_CHECKMATE:
9939               case MT_STAINMATE:
9940                 if (WhiteOnMove(currentMove)) {
9941                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9942                 } else {
9943                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9944                 }
9945                 break;
9946
9947               case MT_STALEMATE:
9948                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9949                 break;
9950             }
9951
9952             break;
9953
9954           case CMAIL_RESIGN:
9955             if (WhiteOnMove(currentMove)) {
9956                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9957             } else {
9958                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9959             }
9960             break;
9961
9962           case CMAIL_ACCEPT:
9963             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9964             break;
9965
9966           default:
9967             break;
9968         }
9969     }
9970
9971     return;
9972 }
9973
9974 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9975 int
9976 CmailLoadGame(f, gameNumber, title, useList)
9977      FILE *f;
9978      int gameNumber;
9979      char *title;
9980      int useList;
9981 {
9982     int retVal;
9983
9984     if (gameNumber > nCmailGames) {
9985         DisplayError(_("No more games in this message"), 0);
9986         return FALSE;
9987     }
9988     if (f == lastLoadGameFP) {
9989         int offset = gameNumber - lastLoadGameNumber;
9990         if (offset == 0) {
9991             cmailMsg[0] = NULLCHAR;
9992             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9993                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9994                 nCmailMovesRegistered--;
9995             }
9996             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9997             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9998                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9999             }
10000         } else {
10001             if (! RegisterMove()) return FALSE;
10002         }
10003     }
10004
10005     retVal = LoadGame(f, gameNumber, title, useList);
10006
10007     /* Make move registered during previous look at this game, if any */
10008     MakeRegisteredMove();
10009
10010     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10011         commentList[currentMove]
10012           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10013         DisplayComment(currentMove - 1, commentList[currentMove]);
10014     }
10015
10016     return retVal;
10017 }
10018
10019 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10020 int
10021 ReloadGame(offset)
10022      int offset;
10023 {
10024     int gameNumber = lastLoadGameNumber + offset;
10025     if (lastLoadGameFP == NULL) {
10026         DisplayError(_("No game has been loaded yet"), 0);
10027         return FALSE;
10028     }
10029     if (gameNumber <= 0) {
10030         DisplayError(_("Can't back up any further"), 0);
10031         return FALSE;
10032     }
10033     if (cmailMsgLoaded) {
10034         return CmailLoadGame(lastLoadGameFP, gameNumber,
10035                              lastLoadGameTitle, lastLoadGameUseList);
10036     } else {
10037         return LoadGame(lastLoadGameFP, gameNumber,
10038                         lastLoadGameTitle, lastLoadGameUseList);
10039     }
10040 }
10041
10042
10043
10044 /* Load the nth game from open file f */
10045 int
10046 LoadGame(f, gameNumber, title, useList)
10047      FILE *f;
10048      int gameNumber;
10049      char *title;
10050      int useList;
10051 {
10052     ChessMove cm;
10053     char buf[MSG_SIZ];
10054     int gn = gameNumber;
10055     ListGame *lg = NULL;
10056     int numPGNTags = 0;
10057     int err;
10058     GameMode oldGameMode;
10059     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10060
10061     if (appData.debugMode)
10062         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10063
10064     if (gameMode == Training )
10065         SetTrainingModeOff();
10066
10067     oldGameMode = gameMode;
10068     if (gameMode != BeginningOfGame) {
10069       Reset(FALSE, TRUE);
10070     }
10071
10072     gameFileFP = f;
10073     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10074         fclose(lastLoadGameFP);
10075     }
10076
10077     if (useList) {
10078         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10079
10080         if (lg) {
10081             fseek(f, lg->offset, 0);
10082             GameListHighlight(gameNumber);
10083             gn = 1;
10084         }
10085         else {
10086             DisplayError(_("Game number out of range"), 0);
10087             return FALSE;
10088         }
10089     } else {
10090         GameListDestroy();
10091         if (fseek(f, 0, 0) == -1) {
10092             if (f == lastLoadGameFP ?
10093                 gameNumber == lastLoadGameNumber + 1 :
10094                 gameNumber == 1) {
10095                 gn = 1;
10096             } else {
10097                 DisplayError(_("Can't seek on game file"), 0);
10098                 return FALSE;
10099             }
10100         }
10101     }
10102     lastLoadGameFP = f;
10103     lastLoadGameNumber = gameNumber;
10104     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10105     lastLoadGameUseList = useList;
10106
10107     yynewfile(f);
10108
10109     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10110       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10111                 lg->gameInfo.black);
10112             DisplayTitle(buf);
10113     } else if (*title != NULLCHAR) {
10114         if (gameNumber > 1) {
10115           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10116             DisplayTitle(buf);
10117         } else {
10118             DisplayTitle(title);
10119         }
10120     }
10121
10122     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10123         gameMode = PlayFromGameFile;
10124         ModeHighlight();
10125     }
10126
10127     currentMove = forwardMostMove = backwardMostMove = 0;
10128     CopyBoard(boards[0], initialPosition);
10129     StopClocks();
10130
10131     /*
10132      * Skip the first gn-1 games in the file.
10133      * Also skip over anything that precedes an identifiable
10134      * start of game marker, to avoid being confused by
10135      * garbage at the start of the file.  Currently
10136      * recognized start of game markers are the move number "1",
10137      * the pattern "gnuchess .* game", the pattern
10138      * "^[#;%] [^ ]* game file", and a PGN tag block.
10139      * A game that starts with one of the latter two patterns
10140      * will also have a move number 1, possibly
10141      * following a position diagram.
10142      * 5-4-02: Let's try being more lenient and allowing a game to
10143      * start with an unnumbered move.  Does that break anything?
10144      */
10145     cm = lastLoadGameStart = EndOfFile;
10146     while (gn > 0) {
10147         yyboardindex = forwardMostMove;
10148         cm = (ChessMove) yylex();
10149         switch (cm) {
10150           case EndOfFile:
10151             if (cmailMsgLoaded) {
10152                 nCmailGames = CMAIL_MAX_GAMES - gn;
10153             } else {
10154                 Reset(TRUE, TRUE);
10155                 DisplayError(_("Game not found in file"), 0);
10156             }
10157             return FALSE;
10158
10159           case GNUChessGame:
10160           case XBoardGame:
10161             gn--;
10162             lastLoadGameStart = cm;
10163             break;
10164
10165           case MoveNumberOne:
10166             switch (lastLoadGameStart) {
10167               case GNUChessGame:
10168               case XBoardGame:
10169               case PGNTag:
10170                 break;
10171               case MoveNumberOne:
10172               case EndOfFile:
10173                 gn--;           /* count this game */
10174                 lastLoadGameStart = cm;
10175                 break;
10176               default:
10177                 /* impossible */
10178                 break;
10179             }
10180             break;
10181
10182           case PGNTag:
10183             switch (lastLoadGameStart) {
10184               case GNUChessGame:
10185               case PGNTag:
10186               case MoveNumberOne:
10187               case EndOfFile:
10188                 gn--;           /* count this game */
10189                 lastLoadGameStart = cm;
10190                 break;
10191               case XBoardGame:
10192                 lastLoadGameStart = cm; /* game counted already */
10193                 break;
10194               default:
10195                 /* impossible */
10196                 break;
10197             }
10198             if (gn > 0) {
10199                 do {
10200                     yyboardindex = forwardMostMove;
10201                     cm = (ChessMove) yylex();
10202                 } while (cm == PGNTag || cm == Comment);
10203             }
10204             break;
10205
10206           case WhiteWins:
10207           case BlackWins:
10208           case GameIsDrawn:
10209             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10210                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10211                     != CMAIL_OLD_RESULT) {
10212                     nCmailResults ++ ;
10213                     cmailResult[  CMAIL_MAX_GAMES
10214                                 - gn - 1] = CMAIL_OLD_RESULT;
10215                 }
10216             }
10217             break;
10218
10219           case NormalMove:
10220             /* Only a NormalMove can be at the start of a game
10221              * without a position diagram. */
10222             if (lastLoadGameStart == EndOfFile ) {
10223               gn--;
10224               lastLoadGameStart = MoveNumberOne;
10225             }
10226             break;
10227
10228           default:
10229             break;
10230         }
10231     }
10232
10233     if (appData.debugMode)
10234       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10235
10236     if (cm == XBoardGame) {
10237         /* Skip any header junk before position diagram and/or move 1 */
10238         for (;;) {
10239             yyboardindex = forwardMostMove;
10240             cm = (ChessMove) yylex();
10241
10242             if (cm == EndOfFile ||
10243                 cm == GNUChessGame || cm == XBoardGame) {
10244                 /* Empty game; pretend end-of-file and handle later */
10245                 cm = EndOfFile;
10246                 break;
10247             }
10248
10249             if (cm == MoveNumberOne || cm == PositionDiagram ||
10250                 cm == PGNTag || cm == Comment)
10251               break;
10252         }
10253     } else if (cm == GNUChessGame) {
10254         if (gameInfo.event != NULL) {
10255             free(gameInfo.event);
10256         }
10257         gameInfo.event = StrSave(yy_text);
10258     }
10259
10260     startedFromSetupPosition = FALSE;
10261     while (cm == PGNTag) {
10262         if (appData.debugMode)
10263           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10264         err = ParsePGNTag(yy_text, &gameInfo);
10265         if (!err) numPGNTags++;
10266
10267         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10268         if(gameInfo.variant != oldVariant) {
10269             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10270             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10271             InitPosition(TRUE);
10272             oldVariant = gameInfo.variant;
10273             if (appData.debugMode)
10274               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10275         }
10276
10277
10278         if (gameInfo.fen != NULL) {
10279           Board initial_position;
10280           startedFromSetupPosition = TRUE;
10281           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10282             Reset(TRUE, TRUE);
10283             DisplayError(_("Bad FEN position in file"), 0);
10284             return FALSE;
10285           }
10286           CopyBoard(boards[0], initial_position);
10287           if (blackPlaysFirst) {
10288             currentMove = forwardMostMove = backwardMostMove = 1;
10289             CopyBoard(boards[1], initial_position);
10290             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10291             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10292             timeRemaining[0][1] = whiteTimeRemaining;
10293             timeRemaining[1][1] = blackTimeRemaining;
10294             if (commentList[0] != NULL) {
10295               commentList[1] = commentList[0];
10296               commentList[0] = NULL;
10297             }
10298           } else {
10299             currentMove = forwardMostMove = backwardMostMove = 0;
10300           }
10301           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10302           {   int i;
10303               initialRulePlies = FENrulePlies;
10304               for( i=0; i< nrCastlingRights; i++ )
10305                   initialRights[i] = initial_position[CASTLING][i];
10306           }
10307           yyboardindex = forwardMostMove;
10308           free(gameInfo.fen);
10309           gameInfo.fen = NULL;
10310         }
10311
10312         yyboardindex = forwardMostMove;
10313         cm = (ChessMove) yylex();
10314
10315         /* Handle comments interspersed among the tags */
10316         while (cm == Comment) {
10317             char *p;
10318             if (appData.debugMode)
10319               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10320             p = yy_text;
10321             AppendComment(currentMove, p, FALSE);
10322             yyboardindex = forwardMostMove;
10323             cm = (ChessMove) yylex();
10324         }
10325     }
10326
10327     /* don't rely on existence of Event tag since if game was
10328      * pasted from clipboard the Event tag may not exist
10329      */
10330     if (numPGNTags > 0){
10331         char *tags;
10332         if (gameInfo.variant == VariantNormal) {
10333           VariantClass v = StringToVariant(gameInfo.event);
10334           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10335           if(v < VariantShogi) gameInfo.variant = v;
10336         }
10337         if (!matchMode) {
10338           if( appData.autoDisplayTags ) {
10339             tags = PGNTags(&gameInfo);
10340             TagsPopUp(tags, CmailMsg());
10341             free(tags);
10342           }
10343         }
10344     } else {
10345         /* Make something up, but don't display it now */
10346         SetGameInfo();
10347         TagsPopDown();
10348     }
10349
10350     if (cm == PositionDiagram) {
10351         int i, j;
10352         char *p;
10353         Board initial_position;
10354
10355         if (appData.debugMode)
10356           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10357
10358         if (!startedFromSetupPosition) {
10359             p = yy_text;
10360             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10361               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10362                 switch (*p) {
10363                   case '[':
10364                   case '-':
10365                   case ' ':
10366                   case '\t':
10367                   case '\n':
10368                   case '\r':
10369                     break;
10370                   default:
10371                     initial_position[i][j++] = CharToPiece(*p);
10372                     break;
10373                 }
10374             while (*p == ' ' || *p == '\t' ||
10375                    *p == '\n' || *p == '\r') p++;
10376
10377             if (strncmp(p, "black", strlen("black"))==0)
10378               blackPlaysFirst = TRUE;
10379             else
10380               blackPlaysFirst = FALSE;
10381             startedFromSetupPosition = TRUE;
10382
10383             CopyBoard(boards[0], initial_position);
10384             if (blackPlaysFirst) {
10385                 currentMove = forwardMostMove = backwardMostMove = 1;
10386                 CopyBoard(boards[1], initial_position);
10387                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10388                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10389                 timeRemaining[0][1] = whiteTimeRemaining;
10390                 timeRemaining[1][1] = blackTimeRemaining;
10391                 if (commentList[0] != NULL) {
10392                     commentList[1] = commentList[0];
10393                     commentList[0] = NULL;
10394                 }
10395             } else {
10396                 currentMove = forwardMostMove = backwardMostMove = 0;
10397             }
10398         }
10399         yyboardindex = forwardMostMove;
10400         cm = (ChessMove) yylex();
10401     }
10402
10403     if (first.pr == NoProc) {
10404         StartChessProgram(&first);
10405     }
10406     InitChessProgram(&first, FALSE);
10407     SendToProgram("force\n", &first);
10408     if (startedFromSetupPosition) {
10409         SendBoard(&first, forwardMostMove);
10410     if (appData.debugMode) {
10411         fprintf(debugFP, "Load Game\n");
10412     }
10413         DisplayBothClocks();
10414     }
10415
10416     /* [HGM] server: flag to write setup moves in broadcast file as one */
10417     loadFlag = appData.suppressLoadMoves;
10418
10419     while (cm == Comment) {
10420         char *p;
10421         if (appData.debugMode)
10422           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10423         p = yy_text;
10424         AppendComment(currentMove, p, FALSE);
10425         yyboardindex = forwardMostMove;
10426         cm = (ChessMove) yylex();
10427     }
10428
10429     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10430         cm == WhiteWins || cm == BlackWins ||
10431         cm == GameIsDrawn || cm == GameUnfinished) {
10432         DisplayMessage("", _("No moves in game"));
10433         if (cmailMsgLoaded) {
10434             if (appData.debugMode)
10435               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10436             ClearHighlights();
10437             flipView = FALSE;
10438         }
10439         DrawPosition(FALSE, boards[currentMove]);
10440         DisplayBothClocks();
10441         gameMode = EditGame;
10442         ModeHighlight();
10443         gameFileFP = NULL;
10444         cmailOldMove = 0;
10445         return TRUE;
10446     }
10447
10448     // [HGM] PV info: routine tests if comment empty
10449     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10450         DisplayComment(currentMove - 1, commentList[currentMove]);
10451     }
10452     if (!matchMode && appData.timeDelay != 0)
10453       DrawPosition(FALSE, boards[currentMove]);
10454
10455     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10456       programStats.ok_to_send = 1;
10457     }
10458
10459     /* if the first token after the PGN tags is a move
10460      * and not move number 1, retrieve it from the parser
10461      */
10462     if (cm != MoveNumberOne)
10463         LoadGameOneMove(cm);
10464
10465     /* load the remaining moves from the file */
10466     while (LoadGameOneMove(EndOfFile)) {
10467       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10468       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10469     }
10470
10471     /* rewind to the start of the game */
10472     currentMove = backwardMostMove;
10473
10474     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10475
10476     if (oldGameMode == AnalyzeFile ||
10477         oldGameMode == AnalyzeMode) {
10478       AnalyzeFileEvent();
10479     }
10480
10481     if (matchMode || appData.timeDelay == 0) {
10482       ToEndEvent();
10483       gameMode = EditGame;
10484       ModeHighlight();
10485     } else if (appData.timeDelay > 0) {
10486       AutoPlayGameLoop();
10487     }
10488
10489     if (appData.debugMode)
10490         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10491
10492     loadFlag = 0; /* [HGM] true game starts */
10493     return TRUE;
10494 }
10495
10496 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10497 int
10498 ReloadPosition(offset)
10499      int offset;
10500 {
10501     int positionNumber = lastLoadPositionNumber + offset;
10502     if (lastLoadPositionFP == NULL) {
10503         DisplayError(_("No position has been loaded yet"), 0);
10504         return FALSE;
10505     }
10506     if (positionNumber <= 0) {
10507         DisplayError(_("Can't back up any further"), 0);
10508         return FALSE;
10509     }
10510     return LoadPosition(lastLoadPositionFP, positionNumber,
10511                         lastLoadPositionTitle);
10512 }
10513
10514 /* Load the nth position from the given file */
10515 int
10516 LoadPositionFromFile(filename, n, title)
10517      char *filename;
10518      int n;
10519      char *title;
10520 {
10521     FILE *f;
10522     char buf[MSG_SIZ];
10523
10524     if (strcmp(filename, "-") == 0) {
10525         return LoadPosition(stdin, n, "stdin");
10526     } else {
10527         f = fopen(filename, "rb");
10528         if (f == NULL) {
10529             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10530             DisplayError(buf, errno);
10531             return FALSE;
10532         } else {
10533             return LoadPosition(f, n, title);
10534         }
10535     }
10536 }
10537
10538 /* Load the nth position from the given open file, and close it */
10539 int
10540 LoadPosition(f, positionNumber, title)
10541      FILE *f;
10542      int positionNumber;
10543      char *title;
10544 {
10545     char *p, line[MSG_SIZ];
10546     Board initial_position;
10547     int i, j, fenMode, pn;
10548
10549     if (gameMode == Training )
10550         SetTrainingModeOff();
10551
10552     if (gameMode != BeginningOfGame) {
10553         Reset(FALSE, TRUE);
10554     }
10555     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10556         fclose(lastLoadPositionFP);
10557     }
10558     if (positionNumber == 0) positionNumber = 1;
10559     lastLoadPositionFP = f;
10560     lastLoadPositionNumber = positionNumber;
10561     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10562     if (first.pr == NoProc) {
10563       StartChessProgram(&first);
10564       InitChessProgram(&first, FALSE);
10565     }
10566     pn = positionNumber;
10567     if (positionNumber < 0) {
10568         /* Negative position number means to seek to that byte offset */
10569         if (fseek(f, -positionNumber, 0) == -1) {
10570             DisplayError(_("Can't seek on position file"), 0);
10571             return FALSE;
10572         };
10573         pn = 1;
10574     } else {
10575         if (fseek(f, 0, 0) == -1) {
10576             if (f == lastLoadPositionFP ?
10577                 positionNumber == lastLoadPositionNumber + 1 :
10578                 positionNumber == 1) {
10579                 pn = 1;
10580             } else {
10581                 DisplayError(_("Can't seek on position file"), 0);
10582                 return FALSE;
10583             }
10584         }
10585     }
10586     /* See if this file is FEN or old-style xboard */
10587     if (fgets(line, MSG_SIZ, f) == NULL) {
10588         DisplayError(_("Position not found in file"), 0);
10589         return FALSE;
10590     }
10591     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10592     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10593
10594     if (pn >= 2) {
10595         if (fenMode || line[0] == '#') pn--;
10596         while (pn > 0) {
10597             /* skip positions before number pn */
10598             if (fgets(line, MSG_SIZ, f) == NULL) {
10599                 Reset(TRUE, TRUE);
10600                 DisplayError(_("Position not found in file"), 0);
10601                 return FALSE;
10602             }
10603             if (fenMode || line[0] == '#') pn--;
10604         }
10605     }
10606
10607     if (fenMode) {
10608         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10609             DisplayError(_("Bad FEN position in file"), 0);
10610             return FALSE;
10611         }
10612     } else {
10613         (void) fgets(line, MSG_SIZ, f);
10614         (void) fgets(line, MSG_SIZ, f);
10615
10616         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10617             (void) fgets(line, MSG_SIZ, f);
10618             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10619                 if (*p == ' ')
10620                   continue;
10621                 initial_position[i][j++] = CharToPiece(*p);
10622             }
10623         }
10624
10625         blackPlaysFirst = FALSE;
10626         if (!feof(f)) {
10627             (void) fgets(line, MSG_SIZ, f);
10628             if (strncmp(line, "black", strlen("black"))==0)
10629               blackPlaysFirst = TRUE;
10630         }
10631     }
10632     startedFromSetupPosition = TRUE;
10633
10634     SendToProgram("force\n", &first);
10635     CopyBoard(boards[0], initial_position);
10636     if (blackPlaysFirst) {
10637         currentMove = forwardMostMove = backwardMostMove = 1;
10638         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10639         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10640         CopyBoard(boards[1], initial_position);
10641         DisplayMessage("", _("Black to play"));
10642     } else {
10643         currentMove = forwardMostMove = backwardMostMove = 0;
10644         DisplayMessage("", _("White to play"));
10645     }
10646     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10647     SendBoard(&first, forwardMostMove);
10648     if (appData.debugMode) {
10649 int i, j;
10650   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10651   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10652         fprintf(debugFP, "Load Position\n");
10653     }
10654
10655     if (positionNumber > 1) {
10656       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10657         DisplayTitle(line);
10658     } else {
10659         DisplayTitle(title);
10660     }
10661     gameMode = EditGame;
10662     ModeHighlight();
10663     ResetClocks();
10664     timeRemaining[0][1] = whiteTimeRemaining;
10665     timeRemaining[1][1] = blackTimeRemaining;
10666     DrawPosition(FALSE, boards[currentMove]);
10667
10668     return TRUE;
10669 }
10670
10671
10672 void
10673 CopyPlayerNameIntoFileName(dest, src)
10674      char **dest, *src;
10675 {
10676     while (*src != NULLCHAR && *src != ',') {
10677         if (*src == ' ') {
10678             *(*dest)++ = '_';
10679             src++;
10680         } else {
10681             *(*dest)++ = *src++;
10682         }
10683     }
10684 }
10685
10686 char *DefaultFileName(ext)
10687      char *ext;
10688 {
10689     static char def[MSG_SIZ];
10690     char *p;
10691
10692     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10693         p = def;
10694         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10695         *p++ = '-';
10696         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10697         *p++ = '.';
10698         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10699     } else {
10700         def[0] = NULLCHAR;
10701     }
10702     return def;
10703 }
10704
10705 /* Save the current game to the given file */
10706 int
10707 SaveGameToFile(filename, append)
10708      char *filename;
10709      int append;
10710 {
10711     FILE *f;
10712     char buf[MSG_SIZ];
10713
10714     if (strcmp(filename, "-") == 0) {
10715         return SaveGame(stdout, 0, NULL);
10716     } else {
10717         f = fopen(filename, append ? "a" : "w");
10718         if (f == NULL) {
10719             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10720             DisplayError(buf, errno);
10721             return FALSE;
10722         } else {
10723             return SaveGame(f, 0, NULL);
10724         }
10725     }
10726 }
10727
10728 char *
10729 SavePart(str)
10730      char *str;
10731 {
10732     static char buf[MSG_SIZ];
10733     char *p;
10734
10735     p = strchr(str, ' ');
10736     if (p == NULL) return str;
10737     strncpy(buf, str, p - str);
10738     buf[p - str] = NULLCHAR;
10739     return buf;
10740 }
10741
10742 #define PGN_MAX_LINE 75
10743
10744 #define PGN_SIDE_WHITE  0
10745 #define PGN_SIDE_BLACK  1
10746
10747 /* [AS] */
10748 static int FindFirstMoveOutOfBook( int side )
10749 {
10750     int result = -1;
10751
10752     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10753         int index = backwardMostMove;
10754         int has_book_hit = 0;
10755
10756         if( (index % 2) != side ) {
10757             index++;
10758         }
10759
10760         while( index < forwardMostMove ) {
10761             /* Check to see if engine is in book */
10762             int depth = pvInfoList[index].depth;
10763             int score = pvInfoList[index].score;
10764             int in_book = 0;
10765
10766             if( depth <= 2 ) {
10767                 in_book = 1;
10768             }
10769             else if( score == 0 && depth == 63 ) {
10770                 in_book = 1; /* Zappa */
10771             }
10772             else if( score == 2 && depth == 99 ) {
10773                 in_book = 1; /* Abrok */
10774             }
10775
10776             has_book_hit += in_book;
10777
10778             if( ! in_book ) {
10779                 result = index;
10780
10781                 break;
10782             }
10783
10784             index += 2;
10785         }
10786     }
10787
10788     return result;
10789 }
10790
10791 /* [AS] */
10792 void GetOutOfBookInfo( char * buf )
10793 {
10794     int oob[2];
10795     int i;
10796     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10797
10798     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10799     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10800
10801     *buf = '\0';
10802
10803     if( oob[0] >= 0 || oob[1] >= 0 ) {
10804         for( i=0; i<2; i++ ) {
10805             int idx = oob[i];
10806
10807             if( idx >= 0 ) {
10808                 if( i > 0 && oob[0] >= 0 ) {
10809                     strcat( buf, "   " );
10810                 }
10811
10812                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10813                 sprintf( buf+strlen(buf), "%s%.2f",
10814                     pvInfoList[idx].score >= 0 ? "+" : "",
10815                     pvInfoList[idx].score / 100.0 );
10816             }
10817         }
10818     }
10819 }
10820
10821 /* Save game in PGN style and close the file */
10822 int
10823 SaveGamePGN(f)
10824      FILE *f;
10825 {
10826     int i, offset, linelen, newblock;
10827     time_t tm;
10828 //    char *movetext;
10829     char numtext[32];
10830     int movelen, numlen, blank;
10831     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10832
10833     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10834
10835     tm = time((time_t *) NULL);
10836
10837     PrintPGNTags(f, &gameInfo);
10838
10839     if (backwardMostMove > 0 || startedFromSetupPosition) {
10840         char *fen = PositionToFEN(backwardMostMove, NULL);
10841         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10842         fprintf(f, "\n{--------------\n");
10843         PrintPosition(f, backwardMostMove);
10844         fprintf(f, "--------------}\n");
10845         free(fen);
10846     }
10847     else {
10848         /* [AS] Out of book annotation */
10849         if( appData.saveOutOfBookInfo ) {
10850             char buf[64];
10851
10852             GetOutOfBookInfo( buf );
10853
10854             if( buf[0] != '\0' ) {
10855                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10856             }
10857         }
10858
10859         fprintf(f, "\n");
10860     }
10861
10862     i = backwardMostMove;
10863     linelen = 0;
10864     newblock = TRUE;
10865
10866     while (i < forwardMostMove) {
10867         /* Print comments preceding this move */
10868         if (commentList[i] != NULL) {
10869             if (linelen > 0) fprintf(f, "\n");
10870             fprintf(f, "%s", commentList[i]);
10871             linelen = 0;
10872             newblock = TRUE;
10873         }
10874
10875         /* Format move number */
10876         if ((i % 2) == 0)
10877           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10878         else
10879           if (newblock)
10880             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10881           else
10882             numtext[0] = NULLCHAR;
10883
10884         numlen = strlen(numtext);
10885         newblock = FALSE;
10886
10887         /* Print move number */
10888         blank = linelen > 0 && numlen > 0;
10889         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10890             fprintf(f, "\n");
10891             linelen = 0;
10892             blank = 0;
10893         }
10894         if (blank) {
10895             fprintf(f, " ");
10896             linelen++;
10897         }
10898         fprintf(f, "%s", numtext);
10899         linelen += numlen;
10900
10901         /* Get move */
10902         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10903         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10904
10905         /* Print move */
10906         blank = linelen > 0 && movelen > 0;
10907         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10908             fprintf(f, "\n");
10909             linelen = 0;
10910             blank = 0;
10911         }
10912         if (blank) {
10913             fprintf(f, " ");
10914             linelen++;
10915         }
10916         fprintf(f, "%s", move_buffer);
10917         linelen += movelen;
10918
10919         /* [AS] Add PV info if present */
10920         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10921             /* [HGM] add time */
10922             char buf[MSG_SIZ]; int seconds;
10923
10924             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10925
10926             if( seconds <= 0)
10927               buf[0] = 0;
10928             else
10929               if( seconds < 30 )
10930                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10931               else
10932                 {
10933                   seconds = (seconds + 4)/10; // round to full seconds
10934                   if( seconds < 60 )
10935                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10936                   else
10937                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10938                 }
10939
10940             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10941                       pvInfoList[i].score >= 0 ? "+" : "",
10942                       pvInfoList[i].score / 100.0,
10943                       pvInfoList[i].depth,
10944                       buf );
10945
10946             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10947
10948             /* Print score/depth */
10949             blank = linelen > 0 && movelen > 0;
10950             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10951                 fprintf(f, "\n");
10952                 linelen = 0;
10953                 blank = 0;
10954             }
10955             if (blank) {
10956                 fprintf(f, " ");
10957                 linelen++;
10958             }
10959             fprintf(f, "%s", move_buffer);
10960             linelen += movelen;
10961         }
10962
10963         i++;
10964     }
10965
10966     /* Start a new line */
10967     if (linelen > 0) fprintf(f, "\n");
10968
10969     /* Print comments after last move */
10970     if (commentList[i] != NULL) {
10971         fprintf(f, "%s\n", commentList[i]);
10972     }
10973
10974     /* Print result */
10975     if (gameInfo.resultDetails != NULL &&
10976         gameInfo.resultDetails[0] != NULLCHAR) {
10977         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10978                 PGNResult(gameInfo.result));
10979     } else {
10980         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10981     }
10982
10983     fclose(f);
10984     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10985     return TRUE;
10986 }
10987
10988 /* Save game in old style and close the file */
10989 int
10990 SaveGameOldStyle(f)
10991      FILE *f;
10992 {
10993     int i, offset;
10994     time_t tm;
10995
10996     tm = time((time_t *) NULL);
10997
10998     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10999     PrintOpponents(f);
11000
11001     if (backwardMostMove > 0 || startedFromSetupPosition) {
11002         fprintf(f, "\n[--------------\n");
11003         PrintPosition(f, backwardMostMove);
11004         fprintf(f, "--------------]\n");
11005     } else {
11006         fprintf(f, "\n");
11007     }
11008
11009     i = backwardMostMove;
11010     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11011
11012     while (i < forwardMostMove) {
11013         if (commentList[i] != NULL) {
11014             fprintf(f, "[%s]\n", commentList[i]);
11015         }
11016
11017         if ((i % 2) == 1) {
11018             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11019             i++;
11020         } else {
11021             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11022             i++;
11023             if (commentList[i] != NULL) {
11024                 fprintf(f, "\n");
11025                 continue;
11026             }
11027             if (i >= forwardMostMove) {
11028                 fprintf(f, "\n");
11029                 break;
11030             }
11031             fprintf(f, "%s\n", parseList[i]);
11032             i++;
11033         }
11034     }
11035
11036     if (commentList[i] != NULL) {
11037         fprintf(f, "[%s]\n", commentList[i]);
11038     }
11039
11040     /* This isn't really the old style, but it's close enough */
11041     if (gameInfo.resultDetails != NULL &&
11042         gameInfo.resultDetails[0] != NULLCHAR) {
11043         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11044                 gameInfo.resultDetails);
11045     } else {
11046         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11047     }
11048
11049     fclose(f);
11050     return TRUE;
11051 }
11052
11053 /* Save the current game to open file f and close the file */
11054 int
11055 SaveGame(f, dummy, dummy2)
11056      FILE *f;
11057      int dummy;
11058      char *dummy2;
11059 {
11060     if (gameMode == EditPosition) EditPositionDone(TRUE);
11061     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11062     if (appData.oldSaveStyle)
11063       return SaveGameOldStyle(f);
11064     else
11065       return SaveGamePGN(f);
11066 }
11067
11068 /* Save the current position to the given file */
11069 int
11070 SavePositionToFile(filename)
11071      char *filename;
11072 {
11073     FILE *f;
11074     char buf[MSG_SIZ];
11075
11076     if (strcmp(filename, "-") == 0) {
11077         return SavePosition(stdout, 0, NULL);
11078     } else {
11079         f = fopen(filename, "a");
11080         if (f == NULL) {
11081             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11082             DisplayError(buf, errno);
11083             return FALSE;
11084         } else {
11085             SavePosition(f, 0, NULL);
11086             return TRUE;
11087         }
11088     }
11089 }
11090
11091 /* Save the current position to the given open file and close the file */
11092 int
11093 SavePosition(f, dummy, dummy2)
11094      FILE *f;
11095      int dummy;
11096      char *dummy2;
11097 {
11098     time_t tm;
11099     char *fen;
11100
11101     if (gameMode == EditPosition) EditPositionDone(TRUE);
11102     if (appData.oldSaveStyle) {
11103         tm = time((time_t *) NULL);
11104
11105         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11106         PrintOpponents(f);
11107         fprintf(f, "[--------------\n");
11108         PrintPosition(f, currentMove);
11109         fprintf(f, "--------------]\n");
11110     } else {
11111         fen = PositionToFEN(currentMove, NULL);
11112         fprintf(f, "%s\n", fen);
11113         free(fen);
11114     }
11115     fclose(f);
11116     return TRUE;
11117 }
11118
11119 void
11120 ReloadCmailMsgEvent(unregister)
11121      int unregister;
11122 {
11123 #if !WIN32
11124     static char *inFilename = NULL;
11125     static char *outFilename;
11126     int i;
11127     struct stat inbuf, outbuf;
11128     int status;
11129
11130     /* Any registered moves are unregistered if unregister is set, */
11131     /* i.e. invoked by the signal handler */
11132     if (unregister) {
11133         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11134             cmailMoveRegistered[i] = FALSE;
11135             if (cmailCommentList[i] != NULL) {
11136                 free(cmailCommentList[i]);
11137                 cmailCommentList[i] = NULL;
11138             }
11139         }
11140         nCmailMovesRegistered = 0;
11141     }
11142
11143     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11144         cmailResult[i] = CMAIL_NOT_RESULT;
11145     }
11146     nCmailResults = 0;
11147
11148     if (inFilename == NULL) {
11149         /* Because the filenames are static they only get malloced once  */
11150         /* and they never get freed                                      */
11151         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11152         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11153
11154         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11155         sprintf(outFilename, "%s.out", appData.cmailGameName);
11156     }
11157
11158     status = stat(outFilename, &outbuf);
11159     if (status < 0) {
11160         cmailMailedMove = FALSE;
11161     } else {
11162         status = stat(inFilename, &inbuf);
11163         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11164     }
11165
11166     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11167        counts the games, notes how each one terminated, etc.
11168
11169        It would be nice to remove this kludge and instead gather all
11170        the information while building the game list.  (And to keep it
11171        in the game list nodes instead of having a bunch of fixed-size
11172        parallel arrays.)  Note this will require getting each game's
11173        termination from the PGN tags, as the game list builder does
11174        not process the game moves.  --mann
11175        */
11176     cmailMsgLoaded = TRUE;
11177     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11178
11179     /* Load first game in the file or popup game menu */
11180     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11181
11182 #endif /* !WIN32 */
11183     return;
11184 }
11185
11186 int
11187 RegisterMove()
11188 {
11189     FILE *f;
11190     char string[MSG_SIZ];
11191
11192     if (   cmailMailedMove
11193         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11194         return TRUE;            /* Allow free viewing  */
11195     }
11196
11197     /* Unregister move to ensure that we don't leave RegisterMove        */
11198     /* with the move registered when the conditions for registering no   */
11199     /* longer hold                                                       */
11200     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11201         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11202         nCmailMovesRegistered --;
11203
11204         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11205           {
11206               free(cmailCommentList[lastLoadGameNumber - 1]);
11207               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11208           }
11209     }
11210
11211     if (cmailOldMove == -1) {
11212         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11213         return FALSE;
11214     }
11215
11216     if (currentMove > cmailOldMove + 1) {
11217         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11218         return FALSE;
11219     }
11220
11221     if (currentMove < cmailOldMove) {
11222         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11223         return FALSE;
11224     }
11225
11226     if (forwardMostMove > currentMove) {
11227         /* Silently truncate extra moves */
11228         TruncateGame();
11229     }
11230
11231     if (   (currentMove == cmailOldMove + 1)
11232         || (   (currentMove == cmailOldMove)
11233             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11234                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11235         if (gameInfo.result != GameUnfinished) {
11236             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11237         }
11238
11239         if (commentList[currentMove] != NULL) {
11240             cmailCommentList[lastLoadGameNumber - 1]
11241               = StrSave(commentList[currentMove]);
11242         }
11243         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11244
11245         if (appData.debugMode)
11246           fprintf(debugFP, "Saving %s for game %d\n",
11247                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11248
11249         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11250
11251         f = fopen(string, "w");
11252         if (appData.oldSaveStyle) {
11253             SaveGameOldStyle(f); /* also closes the file */
11254
11255             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11256             f = fopen(string, "w");
11257             SavePosition(f, 0, NULL); /* also closes the file */
11258         } else {
11259             fprintf(f, "{--------------\n");
11260             PrintPosition(f, currentMove);
11261             fprintf(f, "--------------}\n\n");
11262
11263             SaveGame(f, 0, NULL); /* also closes the file*/
11264         }
11265
11266         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11267         nCmailMovesRegistered ++;
11268     } else if (nCmailGames == 1) {
11269         DisplayError(_("You have not made a move yet"), 0);
11270         return FALSE;
11271     }
11272
11273     return TRUE;
11274 }
11275
11276 void
11277 MailMoveEvent()
11278 {
11279 #if !WIN32
11280     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11281     FILE *commandOutput;
11282     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11283     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11284     int nBuffers;
11285     int i;
11286     int archived;
11287     char *arcDir;
11288
11289     if (! cmailMsgLoaded) {
11290         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11291         return;
11292     }
11293
11294     if (nCmailGames == nCmailResults) {
11295         DisplayError(_("No unfinished games"), 0);
11296         return;
11297     }
11298
11299 #if CMAIL_PROHIBIT_REMAIL
11300     if (cmailMailedMove) {
11301       snprintf(msg, MSG_SIZ, _("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);
11302         DisplayError(msg, 0);
11303         return;
11304     }
11305 #endif
11306
11307     if (! (cmailMailedMove || RegisterMove())) return;
11308
11309     if (   cmailMailedMove
11310         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11311       snprintf(string, MSG_SIZ, partCommandString,
11312                appData.debugMode ? " -v" : "", appData.cmailGameName);
11313         commandOutput = popen(string, "r");
11314
11315         if (commandOutput == NULL) {
11316             DisplayError(_("Failed to invoke cmail"), 0);
11317         } else {
11318             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11319                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11320             }
11321             if (nBuffers > 1) {
11322                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11323                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11324                 nBytes = MSG_SIZ - 1;
11325             } else {
11326                 (void) memcpy(msg, buffer, nBytes);
11327             }
11328             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11329
11330             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11331                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11332
11333                 archived = TRUE;
11334                 for (i = 0; i < nCmailGames; i ++) {
11335                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11336                         archived = FALSE;
11337                     }
11338                 }
11339                 if (   archived
11340                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11341                         != NULL)) {
11342                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11343                            arcDir,
11344                            appData.cmailGameName,
11345                            gameInfo.date);
11346                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11347                     cmailMsgLoaded = FALSE;
11348                 }
11349             }
11350
11351             DisplayInformation(msg);
11352             pclose(commandOutput);
11353         }
11354     } else {
11355         if ((*cmailMsg) != '\0') {
11356             DisplayInformation(cmailMsg);
11357         }
11358     }
11359
11360     return;
11361 #endif /* !WIN32 */
11362 }
11363
11364 char *
11365 CmailMsg()
11366 {
11367 #if WIN32
11368     return NULL;
11369 #else
11370     int  prependComma = 0;
11371     char number[5];
11372     char string[MSG_SIZ];       /* Space for game-list */
11373     int  i;
11374
11375     if (!cmailMsgLoaded) return "";
11376
11377     if (cmailMailedMove) {
11378       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11379     } else {
11380         /* Create a list of games left */
11381       snprintf(string, MSG_SIZ, "[");
11382         for (i = 0; i < nCmailGames; i ++) {
11383             if (! (   cmailMoveRegistered[i]
11384                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11385                 if (prependComma) {
11386                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11387                 } else {
11388                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11389                     prependComma = 1;
11390                 }
11391
11392                 strcat(string, number);
11393             }
11394         }
11395         strcat(string, "]");
11396
11397         if (nCmailMovesRegistered + nCmailResults == 0) {
11398             switch (nCmailGames) {
11399               case 1:
11400                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11401                 break;
11402
11403               case 2:
11404                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11405                 break;
11406
11407               default:
11408                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11409                          nCmailGames);
11410                 break;
11411             }
11412         } else {
11413             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11414               case 1:
11415                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11416                          string);
11417                 break;
11418
11419               case 0:
11420                 if (nCmailResults == nCmailGames) {
11421                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11422                 } else {
11423                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11424                 }
11425                 break;
11426
11427               default:
11428                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11429                          string);
11430             }
11431         }
11432     }
11433     return cmailMsg;
11434 #endif /* WIN32 */
11435 }
11436
11437 void
11438 ResetGameEvent()
11439 {
11440     if (gameMode == Training)
11441       SetTrainingModeOff();
11442
11443     Reset(TRUE, TRUE);
11444     cmailMsgLoaded = FALSE;
11445     if (appData.icsActive) {
11446       SendToICS(ics_prefix);
11447       SendToICS("refresh\n");
11448     }
11449 }
11450
11451 void
11452 ExitEvent(status)
11453      int status;
11454 {
11455     exiting++;
11456     if (exiting > 2) {
11457       /* Give up on clean exit */
11458       exit(status);
11459     }
11460     if (exiting > 1) {
11461       /* Keep trying for clean exit */
11462       return;
11463     }
11464
11465     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11466
11467     if (telnetISR != NULL) {
11468       RemoveInputSource(telnetISR);
11469     }
11470     if (icsPR != NoProc) {
11471       DestroyChildProcess(icsPR, TRUE);
11472     }
11473
11474     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11475     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11476
11477     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11478     /* make sure this other one finishes before killing it!                  */
11479     if(endingGame) { int count = 0;
11480         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11481         while(endingGame && count++ < 10) DoSleep(1);
11482         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11483     }
11484
11485     /* Kill off chess programs */
11486     if (first.pr != NoProc) {
11487         ExitAnalyzeMode();
11488
11489         DoSleep( appData.delayBeforeQuit );
11490         SendToProgram("quit\n", &first);
11491         DoSleep( appData.delayAfterQuit );
11492         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11493     }
11494     if (second.pr != NoProc) {
11495         DoSleep( appData.delayBeforeQuit );
11496         SendToProgram("quit\n", &second);
11497         DoSleep( appData.delayAfterQuit );
11498         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11499     }
11500     if (first.isr != NULL) {
11501         RemoveInputSource(first.isr);
11502     }
11503     if (second.isr != NULL) {
11504         RemoveInputSource(second.isr);
11505     }
11506
11507     ShutDownFrontEnd();
11508     exit(status);
11509 }
11510
11511 void
11512 PauseEvent()
11513 {
11514     if (appData.debugMode)
11515         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11516     if (pausing) {
11517         pausing = FALSE;
11518         ModeHighlight();
11519         if (gameMode == MachinePlaysWhite ||
11520             gameMode == MachinePlaysBlack) {
11521             StartClocks();
11522         } else {
11523             DisplayBothClocks();
11524         }
11525         if (gameMode == PlayFromGameFile) {
11526             if (appData.timeDelay >= 0)
11527                 AutoPlayGameLoop();
11528         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11529             Reset(FALSE, TRUE);
11530             SendToICS(ics_prefix);
11531             SendToICS("refresh\n");
11532         } else if (currentMove < forwardMostMove) {
11533             ForwardInner(forwardMostMove);
11534         }
11535         pauseExamInvalid = FALSE;
11536     } else {
11537         switch (gameMode) {
11538           default:
11539             return;
11540           case IcsExamining:
11541             pauseExamForwardMostMove = forwardMostMove;
11542             pauseExamInvalid = FALSE;
11543             /* fall through */
11544           case IcsObserving:
11545           case IcsPlayingWhite:
11546           case IcsPlayingBlack:
11547             pausing = TRUE;
11548             ModeHighlight();
11549             return;
11550           case PlayFromGameFile:
11551             (void) StopLoadGameTimer();
11552             pausing = TRUE;
11553             ModeHighlight();
11554             break;
11555           case BeginningOfGame:
11556             if (appData.icsActive) return;
11557             /* else fall through */
11558           case MachinePlaysWhite:
11559           case MachinePlaysBlack:
11560           case TwoMachinesPlay:
11561             if (forwardMostMove == 0)
11562               return;           /* don't pause if no one has moved */
11563             if ((gameMode == MachinePlaysWhite &&
11564                  !WhiteOnMove(forwardMostMove)) ||
11565                 (gameMode == MachinePlaysBlack &&
11566                  WhiteOnMove(forwardMostMove))) {
11567                 StopClocks();
11568             }
11569             pausing = TRUE;
11570             ModeHighlight();
11571             break;
11572         }
11573     }
11574 }
11575
11576 void
11577 EditCommentEvent()
11578 {
11579     char title[MSG_SIZ];
11580
11581     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11582       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11583     } else {
11584       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11585                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11586                parseList[currentMove - 1]);
11587     }
11588
11589     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11590 }
11591
11592
11593 void
11594 EditTagsEvent()
11595 {
11596     char *tags = PGNTags(&gameInfo);
11597     EditTagsPopUp(tags);
11598     free(tags);
11599 }
11600
11601 void
11602 AnalyzeModeEvent()
11603 {
11604     if (appData.noChessProgram || gameMode == AnalyzeMode)
11605       return;
11606
11607     if (gameMode != AnalyzeFile) {
11608         if (!appData.icsEngineAnalyze) {
11609                EditGameEvent();
11610                if (gameMode != EditGame) return;
11611         }
11612         ResurrectChessProgram();
11613         SendToProgram("analyze\n", &first);
11614         first.analyzing = TRUE;
11615         /*first.maybeThinking = TRUE;*/
11616         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11617         EngineOutputPopUp();
11618     }
11619     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11620     pausing = FALSE;
11621     ModeHighlight();
11622     SetGameInfo();
11623
11624     StartAnalysisClock();
11625     GetTimeMark(&lastNodeCountTime);
11626     lastNodeCount = 0;
11627 }
11628
11629 void
11630 AnalyzeFileEvent()
11631 {
11632     if (appData.noChessProgram || gameMode == AnalyzeFile)
11633       return;
11634
11635     if (gameMode != AnalyzeMode) {
11636         EditGameEvent();
11637         if (gameMode != EditGame) return;
11638         ResurrectChessProgram();
11639         SendToProgram("analyze\n", &first);
11640         first.analyzing = TRUE;
11641         /*first.maybeThinking = TRUE;*/
11642         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11643         EngineOutputPopUp();
11644     }
11645     gameMode = AnalyzeFile;
11646     pausing = FALSE;
11647     ModeHighlight();
11648     SetGameInfo();
11649
11650     StartAnalysisClock();
11651     GetTimeMark(&lastNodeCountTime);
11652     lastNodeCount = 0;
11653 }
11654
11655 void
11656 MachineWhiteEvent()
11657 {
11658     char buf[MSG_SIZ];
11659     char *bookHit = NULL;
11660
11661     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11662       return;
11663
11664
11665     if (gameMode == PlayFromGameFile ||
11666         gameMode == TwoMachinesPlay  ||
11667         gameMode == Training         ||
11668         gameMode == AnalyzeMode      ||
11669         gameMode == EndOfGame)
11670         EditGameEvent();
11671
11672     if (gameMode == EditPosition)
11673         EditPositionDone(TRUE);
11674
11675     if (!WhiteOnMove(currentMove)) {
11676         DisplayError(_("It is not White's turn"), 0);
11677         return;
11678     }
11679
11680     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11681       ExitAnalyzeMode();
11682
11683     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11684         gameMode == AnalyzeFile)
11685         TruncateGame();
11686
11687     ResurrectChessProgram();    /* in case it isn't running */
11688     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11689         gameMode = MachinePlaysWhite;
11690         ResetClocks();
11691     } else
11692     gameMode = MachinePlaysWhite;
11693     pausing = FALSE;
11694     ModeHighlight();
11695     SetGameInfo();
11696     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11697     DisplayTitle(buf);
11698     if (first.sendName) {
11699       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11700       SendToProgram(buf, &first);
11701     }
11702     if (first.sendTime) {
11703       if (first.useColors) {
11704         SendToProgram("black\n", &first); /*gnu kludge*/
11705       }
11706       SendTimeRemaining(&first, TRUE);
11707     }
11708     if (first.useColors) {
11709       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11710     }
11711     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11712     SetMachineThinkingEnables();
11713     first.maybeThinking = TRUE;
11714     StartClocks();
11715     firstMove = FALSE;
11716
11717     if (appData.autoFlipView && !flipView) {
11718       flipView = !flipView;
11719       DrawPosition(FALSE, NULL);
11720       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11721     }
11722
11723     if(bookHit) { // [HGM] book: simulate book reply
11724         static char bookMove[MSG_SIZ]; // a bit generous?
11725
11726         programStats.nodes = programStats.depth = programStats.time =
11727         programStats.score = programStats.got_only_move = 0;
11728         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11729
11730         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11731         strcat(bookMove, bookHit);
11732         HandleMachineMove(bookMove, &first);
11733     }
11734 }
11735
11736 void
11737 MachineBlackEvent()
11738 {
11739   char buf[MSG_SIZ];
11740   char *bookHit = NULL;
11741
11742     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11743         return;
11744
11745
11746     if (gameMode == PlayFromGameFile ||
11747         gameMode == TwoMachinesPlay  ||
11748         gameMode == Training         ||
11749         gameMode == AnalyzeMode      ||
11750         gameMode == EndOfGame)
11751         EditGameEvent();
11752
11753     if (gameMode == EditPosition)
11754         EditPositionDone(TRUE);
11755
11756     if (WhiteOnMove(currentMove)) {
11757         DisplayError(_("It is not Black's turn"), 0);
11758         return;
11759     }
11760
11761     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11762       ExitAnalyzeMode();
11763
11764     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11765         gameMode == AnalyzeFile)
11766         TruncateGame();
11767
11768     ResurrectChessProgram();    /* in case it isn't running */
11769     gameMode = MachinePlaysBlack;
11770     pausing = FALSE;
11771     ModeHighlight();
11772     SetGameInfo();
11773     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11774     DisplayTitle(buf);
11775     if (first.sendName) {
11776       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11777       SendToProgram(buf, &first);
11778     }
11779     if (first.sendTime) {
11780       if (first.useColors) {
11781         SendToProgram("white\n", &first); /*gnu kludge*/
11782       }
11783       SendTimeRemaining(&first, FALSE);
11784     }
11785     if (first.useColors) {
11786       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11787     }
11788     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11789     SetMachineThinkingEnables();
11790     first.maybeThinking = TRUE;
11791     StartClocks();
11792
11793     if (appData.autoFlipView && flipView) {
11794       flipView = !flipView;
11795       DrawPosition(FALSE, NULL);
11796       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11797     }
11798     if(bookHit) { // [HGM] book: simulate book reply
11799         static char bookMove[MSG_SIZ]; // a bit generous?
11800
11801         programStats.nodes = programStats.depth = programStats.time =
11802         programStats.score = programStats.got_only_move = 0;
11803         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11804
11805         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11806         strcat(bookMove, bookHit);
11807         HandleMachineMove(bookMove, &first);
11808     }
11809 }
11810
11811
11812 void
11813 DisplayTwoMachinesTitle()
11814 {
11815     char buf[MSG_SIZ];
11816     if (appData.matchGames > 0) {
11817         if (first.twoMachinesColor[0] == 'w') {
11818           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11819                    gameInfo.white, gameInfo.black,
11820                    first.matchWins, second.matchWins,
11821                    matchGame - 1 - (first.matchWins + second.matchWins));
11822         } else {
11823           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11824                    gameInfo.white, gameInfo.black,
11825                    second.matchWins, first.matchWins,
11826                    matchGame - 1 - (first.matchWins + second.matchWins));
11827         }
11828     } else {
11829       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11830     }
11831     DisplayTitle(buf);
11832 }
11833
11834 void
11835 TwoMachinesEvent P((void))
11836 {
11837     int i;
11838     char buf[MSG_SIZ];
11839     ChessProgramState *onmove;
11840     char *bookHit = NULL;
11841
11842     if (appData.noChessProgram) return;
11843
11844     switch (gameMode) {
11845       case TwoMachinesPlay:
11846         return;
11847       case MachinePlaysWhite:
11848       case MachinePlaysBlack:
11849         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11850             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11851             return;
11852         }
11853         /* fall through */
11854       case BeginningOfGame:
11855       case PlayFromGameFile:
11856       case EndOfGame:
11857         EditGameEvent();
11858         if (gameMode != EditGame) return;
11859         break;
11860       case EditPosition:
11861         EditPositionDone(TRUE);
11862         break;
11863       case AnalyzeMode:
11864       case AnalyzeFile:
11865         ExitAnalyzeMode();
11866         break;
11867       case EditGame:
11868       default:
11869         break;
11870     }
11871
11872 //    forwardMostMove = currentMove;
11873     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11874     ResurrectChessProgram();    /* in case first program isn't running */
11875
11876     if (second.pr == NULL) {
11877         StartChessProgram(&second);
11878         if (second.protocolVersion == 1) {
11879           TwoMachinesEventIfReady();
11880         } else {
11881           /* kludge: allow timeout for initial "feature" command */
11882           FreezeUI();
11883           DisplayMessage("", _("Starting second chess program"));
11884           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11885         }
11886         return;
11887     }
11888     DisplayMessage("", "");
11889     InitChessProgram(&second, FALSE);
11890     SendToProgram("force\n", &second);
11891     if (startedFromSetupPosition) {
11892         SendBoard(&second, backwardMostMove);
11893     if (appData.debugMode) {
11894         fprintf(debugFP, "Two Machines\n");
11895     }
11896     }
11897     for (i = backwardMostMove; i < forwardMostMove; i++) {
11898         SendMoveToProgram(i, &second);
11899     }
11900
11901     gameMode = TwoMachinesPlay;
11902     pausing = FALSE;
11903     ModeHighlight();
11904     SetGameInfo();
11905     DisplayTwoMachinesTitle();
11906     firstMove = TRUE;
11907     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11908         onmove = &first;
11909     } else {
11910         onmove = &second;
11911     }
11912
11913     SendToProgram(first.computerString, &first);
11914     if (first.sendName) {
11915       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11916       SendToProgram(buf, &first);
11917     }
11918     SendToProgram(second.computerString, &second);
11919     if (second.sendName) {
11920       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11921       SendToProgram(buf, &second);
11922     }
11923
11924     ResetClocks();
11925     if (!first.sendTime || !second.sendTime) {
11926         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11927         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11928     }
11929     if (onmove->sendTime) {
11930       if (onmove->useColors) {
11931         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11932       }
11933       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11934     }
11935     if (onmove->useColors) {
11936       SendToProgram(onmove->twoMachinesColor, onmove);
11937     }
11938     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11939 //    SendToProgram("go\n", onmove);
11940     onmove->maybeThinking = TRUE;
11941     SetMachineThinkingEnables();
11942
11943     StartClocks();
11944
11945     if(bookHit) { // [HGM] book: simulate book reply
11946         static char bookMove[MSG_SIZ]; // a bit generous?
11947
11948         programStats.nodes = programStats.depth = programStats.time =
11949         programStats.score = programStats.got_only_move = 0;
11950         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11951
11952         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11953         strcat(bookMove, bookHit);
11954         savedMessage = bookMove; // args for deferred call
11955         savedState = onmove;
11956         ScheduleDelayedEvent(DeferredBookMove, 1);
11957     }
11958 }
11959
11960 void
11961 TrainingEvent()
11962 {
11963     if (gameMode == Training) {
11964       SetTrainingModeOff();
11965       gameMode = PlayFromGameFile;
11966       DisplayMessage("", _("Training mode off"));
11967     } else {
11968       gameMode = Training;
11969       animateTraining = appData.animate;
11970
11971       /* make sure we are not already at the end of the game */
11972       if (currentMove < forwardMostMove) {
11973         SetTrainingModeOn();
11974         DisplayMessage("", _("Training mode on"));
11975       } else {
11976         gameMode = PlayFromGameFile;
11977         DisplayError(_("Already at end of game"), 0);
11978       }
11979     }
11980     ModeHighlight();
11981 }
11982
11983 void
11984 IcsClientEvent()
11985 {
11986     if (!appData.icsActive) return;
11987     switch (gameMode) {
11988       case IcsPlayingWhite:
11989       case IcsPlayingBlack:
11990       case IcsObserving:
11991       case IcsIdle:
11992       case BeginningOfGame:
11993       case IcsExamining:
11994         return;
11995
11996       case EditGame:
11997         break;
11998
11999       case EditPosition:
12000         EditPositionDone(TRUE);
12001         break;
12002
12003       case AnalyzeMode:
12004       case AnalyzeFile:
12005         ExitAnalyzeMode();
12006         break;
12007
12008       default:
12009         EditGameEvent();
12010         break;
12011     }
12012
12013     gameMode = IcsIdle;
12014     ModeHighlight();
12015     return;
12016 }
12017
12018
12019 void
12020 EditGameEvent()
12021 {
12022     int i;
12023
12024     switch (gameMode) {
12025       case Training:
12026         SetTrainingModeOff();
12027         break;
12028       case MachinePlaysWhite:
12029       case MachinePlaysBlack:
12030       case BeginningOfGame:
12031         SendToProgram("force\n", &first);
12032         SetUserThinkingEnables();
12033         break;
12034       case PlayFromGameFile:
12035         (void) StopLoadGameTimer();
12036         if (gameFileFP != NULL) {
12037             gameFileFP = NULL;
12038         }
12039         break;
12040       case EditPosition:
12041         EditPositionDone(TRUE);
12042         break;
12043       case AnalyzeMode:
12044       case AnalyzeFile:
12045         ExitAnalyzeMode();
12046         SendToProgram("force\n", &first);
12047         break;
12048       case TwoMachinesPlay:
12049         GameEnds(EndOfFile, NULL, GE_PLAYER);
12050         ResurrectChessProgram();
12051         SetUserThinkingEnables();
12052         break;
12053       case EndOfGame:
12054         ResurrectChessProgram();
12055         break;
12056       case IcsPlayingBlack:
12057       case IcsPlayingWhite:
12058         DisplayError(_("Warning: You are still playing a game"), 0);
12059         break;
12060       case IcsObserving:
12061         DisplayError(_("Warning: You are still observing a game"), 0);
12062         break;
12063       case IcsExamining:
12064         DisplayError(_("Warning: You are still examining a game"), 0);
12065         break;
12066       case IcsIdle:
12067         break;
12068       case EditGame:
12069       default:
12070         return;
12071     }
12072
12073     pausing = FALSE;
12074     StopClocks();
12075     first.offeredDraw = second.offeredDraw = 0;
12076
12077     if (gameMode == PlayFromGameFile) {
12078         whiteTimeRemaining = timeRemaining[0][currentMove];
12079         blackTimeRemaining = timeRemaining[1][currentMove];
12080         DisplayTitle("");
12081     }
12082
12083     if (gameMode == MachinePlaysWhite ||
12084         gameMode == MachinePlaysBlack ||
12085         gameMode == TwoMachinesPlay ||
12086         gameMode == EndOfGame) {
12087         i = forwardMostMove;
12088         while (i > currentMove) {
12089             SendToProgram("undo\n", &first);
12090             i--;
12091         }
12092         whiteTimeRemaining = timeRemaining[0][currentMove];
12093         blackTimeRemaining = timeRemaining[1][currentMove];
12094         DisplayBothClocks();
12095         if (whiteFlag || blackFlag) {
12096             whiteFlag = blackFlag = 0;
12097         }
12098         DisplayTitle("");
12099     }
12100
12101     gameMode = EditGame;
12102     ModeHighlight();
12103     SetGameInfo();
12104 }
12105
12106
12107 void
12108 EditPositionEvent()
12109 {
12110     if (gameMode == EditPosition) {
12111         EditGameEvent();
12112         return;
12113     }
12114
12115     EditGameEvent();
12116     if (gameMode != EditGame) return;
12117
12118     gameMode = EditPosition;
12119     ModeHighlight();
12120     SetGameInfo();
12121     if (currentMove > 0)
12122       CopyBoard(boards[0], boards[currentMove]);
12123
12124     blackPlaysFirst = !WhiteOnMove(currentMove);
12125     ResetClocks();
12126     currentMove = forwardMostMove = backwardMostMove = 0;
12127     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12128     DisplayMove(-1);
12129 }
12130
12131 void
12132 ExitAnalyzeMode()
12133 {
12134     /* [DM] icsEngineAnalyze - possible call from other functions */
12135     if (appData.icsEngineAnalyze) {
12136         appData.icsEngineAnalyze = FALSE;
12137
12138         DisplayMessage("",_("Close ICS engine analyze..."));
12139     }
12140     if (first.analysisSupport && first.analyzing) {
12141       SendToProgram("exit\n", &first);
12142       first.analyzing = FALSE;
12143     }
12144     thinkOutput[0] = NULLCHAR;
12145 }
12146
12147 void
12148 EditPositionDone(Boolean fakeRights)
12149 {
12150     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12151
12152     startedFromSetupPosition = TRUE;
12153     InitChessProgram(&first, FALSE);
12154     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12155       boards[0][EP_STATUS] = EP_NONE;
12156       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12157     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12158         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12159         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12160       } else boards[0][CASTLING][2] = NoRights;
12161     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12162         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12163         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12164       } else boards[0][CASTLING][5] = NoRights;
12165     }
12166     SendToProgram("force\n", &first);
12167     if (blackPlaysFirst) {
12168         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12169         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12170         currentMove = forwardMostMove = backwardMostMove = 1;
12171         CopyBoard(boards[1], boards[0]);
12172     } else {
12173         currentMove = forwardMostMove = backwardMostMove = 0;
12174     }
12175     SendBoard(&first, forwardMostMove);
12176     if (appData.debugMode) {
12177         fprintf(debugFP, "EditPosDone\n");
12178     }
12179     DisplayTitle("");
12180     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12181     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12182     gameMode = EditGame;
12183     ModeHighlight();
12184     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12185     ClearHighlights(); /* [AS] */
12186 }
12187
12188 /* Pause for `ms' milliseconds */
12189 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12190 void
12191 TimeDelay(ms)
12192      long ms;
12193 {
12194     TimeMark m1, m2;
12195
12196     GetTimeMark(&m1);
12197     do {
12198         GetTimeMark(&m2);
12199     } while (SubtractTimeMarks(&m2, &m1) < ms);
12200 }
12201
12202 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12203 void
12204 SendMultiLineToICS(buf)
12205      char *buf;
12206 {
12207     char temp[MSG_SIZ+1], *p;
12208     int len;
12209
12210     len = strlen(buf);
12211     if (len > MSG_SIZ)
12212       len = MSG_SIZ;
12213
12214     strncpy(temp, buf, len);
12215     temp[len] = 0;
12216
12217     p = temp;
12218     while (*p) {
12219         if (*p == '\n' || *p == '\r')
12220           *p = ' ';
12221         ++p;
12222     }
12223
12224     strcat(temp, "\n");
12225     SendToICS(temp);
12226     SendToPlayer(temp, strlen(temp));
12227 }
12228
12229 void
12230 SetWhiteToPlayEvent()
12231 {
12232     if (gameMode == EditPosition) {
12233         blackPlaysFirst = FALSE;
12234         DisplayBothClocks();    /* works because currentMove is 0 */
12235     } else if (gameMode == IcsExamining) {
12236         SendToICS(ics_prefix);
12237         SendToICS("tomove white\n");
12238     }
12239 }
12240
12241 void
12242 SetBlackToPlayEvent()
12243 {
12244     if (gameMode == EditPosition) {
12245         blackPlaysFirst = TRUE;
12246         currentMove = 1;        /* kludge */
12247         DisplayBothClocks();
12248         currentMove = 0;
12249     } else if (gameMode == IcsExamining) {
12250         SendToICS(ics_prefix);
12251         SendToICS("tomove black\n");
12252     }
12253 }
12254
12255 void
12256 EditPositionMenuEvent(selection, x, y)
12257      ChessSquare selection;
12258      int x, y;
12259 {
12260     char buf[MSG_SIZ];
12261     ChessSquare piece = boards[0][y][x];
12262
12263     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12264
12265     switch (selection) {
12266       case ClearBoard:
12267         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12268             SendToICS(ics_prefix);
12269             SendToICS("bsetup clear\n");
12270         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12271             SendToICS(ics_prefix);
12272             SendToICS("clearboard\n");
12273         } else {
12274             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12275                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12276                 for (y = 0; y < BOARD_HEIGHT; y++) {
12277                     if (gameMode == IcsExamining) {
12278                         if (boards[currentMove][y][x] != EmptySquare) {
12279                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12280                                     AAA + x, ONE + y);
12281                             SendToICS(buf);
12282                         }
12283                     } else {
12284                         boards[0][y][x] = p;
12285                     }
12286                 }
12287             }
12288         }
12289         if (gameMode == EditPosition) {
12290             DrawPosition(FALSE, boards[0]);
12291         }
12292         break;
12293
12294       case WhitePlay:
12295         SetWhiteToPlayEvent();
12296         break;
12297
12298       case BlackPlay:
12299         SetBlackToPlayEvent();
12300         break;
12301
12302       case EmptySquare:
12303         if (gameMode == IcsExamining) {
12304             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12305             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12306             SendToICS(buf);
12307         } else {
12308             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12309                 if(x == BOARD_LEFT-2) {
12310                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12311                     boards[0][y][1] = 0;
12312                 } else
12313                 if(x == BOARD_RGHT+1) {
12314                     if(y >= gameInfo.holdingsSize) break;
12315                     boards[0][y][BOARD_WIDTH-2] = 0;
12316                 } else break;
12317             }
12318             boards[0][y][x] = EmptySquare;
12319             DrawPosition(FALSE, boards[0]);
12320         }
12321         break;
12322
12323       case PromotePiece:
12324         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12325            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12326             selection = (ChessSquare) (PROMOTED piece);
12327         } else if(piece == EmptySquare) selection = WhiteSilver;
12328         else selection = (ChessSquare)((int)piece - 1);
12329         goto defaultlabel;
12330
12331       case DemotePiece:
12332         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12333            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12334             selection = (ChessSquare) (DEMOTED piece);
12335         } else if(piece == EmptySquare) selection = BlackSilver;
12336         else selection = (ChessSquare)((int)piece + 1);
12337         goto defaultlabel;
12338
12339       case WhiteQueen:
12340       case BlackQueen:
12341         if(gameInfo.variant == VariantShatranj ||
12342            gameInfo.variant == VariantXiangqi  ||
12343            gameInfo.variant == VariantCourier  ||
12344            gameInfo.variant == VariantMakruk     )
12345             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12346         goto defaultlabel;
12347
12348       case WhiteKing:
12349       case BlackKing:
12350         if(gameInfo.variant == VariantXiangqi)
12351             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12352         if(gameInfo.variant == VariantKnightmate)
12353             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12354       default:
12355         defaultlabel:
12356         if (gameMode == IcsExamining) {
12357             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12358             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12359                      PieceToChar(selection), AAA + x, ONE + y);
12360             SendToICS(buf);
12361         } else {
12362             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12363                 int n;
12364                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12365                     n = PieceToNumber(selection - BlackPawn);
12366                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12367                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12368                     boards[0][BOARD_HEIGHT-1-n][1]++;
12369                 } else
12370                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12371                     n = PieceToNumber(selection);
12372                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12373                     boards[0][n][BOARD_WIDTH-1] = selection;
12374                     boards[0][n][BOARD_WIDTH-2]++;
12375                 }
12376             } else
12377             boards[0][y][x] = selection;
12378             DrawPosition(TRUE, boards[0]);
12379         }
12380         break;
12381     }
12382 }
12383
12384
12385 void
12386 DropMenuEvent(selection, x, y)
12387      ChessSquare selection;
12388      int x, y;
12389 {
12390     ChessMove moveType;
12391
12392     switch (gameMode) {
12393       case IcsPlayingWhite:
12394       case MachinePlaysBlack:
12395         if (!WhiteOnMove(currentMove)) {
12396             DisplayMoveError(_("It is Black's turn"));
12397             return;
12398         }
12399         moveType = WhiteDrop;
12400         break;
12401       case IcsPlayingBlack:
12402       case MachinePlaysWhite:
12403         if (WhiteOnMove(currentMove)) {
12404             DisplayMoveError(_("It is White's turn"));
12405             return;
12406         }
12407         moveType = BlackDrop;
12408         break;
12409       case EditGame:
12410         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12411         break;
12412       default:
12413         return;
12414     }
12415
12416     if (moveType == BlackDrop && selection < BlackPawn) {
12417       selection = (ChessSquare) ((int) selection
12418                                  + (int) BlackPawn - (int) WhitePawn);
12419     }
12420     if (boards[currentMove][y][x] != EmptySquare) {
12421         DisplayMoveError(_("That square is occupied"));
12422         return;
12423     }
12424
12425     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12426 }
12427
12428 void
12429 AcceptEvent()
12430 {
12431     /* Accept a pending offer of any kind from opponent */
12432
12433     if (appData.icsActive) {
12434         SendToICS(ics_prefix);
12435         SendToICS("accept\n");
12436     } else if (cmailMsgLoaded) {
12437         if (currentMove == cmailOldMove &&
12438             commentList[cmailOldMove] != NULL &&
12439             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12440                    "Black offers a draw" : "White offers a draw")) {
12441             TruncateGame();
12442             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12443             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12444         } else {
12445             DisplayError(_("There is no pending offer on this move"), 0);
12446             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12447         }
12448     } else {
12449         /* Not used for offers from chess program */
12450     }
12451 }
12452
12453 void
12454 DeclineEvent()
12455 {
12456     /* Decline a pending offer of any kind from opponent */
12457
12458     if (appData.icsActive) {
12459         SendToICS(ics_prefix);
12460         SendToICS("decline\n");
12461     } else if (cmailMsgLoaded) {
12462         if (currentMove == cmailOldMove &&
12463             commentList[cmailOldMove] != NULL &&
12464             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12465                    "Black offers a draw" : "White offers a draw")) {
12466 #ifdef NOTDEF
12467             AppendComment(cmailOldMove, "Draw declined", TRUE);
12468             DisplayComment(cmailOldMove - 1, "Draw declined");
12469 #endif /*NOTDEF*/
12470         } else {
12471             DisplayError(_("There is no pending offer on this move"), 0);
12472         }
12473     } else {
12474         /* Not used for offers from chess program */
12475     }
12476 }
12477
12478 void
12479 RematchEvent()
12480 {
12481     /* Issue ICS rematch command */
12482     if (appData.icsActive) {
12483         SendToICS(ics_prefix);
12484         SendToICS("rematch\n");
12485     }
12486 }
12487
12488 void
12489 CallFlagEvent()
12490 {
12491     /* Call your opponent's flag (claim a win on time) */
12492     if (appData.icsActive) {
12493         SendToICS(ics_prefix);
12494         SendToICS("flag\n");
12495     } else {
12496         switch (gameMode) {
12497           default:
12498             return;
12499           case MachinePlaysWhite:
12500             if (whiteFlag) {
12501                 if (blackFlag)
12502                   GameEnds(GameIsDrawn, "Both players ran out of time",
12503                            GE_PLAYER);
12504                 else
12505                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12506             } else {
12507                 DisplayError(_("Your opponent is not out of time"), 0);
12508             }
12509             break;
12510           case MachinePlaysBlack:
12511             if (blackFlag) {
12512                 if (whiteFlag)
12513                   GameEnds(GameIsDrawn, "Both players ran out of time",
12514                            GE_PLAYER);
12515                 else
12516                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12517             } else {
12518                 DisplayError(_("Your opponent is not out of time"), 0);
12519             }
12520             break;
12521         }
12522     }
12523 }
12524
12525 void
12526 DrawEvent()
12527 {
12528     /* Offer draw or accept pending draw offer from opponent */
12529
12530     if (appData.icsActive) {
12531         /* Note: tournament rules require draw offers to be
12532            made after you make your move but before you punch
12533            your clock.  Currently ICS doesn't let you do that;
12534            instead, you immediately punch your clock after making
12535            a move, but you can offer a draw at any time. */
12536
12537         SendToICS(ics_prefix);
12538         SendToICS("draw\n");
12539         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12540     } else if (cmailMsgLoaded) {
12541         if (currentMove == cmailOldMove &&
12542             commentList[cmailOldMove] != NULL &&
12543             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12544                    "Black offers a draw" : "White offers a draw")) {
12545             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12546             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12547         } else if (currentMove == cmailOldMove + 1) {
12548             char *offer = WhiteOnMove(cmailOldMove) ?
12549               "White offers a draw" : "Black offers a draw";
12550             AppendComment(currentMove, offer, TRUE);
12551             DisplayComment(currentMove - 1, offer);
12552             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12553         } else {
12554             DisplayError(_("You must make your move before offering a draw"), 0);
12555             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12556         }
12557     } else if (first.offeredDraw) {
12558         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12559     } else {
12560         if (first.sendDrawOffers) {
12561             SendToProgram("draw\n", &first);
12562             userOfferedDraw = TRUE;
12563         }
12564     }
12565 }
12566
12567 void
12568 AdjournEvent()
12569 {
12570     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12571
12572     if (appData.icsActive) {
12573         SendToICS(ics_prefix);
12574         SendToICS("adjourn\n");
12575     } else {
12576         /* Currently GNU Chess doesn't offer or accept Adjourns */
12577     }
12578 }
12579
12580
12581 void
12582 AbortEvent()
12583 {
12584     /* Offer Abort or accept pending Abort offer from opponent */
12585
12586     if (appData.icsActive) {
12587         SendToICS(ics_prefix);
12588         SendToICS("abort\n");
12589     } else {
12590         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12591     }
12592 }
12593
12594 void
12595 ResignEvent()
12596 {
12597     /* Resign.  You can do this even if it's not your turn. */
12598
12599     if (appData.icsActive) {
12600         SendToICS(ics_prefix);
12601         SendToICS("resign\n");
12602     } else {
12603         switch (gameMode) {
12604           case MachinePlaysWhite:
12605             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12606             break;
12607           case MachinePlaysBlack:
12608             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12609             break;
12610           case EditGame:
12611             if (cmailMsgLoaded) {
12612                 TruncateGame();
12613                 if (WhiteOnMove(cmailOldMove)) {
12614                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12615                 } else {
12616                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12617                 }
12618                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12619             }
12620             break;
12621           default:
12622             break;
12623         }
12624     }
12625 }
12626
12627
12628 void
12629 StopObservingEvent()
12630 {
12631     /* Stop observing current games */
12632     SendToICS(ics_prefix);
12633     SendToICS("unobserve\n");
12634 }
12635
12636 void
12637 StopExaminingEvent()
12638 {
12639     /* Stop observing current game */
12640     SendToICS(ics_prefix);
12641     SendToICS("unexamine\n");
12642 }
12643
12644 void
12645 ForwardInner(target)
12646      int target;
12647 {
12648     int limit;
12649
12650     if (appData.debugMode)
12651         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12652                 target, currentMove, forwardMostMove);
12653
12654     if (gameMode == EditPosition)
12655       return;
12656
12657     if (gameMode == PlayFromGameFile && !pausing)
12658       PauseEvent();
12659
12660     if (gameMode == IcsExamining && pausing)
12661       limit = pauseExamForwardMostMove;
12662     else
12663       limit = forwardMostMove;
12664
12665     if (target > limit) target = limit;
12666
12667     if (target > 0 && moveList[target - 1][0]) {
12668         int fromX, fromY, toX, toY;
12669         toX = moveList[target - 1][2] - AAA;
12670         toY = moveList[target - 1][3] - ONE;
12671         if (moveList[target - 1][1] == '@') {
12672             if (appData.highlightLastMove) {
12673                 SetHighlights(-1, -1, toX, toY);
12674             }
12675         } else {
12676             fromX = moveList[target - 1][0] - AAA;
12677             fromY = moveList[target - 1][1] - ONE;
12678             if (target == currentMove + 1) {
12679                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12680             }
12681             if (appData.highlightLastMove) {
12682                 SetHighlights(fromX, fromY, toX, toY);
12683             }
12684         }
12685     }
12686     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12687         gameMode == Training || gameMode == PlayFromGameFile ||
12688         gameMode == AnalyzeFile) {
12689         while (currentMove < target) {
12690             SendMoveToProgram(currentMove++, &first);
12691         }
12692     } else {
12693         currentMove = target;
12694     }
12695
12696     if (gameMode == EditGame || gameMode == EndOfGame) {
12697         whiteTimeRemaining = timeRemaining[0][currentMove];
12698         blackTimeRemaining = timeRemaining[1][currentMove];
12699     }
12700     DisplayBothClocks();
12701     DisplayMove(currentMove - 1);
12702     DrawPosition(FALSE, boards[currentMove]);
12703     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12704     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12705         DisplayComment(currentMove - 1, commentList[currentMove]);
12706     }
12707 }
12708
12709
12710 void
12711 ForwardEvent()
12712 {
12713     if (gameMode == IcsExamining && !pausing) {
12714         SendToICS(ics_prefix);
12715         SendToICS("forward\n");
12716     } else {
12717         ForwardInner(currentMove + 1);
12718     }
12719 }
12720
12721 void
12722 ToEndEvent()
12723 {
12724     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12725         /* to optimze, we temporarily turn off analysis mode while we feed
12726          * the remaining moves to the engine. Otherwise we get analysis output
12727          * after each move.
12728          */
12729         if (first.analysisSupport) {
12730           SendToProgram("exit\nforce\n", &first);
12731           first.analyzing = FALSE;
12732         }
12733     }
12734
12735     if (gameMode == IcsExamining && !pausing) {
12736         SendToICS(ics_prefix);
12737         SendToICS("forward 999999\n");
12738     } else {
12739         ForwardInner(forwardMostMove);
12740     }
12741
12742     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12743         /* we have fed all the moves, so reactivate analysis mode */
12744         SendToProgram("analyze\n", &first);
12745         first.analyzing = TRUE;
12746         /*first.maybeThinking = TRUE;*/
12747         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12748     }
12749 }
12750
12751 void
12752 BackwardInner(target)
12753      int target;
12754 {
12755     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12756
12757     if (appData.debugMode)
12758         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12759                 target, currentMove, forwardMostMove);
12760
12761     if (gameMode == EditPosition) return;
12762     if (currentMove <= backwardMostMove) {
12763         ClearHighlights();
12764         DrawPosition(full_redraw, boards[currentMove]);
12765         return;
12766     }
12767     if (gameMode == PlayFromGameFile && !pausing)
12768       PauseEvent();
12769
12770     if (moveList[target][0]) {
12771         int fromX, fromY, toX, toY;
12772         toX = moveList[target][2] - AAA;
12773         toY = moveList[target][3] - ONE;
12774         if (moveList[target][1] == '@') {
12775             if (appData.highlightLastMove) {
12776                 SetHighlights(-1, -1, toX, toY);
12777             }
12778         } else {
12779             fromX = moveList[target][0] - AAA;
12780             fromY = moveList[target][1] - ONE;
12781             if (target == currentMove - 1) {
12782                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12783             }
12784             if (appData.highlightLastMove) {
12785                 SetHighlights(fromX, fromY, toX, toY);
12786             }
12787         }
12788     }
12789     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12790         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12791         while (currentMove > target) {
12792             SendToProgram("undo\n", &first);
12793             currentMove--;
12794         }
12795     } else {
12796         currentMove = target;
12797     }
12798
12799     if (gameMode == EditGame || gameMode == EndOfGame) {
12800         whiteTimeRemaining = timeRemaining[0][currentMove];
12801         blackTimeRemaining = timeRemaining[1][currentMove];
12802     }
12803     DisplayBothClocks();
12804     DisplayMove(currentMove - 1);
12805     DrawPosition(full_redraw, boards[currentMove]);
12806     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12807     // [HGM] PV info: routine tests if comment empty
12808     DisplayComment(currentMove - 1, commentList[currentMove]);
12809 }
12810
12811 void
12812 BackwardEvent()
12813 {
12814     if (gameMode == IcsExamining && !pausing) {
12815         SendToICS(ics_prefix);
12816         SendToICS("backward\n");
12817     } else {
12818         BackwardInner(currentMove - 1);
12819     }
12820 }
12821
12822 void
12823 ToStartEvent()
12824 {
12825     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12826         /* to optimize, we temporarily turn off analysis mode while we undo
12827          * all the moves. Otherwise we get analysis output after each undo.
12828          */
12829         if (first.analysisSupport) {
12830           SendToProgram("exit\nforce\n", &first);
12831           first.analyzing = FALSE;
12832         }
12833     }
12834
12835     if (gameMode == IcsExamining && !pausing) {
12836         SendToICS(ics_prefix);
12837         SendToICS("backward 999999\n");
12838     } else {
12839         BackwardInner(backwardMostMove);
12840     }
12841
12842     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12843         /* we have fed all the moves, so reactivate analysis mode */
12844         SendToProgram("analyze\n", &first);
12845         first.analyzing = TRUE;
12846         /*first.maybeThinking = TRUE;*/
12847         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12848     }
12849 }
12850
12851 void
12852 ToNrEvent(int to)
12853 {
12854   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12855   if (to >= forwardMostMove) to = forwardMostMove;
12856   if (to <= backwardMostMove) to = backwardMostMove;
12857   if (to < currentMove) {
12858     BackwardInner(to);
12859   } else {
12860     ForwardInner(to);
12861   }
12862 }
12863
12864 void
12865 RevertEvent(Boolean annotate)
12866 {
12867     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12868         return;
12869     }
12870     if (gameMode != IcsExamining) {
12871         DisplayError(_("You are not examining a game"), 0);
12872         return;
12873     }
12874     if (pausing) {
12875         DisplayError(_("You can't revert while pausing"), 0);
12876         return;
12877     }
12878     SendToICS(ics_prefix);
12879     SendToICS("revert\n");
12880 }
12881
12882 void
12883 RetractMoveEvent()
12884 {
12885     switch (gameMode) {
12886       case MachinePlaysWhite:
12887       case MachinePlaysBlack:
12888         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12889             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12890             return;
12891         }
12892         if (forwardMostMove < 2) return;
12893         currentMove = forwardMostMove = forwardMostMove - 2;
12894         whiteTimeRemaining = timeRemaining[0][currentMove];
12895         blackTimeRemaining = timeRemaining[1][currentMove];
12896         DisplayBothClocks();
12897         DisplayMove(currentMove - 1);
12898         ClearHighlights();/*!! could figure this out*/
12899         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12900         SendToProgram("remove\n", &first);
12901         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12902         break;
12903
12904       case BeginningOfGame:
12905       default:
12906         break;
12907
12908       case IcsPlayingWhite:
12909       case IcsPlayingBlack:
12910         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12911             SendToICS(ics_prefix);
12912             SendToICS("takeback 2\n");
12913         } else {
12914             SendToICS(ics_prefix);
12915             SendToICS("takeback 1\n");
12916         }
12917         break;
12918     }
12919 }
12920
12921 void
12922 MoveNowEvent()
12923 {
12924     ChessProgramState *cps;
12925
12926     switch (gameMode) {
12927       case MachinePlaysWhite:
12928         if (!WhiteOnMove(forwardMostMove)) {
12929             DisplayError(_("It is your turn"), 0);
12930             return;
12931         }
12932         cps = &first;
12933         break;
12934       case MachinePlaysBlack:
12935         if (WhiteOnMove(forwardMostMove)) {
12936             DisplayError(_("It is your turn"), 0);
12937             return;
12938         }
12939         cps = &first;
12940         break;
12941       case TwoMachinesPlay:
12942         if (WhiteOnMove(forwardMostMove) ==
12943             (first.twoMachinesColor[0] == 'w')) {
12944             cps = &first;
12945         } else {
12946             cps = &second;
12947         }
12948         break;
12949       case BeginningOfGame:
12950       default:
12951         return;
12952     }
12953     SendToProgram("?\n", cps);
12954 }
12955
12956 void
12957 TruncateGameEvent()
12958 {
12959     EditGameEvent();
12960     if (gameMode != EditGame) return;
12961     TruncateGame();
12962 }
12963
12964 void
12965 TruncateGame()
12966 {
12967     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12968     if (forwardMostMove > currentMove) {
12969         if (gameInfo.resultDetails != NULL) {
12970             free(gameInfo.resultDetails);
12971             gameInfo.resultDetails = NULL;
12972             gameInfo.result = GameUnfinished;
12973         }
12974         forwardMostMove = currentMove;
12975         HistorySet(parseList, backwardMostMove, forwardMostMove,
12976                    currentMove-1);
12977     }
12978 }
12979
12980 void
12981 HintEvent()
12982 {
12983     if (appData.noChessProgram) return;
12984     switch (gameMode) {
12985       case MachinePlaysWhite:
12986         if (WhiteOnMove(forwardMostMove)) {
12987             DisplayError(_("Wait until your turn"), 0);
12988             return;
12989         }
12990         break;
12991       case BeginningOfGame:
12992       case MachinePlaysBlack:
12993         if (!WhiteOnMove(forwardMostMove)) {
12994             DisplayError(_("Wait until your turn"), 0);
12995             return;
12996         }
12997         break;
12998       default:
12999         DisplayError(_("No hint available"), 0);
13000         return;
13001     }
13002     SendToProgram("hint\n", &first);
13003     hintRequested = TRUE;
13004 }
13005
13006 void
13007 BookEvent()
13008 {
13009     if (appData.noChessProgram) return;
13010     switch (gameMode) {
13011       case MachinePlaysWhite:
13012         if (WhiteOnMove(forwardMostMove)) {
13013             DisplayError(_("Wait until your turn"), 0);
13014             return;
13015         }
13016         break;
13017       case BeginningOfGame:
13018       case MachinePlaysBlack:
13019         if (!WhiteOnMove(forwardMostMove)) {
13020             DisplayError(_("Wait until your turn"), 0);
13021             return;
13022         }
13023         break;
13024       case EditPosition:
13025         EditPositionDone(TRUE);
13026         break;
13027       case TwoMachinesPlay:
13028         return;
13029       default:
13030         break;
13031     }
13032     SendToProgram("bk\n", &first);
13033     bookOutput[0] = NULLCHAR;
13034     bookRequested = TRUE;
13035 }
13036
13037 void
13038 AboutGameEvent()
13039 {
13040     char *tags = PGNTags(&gameInfo);
13041     TagsPopUp(tags, CmailMsg());
13042     free(tags);
13043 }
13044
13045 /* end button procedures */
13046
13047 void
13048 PrintPosition(fp, move)
13049      FILE *fp;
13050      int move;
13051 {
13052     int i, j;
13053
13054     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13055         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13056             char c = PieceToChar(boards[move][i][j]);
13057             fputc(c == 'x' ? '.' : c, fp);
13058             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13059         }
13060     }
13061     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13062       fprintf(fp, "white to play\n");
13063     else
13064       fprintf(fp, "black to play\n");
13065 }
13066
13067 void
13068 PrintOpponents(fp)
13069      FILE *fp;
13070 {
13071     if (gameInfo.white != NULL) {
13072         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13073     } else {
13074         fprintf(fp, "\n");
13075     }
13076 }
13077
13078 /* Find last component of program's own name, using some heuristics */
13079 void
13080 TidyProgramName(prog, host, buf)
13081      char *prog, *host, buf[MSG_SIZ];
13082 {
13083     char *p, *q;
13084     int local = (strcmp(host, "localhost") == 0);
13085     while (!local && (p = strchr(prog, ';')) != NULL) {
13086         p++;
13087         while (*p == ' ') p++;
13088         prog = p;
13089     }
13090     if (*prog == '"' || *prog == '\'') {
13091         q = strchr(prog + 1, *prog);
13092     } else {
13093         q = strchr(prog, ' ');
13094     }
13095     if (q == NULL) q = prog + strlen(prog);
13096     p = q;
13097     while (p >= prog && *p != '/' && *p != '\\') p--;
13098     p++;
13099     if(p == prog && *p == '"') p++;
13100     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13101     memcpy(buf, p, q - p);
13102     buf[q - p] = NULLCHAR;
13103     if (!local) {
13104         strcat(buf, "@");
13105         strcat(buf, host);
13106     }
13107 }
13108
13109 char *
13110 TimeControlTagValue()
13111 {
13112     char buf[MSG_SIZ];
13113     if (!appData.clockMode) {
13114       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13115     } else if (movesPerSession > 0) {
13116       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13117     } else if (timeIncrement == 0) {
13118       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13119     } else {
13120       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13121     }
13122     return StrSave(buf);
13123 }
13124
13125 void
13126 SetGameInfo()
13127 {
13128     /* This routine is used only for certain modes */
13129     VariantClass v = gameInfo.variant;
13130     ChessMove r = GameUnfinished;
13131     char *p = NULL;
13132
13133     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13134         r = gameInfo.result;
13135         p = gameInfo.resultDetails;
13136         gameInfo.resultDetails = NULL;
13137     }
13138     ClearGameInfo(&gameInfo);
13139     gameInfo.variant = v;
13140
13141     switch (gameMode) {
13142       case MachinePlaysWhite:
13143         gameInfo.event = StrSave( appData.pgnEventHeader );
13144         gameInfo.site = StrSave(HostName());
13145         gameInfo.date = PGNDate();
13146         gameInfo.round = StrSave("-");
13147         gameInfo.white = StrSave(first.tidy);
13148         gameInfo.black = StrSave(UserName());
13149         gameInfo.timeControl = TimeControlTagValue();
13150         break;
13151
13152       case MachinePlaysBlack:
13153         gameInfo.event = StrSave( appData.pgnEventHeader );
13154         gameInfo.site = StrSave(HostName());
13155         gameInfo.date = PGNDate();
13156         gameInfo.round = StrSave("-");
13157         gameInfo.white = StrSave(UserName());
13158         gameInfo.black = StrSave(first.tidy);
13159         gameInfo.timeControl = TimeControlTagValue();
13160         break;
13161
13162       case TwoMachinesPlay:
13163         gameInfo.event = StrSave( appData.pgnEventHeader );
13164         gameInfo.site = StrSave(HostName());
13165         gameInfo.date = PGNDate();
13166         if (matchGame > 0) {
13167             char buf[MSG_SIZ];
13168             snprintf(buf, MSG_SIZ, "%d", matchGame);
13169             gameInfo.round = StrSave(buf);
13170         } else {
13171             gameInfo.round = StrSave("-");
13172         }
13173         if (first.twoMachinesColor[0] == 'w') {
13174             gameInfo.white = StrSave(first.tidy);
13175             gameInfo.black = StrSave(second.tidy);
13176         } else {
13177             gameInfo.white = StrSave(second.tidy);
13178             gameInfo.black = StrSave(first.tidy);
13179         }
13180         gameInfo.timeControl = TimeControlTagValue();
13181         break;
13182
13183       case EditGame:
13184         gameInfo.event = StrSave("Edited game");
13185         gameInfo.site = StrSave(HostName());
13186         gameInfo.date = PGNDate();
13187         gameInfo.round = StrSave("-");
13188         gameInfo.white = StrSave("-");
13189         gameInfo.black = StrSave("-");
13190         gameInfo.result = r;
13191         gameInfo.resultDetails = p;
13192         break;
13193
13194       case EditPosition:
13195         gameInfo.event = StrSave("Edited position");
13196         gameInfo.site = StrSave(HostName());
13197         gameInfo.date = PGNDate();
13198         gameInfo.round = StrSave("-");
13199         gameInfo.white = StrSave("-");
13200         gameInfo.black = StrSave("-");
13201         break;
13202
13203       case IcsPlayingWhite:
13204       case IcsPlayingBlack:
13205       case IcsObserving:
13206       case IcsExamining:
13207         break;
13208
13209       case PlayFromGameFile:
13210         gameInfo.event = StrSave("Game from non-PGN file");
13211         gameInfo.site = StrSave(HostName());
13212         gameInfo.date = PGNDate();
13213         gameInfo.round = StrSave("-");
13214         gameInfo.white = StrSave("?");
13215         gameInfo.black = StrSave("?");
13216         break;
13217
13218       default:
13219         break;
13220     }
13221 }
13222
13223 void
13224 ReplaceComment(index, text)
13225      int index;
13226      char *text;
13227 {
13228     int len;
13229
13230     while (*text == '\n') text++;
13231     len = strlen(text);
13232     while (len > 0 && text[len - 1] == '\n') len--;
13233
13234     if (commentList[index] != NULL)
13235       free(commentList[index]);
13236
13237     if (len == 0) {
13238         commentList[index] = NULL;
13239         return;
13240     }
13241   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13242       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13243       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13244     commentList[index] = (char *) malloc(len + 2);
13245     strncpy(commentList[index], text, len);
13246     commentList[index][len] = '\n';
13247     commentList[index][len + 1] = NULLCHAR;
13248   } else {
13249     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13250     char *p;
13251     commentList[index] = (char *) malloc(len + 7);
13252     safeStrCpy(commentList[index], "{\n", 3);
13253     safeStrCpy(commentList[index]+2, text, len+1);
13254     commentList[index][len+2] = NULLCHAR;
13255     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13256     strcat(commentList[index], "\n}\n");
13257   }
13258 }
13259
13260 void
13261 CrushCRs(text)
13262      char *text;
13263 {
13264   char *p = text;
13265   char *q = text;
13266   char ch;
13267
13268   do {
13269     ch = *p++;
13270     if (ch == '\r') continue;
13271     *q++ = ch;
13272   } while (ch != '\0');
13273 }
13274
13275 void
13276 AppendComment(index, text, addBraces)
13277      int index;
13278      char *text;
13279      Boolean addBraces; // [HGM] braces: tells if we should add {}
13280 {
13281     int oldlen, len;
13282     char *old;
13283
13284 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13285     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13286
13287     CrushCRs(text);
13288     while (*text == '\n') text++;
13289     len = strlen(text);
13290     while (len > 0 && text[len - 1] == '\n') len--;
13291
13292     if (len == 0) return;
13293
13294     if (commentList[index] != NULL) {
13295         old = commentList[index];
13296         oldlen = strlen(old);
13297         while(commentList[index][oldlen-1] ==  '\n')
13298           commentList[index][--oldlen] = NULLCHAR;
13299         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13300         safeStrCpy(commentList[index], old, oldlen);
13301         free(old);
13302         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13303         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13304           if(addBraces) addBraces = FALSE; else { text++; len--; }
13305           while (*text == '\n') { text++; len--; }
13306           commentList[index][--oldlen] = NULLCHAR;
13307       }
13308         if(addBraces) strcat(commentList[index], "\n{\n");
13309         else          strcat(commentList[index], "\n");
13310         strcat(commentList[index], text);
13311         if(addBraces) strcat(commentList[index], "\n}\n");
13312         else          strcat(commentList[index], "\n");
13313     } else {
13314         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13315         if(addBraces)
13316           safeStrCpy(commentList[index], "{\n", sizeof(commentList[index])/sizeof(commentList[index][0]));
13317         else commentList[index][0] = NULLCHAR;
13318         strcat(commentList[index], text);
13319         strcat(commentList[index], "\n");
13320         if(addBraces) strcat(commentList[index], "}\n");
13321     }
13322 }
13323
13324 static char * FindStr( char * text, char * sub_text )
13325 {
13326     char * result = strstr( text, sub_text );
13327
13328     if( result != NULL ) {
13329         result += strlen( sub_text );
13330     }
13331
13332     return result;
13333 }
13334
13335 /* [AS] Try to extract PV info from PGN comment */
13336 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13337 char *GetInfoFromComment( int index, char * text )
13338 {
13339     char * sep = text;
13340
13341     if( text != NULL && index > 0 ) {
13342         int score = 0;
13343         int depth = 0;
13344         int time = -1, sec = 0, deci;
13345         char * s_eval = FindStr( text, "[%eval " );
13346         char * s_emt = FindStr( text, "[%emt " );
13347
13348         if( s_eval != NULL || s_emt != NULL ) {
13349             /* New style */
13350             char delim;
13351
13352             if( s_eval != NULL ) {
13353                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13354                     return text;
13355                 }
13356
13357                 if( delim != ']' ) {
13358                     return text;
13359                 }
13360             }
13361
13362             if( s_emt != NULL ) {
13363             }
13364                 return text;
13365         }
13366         else {
13367             /* We expect something like: [+|-]nnn.nn/dd */
13368             int score_lo = 0;
13369
13370             if(*text != '{') return text; // [HGM] braces: must be normal comment
13371
13372             sep = strchr( text, '/' );
13373             if( sep == NULL || sep < (text+4) ) {
13374                 return text;
13375             }
13376
13377             time = -1; sec = -1; deci = -1;
13378             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13379                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13380                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13381                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13382                 return text;
13383             }
13384
13385             if( score_lo < 0 || score_lo >= 100 ) {
13386                 return text;
13387             }
13388
13389             if(sec >= 0) time = 600*time + 10*sec; else
13390             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13391
13392             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13393
13394             /* [HGM] PV time: now locate end of PV info */
13395             while( *++sep >= '0' && *sep <= '9'); // strip depth
13396             if(time >= 0)
13397             while( *++sep >= '0' && *sep <= '9'); // strip time
13398             if(sec >= 0)
13399             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13400             if(deci >= 0)
13401             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13402             while(*sep == ' ') sep++;
13403         }
13404
13405         if( depth <= 0 ) {
13406             return text;
13407         }
13408
13409         if( time < 0 ) {
13410             time = -1;
13411         }
13412
13413         pvInfoList[index-1].depth = depth;
13414         pvInfoList[index-1].score = score;
13415         pvInfoList[index-1].time  = 10*time; // centi-sec
13416         if(*sep == '}') *sep = 0; else *--sep = '{';
13417     }
13418     return sep;
13419 }
13420
13421 void
13422 SendToProgram(message, cps)
13423      char *message;
13424      ChessProgramState *cps;
13425 {
13426     int count, outCount, error;
13427     char buf[MSG_SIZ];
13428
13429     if (cps->pr == NULL) return;
13430     Attention(cps);
13431
13432     if (appData.debugMode) {
13433         TimeMark now;
13434         GetTimeMark(&now);
13435         fprintf(debugFP, "%ld >%-6s: %s",
13436                 SubtractTimeMarks(&now, &programStartTime),
13437                 cps->which, message);
13438     }
13439
13440     count = strlen(message);
13441     outCount = OutputToProcess(cps->pr, message, count, &error);
13442     if (outCount < count && !exiting
13443                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13444       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13445         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13446             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13447                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13448                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13449             } else {
13450                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13451             }
13452             gameInfo.resultDetails = StrSave(buf);
13453         }
13454         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13455     }
13456 }
13457
13458 void
13459 ReceiveFromProgram(isr, closure, message, count, error)
13460      InputSourceRef isr;
13461      VOIDSTAR closure;
13462      char *message;
13463      int count;
13464      int error;
13465 {
13466     char *end_str;
13467     char buf[MSG_SIZ];
13468     ChessProgramState *cps = (ChessProgramState *)closure;
13469
13470     if (isr != cps->isr) return; /* Killed intentionally */
13471     if (count <= 0) {
13472         if (count == 0) {
13473             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13474                     cps->which, cps->program);
13475         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13476                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13477                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13478                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13479                 } else {
13480                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13481                 }
13482                 gameInfo.resultDetails = StrSave(buf);
13483             }
13484             RemoveInputSource(cps->isr);
13485             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13486         } else {
13487             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13488                     cps->which, cps->program);
13489             RemoveInputSource(cps->isr);
13490
13491             /* [AS] Program is misbehaving badly... kill it */
13492             if( count == -2 ) {
13493                 DestroyChildProcess( cps->pr, 9 );
13494                 cps->pr = NoProc;
13495             }
13496
13497             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13498         }
13499         return;
13500     }
13501
13502     if ((end_str = strchr(message, '\r')) != NULL)
13503       *end_str = NULLCHAR;
13504     if ((end_str = strchr(message, '\n')) != NULL)
13505       *end_str = NULLCHAR;
13506
13507     if (appData.debugMode) {
13508         TimeMark now; int print = 1;
13509         char *quote = ""; char c; int i;
13510
13511         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13512                 char start = message[0];
13513                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13514                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13515                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13516                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13517                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13518                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13519                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13520                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13521                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13522                     print = (appData.engineComments >= 2);
13523                 }
13524                 message[0] = start; // restore original message
13525         }
13526         if(print) {
13527                 GetTimeMark(&now);
13528                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13529                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13530                         quote,
13531                         message);
13532         }
13533     }
13534
13535     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13536     if (appData.icsEngineAnalyze) {
13537         if (strstr(message, "whisper") != NULL ||
13538              strstr(message, "kibitz") != NULL ||
13539             strstr(message, "tellics") != NULL) return;
13540     }
13541
13542     HandleMachineMove(message, cps);
13543 }
13544
13545
13546 void
13547 SendTimeControl(cps, mps, tc, inc, sd, st)
13548      ChessProgramState *cps;
13549      int mps, inc, sd, st;
13550      long tc;
13551 {
13552     char buf[MSG_SIZ];
13553     int seconds;
13554
13555     if( timeControl_2 > 0 ) {
13556         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13557             tc = timeControl_2;
13558         }
13559     }
13560     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13561     inc /= cps->timeOdds;
13562     st  /= cps->timeOdds;
13563
13564     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13565
13566     if (st > 0) {
13567       /* Set exact time per move, normally using st command */
13568       if (cps->stKludge) {
13569         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13570         seconds = st % 60;
13571         if (seconds == 0) {
13572           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13573         } else {
13574           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13575         }
13576       } else {
13577         snprintf(buf, MSG_SIZ, "st %d\n", st);
13578       }
13579     } else {
13580       /* Set conventional or incremental time control, using level command */
13581       if (seconds == 0) {
13582         /* Note old gnuchess bug -- minutes:seconds used to not work.
13583            Fixed in later versions, but still avoid :seconds
13584            when seconds is 0. */
13585         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13586       } else {
13587         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13588                  seconds, inc/1000.);
13589       }
13590     }
13591     SendToProgram(buf, cps);
13592
13593     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13594     /* Orthogonally, limit search to given depth */
13595     if (sd > 0) {
13596       if (cps->sdKludge) {
13597         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13598       } else {
13599         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13600       }
13601       SendToProgram(buf, cps);
13602     }
13603
13604     if(cps->nps > 0) { /* [HGM] nps */
13605         if(cps->supportsNPS == FALSE)
13606           cps->nps = -1; // don't use if engine explicitly says not supported!
13607         else {
13608           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13609           SendToProgram(buf, cps);
13610         }
13611     }
13612 }
13613
13614 ChessProgramState *WhitePlayer()
13615 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13616 {
13617     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13618        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13619         return &second;
13620     return &first;
13621 }
13622
13623 void
13624 SendTimeRemaining(cps, machineWhite)
13625      ChessProgramState *cps;
13626      int /*boolean*/ machineWhite;
13627 {
13628     char message[MSG_SIZ];
13629     long time, otime;
13630
13631     /* Note: this routine must be called when the clocks are stopped
13632        or when they have *just* been set or switched; otherwise
13633        it will be off by the time since the current tick started.
13634     */
13635     if (machineWhite) {
13636         time = whiteTimeRemaining / 10;
13637         otime = blackTimeRemaining / 10;
13638     } else {
13639         time = blackTimeRemaining / 10;
13640         otime = whiteTimeRemaining / 10;
13641     }
13642     /* [HGM] translate opponent's time by time-odds factor */
13643     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13644     if (appData.debugMode) {
13645         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13646     }
13647
13648     if (time <= 0) time = 1;
13649     if (otime <= 0) otime = 1;
13650
13651     snprintf(message, MSG_SIZ, "time %ld\n", time);
13652     SendToProgram(message, cps);
13653
13654     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13655     SendToProgram(message, cps);
13656 }
13657
13658 int
13659 BoolFeature(p, name, loc, cps)
13660      char **p;
13661      char *name;
13662      int *loc;
13663      ChessProgramState *cps;
13664 {
13665   char buf[MSG_SIZ];
13666   int len = strlen(name);
13667   int val;
13668
13669   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13670     (*p) += len + 1;
13671     sscanf(*p, "%d", &val);
13672     *loc = (val != 0);
13673     while (**p && **p != ' ')
13674       (*p)++;
13675     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13676     SendToProgram(buf, cps);
13677     return TRUE;
13678   }
13679   return FALSE;
13680 }
13681
13682 int
13683 IntFeature(p, name, loc, cps)
13684      char **p;
13685      char *name;
13686      int *loc;
13687      ChessProgramState *cps;
13688 {
13689   char buf[MSG_SIZ];
13690   int len = strlen(name);
13691   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13692     (*p) += len + 1;
13693     sscanf(*p, "%d", loc);
13694     while (**p && **p != ' ') (*p)++;
13695     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13696     SendToProgram(buf, cps);
13697     return TRUE;
13698   }
13699   return FALSE;
13700 }
13701
13702 int
13703 StringFeature(p, name, loc, cps)
13704      char **p;
13705      char *name;
13706      char loc[];
13707      ChessProgramState *cps;
13708 {
13709   char buf[MSG_SIZ];
13710   int len = strlen(name);
13711   if (strncmp((*p), name, len) == 0
13712       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13713     (*p) += len + 2;
13714     sscanf(*p, "%[^\"]", loc);
13715     while (**p && **p != '\"') (*p)++;
13716     if (**p == '\"') (*p)++;
13717     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13718     SendToProgram(buf, cps);
13719     return TRUE;
13720   }
13721   return FALSE;
13722 }
13723
13724 int
13725 ParseOption(Option *opt, ChessProgramState *cps)
13726 // [HGM] options: process the string that defines an engine option, and determine
13727 // name, type, default value, and allowed value range
13728 {
13729         char *p, *q, buf[MSG_SIZ];
13730         int n, min = (-1)<<31, max = 1<<31, def;
13731
13732         if(p = strstr(opt->name, " -spin ")) {
13733             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13734             if(max < min) max = min; // enforce consistency
13735             if(def < min) def = min;
13736             if(def > max) def = max;
13737             opt->value = def;
13738             opt->min = min;
13739             opt->max = max;
13740             opt->type = Spin;
13741         } else if((p = strstr(opt->name, " -slider "))) {
13742             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13743             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13744             if(max < min) max = min; // enforce consistency
13745             if(def < min) def = min;
13746             if(def > max) def = max;
13747             opt->value = def;
13748             opt->min = min;
13749             opt->max = max;
13750             opt->type = Spin; // Slider;
13751         } else if((p = strstr(opt->name, " -string "))) {
13752             opt->textValue = p+9;
13753             opt->type = TextBox;
13754         } else if((p = strstr(opt->name, " -file "))) {
13755             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13756             opt->textValue = p+7;
13757             opt->type = TextBox; // FileName;
13758         } else if((p = strstr(opt->name, " -path "))) {
13759             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13760             opt->textValue = p+7;
13761             opt->type = TextBox; // PathName;
13762         } else if(p = strstr(opt->name, " -check ")) {
13763             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13764             opt->value = (def != 0);
13765             opt->type = CheckBox;
13766         } else if(p = strstr(opt->name, " -combo ")) {
13767             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13768             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13769             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13770             opt->value = n = 0;
13771             while(q = StrStr(q, " /// ")) {
13772                 n++; *q = 0;    // count choices, and null-terminate each of them
13773                 q += 5;
13774                 if(*q == '*') { // remember default, which is marked with * prefix
13775                     q++;
13776                     opt->value = n;
13777                 }
13778                 cps->comboList[cps->comboCnt++] = q;
13779             }
13780             cps->comboList[cps->comboCnt++] = NULL;
13781             opt->max = n + 1;
13782             opt->type = ComboBox;
13783         } else if(p = strstr(opt->name, " -button")) {
13784             opt->type = Button;
13785         } else if(p = strstr(opt->name, " -save")) {
13786             opt->type = SaveButton;
13787         } else return FALSE;
13788         *p = 0; // terminate option name
13789         // now look if the command-line options define a setting for this engine option.
13790         if(cps->optionSettings && cps->optionSettings[0])
13791             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13792         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13793           snprintf(buf, MSG_SIZ, "option %s", p);
13794                 if(p = strstr(buf, ",")) *p = 0;
13795                 strcat(buf, "\n");
13796                 SendToProgram(buf, cps);
13797         }
13798         return TRUE;
13799 }
13800
13801 void
13802 FeatureDone(cps, val)
13803      ChessProgramState* cps;
13804      int val;
13805 {
13806   DelayedEventCallback cb = GetDelayedEvent();
13807   if ((cb == InitBackEnd3 && cps == &first) ||
13808       (cb == TwoMachinesEventIfReady && cps == &second)) {
13809     CancelDelayedEvent();
13810     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13811   }
13812   cps->initDone = val;
13813 }
13814
13815 /* Parse feature command from engine */
13816 void
13817 ParseFeatures(args, cps)
13818      char* args;
13819      ChessProgramState *cps;
13820 {
13821   char *p = args;
13822   char *q;
13823   int val;
13824   char buf[MSG_SIZ];
13825
13826   for (;;) {
13827     while (*p == ' ') p++;
13828     if (*p == NULLCHAR) return;
13829
13830     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13831     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13832     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13833     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13834     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13835     if (BoolFeature(&p, "reuse", &val, cps)) {
13836       /* Engine can disable reuse, but can't enable it if user said no */
13837       if (!val) cps->reuse = FALSE;
13838       continue;
13839     }
13840     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13841     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13842       if (gameMode == TwoMachinesPlay) {
13843         DisplayTwoMachinesTitle();
13844       } else {
13845         DisplayTitle("");
13846       }
13847       continue;
13848     }
13849     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13850     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13851     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13852     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13853     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13854     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13855     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13856     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13857     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13858     if (IntFeature(&p, "done", &val, cps)) {
13859       FeatureDone(cps, val);
13860       continue;
13861     }
13862     /* Added by Tord: */
13863     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13864     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13865     /* End of additions by Tord */
13866
13867     /* [HGM] added features: */
13868     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13869     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13870     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13871     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13872     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13873     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13874     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13875         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13876           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13877             SendToProgram(buf, cps);
13878             continue;
13879         }
13880         if(cps->nrOptions >= MAX_OPTIONS) {
13881             cps->nrOptions--;
13882             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13883             DisplayError(buf, 0);
13884         }
13885         continue;
13886     }
13887     /* End of additions by HGM */
13888
13889     /* unknown feature: complain and skip */
13890     q = p;
13891     while (*q && *q != '=') q++;
13892     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13893     SendToProgram(buf, cps);
13894     p = q;
13895     if (*p == '=') {
13896       p++;
13897       if (*p == '\"') {
13898         p++;
13899         while (*p && *p != '\"') p++;
13900         if (*p == '\"') p++;
13901       } else {
13902         while (*p && *p != ' ') p++;
13903       }
13904     }
13905   }
13906
13907 }
13908
13909 void
13910 PeriodicUpdatesEvent(newState)
13911      int newState;
13912 {
13913     if (newState == appData.periodicUpdates)
13914       return;
13915
13916     appData.periodicUpdates=newState;
13917
13918     /* Display type changes, so update it now */
13919 //    DisplayAnalysis();
13920
13921     /* Get the ball rolling again... */
13922     if (newState) {
13923         AnalysisPeriodicEvent(1);
13924         StartAnalysisClock();
13925     }
13926 }
13927
13928 void
13929 PonderNextMoveEvent(newState)
13930      int newState;
13931 {
13932     if (newState == appData.ponderNextMove) return;
13933     if (gameMode == EditPosition) EditPositionDone(TRUE);
13934     if (newState) {
13935         SendToProgram("hard\n", &first);
13936         if (gameMode == TwoMachinesPlay) {
13937             SendToProgram("hard\n", &second);
13938         }
13939     } else {
13940         SendToProgram("easy\n", &first);
13941         thinkOutput[0] = NULLCHAR;
13942         if (gameMode == TwoMachinesPlay) {
13943             SendToProgram("easy\n", &second);
13944         }
13945     }
13946     appData.ponderNextMove = newState;
13947 }
13948
13949 void
13950 NewSettingEvent(option, feature, command, value)
13951      char *command;
13952      int option, value, *feature;
13953 {
13954     char buf[MSG_SIZ];
13955
13956     if (gameMode == EditPosition) EditPositionDone(TRUE);
13957     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
13958     if(feature == NULL || *feature) SendToProgram(buf, &first);
13959     if (gameMode == TwoMachinesPlay) {
13960         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13961     }
13962 }
13963
13964 void
13965 ShowThinkingEvent()
13966 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13967 {
13968     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13969     int newState = appData.showThinking
13970         // [HGM] thinking: other features now need thinking output as well
13971         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13972
13973     if (oldState == newState) return;
13974     oldState = newState;
13975     if (gameMode == EditPosition) EditPositionDone(TRUE);
13976     if (oldState) {
13977         SendToProgram("post\n", &first);
13978         if (gameMode == TwoMachinesPlay) {
13979             SendToProgram("post\n", &second);
13980         }
13981     } else {
13982         SendToProgram("nopost\n", &first);
13983         thinkOutput[0] = NULLCHAR;
13984         if (gameMode == TwoMachinesPlay) {
13985             SendToProgram("nopost\n", &second);
13986         }
13987     }
13988 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13989 }
13990
13991 void
13992 AskQuestionEvent(title, question, replyPrefix, which)
13993      char *title; char *question; char *replyPrefix; char *which;
13994 {
13995   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13996   if (pr == NoProc) return;
13997   AskQuestion(title, question, replyPrefix, pr);
13998 }
13999
14000 void
14001 DisplayMove(moveNumber)
14002      int moveNumber;
14003 {
14004     char message[MSG_SIZ];
14005     char res[MSG_SIZ];
14006     char cpThinkOutput[MSG_SIZ];
14007
14008     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14009
14010     if (moveNumber == forwardMostMove - 1 ||
14011         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14012
14013         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14014
14015         if (strchr(cpThinkOutput, '\n')) {
14016             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14017         }
14018     } else {
14019         *cpThinkOutput = NULLCHAR;
14020     }
14021
14022     /* [AS] Hide thinking from human user */
14023     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14024         *cpThinkOutput = NULLCHAR;
14025         if( thinkOutput[0] != NULLCHAR ) {
14026             int i;
14027
14028             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14029                 cpThinkOutput[i] = '.';
14030             }
14031             cpThinkOutput[i] = NULLCHAR;
14032             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14033         }
14034     }
14035
14036     if (moveNumber == forwardMostMove - 1 &&
14037         gameInfo.resultDetails != NULL) {
14038         if (gameInfo.resultDetails[0] == NULLCHAR) {
14039           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14040         } else {
14041           snprintf(res, MSG_SIZ, " {%s} %s",
14042                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14043         }
14044     } else {
14045         res[0] = NULLCHAR;
14046     }
14047
14048     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14049         DisplayMessage(res, cpThinkOutput);
14050     } else {
14051       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14052                 WhiteOnMove(moveNumber) ? " " : ".. ",
14053                 parseList[moveNumber], res);
14054         DisplayMessage(message, cpThinkOutput);
14055     }
14056 }
14057
14058 void
14059 DisplayComment(moveNumber, text)
14060      int moveNumber;
14061      char *text;
14062 {
14063     char title[MSG_SIZ];
14064     char buf[8000]; // comment can be long!
14065     int score, depth;
14066
14067     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14068       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14069     } else {
14070       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14071               WhiteOnMove(moveNumber) ? " " : ".. ",
14072               parseList[moveNumber]);
14073     }
14074     // [HGM] PV info: display PV info together with (or as) comment
14075     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14076       if(text == NULL) text = "";
14077       score = pvInfoList[moveNumber].score;
14078       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14079               depth, (pvInfoList[moveNumber].time+50)/100, text);
14080       text = buf;
14081     }
14082     if (text != NULL && (appData.autoDisplayComment || commentUp))
14083         CommentPopUp(title, text);
14084 }
14085
14086 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14087  * might be busy thinking or pondering.  It can be omitted if your
14088  * gnuchess is configured to stop thinking immediately on any user
14089  * input.  However, that gnuchess feature depends on the FIONREAD
14090  * ioctl, which does not work properly on some flavors of Unix.
14091  */
14092 void
14093 Attention(cps)
14094      ChessProgramState *cps;
14095 {
14096 #if ATTENTION
14097     if (!cps->useSigint) return;
14098     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14099     switch (gameMode) {
14100       case MachinePlaysWhite:
14101       case MachinePlaysBlack:
14102       case TwoMachinesPlay:
14103       case IcsPlayingWhite:
14104       case IcsPlayingBlack:
14105       case AnalyzeMode:
14106       case AnalyzeFile:
14107         /* Skip if we know it isn't thinking */
14108         if (!cps->maybeThinking) return;
14109         if (appData.debugMode)
14110           fprintf(debugFP, "Interrupting %s\n", cps->which);
14111         InterruptChildProcess(cps->pr);
14112         cps->maybeThinking = FALSE;
14113         break;
14114       default:
14115         break;
14116     }
14117 #endif /*ATTENTION*/
14118 }
14119
14120 int
14121 CheckFlags()
14122 {
14123     if (whiteTimeRemaining <= 0) {
14124         if (!whiteFlag) {
14125             whiteFlag = TRUE;
14126             if (appData.icsActive) {
14127                 if (appData.autoCallFlag &&
14128                     gameMode == IcsPlayingBlack && !blackFlag) {
14129                   SendToICS(ics_prefix);
14130                   SendToICS("flag\n");
14131                 }
14132             } else {
14133                 if (blackFlag) {
14134                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14135                 } else {
14136                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14137                     if (appData.autoCallFlag) {
14138                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14139                         return TRUE;
14140                     }
14141                 }
14142             }
14143         }
14144     }
14145     if (blackTimeRemaining <= 0) {
14146         if (!blackFlag) {
14147             blackFlag = TRUE;
14148             if (appData.icsActive) {
14149                 if (appData.autoCallFlag &&
14150                     gameMode == IcsPlayingWhite && !whiteFlag) {
14151                   SendToICS(ics_prefix);
14152                   SendToICS("flag\n");
14153                 }
14154             } else {
14155                 if (whiteFlag) {
14156                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14157                 } else {
14158                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14159                     if (appData.autoCallFlag) {
14160                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14161                         return TRUE;
14162                     }
14163                 }
14164             }
14165         }
14166     }
14167     return FALSE;
14168 }
14169
14170 void
14171 CheckTimeControl()
14172 {
14173     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14174         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14175
14176     /*
14177      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14178      */
14179     if ( !WhiteOnMove(forwardMostMove) ) {
14180         /* White made time control */
14181         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14182         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14183         /* [HGM] time odds: correct new time quota for time odds! */
14184                                             / WhitePlayer()->timeOdds;
14185         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14186     } else {
14187         lastBlack -= blackTimeRemaining;
14188         /* Black made time control */
14189         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14190                                             / WhitePlayer()->other->timeOdds;
14191         lastWhite = whiteTimeRemaining;
14192     }
14193 }
14194
14195 void
14196 DisplayBothClocks()
14197 {
14198     int wom = gameMode == EditPosition ?
14199       !blackPlaysFirst : WhiteOnMove(currentMove);
14200     DisplayWhiteClock(whiteTimeRemaining, wom);
14201     DisplayBlackClock(blackTimeRemaining, !wom);
14202 }
14203
14204
14205 /* Timekeeping seems to be a portability nightmare.  I think everyone
14206    has ftime(), but I'm really not sure, so I'm including some ifdefs
14207    to use other calls if you don't.  Clocks will be less accurate if
14208    you have neither ftime nor gettimeofday.
14209 */
14210
14211 /* VS 2008 requires the #include outside of the function */
14212 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14213 #include <sys/timeb.h>
14214 #endif
14215
14216 /* Get the current time as a TimeMark */
14217 void
14218 GetTimeMark(tm)
14219      TimeMark *tm;
14220 {
14221 #if HAVE_GETTIMEOFDAY
14222
14223     struct timeval timeVal;
14224     struct timezone timeZone;
14225
14226     gettimeofday(&timeVal, &timeZone);
14227     tm->sec = (long) timeVal.tv_sec;
14228     tm->ms = (int) (timeVal.tv_usec / 1000L);
14229
14230 #else /*!HAVE_GETTIMEOFDAY*/
14231 #if HAVE_FTIME
14232
14233 // include <sys/timeb.h> / moved to just above start of function
14234     struct timeb timeB;
14235
14236     ftime(&timeB);
14237     tm->sec = (long) timeB.time;
14238     tm->ms = (int) timeB.millitm;
14239
14240 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14241     tm->sec = (long) time(NULL);
14242     tm->ms = 0;
14243 #endif
14244 #endif
14245 }
14246
14247 /* Return the difference in milliseconds between two
14248    time marks.  We assume the difference will fit in a long!
14249 */
14250 long
14251 SubtractTimeMarks(tm2, tm1)
14252      TimeMark *tm2, *tm1;
14253 {
14254     return 1000L*(tm2->sec - tm1->sec) +
14255            (long) (tm2->ms - tm1->ms);
14256 }
14257
14258
14259 /*
14260  * Code to manage the game clocks.
14261  *
14262  * In tournament play, black starts the clock and then white makes a move.
14263  * We give the human user a slight advantage if he is playing white---the
14264  * clocks don't run until he makes his first move, so it takes zero time.
14265  * Also, we don't account for network lag, so we could get out of sync
14266  * with GNU Chess's clock -- but then, referees are always right.
14267  */
14268
14269 static TimeMark tickStartTM;
14270 static long intendedTickLength;
14271
14272 long
14273 NextTickLength(timeRemaining)
14274      long timeRemaining;
14275 {
14276     long nominalTickLength, nextTickLength;
14277
14278     if (timeRemaining > 0L && timeRemaining <= 10000L)
14279       nominalTickLength = 100L;
14280     else
14281       nominalTickLength = 1000L;
14282     nextTickLength = timeRemaining % nominalTickLength;
14283     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14284
14285     return nextTickLength;
14286 }
14287
14288 /* Adjust clock one minute up or down */
14289 void
14290 AdjustClock(Boolean which, int dir)
14291 {
14292     if(which) blackTimeRemaining += 60000*dir;
14293     else      whiteTimeRemaining += 60000*dir;
14294     DisplayBothClocks();
14295 }
14296
14297 /* Stop clocks and reset to a fresh time control */
14298 void
14299 ResetClocks()
14300 {
14301     (void) StopClockTimer();
14302     if (appData.icsActive) {
14303         whiteTimeRemaining = blackTimeRemaining = 0;
14304     } else if (searchTime) {
14305         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14306         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14307     } else { /* [HGM] correct new time quote for time odds */
14308         whiteTC = blackTC = fullTimeControlString;
14309         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14310         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14311     }
14312     if (whiteFlag || blackFlag) {
14313         DisplayTitle("");
14314         whiteFlag = blackFlag = FALSE;
14315     }
14316     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14317     DisplayBothClocks();
14318 }
14319
14320 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14321
14322 /* Decrement running clock by amount of time that has passed */
14323 void
14324 DecrementClocks()
14325 {
14326     long timeRemaining;
14327     long lastTickLength, fudge;
14328     TimeMark now;
14329
14330     if (!appData.clockMode) return;
14331     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14332
14333     GetTimeMark(&now);
14334
14335     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14336
14337     /* Fudge if we woke up a little too soon */
14338     fudge = intendedTickLength - lastTickLength;
14339     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14340
14341     if (WhiteOnMove(forwardMostMove)) {
14342         if(whiteNPS >= 0) lastTickLength = 0;
14343         timeRemaining = whiteTimeRemaining -= lastTickLength;
14344         if(timeRemaining < 0 && !appData.icsActive) {
14345             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14346             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14347                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14348                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14349             }
14350         }
14351         DisplayWhiteClock(whiteTimeRemaining - fudge,
14352                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14353     } else {
14354         if(blackNPS >= 0) lastTickLength = 0;
14355         timeRemaining = blackTimeRemaining -= lastTickLength;
14356         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14357             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14358             if(suddenDeath) {
14359                 blackStartMove = forwardMostMove;
14360                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14361             }
14362         }
14363         DisplayBlackClock(blackTimeRemaining - fudge,
14364                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14365     }
14366     if (CheckFlags()) return;
14367
14368     tickStartTM = now;
14369     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14370     StartClockTimer(intendedTickLength);
14371
14372     /* if the time remaining has fallen below the alarm threshold, sound the
14373      * alarm. if the alarm has sounded and (due to a takeback or time control
14374      * with increment) the time remaining has increased to a level above the
14375      * threshold, reset the alarm so it can sound again.
14376      */
14377
14378     if (appData.icsActive && appData.icsAlarm) {
14379
14380         /* make sure we are dealing with the user's clock */
14381         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14382                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14383            )) return;
14384
14385         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14386             alarmSounded = FALSE;
14387         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14388             PlayAlarmSound();
14389             alarmSounded = TRUE;
14390         }
14391     }
14392 }
14393
14394
14395 /* A player has just moved, so stop the previously running
14396    clock and (if in clock mode) start the other one.
14397    We redisplay both clocks in case we're in ICS mode, because
14398    ICS gives us an update to both clocks after every move.
14399    Note that this routine is called *after* forwardMostMove
14400    is updated, so the last fractional tick must be subtracted
14401    from the color that is *not* on move now.
14402 */
14403 void
14404 SwitchClocks(int newMoveNr)
14405 {
14406     long lastTickLength;
14407     TimeMark now;
14408     int flagged = FALSE;
14409
14410     GetTimeMark(&now);
14411
14412     if (StopClockTimer() && appData.clockMode) {
14413         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14414         if (!WhiteOnMove(forwardMostMove)) {
14415             if(blackNPS >= 0) lastTickLength = 0;
14416             blackTimeRemaining -= lastTickLength;
14417            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14418 //         if(pvInfoList[forwardMostMove-1].time == -1)
14419                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14420                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14421         } else {
14422            if(whiteNPS >= 0) lastTickLength = 0;
14423            whiteTimeRemaining -= lastTickLength;
14424            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14425 //         if(pvInfoList[forwardMostMove-1].time == -1)
14426                  pvInfoList[forwardMostMove-1].time =
14427                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14428         }
14429         flagged = CheckFlags();
14430     }
14431     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14432     CheckTimeControl();
14433
14434     if (flagged || !appData.clockMode) return;
14435
14436     switch (gameMode) {
14437       case MachinePlaysBlack:
14438       case MachinePlaysWhite:
14439       case BeginningOfGame:
14440         if (pausing) return;
14441         break;
14442
14443       case EditGame:
14444       case PlayFromGameFile:
14445       case IcsExamining:
14446         return;
14447
14448       default:
14449         break;
14450     }
14451
14452     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14453         if(WhiteOnMove(forwardMostMove))
14454              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14455         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14456     }
14457
14458     tickStartTM = now;
14459     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14460       whiteTimeRemaining : blackTimeRemaining);
14461     StartClockTimer(intendedTickLength);
14462 }
14463
14464
14465 /* Stop both clocks */
14466 void
14467 StopClocks()
14468 {
14469     long lastTickLength;
14470     TimeMark now;
14471
14472     if (!StopClockTimer()) return;
14473     if (!appData.clockMode) return;
14474
14475     GetTimeMark(&now);
14476
14477     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14478     if (WhiteOnMove(forwardMostMove)) {
14479         if(whiteNPS >= 0) lastTickLength = 0;
14480         whiteTimeRemaining -= lastTickLength;
14481         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14482     } else {
14483         if(blackNPS >= 0) lastTickLength = 0;
14484         blackTimeRemaining -= lastTickLength;
14485         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14486     }
14487     CheckFlags();
14488 }
14489
14490 /* Start clock of player on move.  Time may have been reset, so
14491    if clock is already running, stop and restart it. */
14492 void
14493 StartClocks()
14494 {
14495     (void) StopClockTimer(); /* in case it was running already */
14496     DisplayBothClocks();
14497     if (CheckFlags()) return;
14498
14499     if (!appData.clockMode) return;
14500     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14501
14502     GetTimeMark(&tickStartTM);
14503     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14504       whiteTimeRemaining : blackTimeRemaining);
14505
14506    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14507     whiteNPS = blackNPS = -1;
14508     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14509        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14510         whiteNPS = first.nps;
14511     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14512        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14513         blackNPS = first.nps;
14514     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14515         whiteNPS = second.nps;
14516     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14517         blackNPS = second.nps;
14518     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14519
14520     StartClockTimer(intendedTickLength);
14521 }
14522
14523 char *
14524 TimeString(ms)
14525      long ms;
14526 {
14527     long second, minute, hour, day;
14528     char *sign = "";
14529     static char buf[32];
14530
14531     if (ms > 0 && ms <= 9900) {
14532       /* convert milliseconds to tenths, rounding up */
14533       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14534
14535       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14536       return buf;
14537     }
14538
14539     /* convert milliseconds to seconds, rounding up */
14540     /* use floating point to avoid strangeness of integer division
14541        with negative dividends on many machines */
14542     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14543
14544     if (second < 0) {
14545         sign = "-";
14546         second = -second;
14547     }
14548
14549     day = second / (60 * 60 * 24);
14550     second = second % (60 * 60 * 24);
14551     hour = second / (60 * 60);
14552     second = second % (60 * 60);
14553     minute = second / 60;
14554     second = second % 60;
14555
14556     if (day > 0)
14557       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14558               sign, day, hour, minute, second);
14559     else if (hour > 0)
14560       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14561     else
14562       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14563
14564     return buf;
14565 }
14566
14567
14568 /*
14569  * This is necessary because some C libraries aren't ANSI C compliant yet.
14570  */
14571 char *
14572 StrStr(string, match)
14573      char *string, *match;
14574 {
14575     int i, length;
14576
14577     length = strlen(match);
14578
14579     for (i = strlen(string) - length; i >= 0; i--, string++)
14580       if (!strncmp(match, string, length))
14581         return string;
14582
14583     return NULL;
14584 }
14585
14586 char *
14587 StrCaseStr(string, match)
14588      char *string, *match;
14589 {
14590     int i, j, length;
14591
14592     length = strlen(match);
14593
14594     for (i = strlen(string) - length; i >= 0; i--, string++) {
14595         for (j = 0; j < length; j++) {
14596             if (ToLower(match[j]) != ToLower(string[j]))
14597               break;
14598         }
14599         if (j == length) return string;
14600     }
14601
14602     return NULL;
14603 }
14604
14605 #ifndef _amigados
14606 int
14607 StrCaseCmp(s1, s2)
14608      char *s1, *s2;
14609 {
14610     char c1, c2;
14611
14612     for (;;) {
14613         c1 = ToLower(*s1++);
14614         c2 = ToLower(*s2++);
14615         if (c1 > c2) return 1;
14616         if (c1 < c2) return -1;
14617         if (c1 == NULLCHAR) return 0;
14618     }
14619 }
14620
14621
14622 int
14623 ToLower(c)
14624      int c;
14625 {
14626     return isupper(c) ? tolower(c) : c;
14627 }
14628
14629
14630 int
14631 ToUpper(c)
14632      int c;
14633 {
14634     return islower(c) ? toupper(c) : c;
14635 }
14636 #endif /* !_amigados    */
14637
14638 char *
14639 StrSave(s)
14640      char *s;
14641 {
14642   char *ret;
14643
14644   if ((ret = (char *) malloc(strlen(s) + 1)))
14645     {
14646       safeStrCpy(ret, s, strlen(s)+1);
14647     }
14648   return ret;
14649 }
14650
14651 char *
14652 StrSavePtr(s, savePtr)
14653      char *s, **savePtr;
14654 {
14655     if (*savePtr) {
14656         free(*savePtr);
14657     }
14658     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14659       safeStrCpy(*savePtr, s, strlen(s)+1);
14660     }
14661     return(*savePtr);
14662 }
14663
14664 char *
14665 PGNDate()
14666 {
14667     time_t clock;
14668     struct tm *tm;
14669     char buf[MSG_SIZ];
14670
14671     clock = time((time_t *)NULL);
14672     tm = localtime(&clock);
14673     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14674             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14675     return StrSave(buf);
14676 }
14677
14678
14679 char *
14680 PositionToFEN(move, overrideCastling)
14681      int move;
14682      char *overrideCastling;
14683 {
14684     int i, j, fromX, fromY, toX, toY;
14685     int whiteToPlay;
14686     char buf[128];
14687     char *p, *q;
14688     int emptycount;
14689     ChessSquare piece;
14690
14691     whiteToPlay = (gameMode == EditPosition) ?
14692       !blackPlaysFirst : (move % 2 == 0);
14693     p = buf;
14694
14695     /* Piece placement data */
14696     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14697         emptycount = 0;
14698         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14699             if (boards[move][i][j] == EmptySquare) {
14700                 emptycount++;
14701             } else { ChessSquare piece = boards[move][i][j];
14702                 if (emptycount > 0) {
14703                     if(emptycount<10) /* [HGM] can be >= 10 */
14704                         *p++ = '0' + emptycount;
14705                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14706                     emptycount = 0;
14707                 }
14708                 if(PieceToChar(piece) == '+') {
14709                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14710                     *p++ = '+';
14711                     piece = (ChessSquare)(DEMOTED piece);
14712                 }
14713                 *p++ = PieceToChar(piece);
14714                 if(p[-1] == '~') {
14715                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14716                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14717                     *p++ = '~';
14718                 }
14719             }
14720         }
14721         if (emptycount > 0) {
14722             if(emptycount<10) /* [HGM] can be >= 10 */
14723                 *p++ = '0' + emptycount;
14724             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14725             emptycount = 0;
14726         }
14727         *p++ = '/';
14728     }
14729     *(p - 1) = ' ';
14730
14731     /* [HGM] print Crazyhouse or Shogi holdings */
14732     if( gameInfo.holdingsWidth ) {
14733         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14734         q = p;
14735         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14736             piece = boards[move][i][BOARD_WIDTH-1];
14737             if( piece != EmptySquare )
14738               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14739                   *p++ = PieceToChar(piece);
14740         }
14741         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14742             piece = boards[move][BOARD_HEIGHT-i-1][0];
14743             if( piece != EmptySquare )
14744               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14745                   *p++ = PieceToChar(piece);
14746         }
14747
14748         if( q == p ) *p++ = '-';
14749         *p++ = ']';
14750         *p++ = ' ';
14751     }
14752
14753     /* Active color */
14754     *p++ = whiteToPlay ? 'w' : 'b';
14755     *p++ = ' ';
14756
14757   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14758     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14759   } else {
14760   if(nrCastlingRights) {
14761      q = p;
14762      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14763        /* [HGM] write directly from rights */
14764            if(boards[move][CASTLING][2] != NoRights &&
14765               boards[move][CASTLING][0] != NoRights   )
14766                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14767            if(boards[move][CASTLING][2] != NoRights &&
14768               boards[move][CASTLING][1] != NoRights   )
14769                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14770            if(boards[move][CASTLING][5] != NoRights &&
14771               boards[move][CASTLING][3] != NoRights   )
14772                 *p++ = boards[move][CASTLING][3] + AAA;
14773            if(boards[move][CASTLING][5] != NoRights &&
14774               boards[move][CASTLING][4] != NoRights   )
14775                 *p++ = boards[move][CASTLING][4] + AAA;
14776      } else {
14777
14778         /* [HGM] write true castling rights */
14779         if( nrCastlingRights == 6 ) {
14780             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14781                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14782             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14783                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14784             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14785                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14786             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14787                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14788         }
14789      }
14790      if (q == p) *p++ = '-'; /* No castling rights */
14791      *p++ = ' ';
14792   }
14793
14794   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14795      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14796     /* En passant target square */
14797     if (move > backwardMostMove) {
14798         fromX = moveList[move - 1][0] - AAA;
14799         fromY = moveList[move - 1][1] - ONE;
14800         toX = moveList[move - 1][2] - AAA;
14801         toY = moveList[move - 1][3] - ONE;
14802         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14803             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14804             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14805             fromX == toX) {
14806             /* 2-square pawn move just happened */
14807             *p++ = toX + AAA;
14808             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14809         } else {
14810             *p++ = '-';
14811         }
14812     } else if(move == backwardMostMove) {
14813         // [HGM] perhaps we should always do it like this, and forget the above?
14814         if((signed char)boards[move][EP_STATUS] >= 0) {
14815             *p++ = boards[move][EP_STATUS] + AAA;
14816             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14817         } else {
14818             *p++ = '-';
14819         }
14820     } else {
14821         *p++ = '-';
14822     }
14823     *p++ = ' ';
14824   }
14825   }
14826
14827     /* [HGM] find reversible plies */
14828     {   int i = 0, j=move;
14829
14830         if (appData.debugMode) { int k;
14831             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14832             for(k=backwardMostMove; k<=forwardMostMove; k++)
14833                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14834
14835         }
14836
14837         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14838         if( j == backwardMostMove ) i += initialRulePlies;
14839         sprintf(p, "%d ", i);
14840         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14841     }
14842     /* Fullmove number */
14843     sprintf(p, "%d", (move / 2) + 1);
14844
14845     return StrSave(buf);
14846 }
14847
14848 Boolean
14849 ParseFEN(board, blackPlaysFirst, fen)
14850     Board board;
14851      int *blackPlaysFirst;
14852      char *fen;
14853 {
14854     int i, j;
14855     char *p, c;
14856     int emptycount;
14857     ChessSquare piece;
14858
14859     p = fen;
14860
14861     /* [HGM] by default clear Crazyhouse holdings, if present */
14862     if(gameInfo.holdingsWidth) {
14863        for(i=0; i<BOARD_HEIGHT; i++) {
14864            board[i][0]             = EmptySquare; /* black holdings */
14865            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14866            board[i][1]             = (ChessSquare) 0; /* black counts */
14867            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14868        }
14869     }
14870
14871     /* Piece placement data */
14872     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14873         j = 0;
14874         for (;;) {
14875             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14876                 if (*p == '/') p++;
14877                 emptycount = gameInfo.boardWidth - j;
14878                 while (emptycount--)
14879                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14880                 break;
14881 #if(BOARD_FILES >= 10)
14882             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14883                 p++; emptycount=10;
14884                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14885                 while (emptycount--)
14886                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14887 #endif
14888             } else if (isdigit(*p)) {
14889                 emptycount = *p++ - '0';
14890                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14891                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14892                 while (emptycount--)
14893                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14894             } else if (*p == '+' || isalpha(*p)) {
14895                 if (j >= gameInfo.boardWidth) return FALSE;
14896                 if(*p=='+') {
14897                     piece = CharToPiece(*++p);
14898                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14899                     piece = (ChessSquare) (PROMOTED piece ); p++;
14900                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14901                 } else piece = CharToPiece(*p++);
14902
14903                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14904                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14905                     piece = (ChessSquare) (PROMOTED piece);
14906                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14907                     p++;
14908                 }
14909                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14910             } else {
14911                 return FALSE;
14912             }
14913         }
14914     }
14915     while (*p == '/' || *p == ' ') p++;
14916
14917     /* [HGM] look for Crazyhouse holdings here */
14918     while(*p==' ') p++;
14919     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14920         if(*p == '[') p++;
14921         if(*p == '-' ) p++; /* empty holdings */ else {
14922             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14923             /* if we would allow FEN reading to set board size, we would   */
14924             /* have to add holdings and shift the board read so far here   */
14925             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14926                 p++;
14927                 if((int) piece >= (int) BlackPawn ) {
14928                     i = (int)piece - (int)BlackPawn;
14929                     i = PieceToNumber((ChessSquare)i);
14930                     if( i >= gameInfo.holdingsSize ) return FALSE;
14931                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14932                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14933                 } else {
14934                     i = (int)piece - (int)WhitePawn;
14935                     i = PieceToNumber((ChessSquare)i);
14936                     if( i >= gameInfo.holdingsSize ) return FALSE;
14937                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14938                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14939                 }
14940             }
14941         }
14942         if(*p == ']') p++;
14943     }
14944
14945     while(*p == ' ') p++;
14946
14947     /* Active color */
14948     c = *p++;
14949     if(appData.colorNickNames) {
14950       if( c == appData.colorNickNames[0] ) c = 'w'; else
14951       if( c == appData.colorNickNames[1] ) c = 'b';
14952     }
14953     switch (c) {
14954       case 'w':
14955         *blackPlaysFirst = FALSE;
14956         break;
14957       case 'b':
14958         *blackPlaysFirst = TRUE;
14959         break;
14960       default:
14961         return FALSE;
14962     }
14963
14964     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14965     /* return the extra info in global variiables             */
14966
14967     /* set defaults in case FEN is incomplete */
14968     board[EP_STATUS] = EP_UNKNOWN;
14969     for(i=0; i<nrCastlingRights; i++ ) {
14970         board[CASTLING][i] =
14971             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14972     }   /* assume possible unless obviously impossible */
14973     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14974     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14975     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14976                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14977     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14978     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14979     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14980                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14981     FENrulePlies = 0;
14982
14983     while(*p==' ') p++;
14984     if(nrCastlingRights) {
14985       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14986           /* castling indicator present, so default becomes no castlings */
14987           for(i=0; i<nrCastlingRights; i++ ) {
14988                  board[CASTLING][i] = NoRights;
14989           }
14990       }
14991       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14992              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14993              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14994              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14995         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14996
14997         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14998             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14999             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15000         }
15001         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15002             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15003         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15004                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15005         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15006                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15007         switch(c) {
15008           case'K':
15009               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15010               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15011               board[CASTLING][2] = whiteKingFile;
15012               break;
15013           case'Q':
15014               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15015               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15016               board[CASTLING][2] = whiteKingFile;
15017               break;
15018           case'k':
15019               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15020               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15021               board[CASTLING][5] = blackKingFile;
15022               break;
15023           case'q':
15024               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15025               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15026               board[CASTLING][5] = blackKingFile;
15027           case '-':
15028               break;
15029           default: /* FRC castlings */
15030               if(c >= 'a') { /* black rights */
15031                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15032                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15033                   if(i == BOARD_RGHT) break;
15034                   board[CASTLING][5] = i;
15035                   c -= AAA;
15036                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15037                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15038                   if(c > i)
15039                       board[CASTLING][3] = c;
15040                   else
15041                       board[CASTLING][4] = c;
15042               } else { /* white rights */
15043                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15044                     if(board[0][i] == WhiteKing) break;
15045                   if(i == BOARD_RGHT) break;
15046                   board[CASTLING][2] = i;
15047                   c -= AAA - 'a' + 'A';
15048                   if(board[0][c] >= WhiteKing) break;
15049                   if(c > i)
15050                       board[CASTLING][0] = c;
15051                   else
15052                       board[CASTLING][1] = c;
15053               }
15054         }
15055       }
15056       for(i=0; i<nrCastlingRights; i++)
15057         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15058     if (appData.debugMode) {
15059         fprintf(debugFP, "FEN castling rights:");
15060         for(i=0; i<nrCastlingRights; i++)
15061         fprintf(debugFP, " %d", board[CASTLING][i]);
15062         fprintf(debugFP, "\n");
15063     }
15064
15065       while(*p==' ') p++;
15066     }
15067
15068     /* read e.p. field in games that know e.p. capture */
15069     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15070        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15071       if(*p=='-') {
15072         p++; board[EP_STATUS] = EP_NONE;
15073       } else {
15074          char c = *p++ - AAA;
15075
15076          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15077          if(*p >= '0' && *p <='9') p++;
15078          board[EP_STATUS] = c;
15079       }
15080     }
15081
15082
15083     if(sscanf(p, "%d", &i) == 1) {
15084         FENrulePlies = i; /* 50-move ply counter */
15085         /* (The move number is still ignored)    */
15086     }
15087
15088     return TRUE;
15089 }
15090
15091 void
15092 EditPositionPasteFEN(char *fen)
15093 {
15094   if (fen != NULL) {
15095     Board initial_position;
15096
15097     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15098       DisplayError(_("Bad FEN position in clipboard"), 0);
15099       return ;
15100     } else {
15101       int savedBlackPlaysFirst = blackPlaysFirst;
15102       EditPositionEvent();
15103       blackPlaysFirst = savedBlackPlaysFirst;
15104       CopyBoard(boards[0], initial_position);
15105       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15106       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15107       DisplayBothClocks();
15108       DrawPosition(FALSE, boards[currentMove]);
15109     }
15110   }
15111 }
15112
15113 static char cseq[12] = "\\   ";
15114
15115 Boolean set_cont_sequence(char *new_seq)
15116 {
15117     int len;
15118     Boolean ret;
15119
15120     // handle bad attempts to set the sequence
15121         if (!new_seq)
15122                 return 0; // acceptable error - no debug
15123
15124     len = strlen(new_seq);
15125     ret = (len > 0) && (len < sizeof(cseq));
15126     if (ret)
15127       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15128     else if (appData.debugMode)
15129       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15130     return ret;
15131 }
15132
15133 /*
15134     reformat a source message so words don't cross the width boundary.  internal
15135     newlines are not removed.  returns the wrapped size (no null character unless
15136     included in source message).  If dest is NULL, only calculate the size required
15137     for the dest buffer.  lp argument indicats line position upon entry, and it's
15138     passed back upon exit.
15139 */
15140 int wrap(char *dest, char *src, int count, int width, int *lp)
15141 {
15142     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15143
15144     cseq_len = strlen(cseq);
15145     old_line = line = *lp;
15146     ansi = len = clen = 0;
15147
15148     for (i=0; i < count; i++)
15149     {
15150         if (src[i] == '\033')
15151             ansi = 1;
15152
15153         // if we hit the width, back up
15154         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15155         {
15156             // store i & len in case the word is too long
15157             old_i = i, old_len = len;
15158
15159             // find the end of the last word
15160             while (i && src[i] != ' ' && src[i] != '\n')
15161             {
15162                 i--;
15163                 len--;
15164             }
15165
15166             // word too long?  restore i & len before splitting it
15167             if ((old_i-i+clen) >= width)
15168             {
15169                 i = old_i;
15170                 len = old_len;
15171             }
15172
15173             // extra space?
15174             if (i && src[i-1] == ' ')
15175                 len--;
15176
15177             if (src[i] != ' ' && src[i] != '\n')
15178             {
15179                 i--;
15180                 if (len)
15181                     len--;
15182             }
15183
15184             // now append the newline and continuation sequence
15185             if (dest)
15186                 dest[len] = '\n';
15187             len++;
15188             if (dest)
15189                 strncpy(dest+len, cseq, cseq_len);
15190             len += cseq_len;
15191             line = cseq_len;
15192             clen = cseq_len;
15193             continue;
15194         }
15195
15196         if (dest)
15197             dest[len] = src[i];
15198         len++;
15199         if (!ansi)
15200             line++;
15201         if (src[i] == '\n')
15202             line = 0;
15203         if (src[i] == 'm')
15204             ansi = 0;
15205     }
15206     if (dest && appData.debugMode)
15207     {
15208         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15209             count, width, line, len, *lp);
15210         show_bytes(debugFP, src, count);
15211         fprintf(debugFP, "\ndest: ");
15212         show_bytes(debugFP, dest, len);
15213         fprintf(debugFP, "\n");
15214     }
15215     *lp = dest ? line : old_line;
15216
15217     return len;
15218 }
15219
15220 // [HGM] vari: routines for shelving variations
15221
15222 void
15223 PushTail(int firstMove, int lastMove)
15224 {
15225         int i, j, nrMoves = lastMove - firstMove;
15226
15227         if(appData.icsActive) { // only in local mode
15228                 forwardMostMove = currentMove; // mimic old ICS behavior
15229                 return;
15230         }
15231         if(storedGames >= MAX_VARIATIONS-1) return;
15232
15233         // push current tail of game on stack
15234         savedResult[storedGames] = gameInfo.result;
15235         savedDetails[storedGames] = gameInfo.resultDetails;
15236         gameInfo.resultDetails = NULL;
15237         savedFirst[storedGames] = firstMove;
15238         savedLast [storedGames] = lastMove;
15239         savedFramePtr[storedGames] = framePtr;
15240         framePtr -= nrMoves; // reserve space for the boards
15241         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15242             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15243             for(j=0; j<MOVE_LEN; j++)
15244                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15245             for(j=0; j<2*MOVE_LEN; j++)
15246                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15247             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15248             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15249             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15250             pvInfoList[firstMove+i-1].depth = 0;
15251             commentList[framePtr+i] = commentList[firstMove+i];
15252             commentList[firstMove+i] = NULL;
15253         }
15254
15255         storedGames++;
15256         forwardMostMove = firstMove; // truncate game so we can start variation
15257         if(storedGames == 1) GreyRevert(FALSE);
15258 }
15259
15260 Boolean
15261 PopTail(Boolean annotate)
15262 {
15263         int i, j, nrMoves;
15264         char buf[8000], moveBuf[20];
15265
15266         if(appData.icsActive) return FALSE; // only in local mode
15267         if(!storedGames) return FALSE; // sanity
15268         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15269
15270         storedGames--;
15271         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15272         nrMoves = savedLast[storedGames] - currentMove;
15273         if(annotate) {
15274                 int cnt = 10;
15275                 if(!WhiteOnMove(currentMove))
15276                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15277                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15278                 for(i=currentMove; i<forwardMostMove; i++) {
15279                         if(WhiteOnMove(i))
15280                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15281                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15282                         strcat(buf, moveBuf);
15283                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15284                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15285                 }
15286                 strcat(buf, ")");
15287         }
15288         for(i=1; i<=nrMoves; i++) { // copy last variation back
15289             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15290             for(j=0; j<MOVE_LEN; j++)
15291                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15292             for(j=0; j<2*MOVE_LEN; j++)
15293                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15294             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15295             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15296             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15297             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15298             commentList[currentMove+i] = commentList[framePtr+i];
15299             commentList[framePtr+i] = NULL;
15300         }
15301         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15302         framePtr = savedFramePtr[storedGames];
15303         gameInfo.result = savedResult[storedGames];
15304         if(gameInfo.resultDetails != NULL) {
15305             free(gameInfo.resultDetails);
15306       }
15307         gameInfo.resultDetails = savedDetails[storedGames];
15308         forwardMostMove = currentMove + nrMoves;
15309         if(storedGames == 0) GreyRevert(TRUE);
15310         return TRUE;
15311 }
15312
15313 void
15314 CleanupTail()
15315 {       // remove all shelved variations
15316         int i;
15317         for(i=0; i<storedGames; i++) {
15318             if(savedDetails[i])
15319                 free(savedDetails[i]);
15320             savedDetails[i] = NULL;
15321         }
15322         for(i=framePtr; i<MAX_MOVES; i++) {
15323                 if(commentList[i]) free(commentList[i]);
15324                 commentList[i] = NULL;
15325         }
15326         framePtr = MAX_MOVES-1;
15327         storedGames = 0;
15328 }
15329
15330 void
15331 LoadVariation(int index, char *text)
15332 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15333         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15334         int level = 0, move;
15335
15336         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15337         // first find outermost bracketing variation
15338         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15339             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15340                 if(*p == '{') wait = '}'; else
15341                 if(*p == '[') wait = ']'; else
15342                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15343                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15344             }
15345             if(*p == wait) wait = NULLCHAR; // closing ]} found
15346             p++;
15347         }
15348         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15349         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15350         end[1] = NULLCHAR; // clip off comment beyond variation
15351         ToNrEvent(currentMove-1);
15352         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15353         // kludge: use ParsePV() to append variation to game
15354         move = currentMove;
15355         ParsePV(start, TRUE);
15356         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15357         ClearPremoveHighlights();
15358         CommentPopDown();
15359         ToNrEvent(currentMove+1);
15360 }
15361