Allow -timeIncrement to be a float
[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
453 int have_sent_ICS_logon = 0;
454 int sending_ICS_login    = 0;
455 int sending_ICS_password = 0;
456
457 int movesPerSession;
458 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
459 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0;
464 TimeMark programStartTime;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
467
468 /* animateTraining preserves the state of appData.animate
469  * when Training mode is activated. This allows the
470  * response to be animated when appData.animate == TRUE and
471  * appData.animateDragging == TRUE.
472  */
473 Boolean animateTraining;
474
475 GameInfo gameInfo;
476
477 AppData appData;
478
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char  initialRights[BOARD_FILES];
483 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int   initialRulePlies, FENrulePlies;
485 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 int loadFlag = 0;
487 int shuffleOpenings;
488 int mute; // mute all sounds
489
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int storedGames = 0;
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
499
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void CleanupTail P((void));
503
504 ChessSquare  FIDEArray[2][BOARD_FILES] = {
505     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
506         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
507     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
508         BlackKing, BlackBishop, BlackKnight, BlackRook }
509 };
510
511 ChessSquare twoKingsArray[2][BOARD_FILES] = {
512     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
513         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
515         BlackKing, BlackKing, BlackKnight, BlackRook }
516 };
517
518 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
520         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
521     { BlackRook, BlackMan, BlackBishop, BlackQueen,
522         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
523 };
524
525 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
526     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
527         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
528     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
529         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
530 };
531
532 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
533     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
534         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
536         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
537 };
538
539 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
540     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
541         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
542     { BlackRook, BlackKnight, BlackMan, BlackFerz,
543         BlackKing, BlackMan, BlackKnight, BlackRook }
544 };
545
546
547 #if (BOARD_FILES>=10)
548 ChessSquare ShogiArray[2][BOARD_FILES] = {
549     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
550         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
551     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
552         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
553 };
554
555 ChessSquare XiangqiArray[2][BOARD_FILES] = {
556     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
557         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
559         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
560 };
561
562 ChessSquare CapablancaArray[2][BOARD_FILES] = {
563     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
564         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
566         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
567 };
568
569 ChessSquare GreatArray[2][BOARD_FILES] = {
570     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
571         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
572     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
573         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
574 };
575
576 ChessSquare JanusArray[2][BOARD_FILES] = {
577     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
578         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
579     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
580         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
581 };
582
583 #ifdef GOTHIC
584 ChessSquare GothicArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
586         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
587     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
588         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
589 };
590 #else // !GOTHIC
591 #define GothicArray CapablancaArray
592 #endif // !GOTHIC
593
594 #ifdef FALCON
595 ChessSquare FalconArray[2][BOARD_FILES] = {
596     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
597         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
598     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
599         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
600 };
601 #else // !FALCON
602 #define FalconArray CapablancaArray
603 #endif // !FALCON
604
605 #else // !(BOARD_FILES>=10)
606 #define XiangqiPosition FIDEArray
607 #define CapablancaArray FIDEArray
608 #define GothicArray FIDEArray
609 #define GreatArray FIDEArray
610 #endif // !(BOARD_FILES>=10)
611
612 #if (BOARD_FILES>=12)
613 ChessSquare CourierArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
615         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
617         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
618 };
619 #else // !(BOARD_FILES>=12)
620 #define CourierArray CapablancaArray
621 #endif // !(BOARD_FILES>=12)
622
623
624 Board initialPosition;
625
626
627 /* Convert str to a rating. Checks for special cases of "----",
628
629    "++++", etc. Also strips ()'s */
630 int
631 string_to_rating(str)
632   char *str;
633 {
634   while(*str && !isdigit(*str)) ++str;
635   if (!*str)
636     return 0;   /* One of the special "no rating" cases */
637   else
638     return atoi(str);
639 }
640
641 void
642 ClearProgramStats()
643 {
644     /* Init programStats */
645     programStats.movelist[0] = 0;
646     programStats.depth = 0;
647     programStats.nr_moves = 0;
648     programStats.moves_left = 0;
649     programStats.nodes = 0;
650     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
651     programStats.score = 0;
652     programStats.got_only_move = 0;
653     programStats.got_fail = 0;
654     programStats.line_is_book = 0;
655 }
656
657 void
658 InitBackEnd1()
659 {
660     int matched, min, sec;
661
662     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
663     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
664
665     GetTimeMark(&programStartTime);
666     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
667
668     ClearProgramStats();
669     programStats.ok_to_send = 1;
670     programStats.seen_stat = 0;
671
672     /*
673      * Initialize game list
674      */
675     ListNew(&gameList);
676
677
678     /*
679      * Internet chess server status
680      */
681     if (appData.icsActive) {
682         appData.matchMode = FALSE;
683         appData.matchGames = 0;
684 #if ZIPPY
685         appData.noChessProgram = !appData.zippyPlay;
686 #else
687         appData.zippyPlay = FALSE;
688         appData.zippyTalk = FALSE;
689         appData.noChessProgram = TRUE;
690 #endif
691         if (*appData.icsHelper != NULLCHAR) {
692             appData.useTelnet = TRUE;
693             appData.telnetProgram = appData.icsHelper;
694         }
695     } else {
696         appData.zippyTalk = appData.zippyPlay = FALSE;
697     }
698
699     /* [AS] Initialize pv info list [HGM] and game state */
700     {
701         int i, j;
702
703         for( i=0; i<=framePtr; i++ ) {
704             pvInfoList[i].depth = -1;
705             boards[i][EP_STATUS] = EP_NONE;
706             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
707         }
708     }
709
710     /*
711      * Parse timeControl resource
712      */
713     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
714                           appData.movesPerSession)) {
715         char buf[MSG_SIZ];
716         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
717         DisplayFatalError(buf, 0, 2);
718     }
719
720     /*
721      * Parse searchTime resource
722      */
723     if (*appData.searchTime != NULLCHAR) {
724         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
725         if (matched == 1) {
726             searchTime = min * 60;
727         } else if (matched == 2) {
728             searchTime = min * 60 + sec;
729         } else {
730             char buf[MSG_SIZ];
731             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
732             DisplayFatalError(buf, 0, 2);
733         }
734     }
735
736     /* [AS] Adjudication threshold */
737     adjudicateLossThreshold = appData.adjudicateLossThreshold;
738
739     first.which = _("first");
740     second.which = _("second");
741     first.maybeThinking = second.maybeThinking = FALSE;
742     first.pr = second.pr = NoProc;
743     first.isr = second.isr = NULL;
744     first.sendTime = second.sendTime = 2;
745     first.sendDrawOffers = 1;
746     if (appData.firstPlaysBlack) {
747         first.twoMachinesColor = "black\n";
748         second.twoMachinesColor = "white\n";
749     } else {
750         first.twoMachinesColor = "white\n";
751         second.twoMachinesColor = "black\n";
752     }
753     first.program = appData.firstChessProgram;
754     second.program = appData.secondChessProgram;
755     first.host = appData.firstHost;
756     second.host = appData.secondHost;
757     first.dir = appData.firstDirectory;
758     second.dir = appData.secondDirectory;
759     first.other = &second;
760     second.other = &first;
761     first.initString = appData.initString;
762     second.initString = appData.secondInitString;
763     first.computerString = appData.firstComputerString;
764     second.computerString = appData.secondComputerString;
765     first.useSigint = second.useSigint = TRUE;
766     first.useSigterm = second.useSigterm = TRUE;
767     first.reuse = appData.reuseFirst;
768     second.reuse = appData.reuseSecond;
769     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
770     second.nps = appData.secondNPS;
771     first.useSetboard = second.useSetboard = FALSE;
772     first.useSAN = second.useSAN = FALSE;
773     first.usePing = second.usePing = FALSE;
774     first.lastPing = second.lastPing = 0;
775     first.lastPong = second.lastPong = 0;
776     first.usePlayother = second.usePlayother = FALSE;
777     first.useColors = second.useColors = TRUE;
778     first.useUsermove = second.useUsermove = FALSE;
779     first.sendICS = second.sendICS = FALSE;
780     first.sendName = second.sendName = appData.icsActive;
781     first.sdKludge = second.sdKludge = FALSE;
782     first.stKludge = second.stKludge = FALSE;
783     TidyProgramName(first.program, first.host, first.tidy);
784     TidyProgramName(second.program, second.host, second.tidy);
785     first.matchWins = second.matchWins = 0;
786     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
787     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
788     first.analysisSupport = second.analysisSupport = 2; /* detect */
789     first.analyzing = second.analyzing = FALSE;
790     first.initDone = second.initDone = FALSE;
791
792     /* New features added by Tord: */
793     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
794     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
795     /* End of new features added by Tord. */
796     first.fenOverride  = appData.fenOverride1;
797     second.fenOverride = appData.fenOverride2;
798
799     /* [HGM] time odds: set factor for each machine */
800     first.timeOdds  = appData.firstTimeOdds;
801     second.timeOdds = appData.secondTimeOdds;
802     { float norm = 1;
803         if(appData.timeOddsMode) {
804             norm = first.timeOdds;
805             if(norm > second.timeOdds) norm = second.timeOdds;
806         }
807         first.timeOdds /= norm;
808         second.timeOdds /= norm;
809     }
810
811     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
812     first.accumulateTC = appData.firstAccumulateTC;
813     second.accumulateTC = appData.secondAccumulateTC;
814     first.maxNrOfSessions = second.maxNrOfSessions = 1;
815
816     /* [HGM] debug */
817     first.debug = second.debug = FALSE;
818     first.supportsNPS = second.supportsNPS = UNKNOWN;
819
820     /* [HGM] options */
821     first.optionSettings  = appData.firstOptions;
822     second.optionSettings = appData.secondOptions;
823
824     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
825     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
826     first.isUCI = appData.firstIsUCI; /* [AS] */
827     second.isUCI = appData.secondIsUCI; /* [AS] */
828     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
829     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
830
831     if (appData.firstProtocolVersion > PROTOVER
832         || appData.firstProtocolVersion < 1)
833       {
834         char buf[MSG_SIZ];
835         int len;
836
837         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
838                        appData.firstProtocolVersion);
839         if( (len > MSG_SIZ) && appData.debugMode )
840           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
841
842         DisplayFatalError(buf, 0, 2);
843       }
844     else
845       {
846         first.protocolVersion = appData.firstProtocolVersion;
847       }
848
849     if (appData.secondProtocolVersion > PROTOVER
850         || appData.secondProtocolVersion < 1)
851       {
852         char buf[MSG_SIZ];
853         int len;
854
855         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
856                        appData.secondProtocolVersion);
857         if( (len > MSG_SIZ) && appData.debugMode )
858           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
859
860         DisplayFatalError(buf, 0, 2);
861       }
862     else
863       {
864         second.protocolVersion = appData.secondProtocolVersion;
865       }
866
867     if (appData.icsActive) {
868         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
869 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
870     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
871         appData.clockMode = FALSE;
872         first.sendTime = second.sendTime = 0;
873     }
874
875 #if ZIPPY
876     /* Override some settings from environment variables, for backward
877        compatibility.  Unfortunately it's not feasible to have the env
878        vars just set defaults, at least in xboard.  Ugh.
879     */
880     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
881       ZippyInit();
882     }
883 #endif
884
885     if (appData.noChessProgram) {
886         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
887         sprintf(programVersion, "%s", PACKAGE_STRING);
888     } else {
889       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
890       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
891       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
892     }
893
894     if (!appData.icsActive) {
895       char buf[MSG_SIZ];
896       int len;
897
898       /* Check for variants that are supported only in ICS mode,
899          or not at all.  Some that are accepted here nevertheless
900          have bugs; see comments below.
901       */
902       VariantClass variant = StringToVariant(appData.variant);
903       switch (variant) {
904       case VariantBughouse:     /* need four players and two boards */
905       case VariantKriegspiel:   /* need to hide pieces and move details */
906         /* case VariantFischeRandom: (Fabien: moved below) */
907         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
908         if( (len > MSG_SIZ) && appData.debugMode )
909           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
910
911         DisplayFatalError(buf, 0, 2);
912         return;
913
914       case VariantUnknown:
915       case VariantLoadable:
916       case Variant29:
917       case Variant30:
918       case Variant31:
919       case Variant32:
920       case Variant33:
921       case Variant34:
922       case Variant35:
923       case Variant36:
924       default:
925         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
926         if( (len > MSG_SIZ) && appData.debugMode )
927           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
928
929         DisplayFatalError(buf, 0, 2);
930         return;
931
932       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
933       case VariantFairy:      /* [HGM] TestLegality definitely off! */
934       case VariantGothic:     /* [HGM] should work */
935       case VariantCapablanca: /* [HGM] should work */
936       case VariantCourier:    /* [HGM] initial forced moves not implemented */
937       case VariantShogi:      /* [HGM] could still mate with pawn drop */
938       case VariantKnightmate: /* [HGM] should work */
939       case VariantCylinder:   /* [HGM] untested */
940       case VariantFalcon:     /* [HGM] untested */
941       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
942                                  offboard interposition not understood */
943       case VariantNormal:     /* definitely works! */
944       case VariantWildCastle: /* pieces not automatically shuffled */
945       case VariantNoCastle:   /* pieces not automatically shuffled */
946       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
947       case VariantLosers:     /* should work except for win condition,
948                                  and doesn't know captures are mandatory */
949       case VariantSuicide:    /* should work except for win condition,
950                                  and doesn't know captures are mandatory */
951       case VariantGiveaway:   /* should work except for win condition,
952                                  and doesn't know captures are mandatory */
953       case VariantTwoKings:   /* should work */
954       case VariantAtomic:     /* should work except for win condition */
955       case Variant3Check:     /* should work except for win condition */
956       case VariantShatranj:   /* should work except for all win conditions */
957       case VariantMakruk:     /* should work except for daw countdown */
958       case VariantBerolina:   /* might work if TestLegality is off */
959       case VariantCapaRandom: /* should work */
960       case VariantJanus:      /* should work */
961       case VariantSuper:      /* experimental */
962       case VariantGreat:      /* experimental, requires legality testing to be off */
963         break;
964       }
965     }
966
967     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
968     InitEngineUCI( installDir, &second );
969 }
970
971 int NextIntegerFromString( char ** str, long * value )
972 {
973     int result = -1;
974     char * s = *str;
975
976     while( *s == ' ' || *s == '\t' ) {
977         s++;
978     }
979
980     *value = 0;
981
982     if( *s >= '0' && *s <= '9' ) {
983         while( *s >= '0' && *s <= '9' ) {
984             *value = *value * 10 + (*s - '0');
985             s++;
986         }
987
988         result = 0;
989     }
990
991     *str = s;
992
993     return result;
994 }
995
996 int NextTimeControlFromString( char ** str, long * value )
997 {
998     long temp;
999     int result = NextIntegerFromString( str, &temp );
1000
1001     if( result == 0 ) {
1002         *value = temp * 60; /* Minutes */
1003         if( **str == ':' ) {
1004             (*str)++;
1005             result = NextIntegerFromString( str, &temp );
1006             *value += temp; /* Seconds */
1007         }
1008     }
1009
1010     return result;
1011 }
1012
1013 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1014 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1015     int result = -1, type = 0; long temp, temp2;
1016
1017     if(**str != ':') return -1; // old params remain in force!
1018     (*str)++;
1019     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1020     if( NextIntegerFromString( str, &temp ) ) return -1;
1021     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1022
1023     if(**str != '/') {
1024         /* time only: incremental or sudden-death time control */
1025         if(**str == '+') { /* increment follows; read it */
1026             (*str)++;
1027             if(**str == '!') type = *(*str)++; // Bronstein TC
1028             if(result = NextIntegerFromString( str, &temp2)) return -1;
1029             *inc = temp2 * 1000;
1030             if(**str == '.') { // read fraction of increment
1031                 char *start = ++(*str);
1032                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1033                 temp2 *= 1000;
1034                 while(start++ < *str) temp2 /= 10;
1035                 *inc += temp2;
1036             }
1037         } else *inc = 0;
1038         *moves = 0; *tc = temp * 1000; *incType = type;
1039         return 0;
1040     }
1041
1042     (*str)++; /* classical time control */
1043     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1044
1045     if(result == 0) {
1046         *moves = temp;
1047         *tc    = temp2 * 1000;
1048         *inc   = 0;
1049         *incType = type;
1050     }
1051     return result;
1052 }
1053
1054 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1055 {   /* [HGM] get time to add from the multi-session time-control string */
1056     int incType, moves=1; /* kludge to force reading of first session */
1057     long time, increment;
1058     char *s = tcString;
1059
1060     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1061     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1062     do {
1063         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1064         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1065         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1066         if(movenr == -1) return time;    /* last move before new session     */
1067         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1068         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1069         if(!moves) return increment;     /* current session is incremental   */
1070         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1071     } while(movenr >= -1);               /* try again for next session       */
1072
1073     return 0; // no new time quota on this move
1074 }
1075
1076 int
1077 ParseTimeControl(tc, ti, mps)
1078      char *tc;
1079      float ti;
1080      int mps;
1081 {
1082   long tc1;
1083   long tc2;
1084   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1085   int min, sec=0;
1086   int len;
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                 sending_ICS_password = 0; // in case we come back to login
3123                 sending_ICS_login = 1; 
3124                 continue;
3125             }
3126             /* need to shadow the password */
3127             if (!sending_ICS_password && looking_at(buf, &i, "password:")) {
3128               sending_ICS_password = 1;
3129               continue;
3130             }
3131               
3132             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3133                 (looking_at(buf, &i, "\n<12> ") ||
3134                  looking_at(buf, &i, "<12> "))) {
3135                 loggedOn = TRUE;
3136                 if (oldi > next_out) {
3137                     SendToPlayer(&buf[next_out], oldi - next_out);
3138                 }
3139                 next_out = i;
3140                 started = STARTED_BOARD;
3141                 parse_pos = 0;
3142                 continue;
3143             }
3144
3145             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3146                 looking_at(buf, &i, "<b1> ")) {
3147                 if (oldi > next_out) {
3148                     SendToPlayer(&buf[next_out], oldi - next_out);
3149                 }
3150                 next_out = i;
3151                 started = STARTED_HOLDINGS;
3152                 parse_pos = 0;
3153                 continue;
3154             }
3155
3156             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3157                 loggedOn = TRUE;
3158                 /* Header for a move list -- first line */
3159
3160                 switch (ics_getting_history) {
3161                   case H_FALSE:
3162                     switch (gameMode) {
3163                       case IcsIdle:
3164                       case BeginningOfGame:
3165                         /* User typed "moves" or "oldmoves" while we
3166                            were idle.  Pretend we asked for these
3167                            moves and soak them up so user can step
3168                            through them and/or save them.
3169                            */
3170                         Reset(FALSE, TRUE);
3171                         gameMode = IcsObserving;
3172                         ModeHighlight();
3173                         ics_gamenum = -1;
3174                         ics_getting_history = H_GOT_UNREQ_HEADER;
3175                         break;
3176                       case EditGame: /*?*/
3177                       case EditPosition: /*?*/
3178                         /* Should above feature work in these modes too? */
3179                         /* For now it doesn't */
3180                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3181                         break;
3182                       default:
3183                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3184                         break;
3185                     }
3186                     break;
3187                   case H_REQUESTED:
3188                     /* Is this the right one? */
3189                     if (gameInfo.white && gameInfo.black &&
3190                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3191                         strcmp(gameInfo.black, star_match[2]) == 0) {
3192                         /* All is well */
3193                         ics_getting_history = H_GOT_REQ_HEADER;
3194                     }
3195                     break;
3196                   case H_GOT_REQ_HEADER:
3197                   case H_GOT_UNREQ_HEADER:
3198                   case H_GOT_UNWANTED_HEADER:
3199                   case H_GETTING_MOVES:
3200                     /* Should not happen */
3201                     DisplayError(_("Error gathering move list: two headers"), 0);
3202                     ics_getting_history = H_FALSE;
3203                     break;
3204                 }
3205
3206                 /* Save player ratings into gameInfo if needed */
3207                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3208                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3209                     (gameInfo.whiteRating == -1 ||
3210                      gameInfo.blackRating == -1)) {
3211
3212                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3213                     gameInfo.blackRating = string_to_rating(star_match[3]);
3214                     if (appData.debugMode)
3215                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3216                               gameInfo.whiteRating, gameInfo.blackRating);
3217                 }
3218                 continue;
3219             }
3220
3221             if (looking_at(buf, &i,
3222               "* * match, initial time: * minute*, increment: * second")) {
3223                 /* Header for a move list -- second line */
3224                 /* Initial board will follow if this is a wild game */
3225                 if (gameInfo.event != NULL) free(gameInfo.event);
3226                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3227                 gameInfo.event = StrSave(str);
3228                 /* [HGM] we switched variant. Translate boards if needed. */
3229                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3230                 continue;
3231             }
3232
3233             if (looking_at(buf, &i, "Move  ")) {
3234                 /* Beginning of a move list */
3235                 switch (ics_getting_history) {
3236                   case H_FALSE:
3237                     /* Normally should not happen */
3238                     /* Maybe user hit reset while we were parsing */
3239                     break;
3240                   case H_REQUESTED:
3241                     /* Happens if we are ignoring a move list that is not
3242                      * the one we just requested.  Common if the user
3243                      * tries to observe two games without turning off
3244                      * getMoveList */
3245                     break;
3246                   case H_GETTING_MOVES:
3247                     /* Should not happen */
3248                     DisplayError(_("Error gathering move list: nested"), 0);
3249                     ics_getting_history = H_FALSE;
3250                     break;
3251                   case H_GOT_REQ_HEADER:
3252                     ics_getting_history = H_GETTING_MOVES;
3253                     started = STARTED_MOVES;
3254                     parse_pos = 0;
3255                     if (oldi > next_out) {
3256                         SendToPlayer(&buf[next_out], oldi - next_out);
3257                     }
3258                     break;
3259                   case H_GOT_UNREQ_HEADER:
3260                     ics_getting_history = H_GETTING_MOVES;
3261                     started = STARTED_MOVES_NOHIDE;
3262                     parse_pos = 0;
3263                     break;
3264                   case H_GOT_UNWANTED_HEADER:
3265                     ics_getting_history = H_FALSE;
3266                     break;
3267                 }
3268                 continue;
3269             }
3270
3271             if (looking_at(buf, &i, "% ") ||
3272                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3273                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3274                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3275                     soughtPending = FALSE;
3276                     seekGraphUp = TRUE;
3277                     DrawSeekGraph();
3278                 }
3279                 if(suppressKibitz) next_out = i;
3280                 savingComment = FALSE;
3281                 suppressKibitz = 0;
3282                 switch (started) {
3283                   case STARTED_MOVES:
3284                   case STARTED_MOVES_NOHIDE:
3285                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3286                     parse[parse_pos + i - oldi] = NULLCHAR;
3287                     ParseGameHistory(parse);
3288 #if ZIPPY
3289                     if (appData.zippyPlay && first.initDone) {
3290                         FeedMovesToProgram(&first, forwardMostMove);
3291                         if (gameMode == IcsPlayingWhite) {
3292                             if (WhiteOnMove(forwardMostMove)) {
3293                                 if (first.sendTime) {
3294                                   if (first.useColors) {
3295                                     SendToProgram("black\n", &first);
3296                                   }
3297                                   SendTimeRemaining(&first, TRUE);
3298                                 }
3299                                 if (first.useColors) {
3300                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3301                                 }
3302                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3303                                 first.maybeThinking = TRUE;
3304                             } else {
3305                                 if (first.usePlayother) {
3306                                   if (first.sendTime) {
3307                                     SendTimeRemaining(&first, TRUE);
3308                                   }
3309                                   SendToProgram("playother\n", &first);
3310                                   firstMove = FALSE;
3311                                 } else {
3312                                   firstMove = TRUE;
3313                                 }
3314                             }
3315                         } else if (gameMode == IcsPlayingBlack) {
3316                             if (!WhiteOnMove(forwardMostMove)) {
3317                                 if (first.sendTime) {
3318                                   if (first.useColors) {
3319                                     SendToProgram("white\n", &first);
3320                                   }
3321                                   SendTimeRemaining(&first, FALSE);
3322                                 }
3323                                 if (first.useColors) {
3324                                   SendToProgram("black\n", &first);
3325                                 }
3326                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3327                                 first.maybeThinking = TRUE;
3328                             } else {
3329                                 if (first.usePlayother) {
3330                                   if (first.sendTime) {
3331                                     SendTimeRemaining(&first, FALSE);
3332                                   }
3333                                   SendToProgram("playother\n", &first);
3334                                   firstMove = FALSE;
3335                                 } else {
3336                                   firstMove = TRUE;
3337                                 }
3338                             }
3339                         }
3340                     }
3341 #endif
3342                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3343                         /* Moves came from oldmoves or moves command
3344                            while we weren't doing anything else.
3345                            */
3346                         currentMove = forwardMostMove;
3347                         ClearHighlights();/*!!could figure this out*/
3348                         flipView = appData.flipView;
3349                         DrawPosition(TRUE, boards[currentMove]);
3350                         DisplayBothClocks();
3351                         snprintf(str, MSG_SIZ, "%s vs. %s",
3352                                 gameInfo.white, gameInfo.black);
3353                         DisplayTitle(str);
3354                         gameMode = IcsIdle;
3355                     } else {
3356                         /* Moves were history of an active game */
3357                         if (gameInfo.resultDetails != NULL) {
3358                             free(gameInfo.resultDetails);
3359                             gameInfo.resultDetails = NULL;
3360                         }
3361                     }
3362                     HistorySet(parseList, backwardMostMove,
3363                                forwardMostMove, currentMove-1);
3364                     DisplayMove(currentMove - 1);
3365                     if (started == STARTED_MOVES) next_out = i;
3366                     started = STARTED_NONE;
3367                     ics_getting_history = H_FALSE;
3368                     break;
3369
3370                   case STARTED_OBSERVE:
3371                     started = STARTED_NONE;
3372                     SendToICS(ics_prefix);
3373                     SendToICS("refresh\n");
3374                     break;
3375
3376                   default:
3377                     break;
3378                 }
3379                 if(bookHit) { // [HGM] book: simulate book reply
3380                     static char bookMove[MSG_SIZ]; // a bit generous?
3381
3382                     programStats.nodes = programStats.depth = programStats.time =
3383                     programStats.score = programStats.got_only_move = 0;
3384                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3385
3386                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3387                     strcat(bookMove, bookHit);
3388                     HandleMachineMove(bookMove, &first);
3389                 }
3390                 continue;
3391             }
3392
3393             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3394                  started == STARTED_HOLDINGS ||
3395                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3396                 /* Accumulate characters in move list or board */
3397                 parse[parse_pos++] = buf[i];
3398             }
3399
3400             /* Start of game messages.  Mostly we detect start of game
3401                when the first board image arrives.  On some versions
3402                of the ICS, though, we need to do a "refresh" after starting
3403                to observe in order to get the current board right away. */
3404             if (looking_at(buf, &i, "Adding game * to observation list")) {
3405                 started = STARTED_OBSERVE;
3406                 continue;
3407             }
3408
3409             /* Handle auto-observe */
3410             if (appData.autoObserve &&
3411                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3412                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3413                 char *player;
3414                 /* Choose the player that was highlighted, if any. */
3415                 if (star_match[0][0] == '\033' ||
3416                     star_match[1][0] != '\033') {
3417                     player = star_match[0];
3418                 } else {
3419                     player = star_match[2];
3420                 }
3421                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3422                         ics_prefix, StripHighlightAndTitle(player));
3423                 SendToICS(str);
3424
3425                 /* Save ratings from notify string */
3426                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3427                 player1Rating = string_to_rating(star_match[1]);
3428                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3429                 player2Rating = string_to_rating(star_match[3]);
3430
3431                 if (appData.debugMode)
3432                   fprintf(debugFP,
3433                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3434                           player1Name, player1Rating,
3435                           player2Name, player2Rating);
3436
3437                 continue;
3438             }
3439
3440             /* Deal with automatic examine mode after a game,
3441                and with IcsObserving -> IcsExamining transition */
3442             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3443                 looking_at(buf, &i, "has made you an examiner of game *")) {
3444
3445                 int gamenum = atoi(star_match[0]);
3446                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3447                     gamenum == ics_gamenum) {
3448                     /* We were already playing or observing this game;
3449                        no need to refetch history */
3450                     gameMode = IcsExamining;
3451                     if (pausing) {
3452                         pauseExamForwardMostMove = forwardMostMove;
3453                     } else if (currentMove < forwardMostMove) {
3454                         ForwardInner(forwardMostMove);
3455                     }
3456                 } else {
3457                     /* I don't think this case really can happen */
3458                     SendToICS(ics_prefix);
3459                     SendToICS("refresh\n");
3460                 }
3461                 continue;
3462             }
3463
3464             /* Error messages */
3465 //          if (ics_user_moved) {
3466             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3467                 if (looking_at(buf, &i, "Illegal move") ||
3468                     looking_at(buf, &i, "Not a legal move") ||
3469                     looking_at(buf, &i, "Your king is in check") ||
3470                     looking_at(buf, &i, "It isn't your turn") ||
3471                     looking_at(buf, &i, "It is not your move")) {
3472                     /* Illegal move */
3473                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3474                         currentMove = forwardMostMove-1;
3475                         DisplayMove(currentMove - 1); /* before DMError */
3476                         DrawPosition(FALSE, boards[currentMove]);
3477                         SwitchClocks(forwardMostMove-1); // [HGM] race
3478                         DisplayBothClocks();
3479                     }
3480                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3481                     ics_user_moved = 0;
3482                     continue;
3483                 }
3484             }
3485
3486             if (looking_at(buf, &i, "still have time") ||
3487                 looking_at(buf, &i, "not out of time") ||
3488                 looking_at(buf, &i, "either player is out of time") ||
3489                 looking_at(buf, &i, "has timeseal; checking")) {
3490                 /* We must have called his flag a little too soon */
3491                 whiteFlag = blackFlag = FALSE;
3492                 continue;
3493             }
3494
3495             if (looking_at(buf, &i, "added * seconds to") ||
3496                 looking_at(buf, &i, "seconds were added to")) {
3497                 /* Update the clocks */
3498                 SendToICS(ics_prefix);
3499                 SendToICS("refresh\n");
3500                 continue;
3501             }
3502
3503             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3504                 ics_clock_paused = TRUE;
3505                 StopClocks();
3506                 continue;
3507             }
3508
3509             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3510                 ics_clock_paused = FALSE;
3511                 StartClocks();
3512                 continue;
3513             }
3514
3515             /* Grab player ratings from the Creating: message.
3516                Note we have to check for the special case when
3517                the ICS inserts things like [white] or [black]. */
3518             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3519                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3520                 /* star_matches:
3521                    0    player 1 name (not necessarily white)
3522                    1    player 1 rating
3523                    2    empty, white, or black (IGNORED)
3524                    3    player 2 name (not necessarily black)
3525                    4    player 2 rating
3526
3527                    The names/ratings are sorted out when the game
3528                    actually starts (below).
3529                 */
3530                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3531                 player1Rating = string_to_rating(star_match[1]);
3532                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3533                 player2Rating = string_to_rating(star_match[4]);
3534
3535                 if (appData.debugMode)
3536                   fprintf(debugFP,
3537                           "Ratings from 'Creating:' %s %d, %s %d\n",
3538                           player1Name, player1Rating,
3539                           player2Name, player2Rating);
3540
3541                 continue;
3542             }
3543
3544             /* Improved generic start/end-of-game messages */
3545             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3546                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3547                 /* If tkind == 0: */
3548                 /* star_match[0] is the game number */
3549                 /*           [1] is the white player's name */
3550                 /*           [2] is the black player's name */
3551                 /* For end-of-game: */
3552                 /*           [3] is the reason for the game end */
3553                 /*           [4] is a PGN end game-token, preceded by " " */
3554                 /* For start-of-game: */
3555                 /*           [3] begins with "Creating" or "Continuing" */
3556                 /*           [4] is " *" or empty (don't care). */
3557                 int gamenum = atoi(star_match[0]);
3558                 char *whitename, *blackname, *why, *endtoken;
3559                 ChessMove endtype = EndOfFile;
3560
3561                 if (tkind == 0) {
3562                   whitename = star_match[1];
3563                   blackname = star_match[2];
3564                   why = star_match[3];
3565                   endtoken = star_match[4];
3566                 } else {
3567                   whitename = star_match[1];
3568                   blackname = star_match[3];
3569                   why = star_match[5];
3570                   endtoken = star_match[6];
3571                 }
3572
3573                 /* Game start messages */
3574                 if (strncmp(why, "Creating ", 9) == 0 ||
3575                     strncmp(why, "Continuing ", 11) == 0) {
3576                     gs_gamenum = gamenum;
3577                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3578                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3579 #if ZIPPY
3580                     if (appData.zippyPlay) {
3581                         ZippyGameStart(whitename, blackname);
3582                     }
3583 #endif /*ZIPPY*/
3584                     partnerBoardValid = FALSE; // [HGM] bughouse
3585                     continue;
3586                 }
3587
3588                 /* Game end messages */
3589                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3590                     ics_gamenum != gamenum) {
3591                     continue;
3592                 }
3593                 while (endtoken[0] == ' ') endtoken++;
3594                 switch (endtoken[0]) {
3595                   case '*':
3596                   default:
3597                     endtype = GameUnfinished;
3598                     break;
3599                   case '0':
3600                     endtype = BlackWins;
3601                     break;
3602                   case '1':
3603                     if (endtoken[1] == '/')
3604                       endtype = GameIsDrawn;
3605                     else
3606                       endtype = WhiteWins;
3607                     break;
3608                 }
3609                 GameEnds(endtype, why, GE_ICS);
3610 #if ZIPPY
3611                 if (appData.zippyPlay && first.initDone) {
3612                     ZippyGameEnd(endtype, why);
3613                     if (first.pr == NULL) {
3614                       /* Start the next process early so that we'll
3615                          be ready for the next challenge */
3616                       StartChessProgram(&first);
3617                     }
3618                     /* Send "new" early, in case this command takes
3619                        a long time to finish, so that we'll be ready
3620                        for the next challenge. */
3621                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3622                     Reset(TRUE, TRUE);
3623                 }
3624 #endif /*ZIPPY*/
3625                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3626                 continue;
3627             }
3628
3629             if (looking_at(buf, &i, "Removing game * from observation") ||
3630                 looking_at(buf, &i, "no longer observing game *") ||
3631                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3632                 if (gameMode == IcsObserving &&
3633                     atoi(star_match[0]) == ics_gamenum)
3634                   {
3635                       /* icsEngineAnalyze */
3636                       if (appData.icsEngineAnalyze) {
3637                             ExitAnalyzeMode();
3638                             ModeHighlight();
3639                       }
3640                       StopClocks();
3641                       gameMode = IcsIdle;
3642                       ics_gamenum = -1;
3643                       ics_user_moved = FALSE;
3644                   }
3645                 continue;
3646             }
3647
3648             if (looking_at(buf, &i, "no longer examining game *")) {
3649                 if (gameMode == IcsExamining &&
3650                     atoi(star_match[0]) == ics_gamenum)
3651                   {
3652                       gameMode = IcsIdle;
3653                       ics_gamenum = -1;
3654                       ics_user_moved = FALSE;
3655                   }
3656                 continue;
3657             }
3658
3659             /* Advance leftover_start past any newlines we find,
3660                so only partial lines can get reparsed */
3661             if (looking_at(buf, &i, "\n")) {
3662                 prevColor = curColor;
3663                 if (curColor != ColorNormal) {
3664                     if (oldi > next_out) {
3665                         SendToPlayer(&buf[next_out], oldi - next_out);
3666                         next_out = oldi;
3667                     }
3668                     Colorize(ColorNormal, FALSE);
3669                     curColor = ColorNormal;
3670                 }
3671                 if (started == STARTED_BOARD) {
3672                     started = STARTED_NONE;
3673                     parse[parse_pos] = NULLCHAR;
3674                     ParseBoard12(parse);
3675                     ics_user_moved = 0;
3676
3677                     /* Send premove here */
3678                     if (appData.premove) {
3679                       char str[MSG_SIZ];
3680                       if (currentMove == 0 &&
3681                           gameMode == IcsPlayingWhite &&
3682                           appData.premoveWhite) {
3683                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3684                         if (appData.debugMode)
3685                           fprintf(debugFP, "Sending premove:\n");
3686                         SendToICS(str);
3687                       } else if (currentMove == 1 &&
3688                                  gameMode == IcsPlayingBlack &&
3689                                  appData.premoveBlack) {
3690                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3691                         if (appData.debugMode)
3692                           fprintf(debugFP, "Sending premove:\n");
3693                         SendToICS(str);
3694                       } else if (gotPremove) {
3695                         gotPremove = 0;
3696                         ClearPremoveHighlights();
3697                         if (appData.debugMode)
3698                           fprintf(debugFP, "Sending premove:\n");
3699                           UserMoveEvent(premoveFromX, premoveFromY,
3700                                         premoveToX, premoveToY,
3701                                         premovePromoChar);
3702                       }
3703                     }
3704
3705                     /* Usually suppress following prompt */
3706                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3707                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3708                         if (looking_at(buf, &i, "*% ")) {
3709                             savingComment = FALSE;
3710                             suppressKibitz = 0;
3711                         }
3712                     }
3713                     next_out = i;
3714                 } else if (started == STARTED_HOLDINGS) {
3715                     int gamenum;
3716                     char new_piece[MSG_SIZ];
3717                     started = STARTED_NONE;
3718                     parse[parse_pos] = NULLCHAR;
3719                     if (appData.debugMode)
3720                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3721                                                         parse, currentMove);
3722                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3723                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3724                         if (gameInfo.variant == VariantNormal) {
3725                           /* [HGM] We seem to switch variant during a game!
3726                            * Presumably no holdings were displayed, so we have
3727                            * to move the position two files to the right to
3728                            * create room for them!
3729                            */
3730                           VariantClass newVariant;
3731                           switch(gameInfo.boardWidth) { // base guess on board width
3732                                 case 9:  newVariant = VariantShogi; break;
3733                                 case 10: newVariant = VariantGreat; break;
3734                                 default: newVariant = VariantCrazyhouse; break;
3735                           }
3736                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3737                           /* Get a move list just to see the header, which
3738                              will tell us whether this is really bug or zh */
3739                           if (ics_getting_history == H_FALSE) {
3740                             ics_getting_history = H_REQUESTED;
3741                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3742                             SendToICS(str);
3743                           }
3744                         }
3745                         new_piece[0] = NULLCHAR;
3746                         sscanf(parse, "game %d white [%s black [%s <- %s",
3747                                &gamenum, white_holding, black_holding,
3748                                new_piece);
3749                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3750                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3751                         /* [HGM] copy holdings to board holdings area */
3752                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3753                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3754                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3755 #if ZIPPY
3756                         if (appData.zippyPlay && first.initDone) {
3757                             ZippyHoldings(white_holding, black_holding,
3758                                           new_piece);
3759                         }
3760 #endif /*ZIPPY*/
3761                         if (tinyLayout || smallLayout) {
3762                             char wh[16], bh[16];
3763                             PackHolding(wh, white_holding);
3764                             PackHolding(bh, black_holding);
3765                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3766                                     gameInfo.white, gameInfo.black);
3767                         } else {
3768                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3769                                     gameInfo.white, white_holding,
3770                                     gameInfo.black, black_holding);
3771                         }
3772                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3773                         DrawPosition(FALSE, boards[currentMove]);
3774                         DisplayTitle(str);
3775                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3776                         sscanf(parse, "game %d white [%s black [%s <- %s",
3777                                &gamenum, white_holding, black_holding,
3778                                new_piece);
3779                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3780                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3781                         /* [HGM] copy holdings to partner-board holdings area */
3782                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3783                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3784                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3785                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3786                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3787                       }
3788                     }
3789                     /* Suppress following prompt */
3790                     if (looking_at(buf, &i, "*% ")) {
3791                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3792                         savingComment = FALSE;
3793                         suppressKibitz = 0;
3794                     }
3795                     next_out = i;
3796                 }
3797                 continue;
3798             }
3799
3800             i++;                /* skip unparsed character and loop back */
3801         }
3802
3803         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3804 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3805 //          SendToPlayer(&buf[next_out], i - next_out);
3806             started != STARTED_HOLDINGS && leftover_start > next_out) {
3807             SendToPlayer(&buf[next_out], leftover_start - next_out);
3808             next_out = i;
3809         }
3810
3811         leftover_len = buf_len - leftover_start;
3812         /* if buffer ends with something we couldn't parse,
3813            reparse it after appending the next read */
3814
3815     } else if (count == 0) {
3816         RemoveInputSource(isr);
3817         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3818     } else {
3819         DisplayFatalError(_("Error reading from ICS"), error, 1);
3820     }
3821 }
3822
3823
3824 /* Board style 12 looks like this:
3825
3826    <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
3827
3828  * The "<12> " is stripped before it gets to this routine.  The two
3829  * trailing 0's (flip state and clock ticking) are later addition, and
3830  * some chess servers may not have them, or may have only the first.
3831  * Additional trailing fields may be added in the future.
3832  */
3833
3834 #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"
3835
3836 #define RELATION_OBSERVING_PLAYED    0
3837 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3838 #define RELATION_PLAYING_MYMOVE      1
3839 #define RELATION_PLAYING_NOTMYMOVE  -1
3840 #define RELATION_EXAMINING           2
3841 #define RELATION_ISOLATED_BOARD     -3
3842 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3843
3844 void
3845 ParseBoard12(string)
3846      char *string;
3847 {
3848     GameMode newGameMode;
3849     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3850     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3851     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3852     char to_play, board_chars[200];
3853     char move_str[500], str[500], elapsed_time[500];
3854     char black[32], white[32];
3855     Board board;
3856     int prevMove = currentMove;
3857     int ticking = 2;
3858     ChessMove moveType;
3859     int fromX, fromY, toX, toY;
3860     char promoChar;
3861     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3862     char *bookHit = NULL; // [HGM] book
3863     Boolean weird = FALSE, reqFlag = FALSE;
3864
3865     fromX = fromY = toX = toY = -1;
3866
3867     newGame = FALSE;
3868
3869     if (appData.debugMode)
3870       fprintf(debugFP, _("Parsing board: %s\n"), string);
3871
3872     move_str[0] = NULLCHAR;
3873     elapsed_time[0] = NULLCHAR;
3874     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3875         int  i = 0, j;
3876         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3877             if(string[i] == ' ') { ranks++; files = 0; }
3878             else files++;
3879             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3880             i++;
3881         }
3882         for(j = 0; j <i; j++) board_chars[j] = string[j];
3883         board_chars[i] = '\0';
3884         string += i + 1;
3885     }
3886     n = sscanf(string, PATTERN, &to_play, &double_push,
3887                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3888                &gamenum, white, black, &relation, &basetime, &increment,
3889                &white_stren, &black_stren, &white_time, &black_time,
3890                &moveNum, str, elapsed_time, move_str, &ics_flip,
3891                &ticking);
3892
3893     if (n < 21) {
3894         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3895         DisplayError(str, 0);
3896         return;
3897     }
3898
3899     /* Convert the move number to internal form */
3900     moveNum = (moveNum - 1) * 2;
3901     if (to_play == 'B') moveNum++;
3902     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3903       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3904                         0, 1);
3905       return;
3906     }
3907
3908     switch (relation) {
3909       case RELATION_OBSERVING_PLAYED:
3910       case RELATION_OBSERVING_STATIC:
3911         if (gamenum == -1) {
3912             /* Old ICC buglet */
3913             relation = RELATION_OBSERVING_STATIC;
3914         }
3915         newGameMode = IcsObserving;
3916         break;
3917       case RELATION_PLAYING_MYMOVE:
3918       case RELATION_PLAYING_NOTMYMOVE:
3919         newGameMode =
3920           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3921             IcsPlayingWhite : IcsPlayingBlack;
3922         break;
3923       case RELATION_EXAMINING:
3924         newGameMode = IcsExamining;
3925         break;
3926       case RELATION_ISOLATED_BOARD:
3927       default:
3928         /* Just display this board.  If user was doing something else,
3929            we will forget about it until the next board comes. */
3930         newGameMode = IcsIdle;
3931         break;
3932       case RELATION_STARTING_POSITION:
3933         newGameMode = gameMode;
3934         break;
3935     }
3936
3937     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3938          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3939       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3940       char *toSqr;
3941       for (k = 0; k < ranks; k++) {
3942         for (j = 0; j < files; j++)
3943           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3944         if(gameInfo.holdingsWidth > 1) {
3945              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3946              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3947         }
3948       }
3949       CopyBoard(partnerBoard, board);
3950       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3951         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3952         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3953       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3954       if(toSqr = strchr(str, '-')) {
3955         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3956         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3957       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3958       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3959       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3960       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3961       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3962       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3963                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3964       DisplayMessage(partnerStatus, "");
3965         partnerBoardValid = TRUE;
3966       return;
3967     }
3968
3969     /* Modify behavior for initial board display on move listing
3970        of wild games.
3971        */
3972     switch (ics_getting_history) {
3973       case H_FALSE:
3974       case H_REQUESTED:
3975         break;
3976       case H_GOT_REQ_HEADER:
3977       case H_GOT_UNREQ_HEADER:
3978         /* This is the initial position of the current game */
3979         gamenum = ics_gamenum;
3980         moveNum = 0;            /* old ICS bug workaround */
3981         if (to_play == 'B') {
3982           startedFromSetupPosition = TRUE;
3983           blackPlaysFirst = TRUE;
3984           moveNum = 1;
3985           if (forwardMostMove == 0) forwardMostMove = 1;
3986           if (backwardMostMove == 0) backwardMostMove = 1;
3987           if (currentMove == 0) currentMove = 1;
3988         }
3989         newGameMode = gameMode;
3990         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3991         break;
3992       case H_GOT_UNWANTED_HEADER:
3993         /* This is an initial board that we don't want */
3994         return;
3995       case H_GETTING_MOVES:
3996         /* Should not happen */
3997         DisplayError(_("Error gathering move list: extra board"), 0);
3998         ics_getting_history = H_FALSE;
3999         return;
4000     }
4001
4002    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4003                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
4004      /* [HGM] We seem to have switched variant unexpectedly
4005       * Try to guess new variant from board size
4006       */
4007           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4008           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4009           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4010           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4011           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4012           if(!weird) newVariant = VariantNormal;
4013           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4014           /* Get a move list just to see the header, which
4015              will tell us whether this is really bug or zh */
4016           if (ics_getting_history == H_FALSE) {
4017             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4018             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4019             SendToICS(str);
4020           }
4021     }
4022
4023     /* Take action if this is the first board of a new game, or of a
4024        different game than is currently being displayed.  */
4025     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4026         relation == RELATION_ISOLATED_BOARD) {
4027
4028         /* Forget the old game and get the history (if any) of the new one */
4029         if (gameMode != BeginningOfGame) {
4030           Reset(TRUE, TRUE);
4031         }
4032         newGame = TRUE;
4033         if (appData.autoRaiseBoard) BoardToTop();
4034         prevMove = -3;
4035         if (gamenum == -1) {
4036             newGameMode = IcsIdle;
4037         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4038                    appData.getMoveList && !reqFlag) {
4039             /* Need to get game history */
4040             ics_getting_history = H_REQUESTED;
4041             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4042             SendToICS(str);
4043         }
4044
4045         /* Initially flip the board to have black on the bottom if playing
4046            black or if the ICS flip flag is set, but let the user change
4047            it with the Flip View button. */
4048         flipView = appData.autoFlipView ?
4049           (newGameMode == IcsPlayingBlack) || ics_flip :
4050           appData.flipView;
4051
4052         /* Done with values from previous mode; copy in new ones */
4053         gameMode = newGameMode;
4054         ModeHighlight();
4055         ics_gamenum = gamenum;
4056         if (gamenum == gs_gamenum) {
4057             int klen = strlen(gs_kind);
4058             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4059             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4060             gameInfo.event = StrSave(str);
4061         } else {
4062             gameInfo.event = StrSave("ICS game");
4063         }
4064         gameInfo.site = StrSave(appData.icsHost);
4065         gameInfo.date = PGNDate();
4066         gameInfo.round = StrSave("-");
4067         gameInfo.white = StrSave(white);
4068         gameInfo.black = StrSave(black);
4069         timeControl = basetime * 60 * 1000;
4070         timeControl_2 = 0;
4071         timeIncrement = increment * 1000;
4072         movesPerSession = 0;
4073         gameInfo.timeControl = TimeControlTagValue();
4074         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4075   if (appData.debugMode) {
4076     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4077     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4078     setbuf(debugFP, NULL);
4079   }
4080
4081         gameInfo.outOfBook = NULL;
4082
4083         /* Do we have the ratings? */
4084         if (strcmp(player1Name, white) == 0 &&
4085             strcmp(player2Name, black) == 0) {
4086             if (appData.debugMode)
4087               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4088                       player1Rating, player2Rating);
4089             gameInfo.whiteRating = player1Rating;
4090             gameInfo.blackRating = player2Rating;
4091         } else if (strcmp(player2Name, white) == 0 &&
4092                    strcmp(player1Name, black) == 0) {
4093             if (appData.debugMode)
4094               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4095                       player2Rating, player1Rating);
4096             gameInfo.whiteRating = player2Rating;
4097             gameInfo.blackRating = player1Rating;
4098         }
4099         player1Name[0] = player2Name[0] = NULLCHAR;
4100
4101         /* Silence shouts if requested */
4102         if (appData.quietPlay &&
4103             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4104             SendToICS(ics_prefix);
4105             SendToICS("set shout 0\n");
4106         }
4107     }
4108
4109     /* Deal with midgame name changes */
4110     if (!newGame) {
4111         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4112             if (gameInfo.white) free(gameInfo.white);
4113             gameInfo.white = StrSave(white);
4114         }
4115         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4116             if (gameInfo.black) free(gameInfo.black);
4117             gameInfo.black = StrSave(black);
4118         }
4119     }
4120
4121     /* Throw away game result if anything actually changes in examine mode */
4122     if (gameMode == IcsExamining && !newGame) {
4123         gameInfo.result = GameUnfinished;
4124         if (gameInfo.resultDetails != NULL) {
4125             free(gameInfo.resultDetails);
4126             gameInfo.resultDetails = NULL;
4127         }
4128     }
4129
4130     /* In pausing && IcsExamining mode, we ignore boards coming
4131        in if they are in a different variation than we are. */
4132     if (pauseExamInvalid) return;
4133     if (pausing && gameMode == IcsExamining) {
4134         if (moveNum <= pauseExamForwardMostMove) {
4135             pauseExamInvalid = TRUE;
4136             forwardMostMove = pauseExamForwardMostMove;
4137             return;
4138         }
4139     }
4140
4141   if (appData.debugMode) {
4142     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4143   }
4144     /* Parse the board */
4145     for (k = 0; k < ranks; k++) {
4146       for (j = 0; j < files; j++)
4147         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4148       if(gameInfo.holdingsWidth > 1) {
4149            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4150            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4151       }
4152     }
4153     CopyBoard(boards[moveNum], board);
4154     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4155     if (moveNum == 0) {
4156         startedFromSetupPosition =
4157           !CompareBoards(board, initialPosition);
4158         if(startedFromSetupPosition)
4159             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4160     }
4161
4162     /* [HGM] Set castling rights. Take the outermost Rooks,
4163        to make it also work for FRC opening positions. Note that board12
4164        is really defective for later FRC positions, as it has no way to
4165        indicate which Rook can castle if they are on the same side of King.
4166        For the initial position we grant rights to the outermost Rooks,
4167        and remember thos rights, and we then copy them on positions
4168        later in an FRC game. This means WB might not recognize castlings with
4169        Rooks that have moved back to their original position as illegal,
4170        but in ICS mode that is not its job anyway.
4171     */
4172     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4173     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4174
4175         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4176             if(board[0][i] == WhiteRook) j = i;
4177         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4178         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4179             if(board[0][i] == WhiteRook) j = i;
4180         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4181         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4182             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4183         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4184         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4185             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4186         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4187
4188         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4189         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4190             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4191         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4192             if(board[BOARD_HEIGHT-1][k] == bKing)
4193                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4194         if(gameInfo.variant == VariantTwoKings) {
4195             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4196             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4197             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4198         }
4199     } else { int r;
4200         r = boards[moveNum][CASTLING][0] = initialRights[0];
4201         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4202         r = boards[moveNum][CASTLING][1] = initialRights[1];
4203         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4204         r = boards[moveNum][CASTLING][3] = initialRights[3];
4205         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4206         r = boards[moveNum][CASTLING][4] = initialRights[4];
4207         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4208         /* wildcastle kludge: always assume King has rights */
4209         r = boards[moveNum][CASTLING][2] = initialRights[2];
4210         r = boards[moveNum][CASTLING][5] = initialRights[5];
4211     }
4212     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4213     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4214
4215
4216     if (ics_getting_history == H_GOT_REQ_HEADER ||
4217         ics_getting_history == H_GOT_UNREQ_HEADER) {
4218         /* This was an initial position from a move list, not
4219            the current position */
4220         return;
4221     }
4222
4223     /* Update currentMove and known move number limits */
4224     newMove = newGame || moveNum > forwardMostMove;
4225
4226     if (newGame) {
4227         forwardMostMove = backwardMostMove = currentMove = moveNum;
4228         if (gameMode == IcsExamining && moveNum == 0) {
4229           /* Workaround for ICS limitation: we are not told the wild
4230              type when starting to examine a game.  But if we ask for
4231              the move list, the move list header will tell us */
4232             ics_getting_history = H_REQUESTED;
4233             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4234             SendToICS(str);
4235         }
4236     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4237                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4238 #if ZIPPY
4239         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4240         /* [HGM] applied this also to an engine that is silently watching        */
4241         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4242             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4243             gameInfo.variant == currentlyInitializedVariant) {
4244           takeback = forwardMostMove - moveNum;
4245           for (i = 0; i < takeback; i++) {
4246             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4247             SendToProgram("undo\n", &first);
4248           }
4249         }
4250 #endif
4251
4252         forwardMostMove = moveNum;
4253         if (!pausing || currentMove > forwardMostMove)
4254           currentMove = forwardMostMove;
4255     } else {
4256         /* New part of history that is not contiguous with old part */
4257         if (pausing && gameMode == IcsExamining) {
4258             pauseExamInvalid = TRUE;
4259             forwardMostMove = pauseExamForwardMostMove;
4260             return;
4261         }
4262         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4263 #if ZIPPY
4264             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4265                 // [HGM] when we will receive the move list we now request, it will be
4266                 // fed to the engine from the first move on. So if the engine is not
4267                 // in the initial position now, bring it there.
4268                 InitChessProgram(&first, 0);
4269             }
4270 #endif
4271             ics_getting_history = H_REQUESTED;
4272             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4273             SendToICS(str);
4274         }
4275         forwardMostMove = backwardMostMove = currentMove = moveNum;
4276     }
4277
4278     /* Update the clocks */
4279     if (strchr(elapsed_time, '.')) {
4280       /* Time is in ms */
4281       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4282       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4283     } else {
4284       /* Time is in seconds */
4285       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4286       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4287     }
4288
4289
4290 #if ZIPPY
4291     if (appData.zippyPlay && newGame &&
4292         gameMode != IcsObserving && gameMode != IcsIdle &&
4293         gameMode != IcsExamining)
4294       ZippyFirstBoard(moveNum, basetime, increment);
4295 #endif
4296
4297     /* Put the move on the move list, first converting
4298        to canonical algebraic form. */
4299     if (moveNum > 0) {
4300   if (appData.debugMode) {
4301     if (appData.debugMode) { int f = forwardMostMove;
4302         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4303                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4304                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4305     }
4306     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4307     fprintf(debugFP, "moveNum = %d\n", moveNum);
4308     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4309     setbuf(debugFP, NULL);
4310   }
4311         if (moveNum <= backwardMostMove) {
4312             /* We don't know what the board looked like before
4313                this move.  Punt. */
4314           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4315             strcat(parseList[moveNum - 1], " ");
4316             strcat(parseList[moveNum - 1], elapsed_time);
4317             moveList[moveNum - 1][0] = NULLCHAR;
4318         } else if (strcmp(move_str, "none") == 0) {
4319             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4320             /* Again, we don't know what the board looked like;
4321                this is really the start of the game. */
4322             parseList[moveNum - 1][0] = NULLCHAR;
4323             moveList[moveNum - 1][0] = NULLCHAR;
4324             backwardMostMove = moveNum;
4325             startedFromSetupPosition = TRUE;
4326             fromX = fromY = toX = toY = -1;
4327         } else {
4328           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4329           //                 So we parse the long-algebraic move string in stead of the SAN move
4330           int valid; char buf[MSG_SIZ], *prom;
4331
4332           // str looks something like "Q/a1-a2"; kill the slash
4333           if(str[1] == '/')
4334             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4335           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4336           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4337                 strcat(buf, prom); // long move lacks promo specification!
4338           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4339                 if(appData.debugMode)
4340                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4341                 safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0]));
4342           }
4343           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4344                                 &fromX, &fromY, &toX, &toY, &promoChar)
4345                || ParseOneMove(buf, moveNum - 1, &moveType,
4346                                 &fromX, &fromY, &toX, &toY, &promoChar);
4347           // end of long SAN patch
4348           if (valid) {
4349             (void) CoordsToAlgebraic(boards[moveNum - 1],
4350                                      PosFlags(moveNum - 1),
4351                                      fromY, fromX, toY, toX, promoChar,
4352                                      parseList[moveNum-1]);
4353             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4354               case MT_NONE:
4355               case MT_STALEMATE:
4356               default:
4357                 break;
4358               case MT_CHECK:
4359                 if(gameInfo.variant != VariantShogi)
4360                     strcat(parseList[moveNum - 1], "+");
4361                 break;
4362               case MT_CHECKMATE:
4363               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4364                 strcat(parseList[moveNum - 1], "#");
4365                 break;
4366             }
4367             strcat(parseList[moveNum - 1], " ");
4368             strcat(parseList[moveNum - 1], elapsed_time);
4369             /* currentMoveString is set as a side-effect of ParseOneMove */
4370             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4371             strcat(moveList[moveNum - 1], "\n");
4372           } else {
4373             /* Move from ICS was illegal!?  Punt. */
4374             if (appData.debugMode) {
4375               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4376               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4377             }
4378             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4379             strcat(parseList[moveNum - 1], " ");
4380             strcat(parseList[moveNum - 1], elapsed_time);
4381             moveList[moveNum - 1][0] = NULLCHAR;
4382             fromX = fromY = toX = toY = -1;
4383           }
4384         }
4385   if (appData.debugMode) {
4386     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4387     setbuf(debugFP, NULL);
4388   }
4389
4390 #if ZIPPY
4391         /* Send move to chess program (BEFORE animating it). */
4392         if (appData.zippyPlay && !newGame && newMove &&
4393            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4394
4395             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4396                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4397                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4398                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4399                             move_str);
4400                     DisplayError(str, 0);
4401                 } else {
4402                     if (first.sendTime) {
4403                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4404                     }
4405                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4406                     if (firstMove && !bookHit) {
4407                         firstMove = FALSE;
4408                         if (first.useColors) {
4409                           SendToProgram(gameMode == IcsPlayingWhite ?
4410                                         "white\ngo\n" :
4411                                         "black\ngo\n", &first);
4412                         } else {
4413                           SendToProgram("go\n", &first);
4414                         }
4415                         first.maybeThinking = TRUE;
4416                     }
4417                 }
4418             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4419               if (moveList[moveNum - 1][0] == NULLCHAR) {
4420                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4421                 DisplayError(str, 0);
4422               } else {
4423                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4424                 SendMoveToProgram(moveNum - 1, &first);
4425               }
4426             }
4427         }
4428 #endif
4429     }
4430
4431     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4432         /* If move comes from a remote source, animate it.  If it
4433            isn't remote, it will have already been animated. */
4434         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4435             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4436         }
4437         if (!pausing && appData.highlightLastMove) {
4438             SetHighlights(fromX, fromY, toX, toY);
4439         }
4440     }
4441
4442     /* Start the clocks */
4443     whiteFlag = blackFlag = FALSE;
4444     appData.clockMode = !(basetime == 0 && increment == 0);
4445     if (ticking == 0) {
4446       ics_clock_paused = TRUE;
4447       StopClocks();
4448     } else if (ticking == 1) {
4449       ics_clock_paused = FALSE;
4450     }
4451     if (gameMode == IcsIdle ||
4452         relation == RELATION_OBSERVING_STATIC ||
4453         relation == RELATION_EXAMINING ||
4454         ics_clock_paused)
4455       DisplayBothClocks();
4456     else
4457       StartClocks();
4458
4459     /* Display opponents and material strengths */
4460     if (gameInfo.variant != VariantBughouse &&
4461         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4462         if (tinyLayout || smallLayout) {
4463             if(gameInfo.variant == VariantNormal)
4464               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4465                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4466                     basetime, increment);
4467             else
4468               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4469                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4470                     basetime, increment, (int) gameInfo.variant);
4471         } else {
4472             if(gameInfo.variant == VariantNormal)
4473               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4474                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4475                     basetime, increment);
4476             else
4477               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4478                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4479                     basetime, increment, VariantName(gameInfo.variant));
4480         }
4481         DisplayTitle(str);
4482   if (appData.debugMode) {
4483     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4484   }
4485     }
4486
4487
4488     /* Display the board */
4489     if (!pausing && !appData.noGUI) {
4490
4491       if (appData.premove)
4492           if (!gotPremove ||
4493              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4494              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4495               ClearPremoveHighlights();
4496
4497       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4498         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4499       DrawPosition(j, boards[currentMove]);
4500
4501       DisplayMove(moveNum - 1);
4502       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4503             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4504               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4505         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4506       }
4507     }
4508
4509     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4510 #if ZIPPY
4511     if(bookHit) { // [HGM] book: simulate book reply
4512         static char bookMove[MSG_SIZ]; // a bit generous?
4513
4514         programStats.nodes = programStats.depth = programStats.time =
4515         programStats.score = programStats.got_only_move = 0;
4516         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4517
4518         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4519         strcat(bookMove, bookHit);
4520         HandleMachineMove(bookMove, &first);
4521     }
4522 #endif
4523 }
4524
4525 void
4526 GetMoveListEvent()
4527 {
4528     char buf[MSG_SIZ];
4529     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4530         ics_getting_history = H_REQUESTED;
4531         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4532         SendToICS(buf);
4533     }
4534 }
4535
4536 void
4537 AnalysisPeriodicEvent(force)
4538      int force;
4539 {
4540     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4541          && !force) || !appData.periodicUpdates)
4542       return;
4543
4544     /* Send . command to Crafty to collect stats */
4545     SendToProgram(".\n", &first);
4546
4547     /* Don't send another until we get a response (this makes
4548        us stop sending to old Crafty's which don't understand
4549        the "." command (sending illegal cmds resets node count & time,
4550        which looks bad)) */
4551     programStats.ok_to_send = 0;
4552 }
4553
4554 void ics_update_width(new_width)
4555         int new_width;
4556 {
4557         ics_printf("set width %d\n", new_width);
4558 }
4559
4560 void
4561 SendMoveToProgram(moveNum, cps)
4562      int moveNum;
4563      ChessProgramState *cps;
4564 {
4565     char buf[MSG_SIZ];
4566
4567     if (cps->useUsermove) {
4568       SendToProgram("usermove ", cps);
4569     }
4570     if (cps->useSAN) {
4571       char *space;
4572       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4573         int len = space - parseList[moveNum];
4574         memcpy(buf, parseList[moveNum], len);
4575         buf[len++] = '\n';
4576         buf[len] = NULLCHAR;
4577       } else {
4578         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4579       }
4580       SendToProgram(buf, cps);
4581     } else {
4582       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4583         AlphaRank(moveList[moveNum], 4);
4584         SendToProgram(moveList[moveNum], cps);
4585         AlphaRank(moveList[moveNum], 4); // and back
4586       } else
4587       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4588        * the engine. It would be nice to have a better way to identify castle
4589        * moves here. */
4590       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4591                                                                          && cps->useOOCastle) {
4592         int fromX = moveList[moveNum][0] - AAA;
4593         int fromY = moveList[moveNum][1] - ONE;
4594         int toX = moveList[moveNum][2] - AAA;
4595         int toY = moveList[moveNum][3] - ONE;
4596         if((boards[moveNum][fromY][fromX] == WhiteKing
4597             && boards[moveNum][toY][toX] == WhiteRook)
4598            || (boards[moveNum][fromY][fromX] == BlackKing
4599                && boards[moveNum][toY][toX] == BlackRook)) {
4600           if(toX > fromX) SendToProgram("O-O\n", cps);
4601           else SendToProgram("O-O-O\n", cps);
4602         }
4603         else SendToProgram(moveList[moveNum], cps);
4604       }
4605       else SendToProgram(moveList[moveNum], cps);
4606       /* End of additions by Tord */
4607     }
4608
4609     /* [HGM] setting up the opening has brought engine in force mode! */
4610     /*       Send 'go' if we are in a mode where machine should play. */
4611     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4612         (gameMode == TwoMachinesPlay   ||
4613 #if ZIPPY
4614          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4615 #endif
4616          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4617         SendToProgram("go\n", cps);
4618   if (appData.debugMode) {
4619     fprintf(debugFP, "(extra)\n");
4620   }
4621     }
4622     setboardSpoiledMachineBlack = 0;
4623 }
4624
4625 void
4626 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4627      ChessMove moveType;
4628      int fromX, fromY, toX, toY;
4629      char promoChar;
4630 {
4631     char user_move[MSG_SIZ];
4632
4633     switch (moveType) {
4634       default:
4635         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4636                 (int)moveType, fromX, fromY, toX, toY);
4637         DisplayError(user_move + strlen("say "), 0);
4638         break;
4639       case WhiteKingSideCastle:
4640       case BlackKingSideCastle:
4641       case WhiteQueenSideCastleWild:
4642       case BlackQueenSideCastleWild:
4643       /* PUSH Fabien */
4644       case WhiteHSideCastleFR:
4645       case BlackHSideCastleFR:
4646       /* POP Fabien */
4647         snprintf(user_move, MSG_SIZ, "o-o\n");
4648         break;
4649       case WhiteQueenSideCastle:
4650       case BlackQueenSideCastle:
4651       case WhiteKingSideCastleWild:
4652       case BlackKingSideCastleWild:
4653       /* PUSH Fabien */
4654       case WhiteASideCastleFR:
4655       case BlackASideCastleFR:
4656       /* POP Fabien */
4657         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4658         break;
4659       case WhiteNonPromotion:
4660       case BlackNonPromotion:
4661         sprintf(user_move, "%c%c%c%c=\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4662         break;
4663       case WhitePromotion:
4664       case BlackPromotion:
4665         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4666           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4667                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4668                 PieceToChar(WhiteFerz));
4669         else if(gameInfo.variant == VariantGreat)
4670           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4671                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4672                 PieceToChar(WhiteMan));
4673         else
4674           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4675                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4676                 promoChar);
4677         break;
4678       case WhiteDrop:
4679       case BlackDrop:
4680         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4681                 ToUpper(PieceToChar((ChessSquare) fromX)),
4682                 AAA + toX, ONE + toY);
4683         break;
4684       case NormalMove:
4685       case WhiteCapturesEnPassant:
4686       case BlackCapturesEnPassant:
4687       case IllegalMove:  /* could be a variant we don't quite understand */
4688         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4689                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4690         break;
4691     }
4692     SendToICS(user_move);
4693     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4694         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4695 }
4696
4697 void
4698 UploadGameEvent()
4699 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4700     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4701     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4702     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4703         DisplayError("You cannot do this while you are playing or observing", 0);
4704         return;
4705     }
4706     if(gameMode != IcsExamining) { // is this ever not the case?
4707         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4708
4709         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4710           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4711         } else { // on FICS we must first go to general examine mode
4712           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4713         }
4714         if(gameInfo.variant != VariantNormal) {
4715             // try figure out wild number, as xboard names are not always valid on ICS
4716             for(i=1; i<=36; i++) {
4717               snprintf(buf, MSG_SIZ, "wild/%d", i);
4718                 if(StringToVariant(buf) == gameInfo.variant) break;
4719             }
4720             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4721             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4722             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4723         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4724         SendToICS(ics_prefix);
4725         SendToICS(buf);
4726         if(startedFromSetupPosition || backwardMostMove != 0) {
4727           fen = PositionToFEN(backwardMostMove, NULL);
4728           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4729             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4730             SendToICS(buf);
4731           } else { // FICS: everything has to set by separate bsetup commands
4732             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4733             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4734             SendToICS(buf);
4735             if(!WhiteOnMove(backwardMostMove)) {
4736                 SendToICS("bsetup tomove black\n");
4737             }
4738             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4739             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4740             SendToICS(buf);
4741             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4742             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4743             SendToICS(buf);
4744             i = boards[backwardMostMove][EP_STATUS];
4745             if(i >= 0) { // set e.p.
4746               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4747                 SendToICS(buf);
4748             }
4749             bsetup++;
4750           }
4751         }
4752       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4753             SendToICS("bsetup done\n"); // switch to normal examining.
4754     }
4755     for(i = backwardMostMove; i<last; i++) {
4756         char buf[20];
4757         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4758         SendToICS(buf);
4759     }
4760     SendToICS(ics_prefix);
4761     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4762 }
4763
4764 void
4765 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4766      int rf, ff, rt, ft;
4767      char promoChar;
4768      char move[7];
4769 {
4770     if (rf == DROP_RANK) {
4771       sprintf(move, "%c@%c%c\n",
4772                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4773     } else {
4774         if (promoChar == 'x' || promoChar == NULLCHAR) {
4775           sprintf(move, "%c%c%c%c\n",
4776                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4777         } else {
4778             sprintf(move, "%c%c%c%c%c\n",
4779                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4780         }
4781     }
4782 }
4783
4784 void
4785 ProcessICSInitScript(f)
4786      FILE *f;
4787 {
4788     char buf[MSG_SIZ];
4789
4790     while (fgets(buf, MSG_SIZ, f)) {
4791         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4792     }
4793
4794     fclose(f);
4795 }
4796
4797
4798 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4799 void
4800 AlphaRank(char *move, int n)
4801 {
4802 //    char *p = move, c; int x, y;
4803
4804     if (appData.debugMode) {
4805         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4806     }
4807
4808     if(move[1]=='*' &&
4809        move[2]>='0' && move[2]<='9' &&
4810        move[3]>='a' && move[3]<='x'    ) {
4811         move[1] = '@';
4812         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4813         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4814     } else
4815     if(move[0]>='0' && move[0]<='9' &&
4816        move[1]>='a' && move[1]<='x' &&
4817        move[2]>='0' && move[2]<='9' &&
4818        move[3]>='a' && move[3]<='x'    ) {
4819         /* input move, Shogi -> normal */
4820         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4821         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4822         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4823         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4824     } else
4825     if(move[1]=='@' &&
4826        move[3]>='0' && move[3]<='9' &&
4827        move[2]>='a' && move[2]<='x'    ) {
4828         move[1] = '*';
4829         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4830         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4831     } else
4832     if(
4833        move[0]>='a' && move[0]<='x' &&
4834        move[3]>='0' && move[3]<='9' &&
4835        move[2]>='a' && move[2]<='x'    ) {
4836          /* output move, normal -> Shogi */
4837         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4838         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4839         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4840         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4841         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4842     }
4843     if (appData.debugMode) {
4844         fprintf(debugFP, "   out = '%s'\n", move);
4845     }
4846 }
4847
4848 char yy_textstr[8000];
4849
4850 /* Parser for moves from gnuchess, ICS, or user typein box */
4851 Boolean
4852 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4853      char *move;
4854      int moveNum;
4855      ChessMove *moveType;
4856      int *fromX, *fromY, *toX, *toY;
4857      char *promoChar;
4858 {
4859     if (appData.debugMode) {
4860         fprintf(debugFP, "move to parse: %s\n", move);
4861     }
4862     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4863
4864     switch (*moveType) {
4865       case WhitePromotion:
4866       case BlackPromotion:
4867       case WhiteNonPromotion:
4868       case BlackNonPromotion:
4869       case NormalMove:
4870       case WhiteCapturesEnPassant:
4871       case BlackCapturesEnPassant:
4872       case WhiteKingSideCastle:
4873       case WhiteQueenSideCastle:
4874       case BlackKingSideCastle:
4875       case BlackQueenSideCastle:
4876       case WhiteKingSideCastleWild:
4877       case WhiteQueenSideCastleWild:
4878       case BlackKingSideCastleWild:
4879       case BlackQueenSideCastleWild:
4880       /* Code added by Tord: */
4881       case WhiteHSideCastleFR:
4882       case WhiteASideCastleFR:
4883       case BlackHSideCastleFR:
4884       case BlackASideCastleFR:
4885       /* End of code added by Tord */
4886       case IllegalMove:         /* bug or odd chess variant */
4887         *fromX = currentMoveString[0] - AAA;
4888         *fromY = currentMoveString[1] - ONE;
4889         *toX = currentMoveString[2] - AAA;
4890         *toY = currentMoveString[3] - ONE;
4891         *promoChar = currentMoveString[4];
4892         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4893             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4894     if (appData.debugMode) {
4895         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4896     }
4897             *fromX = *fromY = *toX = *toY = 0;
4898             return FALSE;
4899         }
4900         if (appData.testLegality) {
4901           return (*moveType != IllegalMove);
4902         } else {
4903           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4904                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4905         }
4906
4907       case WhiteDrop:
4908       case BlackDrop:
4909         *fromX = *moveType == WhiteDrop ?
4910           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4911           (int) CharToPiece(ToLower(currentMoveString[0]));
4912         *fromY = DROP_RANK;
4913         *toX = currentMoveString[2] - AAA;
4914         *toY = currentMoveString[3] - ONE;
4915         *promoChar = NULLCHAR;
4916         return TRUE;
4917
4918       case AmbiguousMove:
4919       case ImpossibleMove:
4920       case EndOfFile:
4921       case ElapsedTime:
4922       case Comment:
4923       case PGNTag:
4924       case NAG:
4925       case WhiteWins:
4926       case BlackWins:
4927       case GameIsDrawn:
4928       default:
4929     if (appData.debugMode) {
4930         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4931     }
4932         /* bug? */
4933         *fromX = *fromY = *toX = *toY = 0;
4934         *promoChar = NULLCHAR;
4935         return FALSE;
4936     }
4937 }
4938
4939
4940 void
4941 ParsePV(char *pv, Boolean storeComments)
4942 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4943   int fromX, fromY, toX, toY; char promoChar;
4944   ChessMove moveType;
4945   Boolean valid;
4946   int nr = 0;
4947
4948   endPV = forwardMostMove;
4949   do {
4950     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4951     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4952     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4953 if(appData.debugMode){
4954 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);
4955 }
4956     if(!valid && nr == 0 &&
4957        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4958         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4959         // Hande case where played move is different from leading PV move
4960         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4961         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4962         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4963         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4964           endPV += 2; // if position different, keep this
4965           moveList[endPV-1][0] = fromX + AAA;
4966           moveList[endPV-1][1] = fromY + ONE;
4967           moveList[endPV-1][2] = toX + AAA;
4968           moveList[endPV-1][3] = toY + ONE;
4969           parseList[endPV-1][0] = NULLCHAR;
4970           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4971         }
4972       }
4973     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4974     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4975     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4976     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4977         valid++; // allow comments in PV
4978         continue;
4979     }
4980     nr++;
4981     if(endPV+1 > framePtr) break; // no space, truncate
4982     if(!valid) break;
4983     endPV++;
4984     CopyBoard(boards[endPV], boards[endPV-1]);
4985     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4986     moveList[endPV-1][0] = fromX + AAA;
4987     moveList[endPV-1][1] = fromY + ONE;
4988     moveList[endPV-1][2] = toX + AAA;
4989     moveList[endPV-1][3] = toY + ONE;
4990     if(storeComments)
4991         CoordsToAlgebraic(boards[endPV - 1],
4992                              PosFlags(endPV - 1),
4993                              fromY, fromX, toY, toX, promoChar,
4994                              parseList[endPV - 1]);
4995     else
4996         parseList[endPV-1][0] = NULLCHAR;
4997   } while(valid);
4998   currentMove = endPV;
4999   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5000   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5001                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5002   DrawPosition(TRUE, boards[currentMove]);
5003 }
5004
5005 static int lastX, lastY;
5006
5007 Boolean
5008 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5009 {
5010         int startPV;
5011         char *p;
5012
5013         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5014         lastX = x; lastY = y;
5015         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5016         startPV = index;
5017         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5018         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5019         index = startPV;
5020         do{ while(buf[index] && buf[index] != '\n') index++;
5021         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5022         buf[index] = 0;
5023         ParsePV(buf+startPV, FALSE);
5024         *start = startPV; *end = index-1;
5025         return TRUE;
5026 }
5027
5028 Boolean
5029 LoadPV(int x, int y)
5030 { // called on right mouse click to load PV
5031   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5032   lastX = x; lastY = y;
5033   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5034   return TRUE;
5035 }
5036
5037 void
5038 UnLoadPV()
5039 {
5040   if(endPV < 0) return;
5041   endPV = -1;
5042   currentMove = forwardMostMove;
5043   ClearPremoveHighlights();
5044   DrawPosition(TRUE, boards[currentMove]);
5045 }
5046
5047 void
5048 MovePV(int x, int y, int h)
5049 { // step through PV based on mouse coordinates (called on mouse move)
5050   int margin = h>>3, step = 0;
5051
5052   if(endPV < 0) return;
5053   // we must somehow check if right button is still down (might be released off board!)
5054   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5055   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5056   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5057   if(!step) return;
5058   lastX = x; lastY = y;
5059   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5060   currentMove += step;
5061   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5062   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5063                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5064   DrawPosition(FALSE, boards[currentMove]);
5065 }
5066
5067
5068 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5069 // All positions will have equal probability, but the current method will not provide a unique
5070 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5071 #define DARK 1
5072 #define LITE 2
5073 #define ANY 3
5074
5075 int squaresLeft[4];
5076 int piecesLeft[(int)BlackPawn];
5077 int seed, nrOfShuffles;
5078
5079 void GetPositionNumber()
5080 {       // sets global variable seed
5081         int i;
5082
5083         seed = appData.defaultFrcPosition;
5084         if(seed < 0) { // randomize based on time for negative FRC position numbers
5085                 for(i=0; i<50; i++) seed += random();
5086                 seed = random() ^ random() >> 8 ^ random() << 8;
5087                 if(seed<0) seed = -seed;
5088         }
5089 }
5090
5091 int put(Board board, int pieceType, int rank, int n, int shade)
5092 // put the piece on the (n-1)-th empty squares of the given shade
5093 {
5094         int i;
5095
5096         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5097                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5098                         board[rank][i] = (ChessSquare) pieceType;
5099                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5100                         squaresLeft[ANY]--;
5101                         piecesLeft[pieceType]--;
5102                         return i;
5103                 }
5104         }
5105         return -1;
5106 }
5107
5108
5109 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5110 // calculate where the next piece goes, (any empty square), and put it there
5111 {
5112         int i;
5113
5114         i = seed % squaresLeft[shade];
5115         nrOfShuffles *= squaresLeft[shade];
5116         seed /= squaresLeft[shade];
5117         put(board, pieceType, rank, i, shade);
5118 }
5119
5120 void AddTwoPieces(Board board, int pieceType, int rank)
5121 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5122 {
5123         int i, n=squaresLeft[ANY], j=n-1, k;
5124
5125         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5126         i = seed % k;  // pick one
5127         nrOfShuffles *= k;
5128         seed /= k;
5129         while(i >= j) i -= j--;
5130         j = n - 1 - j; i += j;
5131         put(board, pieceType, rank, j, ANY);
5132         put(board, pieceType, rank, i, ANY);
5133 }
5134
5135 void SetUpShuffle(Board board, int number)
5136 {
5137         int i, p, first=1;
5138
5139         GetPositionNumber(); nrOfShuffles = 1;
5140
5141         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5142         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5143         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5144
5145         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5146
5147         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5148             p = (int) board[0][i];
5149             if(p < (int) BlackPawn) piecesLeft[p] ++;
5150             board[0][i] = EmptySquare;
5151         }
5152
5153         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5154             // shuffles restricted to allow normal castling put KRR first
5155             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5156                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5157             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5158                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5159             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5160                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5161             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5162                 put(board, WhiteRook, 0, 0, ANY);
5163             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5164         }
5165
5166         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5167             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5168             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5169                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5170                 while(piecesLeft[p] >= 2) {
5171                     AddOnePiece(board, p, 0, LITE);
5172                     AddOnePiece(board, p, 0, DARK);
5173                 }
5174                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5175             }
5176
5177         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5178             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5179             // but we leave King and Rooks for last, to possibly obey FRC restriction
5180             if(p == (int)WhiteRook) continue;
5181             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5182             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5183         }
5184
5185         // now everything is placed, except perhaps King (Unicorn) and Rooks
5186
5187         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5188             // Last King gets castling rights
5189             while(piecesLeft[(int)WhiteUnicorn]) {
5190                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5191                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5192             }
5193
5194             while(piecesLeft[(int)WhiteKing]) {
5195                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5196                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5197             }
5198
5199
5200         } else {
5201             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5202             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5203         }
5204
5205         // Only Rooks can be left; simply place them all
5206         while(piecesLeft[(int)WhiteRook]) {
5207                 i = put(board, WhiteRook, 0, 0, ANY);
5208                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5209                         if(first) {
5210                                 first=0;
5211                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5212                         }
5213                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5214                 }
5215         }
5216         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5217             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5218         }
5219
5220         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5221 }
5222
5223 int SetCharTable( char *table, const char * map )
5224 /* [HGM] moved here from winboard.c because of its general usefulness */
5225 /*       Basically a safe strcpy that uses the last character as King */
5226 {
5227     int result = FALSE; int NrPieces;
5228
5229     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5230                     && NrPieces >= 12 && !(NrPieces&1)) {
5231         int i; /* [HGM] Accept even length from 12 to 34 */
5232
5233         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5234         for( i=0; i<NrPieces/2-1; i++ ) {
5235             table[i] = map[i];
5236             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5237         }
5238         table[(int) WhiteKing]  = map[NrPieces/2-1];
5239         table[(int) BlackKing]  = map[NrPieces-1];
5240
5241         result = TRUE;
5242     }
5243
5244     return result;
5245 }
5246
5247 void Prelude(Board board)
5248 {       // [HGM] superchess: random selection of exo-pieces
5249         int i, j, k; ChessSquare p;
5250         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5251
5252         GetPositionNumber(); // use FRC position number
5253
5254         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5255             SetCharTable(pieceToChar, appData.pieceToCharTable);
5256             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5257                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5258         }
5259
5260         j = seed%4;                 seed /= 4;
5261         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5262         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5263         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5264         j = seed%3 + (seed%3 >= j); seed /= 3;
5265         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5266         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5267         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5268         j = seed%3;                 seed /= 3;
5269         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5270         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5271         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5272         j = seed%2 + (seed%2 >= j); seed /= 2;
5273         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5274         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5275         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5276         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5277         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5278         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5279         put(board, exoPieces[0],    0, 0, ANY);
5280         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5281 }
5282
5283 void
5284 InitPosition(redraw)
5285      int redraw;
5286 {
5287     ChessSquare (* pieces)[BOARD_FILES];
5288     int i, j, pawnRow, overrule,
5289     oldx = gameInfo.boardWidth,
5290     oldy = gameInfo.boardHeight,
5291     oldh = gameInfo.holdingsWidth,
5292     oldv = gameInfo.variant;
5293
5294     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5295
5296     /* [AS] Initialize pv info list [HGM] and game status */
5297     {
5298         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5299             pvInfoList[i].depth = 0;
5300             boards[i][EP_STATUS] = EP_NONE;
5301             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5302         }
5303
5304         initialRulePlies = 0; /* 50-move counter start */
5305
5306         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5307         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5308     }
5309
5310
5311     /* [HGM] logic here is completely changed. In stead of full positions */
5312     /* the initialized data only consist of the two backranks. The switch */
5313     /* selects which one we will use, which is than copied to the Board   */
5314     /* initialPosition, which for the rest is initialized by Pawns and    */
5315     /* empty squares. This initial position is then copied to boards[0],  */
5316     /* possibly after shuffling, so that it remains available.            */
5317
5318     gameInfo.holdingsWidth = 0; /* default board sizes */
5319     gameInfo.boardWidth    = 8;
5320     gameInfo.boardHeight   = 8;
5321     gameInfo.holdingsSize  = 0;
5322     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5323     for(i=0; i<BOARD_FILES-2; i++)
5324       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5325     initialPosition[EP_STATUS] = EP_NONE;
5326     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5327     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5328          SetCharTable(pieceNickName, appData.pieceNickNames);
5329     else SetCharTable(pieceNickName, "............");
5330
5331     switch (gameInfo.variant) {
5332     case VariantFischeRandom:
5333       shuffleOpenings = TRUE;
5334     default:
5335       pieces = FIDEArray;
5336       break;
5337     case VariantShatranj:
5338       pieces = ShatranjArray;
5339       nrCastlingRights = 0;
5340       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5341       break;
5342     case VariantMakruk:
5343       pieces = makrukArray;
5344       nrCastlingRights = 0;
5345       startedFromSetupPosition = TRUE;
5346       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5347       break;
5348     case VariantTwoKings:
5349       pieces = twoKingsArray;
5350       break;
5351     case VariantCapaRandom:
5352       shuffleOpenings = TRUE;
5353     case VariantCapablanca:
5354       pieces = CapablancaArray;
5355       gameInfo.boardWidth = 10;
5356       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5357       break;
5358     case VariantGothic:
5359       pieces = GothicArray;
5360       gameInfo.boardWidth = 10;
5361       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5362       break;
5363     case VariantJanus:
5364       pieces = JanusArray;
5365       gameInfo.boardWidth = 10;
5366       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5367       nrCastlingRights = 6;
5368         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5369         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5370         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5371         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5372         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5373         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5374       break;
5375     case VariantFalcon:
5376       pieces = FalconArray;
5377       gameInfo.boardWidth = 10;
5378       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5379       break;
5380     case VariantXiangqi:
5381       pieces = XiangqiArray;
5382       gameInfo.boardWidth  = 9;
5383       gameInfo.boardHeight = 10;
5384       nrCastlingRights = 0;
5385       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5386       break;
5387     case VariantShogi:
5388       pieces = ShogiArray;
5389       gameInfo.boardWidth  = 9;
5390       gameInfo.boardHeight = 9;
5391       gameInfo.holdingsSize = 7;
5392       nrCastlingRights = 0;
5393       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5394       break;
5395     case VariantCourier:
5396       pieces = CourierArray;
5397       gameInfo.boardWidth  = 12;
5398       nrCastlingRights = 0;
5399       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5400       break;
5401     case VariantKnightmate:
5402       pieces = KnightmateArray;
5403       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5404       break;
5405     case VariantFairy:
5406       pieces = fairyArray;
5407       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5408       break;
5409     case VariantGreat:
5410       pieces = GreatArray;
5411       gameInfo.boardWidth = 10;
5412       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5413       gameInfo.holdingsSize = 8;
5414       break;
5415     case VariantSuper:
5416       pieces = FIDEArray;
5417       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5418       gameInfo.holdingsSize = 8;
5419       startedFromSetupPosition = TRUE;
5420       break;
5421     case VariantCrazyhouse:
5422     case VariantBughouse:
5423       pieces = FIDEArray;
5424       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5425       gameInfo.holdingsSize = 5;
5426       break;
5427     case VariantWildCastle:
5428       pieces = FIDEArray;
5429       /* !!?shuffle with kings guaranteed to be on d or e file */
5430       shuffleOpenings = 1;
5431       break;
5432     case VariantNoCastle:
5433       pieces = FIDEArray;
5434       nrCastlingRights = 0;
5435       /* !!?unconstrained back-rank shuffle */
5436       shuffleOpenings = 1;
5437       break;
5438     }
5439
5440     overrule = 0;
5441     if(appData.NrFiles >= 0) {
5442         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5443         gameInfo.boardWidth = appData.NrFiles;
5444     }
5445     if(appData.NrRanks >= 0) {
5446         gameInfo.boardHeight = appData.NrRanks;
5447     }
5448     if(appData.holdingsSize >= 0) {
5449         i = appData.holdingsSize;
5450         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5451         gameInfo.holdingsSize = i;
5452     }
5453     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5454     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5455         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5456
5457     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5458     if(pawnRow < 1) pawnRow = 1;
5459     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5460
5461     /* User pieceToChar list overrules defaults */
5462     if(appData.pieceToCharTable != NULL)
5463         SetCharTable(pieceToChar, appData.pieceToCharTable);
5464
5465     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5466
5467         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5468             s = (ChessSquare) 0; /* account holding counts in guard band */
5469         for( i=0; i<BOARD_HEIGHT; i++ )
5470             initialPosition[i][j] = s;
5471
5472         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5473         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5474         initialPosition[pawnRow][j] = WhitePawn;
5475         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5476         if(gameInfo.variant == VariantXiangqi) {
5477             if(j&1) {
5478                 initialPosition[pawnRow][j] =
5479                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5480                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5481                    initialPosition[2][j] = WhiteCannon;
5482                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5483                 }
5484             }
5485         }
5486         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5487     }
5488     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5489
5490             j=BOARD_LEFT+1;
5491             initialPosition[1][j] = WhiteBishop;
5492             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5493             j=BOARD_RGHT-2;
5494             initialPosition[1][j] = WhiteRook;
5495             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5496     }
5497
5498     if( nrCastlingRights == -1) {
5499         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5500         /*       This sets default castling rights from none to normal corners   */
5501         /* Variants with other castling rights must set them themselves above    */
5502         nrCastlingRights = 6;
5503
5504         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5505         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5506         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5507         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5508         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5509         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5510      }
5511
5512      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5513      if(gameInfo.variant == VariantGreat) { // promotion commoners
5514         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5515         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5516         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5517         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5518      }
5519   if (appData.debugMode) {
5520     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5521   }
5522     if(shuffleOpenings) {
5523         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5524         startedFromSetupPosition = TRUE;
5525     }
5526     if(startedFromPositionFile) {
5527       /* [HGM] loadPos: use PositionFile for every new game */
5528       CopyBoard(initialPosition, filePosition);
5529       for(i=0; i<nrCastlingRights; i++)
5530           initialRights[i] = filePosition[CASTLING][i];
5531       startedFromSetupPosition = TRUE;
5532     }
5533
5534     CopyBoard(boards[0], initialPosition);
5535
5536     if(oldx != gameInfo.boardWidth ||
5537        oldy != gameInfo.boardHeight ||
5538        oldh != gameInfo.holdingsWidth
5539 #ifdef GOTHIC
5540        || oldv == VariantGothic ||        // For licensing popups
5541        gameInfo.variant == VariantGothic
5542 #endif
5543 #ifdef FALCON
5544        || oldv == VariantFalcon ||
5545        gameInfo.variant == VariantFalcon
5546 #endif
5547                                          )
5548             InitDrawingSizes(-2 ,0);
5549
5550     if (redraw)
5551       DrawPosition(TRUE, boards[currentMove]);
5552 }
5553
5554 void
5555 SendBoard(cps, moveNum)
5556      ChessProgramState *cps;
5557      int moveNum;
5558 {
5559     char message[MSG_SIZ];
5560
5561     if (cps->useSetboard) {
5562       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5563       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5564       SendToProgram(message, cps);
5565       free(fen);
5566
5567     } else {
5568       ChessSquare *bp;
5569       int i, j;
5570       /* Kludge to set black to move, avoiding the troublesome and now
5571        * deprecated "black" command.
5572        */
5573       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5574
5575       SendToProgram("edit\n", cps);
5576       SendToProgram("#\n", cps);
5577       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5578         bp = &boards[moveNum][i][BOARD_LEFT];
5579         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5580           if ((int) *bp < (int) BlackPawn) {
5581             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5582                     AAA + j, ONE + i);
5583             if(message[0] == '+' || message[0] == '~') {
5584               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5585                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5586                         AAA + j, ONE + i);
5587             }
5588             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5589                 message[1] = BOARD_RGHT   - 1 - j + '1';
5590                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5591             }
5592             SendToProgram(message, cps);
5593           }
5594         }
5595       }
5596
5597       SendToProgram("c\n", cps);
5598       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5599         bp = &boards[moveNum][i][BOARD_LEFT];
5600         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5601           if (((int) *bp != (int) EmptySquare)
5602               && ((int) *bp >= (int) BlackPawn)) {
5603             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5604                     AAA + j, ONE + i);
5605             if(message[0] == '+' || message[0] == '~') {
5606               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5607                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5608                         AAA + j, ONE + i);
5609             }
5610             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5611                 message[1] = BOARD_RGHT   - 1 - j + '1';
5612                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5613             }
5614             SendToProgram(message, cps);
5615           }
5616         }
5617       }
5618
5619       SendToProgram(".\n", cps);
5620     }
5621     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5622 }
5623
5624 static int autoQueen; // [HGM] oneclick
5625
5626 int
5627 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5628 {
5629     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5630     /* [HGM] add Shogi promotions */
5631     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5632     ChessSquare piece;
5633     ChessMove moveType;
5634     Boolean premove;
5635
5636     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5637     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5638
5639     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5640       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5641         return FALSE;
5642
5643     piece = boards[currentMove][fromY][fromX];
5644     if(gameInfo.variant == VariantShogi) {
5645         promotionZoneSize = BOARD_HEIGHT/3;
5646         highestPromotingPiece = (int)WhiteFerz;
5647     } else if(gameInfo.variant == VariantMakruk) {
5648         promotionZoneSize = 3;
5649     }
5650
5651     // next weed out all moves that do not touch the promotion zone at all
5652     if((int)piece >= BlackPawn) {
5653         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5654              return FALSE;
5655         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5656     } else {
5657         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5658            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5659     }
5660
5661     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5662
5663     // weed out mandatory Shogi promotions
5664     if(gameInfo.variant == VariantShogi) {
5665         if(piece >= BlackPawn) {
5666             if(toY == 0 && piece == BlackPawn ||
5667                toY == 0 && piece == BlackQueen ||
5668                toY <= 1 && piece == BlackKnight) {
5669                 *promoChoice = '+';
5670                 return FALSE;
5671             }
5672         } else {
5673             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5674                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5675                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5676                 *promoChoice = '+';
5677                 return FALSE;
5678             }
5679         }
5680     }
5681
5682     // weed out obviously illegal Pawn moves
5683     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5684         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5685         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5686         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5687         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5688         // note we are not allowed to test for valid (non-)capture, due to premove
5689     }
5690
5691     // we either have a choice what to promote to, or (in Shogi) whether to promote
5692     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5693         *promoChoice = PieceToChar(BlackFerz);  // no choice
5694         return FALSE;
5695     }
5696     if(autoQueen) { // predetermined
5697         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5698              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5699         else *promoChoice = PieceToChar(BlackQueen);
5700         return FALSE;
5701     }
5702
5703     // suppress promotion popup on illegal moves that are not premoves
5704     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5705               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5706     if(appData.testLegality && !premove) {
5707         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5708                         fromY, fromX, toY, toX, NULLCHAR);
5709         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5710             return FALSE;
5711     }
5712
5713     return TRUE;
5714 }
5715
5716 int
5717 InPalace(row, column)
5718      int row, column;
5719 {   /* [HGM] for Xiangqi */
5720     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5721          column < (BOARD_WIDTH + 4)/2 &&
5722          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5723     return FALSE;
5724 }
5725
5726 int
5727 PieceForSquare (x, y)
5728      int x;
5729      int y;
5730 {
5731   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5732      return -1;
5733   else
5734      return boards[currentMove][y][x];
5735 }
5736
5737 int
5738 OKToStartUserMove(x, y)
5739      int x, y;
5740 {
5741     ChessSquare from_piece;
5742     int white_piece;
5743
5744     if (matchMode) return FALSE;
5745     if (gameMode == EditPosition) return TRUE;
5746
5747     if (x >= 0 && y >= 0)
5748       from_piece = boards[currentMove][y][x];
5749     else
5750       from_piece = EmptySquare;
5751
5752     if (from_piece == EmptySquare) return FALSE;
5753
5754     white_piece = (int)from_piece >= (int)WhitePawn &&
5755       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5756
5757     switch (gameMode) {
5758       case PlayFromGameFile:
5759       case AnalyzeFile:
5760       case TwoMachinesPlay:
5761       case EndOfGame:
5762         return FALSE;
5763
5764       case IcsObserving:
5765       case IcsIdle:
5766         return FALSE;
5767
5768       case MachinePlaysWhite:
5769       case IcsPlayingBlack:
5770         if (appData.zippyPlay) return FALSE;
5771         if (white_piece) {
5772             DisplayMoveError(_("You are playing Black"));
5773             return FALSE;
5774         }
5775         break;
5776
5777       case MachinePlaysBlack:
5778       case IcsPlayingWhite:
5779         if (appData.zippyPlay) return FALSE;
5780         if (!white_piece) {
5781             DisplayMoveError(_("You are playing White"));
5782             return FALSE;
5783         }
5784         break;
5785
5786       case EditGame:
5787         if (!white_piece && WhiteOnMove(currentMove)) {
5788             DisplayMoveError(_("It is White's turn"));
5789             return FALSE;
5790         }
5791         if (white_piece && !WhiteOnMove(currentMove)) {
5792             DisplayMoveError(_("It is Black's turn"));
5793             return FALSE;
5794         }
5795         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5796             /* Editing correspondence game history */
5797             /* Could disallow this or prompt for confirmation */
5798             cmailOldMove = -1;
5799         }
5800         break;
5801
5802       case BeginningOfGame:
5803         if (appData.icsActive) return FALSE;
5804         if (!appData.noChessProgram) {
5805             if (!white_piece) {
5806                 DisplayMoveError(_("You are playing White"));
5807                 return FALSE;
5808             }
5809         }
5810         break;
5811
5812       case Training:
5813         if (!white_piece && WhiteOnMove(currentMove)) {
5814             DisplayMoveError(_("It is White's turn"));
5815             return FALSE;
5816         }
5817         if (white_piece && !WhiteOnMove(currentMove)) {
5818             DisplayMoveError(_("It is Black's turn"));
5819             return FALSE;
5820         }
5821         break;
5822
5823       default:
5824       case IcsExamining:
5825         break;
5826     }
5827     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5828         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5829         && gameMode != AnalyzeFile && gameMode != Training) {
5830         DisplayMoveError(_("Displayed position is not current"));
5831         return FALSE;
5832     }
5833     return TRUE;
5834 }
5835
5836 Boolean
5837 OnlyMove(int *x, int *y, Boolean captures) {
5838     DisambiguateClosure cl;
5839     if (appData.zippyPlay) return FALSE;
5840     switch(gameMode) {
5841       case MachinePlaysBlack:
5842       case IcsPlayingWhite:
5843       case BeginningOfGame:
5844         if(!WhiteOnMove(currentMove)) return FALSE;
5845         break;
5846       case MachinePlaysWhite:
5847       case IcsPlayingBlack:
5848         if(WhiteOnMove(currentMove)) return FALSE;
5849         break;
5850       default:
5851         return FALSE;
5852     }
5853     cl.pieceIn = EmptySquare;
5854     cl.rfIn = *y;
5855     cl.ffIn = *x;
5856     cl.rtIn = -1;
5857     cl.ftIn = -1;
5858     cl.promoCharIn = NULLCHAR;
5859     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5860     if( cl.kind == NormalMove ||
5861         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5862         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5863         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5864       fromX = cl.ff;
5865       fromY = cl.rf;
5866       *x = cl.ft;
5867       *y = cl.rt;
5868       return TRUE;
5869     }
5870     if(cl.kind != ImpossibleMove) return FALSE;
5871     cl.pieceIn = EmptySquare;
5872     cl.rfIn = -1;
5873     cl.ffIn = -1;
5874     cl.rtIn = *y;
5875     cl.ftIn = *x;
5876     cl.promoCharIn = NULLCHAR;
5877     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5878     if( cl.kind == NormalMove ||
5879         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5880         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5881         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5882       fromX = cl.ff;
5883       fromY = cl.rf;
5884       *x = cl.ft;
5885       *y = cl.rt;
5886       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5887       return TRUE;
5888     }
5889     return FALSE;
5890 }
5891
5892 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5893 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5894 int lastLoadGameUseList = FALSE;
5895 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5896 ChessMove lastLoadGameStart = EndOfFile;
5897
5898 void
5899 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5900      int fromX, fromY, toX, toY;
5901      int promoChar;
5902 {
5903     ChessMove moveType;
5904     ChessSquare pdown, pup;
5905
5906     /* Check if the user is playing in turn.  This is complicated because we
5907        let the user "pick up" a piece before it is his turn.  So the piece he
5908        tried to pick up may have been captured by the time he puts it down!
5909        Therefore we use the color the user is supposed to be playing in this
5910        test, not the color of the piece that is currently on the starting
5911        square---except in EditGame mode, where the user is playing both
5912        sides; fortunately there the capture race can't happen.  (It can
5913        now happen in IcsExamining mode, but that's just too bad.  The user
5914        will get a somewhat confusing message in that case.)
5915        */
5916
5917     switch (gameMode) {
5918       case PlayFromGameFile:
5919       case AnalyzeFile:
5920       case TwoMachinesPlay:
5921       case EndOfGame:
5922       case IcsObserving:
5923       case IcsIdle:
5924         /* We switched into a game mode where moves are not accepted,
5925            perhaps while the mouse button was down. */
5926         return;
5927
5928       case MachinePlaysWhite:
5929         /* User is moving for Black */
5930         if (WhiteOnMove(currentMove)) {
5931             DisplayMoveError(_("It is White's turn"));
5932             return;
5933         }
5934         break;
5935
5936       case MachinePlaysBlack:
5937         /* User is moving for White */
5938         if (!WhiteOnMove(currentMove)) {
5939             DisplayMoveError(_("It is Black's turn"));
5940             return;
5941         }
5942         break;
5943
5944       case EditGame:
5945       case IcsExamining:
5946       case BeginningOfGame:
5947       case AnalyzeMode:
5948       case Training:
5949         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5950             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5951             /* User is moving for Black */
5952             if (WhiteOnMove(currentMove)) {
5953                 DisplayMoveError(_("It is White's turn"));
5954                 return;
5955             }
5956         } else {
5957             /* User is moving for White */
5958             if (!WhiteOnMove(currentMove)) {
5959                 DisplayMoveError(_("It is Black's turn"));
5960                 return;
5961             }
5962         }
5963         break;
5964
5965       case IcsPlayingBlack:
5966         /* User is moving for Black */
5967         if (WhiteOnMove(currentMove)) {
5968             if (!appData.premove) {
5969                 DisplayMoveError(_("It is White's turn"));
5970             } else if (toX >= 0 && toY >= 0) {
5971                 premoveToX = toX;
5972                 premoveToY = toY;
5973                 premoveFromX = fromX;
5974                 premoveFromY = fromY;
5975                 premovePromoChar = promoChar;
5976                 gotPremove = 1;
5977                 if (appData.debugMode)
5978                     fprintf(debugFP, "Got premove: fromX %d,"
5979                             "fromY %d, toX %d, toY %d\n",
5980                             fromX, fromY, toX, toY);
5981             }
5982             return;
5983         }
5984         break;
5985
5986       case IcsPlayingWhite:
5987         /* User is moving for White */
5988         if (!WhiteOnMove(currentMove)) {
5989             if (!appData.premove) {
5990                 DisplayMoveError(_("It is Black's turn"));
5991             } else if (toX >= 0 && toY >= 0) {
5992                 premoveToX = toX;
5993                 premoveToY = toY;
5994                 premoveFromX = fromX;
5995                 premoveFromY = fromY;
5996                 premovePromoChar = promoChar;
5997                 gotPremove = 1;
5998                 if (appData.debugMode)
5999                     fprintf(debugFP, "Got premove: fromX %d,"
6000                             "fromY %d, toX %d, toY %d\n",
6001                             fromX, fromY, toX, toY);
6002             }
6003             return;
6004         }
6005         break;
6006
6007       default:
6008         break;
6009
6010       case EditPosition:
6011         /* EditPosition, empty square, or different color piece;
6012            click-click move is possible */
6013         if (toX == -2 || toY == -2) {
6014             boards[0][fromY][fromX] = EmptySquare;
6015             DrawPosition(FALSE, boards[currentMove]);
6016             return;
6017         } else if (toX >= 0 && toY >= 0) {
6018             boards[0][toY][toX] = boards[0][fromY][fromX];
6019             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6020                 if(boards[0][fromY][0] != EmptySquare) {
6021                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6022                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6023                 }
6024             } else
6025             if(fromX == BOARD_RGHT+1) {
6026                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6027                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6028                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6029                 }
6030             } else
6031             boards[0][fromY][fromX] = EmptySquare;
6032             DrawPosition(FALSE, boards[currentMove]);
6033             return;
6034         }
6035         return;
6036     }
6037
6038     if(toX < 0 || toY < 0) return;
6039     pdown = boards[currentMove][fromY][fromX];
6040     pup = boards[currentMove][toY][toX];
6041
6042     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6043     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
6044          if( pup != EmptySquare ) return;
6045          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6046            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6047                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6048            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6049            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6050            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6051            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6052          fromY = DROP_RANK;
6053     }
6054
6055     /* [HGM] always test for legality, to get promotion info */
6056     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6057                                          fromY, fromX, toY, toX, promoChar);
6058     /* [HGM] but possibly ignore an IllegalMove result */
6059     if (appData.testLegality) {
6060         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6061             DisplayMoveError(_("Illegal move"));
6062             return;
6063         }
6064     }
6065
6066     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6067 }
6068
6069 /* Common tail of UserMoveEvent and DropMenuEvent */
6070 int
6071 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6072      ChessMove moveType;
6073      int fromX, fromY, toX, toY;
6074      /*char*/int promoChar;
6075 {
6076     char *bookHit = 0;
6077
6078     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6079         // [HGM] superchess: suppress promotions to non-available piece
6080         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6081         if(WhiteOnMove(currentMove)) {
6082             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6083         } else {
6084             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6085         }
6086     }
6087
6088     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6089        move type in caller when we know the move is a legal promotion */
6090     if(moveType == NormalMove && promoChar)
6091         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6092
6093     /* [HGM] <popupFix> The following if has been moved here from
6094        UserMoveEvent(). Because it seemed to belong here (why not allow
6095        piece drops in training games?), and because it can only be
6096        performed after it is known to what we promote. */
6097     if (gameMode == Training) {
6098       /* compare the move played on the board to the next move in the
6099        * game. If they match, display the move and the opponent's response.
6100        * If they don't match, display an error message.
6101        */
6102       int saveAnimate;
6103       Board testBoard;
6104       CopyBoard(testBoard, boards[currentMove]);
6105       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6106
6107       if (CompareBoards(testBoard, boards[currentMove+1])) {
6108         ForwardInner(currentMove+1);
6109
6110         /* Autoplay the opponent's response.
6111          * if appData.animate was TRUE when Training mode was entered,
6112          * the response will be animated.
6113          */
6114         saveAnimate = appData.animate;
6115         appData.animate = animateTraining;
6116         ForwardInner(currentMove+1);
6117         appData.animate = saveAnimate;
6118
6119         /* check for the end of the game */
6120         if (currentMove >= forwardMostMove) {
6121           gameMode = PlayFromGameFile;
6122           ModeHighlight();
6123           SetTrainingModeOff();
6124           DisplayInformation(_("End of game"));
6125         }
6126       } else {
6127         DisplayError(_("Incorrect move"), 0);
6128       }
6129       return 1;
6130     }
6131
6132   /* Ok, now we know that the move is good, so we can kill
6133      the previous line in Analysis Mode */
6134   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6135                                 && currentMove < forwardMostMove) {
6136     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6137   }
6138
6139   /* If we need the chess program but it's dead, restart it */
6140   ResurrectChessProgram();
6141
6142   /* A user move restarts a paused game*/
6143   if (pausing)
6144     PauseEvent();
6145
6146   thinkOutput[0] = NULLCHAR;
6147
6148   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6149
6150   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6151
6152   if (gameMode == BeginningOfGame) {
6153     if (appData.noChessProgram) {
6154       gameMode = EditGame;
6155       SetGameInfo();
6156     } else {
6157       char buf[MSG_SIZ];
6158       gameMode = MachinePlaysBlack;
6159       StartClocks();
6160       SetGameInfo();
6161       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6162       DisplayTitle(buf);
6163       if (first.sendName) {
6164         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6165         SendToProgram(buf, &first);
6166       }
6167       StartClocks();
6168     }
6169     ModeHighlight();
6170   }
6171
6172   /* Relay move to ICS or chess engine */
6173   if (appData.icsActive) {
6174     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6175         gameMode == IcsExamining) {
6176       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6177         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6178         SendToICS("draw ");
6179         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6180       }
6181       // also send plain move, in case ICS does not understand atomic claims
6182       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6183       ics_user_moved = 1;
6184     }
6185   } else {
6186     if (first.sendTime && (gameMode == BeginningOfGame ||
6187                            gameMode == MachinePlaysWhite ||
6188                            gameMode == MachinePlaysBlack)) {
6189       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6190     }
6191     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6192          // [HGM] book: if program might be playing, let it use book
6193         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6194         first.maybeThinking = TRUE;
6195     } else SendMoveToProgram(forwardMostMove-1, &first);
6196     if (currentMove == cmailOldMove + 1) {
6197       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6198     }
6199   }
6200
6201   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6202
6203   switch (gameMode) {
6204   case EditGame:
6205     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6206     case MT_NONE:
6207     case MT_CHECK:
6208       break;
6209     case MT_CHECKMATE:
6210     case MT_STAINMATE:
6211       if (WhiteOnMove(currentMove)) {
6212         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6213       } else {
6214         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6215       }
6216       break;
6217     case MT_STALEMATE:
6218       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6219       break;
6220     }
6221     break;
6222
6223   case MachinePlaysBlack:
6224   case MachinePlaysWhite:
6225     /* disable certain menu options while machine is thinking */
6226     SetMachineThinkingEnables();
6227     break;
6228
6229   default:
6230     break;
6231   }
6232
6233   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6234
6235   if(bookHit) { // [HGM] book: simulate book reply
6236         static char bookMove[MSG_SIZ]; // a bit generous?
6237
6238         programStats.nodes = programStats.depth = programStats.time =
6239         programStats.score = programStats.got_only_move = 0;
6240         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6241
6242         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6243         strcat(bookMove, bookHit);
6244         HandleMachineMove(bookMove, &first);
6245   }
6246   return 1;
6247 }
6248
6249 void
6250 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6251      Board board;
6252      int flags;
6253      ChessMove kind;
6254      int rf, ff, rt, ft;
6255      VOIDSTAR closure;
6256 {
6257     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6258     Markers *m = (Markers *) closure;
6259     if(rf == fromY && ff == fromX)
6260         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6261                          || kind == WhiteCapturesEnPassant
6262                          || kind == BlackCapturesEnPassant);
6263     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6264 }
6265
6266 void
6267 MarkTargetSquares(int clear)
6268 {
6269   int x, y;
6270   if(!appData.markers || !appData.highlightDragging ||
6271      !appData.testLegality || gameMode == EditPosition) return;
6272   if(clear) {
6273     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6274   } else {
6275     int capt = 0;
6276     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6277     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6278       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6279       if(capt)
6280       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6281     }
6282   }
6283   DrawPosition(TRUE, NULL);
6284 }
6285
6286 void LeftClick(ClickType clickType, int xPix, int yPix)
6287 {
6288     int x, y;
6289     Boolean saveAnimate;
6290     static int second = 0, promotionChoice = 0, dragging = 0;
6291     char promoChoice = NULLCHAR;
6292
6293     if(appData.seekGraph && appData.icsActive && loggedOn &&
6294         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6295         SeekGraphClick(clickType, xPix, yPix, 0);
6296         return;
6297     }
6298
6299     if (clickType == Press) ErrorPopDown();
6300     MarkTargetSquares(1);
6301
6302     x = EventToSquare(xPix, BOARD_WIDTH);
6303     y = EventToSquare(yPix, BOARD_HEIGHT);
6304     if (!flipView && y >= 0) {
6305         y = BOARD_HEIGHT - 1 - y;
6306     }
6307     if (flipView && x >= 0) {
6308         x = BOARD_WIDTH - 1 - x;
6309     }
6310
6311     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6312         if(clickType == Release) return; // ignore upclick of click-click destination
6313         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6314         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6315         if(gameInfo.holdingsWidth &&
6316                 (WhiteOnMove(currentMove)
6317                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6318                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6319             // click in right holdings, for determining promotion piece
6320             ChessSquare p = boards[currentMove][y][x];
6321             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6322             if(p != EmptySquare) {
6323                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6324                 fromX = fromY = -1;
6325                 return;
6326             }
6327         }
6328         DrawPosition(FALSE, boards[currentMove]);
6329         return;
6330     }
6331
6332     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6333     if(clickType == Press
6334             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6335               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6336               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6337         return;
6338
6339     autoQueen = appData.alwaysPromoteToQueen;
6340
6341     if (fromX == -1) {
6342       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6343         if (clickType == Press) {
6344             /* First square */
6345             if (OKToStartUserMove(x, y)) {
6346                 fromX = x;
6347                 fromY = y;
6348                 second = 0;
6349                 MarkTargetSquares(0);
6350                 DragPieceBegin(xPix, yPix); dragging = 1;
6351                 if (appData.highlightDragging) {
6352                     SetHighlights(x, y, -1, -1);
6353                 }
6354             }
6355         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6356             DragPieceEnd(xPix, yPix); dragging = 0;
6357             DrawPosition(FALSE, NULL);
6358         }
6359         return;
6360       }
6361     }
6362
6363     /* fromX != -1 */
6364     if (clickType == Press && gameMode != EditPosition) {
6365         ChessSquare fromP;
6366         ChessSquare toP;
6367         int frc;
6368
6369         // ignore off-board to clicks
6370         if(y < 0 || x < 0) return;
6371
6372         /* Check if clicking again on the same color piece */
6373         fromP = boards[currentMove][fromY][fromX];
6374         toP = boards[currentMove][y][x];
6375         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6376         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6377              WhitePawn <= toP && toP <= WhiteKing &&
6378              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6379              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6380             (BlackPawn <= fromP && fromP <= BlackKing &&
6381              BlackPawn <= toP && toP <= BlackKing &&
6382              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6383              !(fromP == BlackKing && toP == BlackRook && frc))) {
6384             /* Clicked again on same color piece -- changed his mind */
6385             second = (x == fromX && y == fromY);
6386            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6387             if (appData.highlightDragging) {
6388                 SetHighlights(x, y, -1, -1);
6389             } else {
6390                 ClearHighlights();
6391             }
6392             if (OKToStartUserMove(x, y)) {
6393                 fromX = x;
6394                 fromY = y; dragging = 1;
6395                 MarkTargetSquares(0);
6396                 DragPieceBegin(xPix, yPix);
6397             }
6398             return;
6399            }
6400         }
6401         // ignore clicks on holdings
6402         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6403     }
6404
6405     if (clickType == Release && x == fromX && y == fromY) {
6406         DragPieceEnd(xPix, yPix); dragging = 0;
6407         if (appData.animateDragging) {
6408             /* Undo animation damage if any */
6409             DrawPosition(FALSE, NULL);
6410         }
6411         if (second) {
6412             /* Second up/down in same square; just abort move */
6413             second = 0;
6414             fromX = fromY = -1;
6415             ClearHighlights();
6416             gotPremove = 0;
6417             ClearPremoveHighlights();
6418         } else {
6419             /* First upclick in same square; start click-click mode */
6420             SetHighlights(x, y, -1, -1);
6421         }
6422         return;
6423     }
6424
6425     /* we now have a different from- and (possibly off-board) to-square */
6426     /* Completed move */
6427     toX = x;
6428     toY = y;
6429     saveAnimate = appData.animate;
6430     if (clickType == Press) {
6431         /* Finish clickclick move */
6432         if (appData.animate || appData.highlightLastMove) {
6433             SetHighlights(fromX, fromY, toX, toY);
6434         } else {
6435             ClearHighlights();
6436         }
6437     } else {
6438         /* Finish drag move */
6439         if (appData.highlightLastMove) {
6440             SetHighlights(fromX, fromY, toX, toY);
6441         } else {
6442             ClearHighlights();
6443         }
6444         DragPieceEnd(xPix, yPix); dragging = 0;
6445         /* Don't animate move and drag both */
6446         appData.animate = FALSE;
6447     }
6448
6449     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6450     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6451         ChessSquare piece = boards[currentMove][fromY][fromX];
6452         if(gameMode == EditPosition && piece != EmptySquare &&
6453            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6454             int n;
6455
6456             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6457                 n = PieceToNumber(piece - (int)BlackPawn);
6458                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6459                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6460                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6461             } else
6462             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6463                 n = PieceToNumber(piece);
6464                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6465                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6466                 boards[currentMove][n][BOARD_WIDTH-2]++;
6467             }
6468             boards[currentMove][fromY][fromX] = EmptySquare;
6469         }
6470         ClearHighlights();
6471         fromX = fromY = -1;
6472         DrawPosition(TRUE, boards[currentMove]);
6473         return;
6474     }
6475
6476     // off-board moves should not be highlighted
6477     if(x < 0 || x < 0) ClearHighlights();
6478
6479     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6480         SetHighlights(fromX, fromY, toX, toY);
6481         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6482             // [HGM] super: promotion to captured piece selected from holdings
6483             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6484             promotionChoice = TRUE;
6485             // kludge follows to temporarily execute move on display, without promoting yet
6486             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6487             boards[currentMove][toY][toX] = p;
6488             DrawPosition(FALSE, boards[currentMove]);
6489             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6490             boards[currentMove][toY][toX] = q;
6491             DisplayMessage("Click in holdings to choose piece", "");
6492             return;
6493         }
6494         PromotionPopUp();
6495     } else {
6496         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6497         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6498         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6499         fromX = fromY = -1;
6500     }
6501     appData.animate = saveAnimate;
6502     if (appData.animate || appData.animateDragging) {
6503         /* Undo animation damage if needed */
6504         DrawPosition(FALSE, NULL);
6505     }
6506 }
6507
6508 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6509 {   // front-end-free part taken out of PieceMenuPopup
6510     int whichMenu; int xSqr, ySqr;
6511
6512     if(seekGraphUp) { // [HGM] seekgraph
6513         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6514         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6515         return -2;
6516     }
6517
6518     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6519          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6520         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6521         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6522         if(action == Press)   {
6523             originalFlip = flipView;
6524             flipView = !flipView; // temporarily flip board to see game from partners perspective
6525             DrawPosition(TRUE, partnerBoard);
6526             DisplayMessage(partnerStatus, "");
6527             partnerUp = TRUE;
6528         } else if(action == Release) {
6529             flipView = originalFlip;
6530             DrawPosition(TRUE, boards[currentMove]);
6531             partnerUp = FALSE;
6532         }
6533         return -2;
6534     }
6535
6536     xSqr = EventToSquare(x, BOARD_WIDTH);
6537     ySqr = EventToSquare(y, BOARD_HEIGHT);
6538     if (action == Release) UnLoadPV(); // [HGM] pv
6539     if (action != Press) return -2; // return code to be ignored
6540     switch (gameMode) {
6541       case IcsExamining:
6542         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6543       case EditPosition:
6544         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6545         if (xSqr < 0 || ySqr < 0) return -1;\r
6546         whichMenu = 0; // edit-position menu
6547         break;
6548       case IcsObserving:
6549         if(!appData.icsEngineAnalyze) return -1;
6550       case IcsPlayingWhite:
6551       case IcsPlayingBlack:
6552         if(!appData.zippyPlay) goto noZip;
6553       case AnalyzeMode:
6554       case AnalyzeFile:
6555       case MachinePlaysWhite:
6556       case MachinePlaysBlack:
6557       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6558         if (!appData.dropMenu) {
6559           LoadPV(x, y);
6560           return 2; // flag front-end to grab mouse events
6561         }
6562         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6563            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6564       case EditGame:
6565       noZip:
6566         if (xSqr < 0 || ySqr < 0) return -1;
6567         if (!appData.dropMenu || appData.testLegality &&
6568             gameInfo.variant != VariantBughouse &&
6569             gameInfo.variant != VariantCrazyhouse) return -1;
6570         whichMenu = 1; // drop menu
6571         break;
6572       default:
6573         return -1;
6574     }
6575
6576     if (((*fromX = xSqr) < 0) ||
6577         ((*fromY = ySqr) < 0)) {
6578         *fromX = *fromY = -1;
6579         return -1;
6580     }
6581     if (flipView)
6582       *fromX = BOARD_WIDTH - 1 - *fromX;
6583     else
6584       *fromY = BOARD_HEIGHT - 1 - *fromY;
6585
6586     return whichMenu;
6587 }
6588
6589 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6590 {
6591 //    char * hint = lastHint;
6592     FrontEndProgramStats stats;
6593
6594     stats.which = cps == &first ? 0 : 1;
6595     stats.depth = cpstats->depth;
6596     stats.nodes = cpstats->nodes;
6597     stats.score = cpstats->score;
6598     stats.time = cpstats->time;
6599     stats.pv = cpstats->movelist;
6600     stats.hint = lastHint;
6601     stats.an_move_index = 0;
6602     stats.an_move_count = 0;
6603
6604     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6605         stats.hint = cpstats->move_name;
6606         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6607         stats.an_move_count = cpstats->nr_moves;
6608     }
6609
6610     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
6611
6612     SetProgramStats( &stats );
6613 }
6614
6615 void
6616 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6617 {       // count all piece types
6618         int p, f, r;
6619         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6620         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6621         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6622                 p = board[r][f];
6623                 pCnt[p]++;
6624                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6625                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6626                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6627                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6628                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6629                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6630         }
6631 }
6632
6633 int
6634 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6635 {
6636         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6637         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6638
6639         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6640         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6641         if(myPawns == 2 && nMine == 3) // KPP
6642             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6643         if(myPawns == 1 && nMine == 2) // KP
6644             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6645         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6646             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6647         if(myPawns) return FALSE;
6648         if(pCnt[WhiteRook+side])
6649             return pCnt[BlackRook-side] ||
6650                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6651                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6652                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6653         if(pCnt[WhiteCannon+side]) {
6654             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6655             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6656         }
6657         if(pCnt[WhiteKnight+side])
6658             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6659         return FALSE;
6660 }
6661
6662 int
6663 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6664 {
6665         VariantClass v = gameInfo.variant;
6666
6667         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6668         if(v == VariantShatranj) return TRUE; // always winnable through baring
6669         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6670         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6671
6672         if(v == VariantXiangqi) {
6673                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6674
6675                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6676                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6677                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6678                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6679                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6680                 if(stale) // we have at least one last-rank P plus perhaps C
6681                     return majors // KPKX
6682                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6683                 else // KCA*E*
6684                     return pCnt[WhiteFerz+side] // KCAK
6685                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6686                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6687                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6688
6689         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6690                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6691
6692                 if(nMine == 1) return FALSE; // bare King
6693                 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
6694                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6695                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6696                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6697                 if(pCnt[WhiteKnight+side])
6698                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6699                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6700                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6701                 if(nBishops)
6702                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6703                 if(pCnt[WhiteAlfil+side])
6704                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6705                 if(pCnt[WhiteWazir+side])
6706                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6707         }
6708
6709         return TRUE;
6710 }
6711
6712 int
6713 Adjudicate(ChessProgramState *cps)
6714 {       // [HGM] some adjudications useful with buggy engines
6715         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6716         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6717         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6718         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6719         int k, count = 0; static int bare = 1;
6720         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6721         Boolean canAdjudicate = !appData.icsActive;
6722
6723         // most tests only when we understand the game, i.e. legality-checking on
6724             if( appData.testLegality )
6725             {   /* [HGM] Some more adjudications for obstinate engines */
6726                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6727                 static int moveCount = 6;
6728                 ChessMove result;
6729                 char *reason = NULL;
6730
6731                 /* Count what is on board. */
6732                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6733
6734                 /* Some material-based adjudications that have to be made before stalemate test */
6735                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6736                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6737                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6738                      if(canAdjudicate && appData.checkMates) {
6739                          if(engineOpponent)
6740                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6741                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6742                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6743                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6744                          return 1;
6745                      }
6746                 }
6747
6748                 /* Bare King in Shatranj (loses) or Losers (wins) */
6749                 if( nrW == 1 || nrB == 1) {
6750                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6751                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6752                      if(canAdjudicate && appData.checkMates) {
6753                          if(engineOpponent)
6754                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6755                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6756                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6757                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6758                          return 1;
6759                      }
6760                   } else
6761                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6762                   {    /* bare King */
6763                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6764                         if(canAdjudicate && appData.checkMates) {
6765                             /* but only adjudicate if adjudication enabled */
6766                             if(engineOpponent)
6767                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6768                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6769                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6770                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6771                             return 1;
6772                         }
6773                   }
6774                 } else bare = 1;
6775
6776
6777             // don't wait for engine to announce game end if we can judge ourselves
6778             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6779               case MT_CHECK:
6780                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6781                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6782                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6783                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6784                             checkCnt++;
6785                         if(checkCnt >= 2) {
6786                             reason = "Xboard adjudication: 3rd check";
6787                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6788                             break;
6789                         }
6790                     }
6791                 }
6792               case MT_NONE:
6793               default:
6794                 break;
6795               case MT_STALEMATE:
6796               case MT_STAINMATE:
6797                 reason = "Xboard adjudication: Stalemate";
6798                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6799                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6800                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6801                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6802                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6803                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6804                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6805                                                                         EP_CHECKMATE : EP_WINS);
6806                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6807                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6808                 }
6809                 break;
6810               case MT_CHECKMATE:
6811                 reason = "Xboard adjudication: Checkmate";
6812                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6813                 break;
6814             }
6815
6816                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6817                     case EP_STALEMATE:
6818                         result = GameIsDrawn; break;
6819                     case EP_CHECKMATE:
6820                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6821                     case EP_WINS:
6822                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6823                     default:
6824                         result = EndOfFile;
6825                 }
6826                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6827                     if(engineOpponent)
6828                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6829                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6830                     GameEnds( result, reason, GE_XBOARD );
6831                     return 1;
6832                 }
6833
6834                 /* Next absolutely insufficient mating material. */
6835                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6836                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6837                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6838
6839                      /* always flag draws, for judging claims */
6840                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6841
6842                      if(canAdjudicate && appData.materialDraws) {
6843                          /* but only adjudicate them if adjudication enabled */
6844                          if(engineOpponent) {
6845                            SendToProgram("force\n", engineOpponent); // suppress reply
6846                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6847                          }
6848                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6849                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6850                          return 1;
6851                      }
6852                 }
6853
6854                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6855                 if(gameInfo.variant == VariantXiangqi ?
6856                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6857                  : nrW + nrB == 4 &&
6858                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6859                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6860                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6861                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6862                    ) ) {
6863                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6864                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6865                           if(engineOpponent) {
6866                             SendToProgram("force\n", engineOpponent); // suppress reply
6867                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6868                           }
6869                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6870                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6871                           return 1;
6872                      }
6873                 } else moveCount = 6;
6874             }
6875         if (appData.debugMode) { int i;
6876             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6877                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6878                     appData.drawRepeats);
6879             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6880               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6881
6882         }
6883
6884         // Repetition draws and 50-move rule can be applied independently of legality testing
6885
6886                 /* Check for rep-draws */
6887                 count = 0;
6888                 for(k = forwardMostMove-2;
6889                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6890                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6891                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6892                     k-=2)
6893                 {   int rights=0;
6894                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6895                         /* compare castling rights */
6896                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6897                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6898                                 rights++; /* King lost rights, while rook still had them */
6899                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6900                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6901                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6902                                    rights++; /* but at least one rook lost them */
6903                         }
6904                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6905                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6906                                 rights++;
6907                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6908                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6909                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6910                                    rights++;
6911                         }
6912                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6913                             && appData.drawRepeats > 1) {
6914                              /* adjudicate after user-specified nr of repeats */
6915                              int result = GameIsDrawn;
6916                              char *details = "XBoard adjudication: repetition draw";
6917                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6918                                 // [HGM] xiangqi: check for forbidden perpetuals
6919                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6920                                 for(m=forwardMostMove; m>k; m-=2) {
6921                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6922                                         ourPerpetual = 0; // the current mover did not always check
6923                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6924                                         hisPerpetual = 0; // the opponent did not always check
6925                                 }
6926                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6927                                                                         ourPerpetual, hisPerpetual);
6928                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6929                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6930                                     details = "Xboard adjudication: perpetual checking";
6931                                 } else
6932                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6933                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6934                                 } else
6935                                 // Now check for perpetual chases
6936                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6937                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6938                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6939                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6940                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6941                                         details = "Xboard adjudication: perpetual chasing";
6942                                     } else
6943                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6944                                         break; // Abort repetition-checking loop.
6945                                 }
6946                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6947                              }
6948                              if(engineOpponent) {
6949                                SendToProgram("force\n", engineOpponent); // suppress reply
6950                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6951                              }
6952                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6953                              GameEnds( result, details, GE_XBOARD );
6954                              return 1;
6955                         }
6956                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6957                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6958                     }
6959                 }
6960
6961                 /* Now we test for 50-move draws. Determine ply count */
6962                 count = forwardMostMove;
6963                 /* look for last irreversble move */
6964                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6965                     count--;
6966                 /* if we hit starting position, add initial plies */
6967                 if( count == backwardMostMove )
6968                     count -= initialRulePlies;
6969                 count = forwardMostMove - count;
6970                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6971                         // adjust reversible move counter for checks in Xiangqi
6972                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6973                         if(i < backwardMostMove) i = backwardMostMove;
6974                         while(i <= forwardMostMove) {
6975                                 lastCheck = inCheck; // check evasion does not count
6976                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6977                                 if(inCheck || lastCheck) count--; // check does not count
6978                                 i++;
6979                         }
6980                 }
6981                 if( count >= 100)
6982                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6983                          /* this is used to judge if draw claims are legal */
6984                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6985                          if(engineOpponent) {
6986                            SendToProgram("force\n", engineOpponent); // suppress reply
6987                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6988                          }
6989                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6990                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6991                          return 1;
6992                 }
6993
6994                 /* if draw offer is pending, treat it as a draw claim
6995                  * when draw condition present, to allow engines a way to
6996                  * claim draws before making their move to avoid a race
6997                  * condition occurring after their move
6998                  */
6999                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7000                          char *p = NULL;
7001                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7002                              p = "Draw claim: 50-move rule";
7003                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7004                              p = "Draw claim: 3-fold repetition";
7005                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7006                              p = "Draw claim: insufficient mating material";
7007                          if( p != NULL && canAdjudicate) {
7008                              if(engineOpponent) {
7009                                SendToProgram("force\n", engineOpponent); // suppress reply
7010                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7011                              }
7012                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7013                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7014                              return 1;
7015                          }
7016                 }
7017
7018                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7019                     if(engineOpponent) {
7020                       SendToProgram("force\n", engineOpponent); // suppress reply
7021                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7022                     }
7023                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7024                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7025                     return 1;
7026                 }
7027         return 0;
7028 }
7029
7030 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7031 {   // [HGM] book: this routine intercepts moves to simulate book replies
7032     char *bookHit = NULL;
7033
7034     //first determine if the incoming move brings opponent into his book
7035     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7036         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7037     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7038     if(bookHit != NULL && !cps->bookSuspend) {
7039         // make sure opponent is not going to reply after receiving move to book position
7040         SendToProgram("force\n", cps);
7041         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7042     }
7043     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7044     // now arrange restart after book miss
7045     if(bookHit) {
7046         // after a book hit we never send 'go', and the code after the call to this routine
7047         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7048         char buf[MSG_SIZ];
7049         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7050         SendToProgram(buf, cps);
7051         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7052     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7053         SendToProgram("go\n", cps);
7054         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7055     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7056         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7057             SendToProgram("go\n", cps);
7058         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7059     }
7060     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7061 }
7062
7063 char *savedMessage;
7064 ChessProgramState *savedState;
7065 void DeferredBookMove(void)
7066 {
7067         if(savedState->lastPing != savedState->lastPong)
7068                     ScheduleDelayedEvent(DeferredBookMove, 10);
7069         else
7070         HandleMachineMove(savedMessage, savedState);
7071 }
7072
7073 void
7074 HandleMachineMove(message, cps)
7075      char *message;
7076      ChessProgramState *cps;
7077 {
7078     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7079     char realname[MSG_SIZ];
7080     int fromX, fromY, toX, toY;
7081     ChessMove moveType;
7082     char promoChar;
7083     char *p;
7084     int machineWhite;
7085     char *bookHit;
7086
7087     cps->userError = 0;
7088
7089 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7090     /*
7091      * Kludge to ignore BEL characters
7092      */
7093     while (*message == '\007') message++;
7094
7095     /*
7096      * [HGM] engine debug message: ignore lines starting with '#' character
7097      */
7098     if(cps->debug && *message == '#') return;
7099
7100     /*
7101      * Look for book output
7102      */
7103     if (cps == &first && bookRequested) {
7104         if (message[0] == '\t' || message[0] == ' ') {
7105             /* Part of the book output is here; append it */
7106             strcat(bookOutput, message);
7107             strcat(bookOutput, "  \n");
7108             return;
7109         } else if (bookOutput[0] != NULLCHAR) {
7110             /* All of book output has arrived; display it */
7111             char *p = bookOutput;
7112             while (*p != NULLCHAR) {
7113                 if (*p == '\t') *p = ' ';
7114                 p++;
7115             }
7116             DisplayInformation(bookOutput);
7117             bookRequested = FALSE;
7118             /* Fall through to parse the current output */
7119         }
7120     }
7121
7122     /*
7123      * Look for machine move.
7124      */
7125     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7126         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7127     {
7128         /* This method is only useful on engines that support ping */
7129         if (cps->lastPing != cps->lastPong) {
7130           if (gameMode == BeginningOfGame) {
7131             /* Extra move from before last new; ignore */
7132             if (appData.debugMode) {
7133                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7134             }
7135           } else {
7136             if (appData.debugMode) {
7137                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7138                         cps->which, gameMode);
7139             }
7140
7141             SendToProgram("undo\n", cps);
7142           }
7143           return;
7144         }
7145
7146         switch (gameMode) {
7147           case BeginningOfGame:
7148             /* Extra move from before last reset; ignore */
7149             if (appData.debugMode) {
7150                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7151             }
7152             return;
7153
7154           case EndOfGame:
7155           case IcsIdle:
7156           default:
7157             /* Extra move after we tried to stop.  The mode test is
7158                not a reliable way of detecting this problem, but it's
7159                the best we can do on engines that don't support ping.
7160             */
7161             if (appData.debugMode) {
7162                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7163                         cps->which, gameMode);
7164             }
7165             SendToProgram("undo\n", cps);
7166             return;
7167
7168           case MachinePlaysWhite:
7169           case IcsPlayingWhite:
7170             machineWhite = TRUE;
7171             break;
7172
7173           case MachinePlaysBlack:
7174           case IcsPlayingBlack:
7175             machineWhite = FALSE;
7176             break;
7177
7178           case TwoMachinesPlay:
7179             machineWhite = (cps->twoMachinesColor[0] == 'w');
7180             break;
7181         }
7182         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7183             if (appData.debugMode) {
7184                 fprintf(debugFP,
7185                         "Ignoring move out of turn by %s, gameMode %d"
7186                         ", forwardMost %d\n",
7187                         cps->which, gameMode, forwardMostMove);
7188             }
7189             return;
7190         }
7191
7192     if (appData.debugMode) { int f = forwardMostMove;
7193         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7194                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7195                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7196     }
7197         if(cps->alphaRank) AlphaRank(machineMove, 4);
7198         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7199                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7200             /* Machine move could not be parsed; ignore it. */
7201           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7202                     machineMove, cps->which);
7203             DisplayError(buf1, 0);
7204             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7205                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7206             if (gameMode == TwoMachinesPlay) {
7207               GameEnds(machineWhite ? BlackWins : WhiteWins,
7208                        buf1, GE_XBOARD);
7209             }
7210             return;
7211         }
7212
7213         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7214         /* So we have to redo legality test with true e.p. status here,  */
7215         /* to make sure an illegal e.p. capture does not slip through,   */
7216         /* to cause a forfeit on a justified illegal-move complaint      */
7217         /* of the opponent.                                              */
7218         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7219            ChessMove moveType;
7220            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7221                              fromY, fromX, toY, toX, promoChar);
7222             if (appData.debugMode) {
7223                 int i;
7224                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7225                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7226                 fprintf(debugFP, "castling rights\n");
7227             }
7228             if(moveType == IllegalMove) {
7229               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7230                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7231                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7232                            buf1, GE_XBOARD);
7233                 return;
7234            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7235            /* [HGM] Kludge to handle engines that send FRC-style castling
7236               when they shouldn't (like TSCP-Gothic) */
7237            switch(moveType) {
7238              case WhiteASideCastleFR:
7239              case BlackASideCastleFR:
7240                toX+=2;
7241                currentMoveString[2]++;
7242                break;
7243              case WhiteHSideCastleFR:
7244              case BlackHSideCastleFR:
7245                toX--;
7246                currentMoveString[2]--;
7247                break;
7248              default: ; // nothing to do, but suppresses warning of pedantic compilers
7249            }
7250         }
7251         hintRequested = FALSE;
7252         lastHint[0] = NULLCHAR;
7253         bookRequested = FALSE;
7254         /* Program may be pondering now */
7255         cps->maybeThinking = TRUE;
7256         if (cps->sendTime == 2) cps->sendTime = 1;
7257         if (cps->offeredDraw) cps->offeredDraw--;
7258
7259         /* currentMoveString is set as a side-effect of ParseOneMove */
7260         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7261         strcat(machineMove, "\n");
7262         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7263
7264         /* [AS] Save move info*/
7265         pvInfoList[ forwardMostMove ].score = programStats.score;
7266         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7267         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7268
7269         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7270
7271         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7272         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7273             int count = 0;
7274
7275             while( count < adjudicateLossPlies ) {
7276                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7277
7278                 if( count & 1 ) {
7279                     score = -score; /* Flip score for winning side */
7280                 }
7281
7282                 if( score > adjudicateLossThreshold ) {
7283                     break;
7284                 }
7285
7286                 count++;
7287             }
7288
7289             if( count >= adjudicateLossPlies ) {
7290                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7291
7292                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7293                     "Xboard adjudication",
7294                     GE_XBOARD );
7295
7296                 return;
7297             }
7298         }
7299
7300         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7301
7302 #if ZIPPY
7303         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7304             first.initDone) {
7305           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7306                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7307                 SendToICS("draw ");
7308                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7309           }
7310           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7311           ics_user_moved = 1;
7312           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7313                 char buf[3*MSG_SIZ];
7314
7315                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7316                         programStats.score / 100.,
7317                         programStats.depth,
7318                         programStats.time / 100.,
7319                         (unsigned int)programStats.nodes,
7320                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7321                         programStats.movelist);
7322                 SendToICS(buf);
7323 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7324           }
7325         }
7326 #endif
7327
7328         /* [AS] Clear stats for next move */
7329         ClearProgramStats();
7330         thinkOutput[0] = NULLCHAR;
7331         hiddenThinkOutputState = 0;
7332
7333         bookHit = NULL;
7334         if (gameMode == TwoMachinesPlay) {
7335             /* [HGM] relaying draw offers moved to after reception of move */
7336             /* and interpreting offer as claim if it brings draw condition */
7337             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7338                 SendToProgram("draw\n", cps->other);
7339             }
7340             if (cps->other->sendTime) {
7341                 SendTimeRemaining(cps->other,
7342                                   cps->other->twoMachinesColor[0] == 'w');
7343             }
7344             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7345             if (firstMove && !bookHit) {
7346                 firstMove = FALSE;
7347                 if (cps->other->useColors) {
7348                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7349                 }
7350                 SendToProgram("go\n", cps->other);
7351             }
7352             cps->other->maybeThinking = TRUE;
7353         }
7354
7355         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7356
7357         if (!pausing && appData.ringBellAfterMoves) {
7358             RingBell();
7359         }
7360
7361         /*
7362          * Reenable menu items that were disabled while
7363          * machine was thinking
7364          */
7365         if (gameMode != TwoMachinesPlay)
7366             SetUserThinkingEnables();
7367
7368         // [HGM] book: after book hit opponent has received move and is now in force mode
7369         // force the book reply into it, and then fake that it outputted this move by jumping
7370         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7371         if(bookHit) {
7372                 static char bookMove[MSG_SIZ]; // a bit generous?
7373
7374                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7375                 strcat(bookMove, bookHit);
7376                 message = bookMove;
7377                 cps = cps->other;
7378                 programStats.nodes = programStats.depth = programStats.time =
7379                 programStats.score = programStats.got_only_move = 0;
7380                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7381
7382                 if(cps->lastPing != cps->lastPong) {
7383                     savedMessage = message; // args for deferred call
7384                     savedState = cps;
7385                     ScheduleDelayedEvent(DeferredBookMove, 10);
7386                     return;
7387                 }
7388                 goto FakeBookMove;
7389         }
7390
7391         return;
7392     }
7393
7394     /* Set special modes for chess engines.  Later something general
7395      *  could be added here; for now there is just one kludge feature,
7396      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7397      *  when "xboard" is given as an interactive command.
7398      */
7399     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7400         cps->useSigint = FALSE;
7401         cps->useSigterm = FALSE;
7402     }
7403     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7404       ParseFeatures(message+8, cps);
7405       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7406     }
7407
7408     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7409      * want this, I was asked to put it in, and obliged.
7410      */
7411     if (!strncmp(message, "setboard ", 9)) {
7412         Board initial_position;
7413
7414         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7415
7416         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7417             DisplayError(_("Bad FEN received from engine"), 0);
7418             return ;
7419         } else {
7420            Reset(TRUE, FALSE);
7421            CopyBoard(boards[0], initial_position);
7422            initialRulePlies = FENrulePlies;
7423            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7424            else gameMode = MachinePlaysBlack;
7425            DrawPosition(FALSE, boards[currentMove]);
7426         }
7427         return;
7428     }
7429
7430     /*
7431      * Look for communication commands
7432      */
7433     if (!strncmp(message, "telluser ", 9)) {
7434         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7435         DisplayNote(message + 9);
7436         return;
7437     }
7438     if (!strncmp(message, "tellusererror ", 14)) {
7439         cps->userError = 1;
7440         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7441         DisplayError(message + 14, 0);
7442         return;
7443     }
7444     if (!strncmp(message, "tellopponent ", 13)) {
7445       if (appData.icsActive) {
7446         if (loggedOn) {
7447           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7448           SendToICS(buf1);
7449         }
7450       } else {
7451         DisplayNote(message + 13);
7452       }
7453       return;
7454     }
7455     if (!strncmp(message, "tellothers ", 11)) {
7456       if (appData.icsActive) {
7457         if (loggedOn) {
7458           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7459           SendToICS(buf1);
7460         }
7461       }
7462       return;
7463     }
7464     if (!strncmp(message, "tellall ", 8)) {
7465       if (appData.icsActive) {
7466         if (loggedOn) {
7467           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7468           SendToICS(buf1);
7469         }
7470       } else {
7471         DisplayNote(message + 8);
7472       }
7473       return;
7474     }
7475     if (strncmp(message, "warning", 7) == 0) {
7476         /* Undocumented feature, use tellusererror in new code */
7477         DisplayError(message, 0);
7478         return;
7479     }
7480     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7481         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7482         strcat(realname, " query");
7483         AskQuestion(realname, buf2, buf1, cps->pr);
7484         return;
7485     }
7486     /* Commands from the engine directly to ICS.  We don't allow these to be
7487      *  sent until we are logged on. Crafty kibitzes have been known to
7488      *  interfere with the login process.
7489      */
7490     if (loggedOn) {
7491         if (!strncmp(message, "tellics ", 8)) {
7492             SendToICS(message + 8);
7493             SendToICS("\n");
7494             return;
7495         }
7496         if (!strncmp(message, "tellicsnoalias ", 15)) {
7497             SendToICS(ics_prefix);
7498             SendToICS(message + 15);
7499             SendToICS("\n");
7500             return;
7501         }
7502         /* The following are for backward compatibility only */
7503         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7504             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7505             SendToICS(ics_prefix);
7506             SendToICS(message);
7507             SendToICS("\n");
7508             return;
7509         }
7510     }
7511     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7512         return;
7513     }
7514     /*
7515      * If the move is illegal, cancel it and redraw the board.
7516      * Also deal with other error cases.  Matching is rather loose
7517      * here to accommodate engines written before the spec.
7518      */
7519     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7520         strncmp(message, "Error", 5) == 0) {
7521         if (StrStr(message, "name") ||
7522             StrStr(message, "rating") || StrStr(message, "?") ||
7523             StrStr(message, "result") || StrStr(message, "board") ||
7524             StrStr(message, "bk") || StrStr(message, "computer") ||
7525             StrStr(message, "variant") || StrStr(message, "hint") ||
7526             StrStr(message, "random") || StrStr(message, "depth") ||
7527             StrStr(message, "accepted")) {
7528             return;
7529         }
7530         if (StrStr(message, "protover")) {
7531           /* Program is responding to input, so it's apparently done
7532              initializing, and this error message indicates it is
7533              protocol version 1.  So we don't need to wait any longer
7534              for it to initialize and send feature commands. */
7535           FeatureDone(cps, 1);
7536           cps->protocolVersion = 1;
7537           return;
7538         }
7539         cps->maybeThinking = FALSE;
7540
7541         if (StrStr(message, "draw")) {
7542             /* Program doesn't have "draw" command */
7543             cps->sendDrawOffers = 0;
7544             return;
7545         }
7546         if (cps->sendTime != 1 &&
7547             (StrStr(message, "time") || StrStr(message, "otim"))) {
7548           /* Program apparently doesn't have "time" or "otim" command */
7549           cps->sendTime = 0;
7550           return;
7551         }
7552         if (StrStr(message, "analyze")) {
7553             cps->analysisSupport = FALSE;
7554             cps->analyzing = FALSE;
7555             Reset(FALSE, TRUE);
7556             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7557             DisplayError(buf2, 0);
7558             return;
7559         }
7560         if (StrStr(message, "(no matching move)st")) {
7561           /* Special kludge for GNU Chess 4 only */
7562           cps->stKludge = TRUE;
7563           SendTimeControl(cps, movesPerSession, timeControl,
7564                           timeIncrement, appData.searchDepth,
7565                           searchTime);
7566           return;
7567         }
7568         if (StrStr(message, "(no matching move)sd")) {
7569           /* Special kludge for GNU Chess 4 only */
7570           cps->sdKludge = TRUE;
7571           SendTimeControl(cps, movesPerSession, timeControl,
7572                           timeIncrement, appData.searchDepth,
7573                           searchTime);
7574           return;
7575         }
7576         if (!StrStr(message, "llegal")) {
7577             return;
7578         }
7579         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7580             gameMode == IcsIdle) return;
7581         if (forwardMostMove <= backwardMostMove) return;
7582         if (pausing) PauseEvent();
7583       if(appData.forceIllegal) {
7584             // [HGM] illegal: machine refused move; force position after move into it
7585           SendToProgram("force\n", cps);
7586           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7587                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7588                 // when black is to move, while there might be nothing on a2 or black
7589                 // might already have the move. So send the board as if white has the move.
7590                 // But first we must change the stm of the engine, as it refused the last move
7591                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7592                 if(WhiteOnMove(forwardMostMove)) {
7593                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7594                     SendBoard(cps, forwardMostMove); // kludgeless board
7595                 } else {
7596                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7597                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7598                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7599                 }
7600           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7601             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7602                  gameMode == TwoMachinesPlay)
7603               SendToProgram("go\n", cps);
7604             return;
7605       } else
7606         if (gameMode == PlayFromGameFile) {
7607             /* Stop reading this game file */
7608             gameMode = EditGame;
7609             ModeHighlight();
7610         }
7611         currentMove = forwardMostMove-1;
7612         DisplayMove(currentMove-1); /* before DisplayMoveError */
7613         SwitchClocks(forwardMostMove-1); // [HGM] race
7614         DisplayBothClocks();
7615         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7616                 parseList[currentMove], cps->which);
7617         DisplayMoveError(buf1);
7618         DrawPosition(FALSE, boards[currentMove]);
7619
7620         /* [HGM] illegal-move claim should forfeit game when Xboard */
7621         /* only passes fully legal moves                            */
7622         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7623             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7624                                 "False illegal-move claim", GE_XBOARD );
7625         }
7626         return;
7627     }
7628     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7629         /* Program has a broken "time" command that
7630            outputs a string not ending in newline.
7631            Don't use it. */
7632         cps->sendTime = 0;
7633     }
7634
7635     /*
7636      * If chess program startup fails, exit with an error message.
7637      * Attempts to recover here are futile.
7638      */
7639     if ((StrStr(message, "unknown host") != NULL)
7640         || (StrStr(message, "No remote directory") != NULL)
7641         || (StrStr(message, "not found") != NULL)
7642         || (StrStr(message, "No such file") != NULL)
7643         || (StrStr(message, "can't alloc") != NULL)
7644         || (StrStr(message, "Permission denied") != NULL)) {
7645
7646         cps->maybeThinking = FALSE;
7647         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7648                 cps->which, cps->program, cps->host, message);
7649         RemoveInputSource(cps->isr);
7650         DisplayFatalError(buf1, 0, 1);
7651         return;
7652     }
7653
7654     /*
7655      * Look for hint output
7656      */
7657     if (sscanf(message, "Hint: %s", buf1) == 1) {
7658         if (cps == &first && hintRequested) {
7659             hintRequested = FALSE;
7660             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7661                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7662                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7663                                     PosFlags(forwardMostMove),
7664                                     fromY, fromX, toY, toX, promoChar, buf1);
7665                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7666                 DisplayInformation(buf2);
7667             } else {
7668                 /* Hint move could not be parsed!? */
7669               snprintf(buf2, sizeof(buf2),
7670                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7671                         buf1, cps->which);
7672                 DisplayError(buf2, 0);
7673             }
7674         } else {
7675           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7676         }
7677         return;
7678     }
7679
7680     /*
7681      * Ignore other messages if game is not in progress
7682      */
7683     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7684         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7685
7686     /*
7687      * look for win, lose, draw, or draw offer
7688      */
7689     if (strncmp(message, "1-0", 3) == 0) {
7690         char *p, *q, *r = "";
7691         p = strchr(message, '{');
7692         if (p) {
7693             q = strchr(p, '}');
7694             if (q) {
7695                 *q = NULLCHAR;
7696                 r = p + 1;
7697             }
7698         }
7699         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7700         return;
7701     } else if (strncmp(message, "0-1", 3) == 0) {
7702         char *p, *q, *r = "";
7703         p = strchr(message, '{');
7704         if (p) {
7705             q = strchr(p, '}');
7706             if (q) {
7707                 *q = NULLCHAR;
7708                 r = p + 1;
7709             }
7710         }
7711         /* Kludge for Arasan 4.1 bug */
7712         if (strcmp(r, "Black resigns") == 0) {
7713             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7714             return;
7715         }
7716         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7717         return;
7718     } else if (strncmp(message, "1/2", 3) == 0) {
7719         char *p, *q, *r = "";
7720         p = strchr(message, '{');
7721         if (p) {
7722             q = strchr(p, '}');
7723             if (q) {
7724                 *q = NULLCHAR;
7725                 r = p + 1;
7726             }
7727         }
7728
7729         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7730         return;
7731
7732     } else if (strncmp(message, "White resign", 12) == 0) {
7733         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7734         return;
7735     } else if (strncmp(message, "Black resign", 12) == 0) {
7736         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7737         return;
7738     } else if (strncmp(message, "White matches", 13) == 0 ||
7739                strncmp(message, "Black matches", 13) == 0   ) {
7740         /* [HGM] ignore GNUShogi noises */
7741         return;
7742     } else if (strncmp(message, "White", 5) == 0 &&
7743                message[5] != '(' &&
7744                StrStr(message, "Black") == NULL) {
7745         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7746         return;
7747     } else if (strncmp(message, "Black", 5) == 0 &&
7748                message[5] != '(') {
7749         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7750         return;
7751     } else if (strcmp(message, "resign") == 0 ||
7752                strcmp(message, "computer resigns") == 0) {
7753         switch (gameMode) {
7754           case MachinePlaysBlack:
7755           case IcsPlayingBlack:
7756             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7757             break;
7758           case MachinePlaysWhite:
7759           case IcsPlayingWhite:
7760             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7761             break;
7762           case TwoMachinesPlay:
7763             if (cps->twoMachinesColor[0] == 'w')
7764               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7765             else
7766               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7767             break;
7768           default:
7769             /* can't happen */
7770             break;
7771         }
7772         return;
7773     } else if (strncmp(message, "opponent mates", 14) == 0) {
7774         switch (gameMode) {
7775           case MachinePlaysBlack:
7776           case IcsPlayingBlack:
7777             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7778             break;
7779           case MachinePlaysWhite:
7780           case IcsPlayingWhite:
7781             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7782             break;
7783           case TwoMachinesPlay:
7784             if (cps->twoMachinesColor[0] == 'w')
7785               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7786             else
7787               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7788             break;
7789           default:
7790             /* can't happen */
7791             break;
7792         }
7793         return;
7794     } else if (strncmp(message, "computer mates", 14) == 0) {
7795         switch (gameMode) {
7796           case MachinePlaysBlack:
7797           case IcsPlayingBlack:
7798             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7799             break;
7800           case MachinePlaysWhite:
7801           case IcsPlayingWhite:
7802             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7803             break;
7804           case TwoMachinesPlay:
7805             if (cps->twoMachinesColor[0] == 'w')
7806               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7807             else
7808               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7809             break;
7810           default:
7811             /* can't happen */
7812             break;
7813         }
7814         return;
7815     } else if (strncmp(message, "checkmate", 9) == 0) {
7816         if (WhiteOnMove(forwardMostMove)) {
7817             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7818         } else {
7819             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7820         }
7821         return;
7822     } else if (strstr(message, "Draw") != NULL ||
7823                strstr(message, "game is a draw") != NULL) {
7824         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7825         return;
7826     } else if (strstr(message, "offer") != NULL &&
7827                strstr(message, "draw") != NULL) {
7828 #if ZIPPY
7829         if (appData.zippyPlay && first.initDone) {
7830             /* Relay offer to ICS */
7831             SendToICS(ics_prefix);
7832             SendToICS("draw\n");
7833         }
7834 #endif
7835         cps->offeredDraw = 2; /* valid until this engine moves twice */
7836         if (gameMode == TwoMachinesPlay) {
7837             if (cps->other->offeredDraw) {
7838                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7839             /* [HGM] in two-machine mode we delay relaying draw offer      */
7840             /* until after we also have move, to see if it is really claim */
7841             }
7842         } else if (gameMode == MachinePlaysWhite ||
7843                    gameMode == MachinePlaysBlack) {
7844           if (userOfferedDraw) {
7845             DisplayInformation(_("Machine accepts your draw offer"));
7846             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7847           } else {
7848             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7849           }
7850         }
7851     }
7852
7853
7854     /*
7855      * Look for thinking output
7856      */
7857     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7858           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7859                                 ) {
7860         int plylev, mvleft, mvtot, curscore, time;
7861         char mvname[MOVE_LEN];
7862         u64 nodes; // [DM]
7863         char plyext;
7864         int ignore = FALSE;
7865         int prefixHint = FALSE;
7866         mvname[0] = NULLCHAR;
7867
7868         switch (gameMode) {
7869           case MachinePlaysBlack:
7870           case IcsPlayingBlack:
7871             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7872             break;
7873           case MachinePlaysWhite:
7874           case IcsPlayingWhite:
7875             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7876             break;
7877           case AnalyzeMode:
7878           case AnalyzeFile:
7879             break;
7880           case IcsObserving: /* [DM] icsEngineAnalyze */
7881             if (!appData.icsEngineAnalyze) ignore = TRUE;
7882             break;
7883           case TwoMachinesPlay:
7884             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7885                 ignore = TRUE;
7886             }
7887             break;
7888           default:
7889             ignore = TRUE;
7890             break;
7891         }
7892
7893         if (!ignore) {
7894             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7895             buf1[0] = NULLCHAR;
7896             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7897                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7898
7899                 if (plyext != ' ' && plyext != '\t') {
7900                     time *= 100;
7901                 }
7902
7903                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7904                 if( cps->scoreIsAbsolute &&
7905                     ( gameMode == MachinePlaysBlack ||
7906                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7907                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7908                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7909                      !WhiteOnMove(currentMove)
7910                     ) )
7911                 {
7912                     curscore = -curscore;
7913                 }
7914
7915
7916                 tempStats.depth = plylev;
7917                 tempStats.nodes = nodes;
7918                 tempStats.time = time;
7919                 tempStats.score = curscore;
7920                 tempStats.got_only_move = 0;
7921
7922                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7923                         int ticklen;
7924
7925                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7926                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7927                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7928                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7929                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7930                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7931                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7932                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7933                 }
7934
7935                 /* Buffer overflow protection */
7936                 if (buf1[0] != NULLCHAR) {
7937                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7938                         && appData.debugMode) {
7939                         fprintf(debugFP,
7940                                 "PV is too long; using the first %u bytes.\n",
7941                                 (unsigned) sizeof(tempStats.movelist) - 1);
7942                     }
7943
7944                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
7945                 } else {
7946                     sprintf(tempStats.movelist, " no PV\n");
7947                 }
7948
7949                 if (tempStats.seen_stat) {
7950                     tempStats.ok_to_send = 1;
7951                 }
7952
7953                 if (strchr(tempStats.movelist, '(') != NULL) {
7954                     tempStats.line_is_book = 1;
7955                     tempStats.nr_moves = 0;
7956                     tempStats.moves_left = 0;
7957                 } else {
7958                     tempStats.line_is_book = 0;
7959                 }
7960
7961                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7962                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7963
7964                 SendProgramStatsToFrontend( cps, &tempStats );
7965
7966                 /*
7967                     [AS] Protect the thinkOutput buffer from overflow... this
7968                     is only useful if buf1 hasn't overflowed first!
7969                 */
7970                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
7971                          plylev,
7972                          (gameMode == TwoMachinesPlay ?
7973                           ToUpper(cps->twoMachinesColor[0]) : ' '),
7974                          ((double) curscore) / 100.0,
7975                          prefixHint ? lastHint : "",
7976                          prefixHint ? " " : "" );
7977
7978                 if( buf1[0] != NULLCHAR ) {
7979                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7980
7981                     if( strlen(buf1) > max_len ) {
7982                         if( appData.debugMode) {
7983                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7984                         }
7985                         buf1[max_len+1] = '\0';
7986                     }
7987
7988                     strcat( thinkOutput, buf1 );
7989                 }
7990
7991                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7992                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7993                     DisplayMove(currentMove - 1);
7994                 }
7995                 return;
7996
7997             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7998                 /* crafty (9.25+) says "(only move) <move>"
7999                  * if there is only 1 legal move
8000                  */
8001                 sscanf(p, "(only move) %s", buf1);
8002                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8003                 sprintf(programStats.movelist, "%s (only move)", buf1);
8004                 programStats.depth = 1;
8005                 programStats.nr_moves = 1;
8006                 programStats.moves_left = 1;
8007                 programStats.nodes = 1;
8008                 programStats.time = 1;
8009                 programStats.got_only_move = 1;
8010
8011                 /* Not really, but we also use this member to
8012                    mean "line isn't going to change" (Crafty
8013                    isn't searching, so stats won't change) */
8014                 programStats.line_is_book = 1;
8015
8016                 SendProgramStatsToFrontend( cps, &programStats );
8017
8018                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8019                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8020                     DisplayMove(currentMove - 1);
8021                 }
8022                 return;
8023             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8024                               &time, &nodes, &plylev, &mvleft,
8025                               &mvtot, mvname) >= 5) {
8026                 /* The stat01: line is from Crafty (9.29+) in response
8027                    to the "." command */
8028                 programStats.seen_stat = 1;
8029                 cps->maybeThinking = TRUE;
8030
8031                 if (programStats.got_only_move || !appData.periodicUpdates)
8032                   return;
8033
8034                 programStats.depth = plylev;
8035                 programStats.time = time;
8036                 programStats.nodes = nodes;
8037                 programStats.moves_left = mvleft;
8038                 programStats.nr_moves = mvtot;
8039                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8040                 programStats.ok_to_send = 1;
8041                 programStats.movelist[0] = '\0';
8042
8043                 SendProgramStatsToFrontend( cps, &programStats );
8044
8045                 return;
8046
8047             } else if (strncmp(message,"++",2) == 0) {
8048                 /* Crafty 9.29+ outputs this */
8049                 programStats.got_fail = 2;
8050                 return;
8051
8052             } else if (strncmp(message,"--",2) == 0) {
8053                 /* Crafty 9.29+ outputs this */
8054                 programStats.got_fail = 1;
8055                 return;
8056
8057             } else if (thinkOutput[0] != NULLCHAR &&
8058                        strncmp(message, "    ", 4) == 0) {
8059                 unsigned message_len;
8060
8061                 p = message;
8062                 while (*p && *p == ' ') p++;
8063
8064                 message_len = strlen( p );
8065
8066                 /* [AS] Avoid buffer overflow */
8067                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8068                     strcat(thinkOutput, " ");
8069                     strcat(thinkOutput, p);
8070                 }
8071
8072                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8073                     strcat(programStats.movelist, " ");
8074                     strcat(programStats.movelist, p);
8075                 }
8076
8077                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8078                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8079                     DisplayMove(currentMove - 1);
8080                 }
8081                 return;
8082             }
8083         }
8084         else {
8085             buf1[0] = NULLCHAR;
8086
8087             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8088                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8089             {
8090                 ChessProgramStats cpstats;
8091
8092                 if (plyext != ' ' && plyext != '\t') {
8093                     time *= 100;
8094                 }
8095
8096                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8097                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8098                     curscore = -curscore;
8099                 }
8100
8101                 cpstats.depth = plylev;
8102                 cpstats.nodes = nodes;
8103                 cpstats.time = time;
8104                 cpstats.score = curscore;
8105                 cpstats.got_only_move = 0;
8106                 cpstats.movelist[0] = '\0';
8107
8108                 if (buf1[0] != NULLCHAR) {
8109                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8110                 }
8111
8112                 cpstats.ok_to_send = 0;
8113                 cpstats.line_is_book = 0;
8114                 cpstats.nr_moves = 0;
8115                 cpstats.moves_left = 0;
8116
8117                 SendProgramStatsToFrontend( cps, &cpstats );
8118             }
8119         }
8120     }
8121 }
8122
8123
8124 /* Parse a game score from the character string "game", and
8125    record it as the history of the current game.  The game
8126    score is NOT assumed to start from the standard position.
8127    The display is not updated in any way.
8128    */
8129 void
8130 ParseGameHistory(game)
8131      char *game;
8132 {
8133     ChessMove moveType;
8134     int fromX, fromY, toX, toY, boardIndex;
8135     char promoChar;
8136     char *p, *q;
8137     char buf[MSG_SIZ];
8138
8139     if (appData.debugMode)
8140       fprintf(debugFP, "Parsing game history: %s\n", game);
8141
8142     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8143     gameInfo.site = StrSave(appData.icsHost);
8144     gameInfo.date = PGNDate();
8145     gameInfo.round = StrSave("-");
8146
8147     /* Parse out names of players */
8148     while (*game == ' ') game++;
8149     p = buf;
8150     while (*game != ' ') *p++ = *game++;
8151     *p = NULLCHAR;
8152     gameInfo.white = StrSave(buf);
8153     while (*game == ' ') game++;
8154     p = buf;
8155     while (*game != ' ' && *game != '\n') *p++ = *game++;
8156     *p = NULLCHAR;
8157     gameInfo.black = StrSave(buf);
8158
8159     /* Parse moves */
8160     boardIndex = blackPlaysFirst ? 1 : 0;
8161     yynewstr(game);
8162     for (;;) {
8163         yyboardindex = boardIndex;
8164         moveType = (ChessMove) yylex();
8165         switch (moveType) {
8166           case IllegalMove:             /* maybe suicide chess, etc. */
8167   if (appData.debugMode) {
8168     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8169     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8170     setbuf(debugFP, NULL);
8171   }
8172           case WhitePromotion:
8173           case BlackPromotion:
8174           case WhiteNonPromotion:
8175           case BlackNonPromotion:
8176           case NormalMove:
8177           case WhiteCapturesEnPassant:
8178           case BlackCapturesEnPassant:
8179           case WhiteKingSideCastle:
8180           case WhiteQueenSideCastle:
8181           case BlackKingSideCastle:
8182           case BlackQueenSideCastle:
8183           case WhiteKingSideCastleWild:
8184           case WhiteQueenSideCastleWild:
8185           case BlackKingSideCastleWild:
8186           case BlackQueenSideCastleWild:
8187           /* PUSH Fabien */
8188           case WhiteHSideCastleFR:
8189           case WhiteASideCastleFR:
8190           case BlackHSideCastleFR:
8191           case BlackASideCastleFR:
8192           /* POP Fabien */
8193             fromX = currentMoveString[0] - AAA;
8194             fromY = currentMoveString[1] - ONE;
8195             toX = currentMoveString[2] - AAA;
8196             toY = currentMoveString[3] - ONE;
8197             promoChar = currentMoveString[4];
8198             break;
8199           case WhiteDrop:
8200           case BlackDrop:
8201             fromX = moveType == WhiteDrop ?
8202               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8203             (int) CharToPiece(ToLower(currentMoveString[0]));
8204             fromY = DROP_RANK;
8205             toX = currentMoveString[2] - AAA;
8206             toY = currentMoveString[3] - ONE;
8207             promoChar = NULLCHAR;
8208             break;
8209           case AmbiguousMove:
8210             /* bug? */
8211             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8212   if (appData.debugMode) {
8213     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8214     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8215     setbuf(debugFP, NULL);
8216   }
8217             DisplayError(buf, 0);
8218             return;
8219           case ImpossibleMove:
8220             /* bug? */
8221             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8222   if (appData.debugMode) {
8223     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8224     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8225     setbuf(debugFP, NULL);
8226   }
8227             DisplayError(buf, 0);
8228             return;
8229           case EndOfFile:
8230             if (boardIndex < backwardMostMove) {
8231                 /* Oops, gap.  How did that happen? */
8232                 DisplayError(_("Gap in move list"), 0);
8233                 return;
8234             }
8235             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8236             if (boardIndex > forwardMostMove) {
8237                 forwardMostMove = boardIndex;
8238             }
8239             return;
8240           case ElapsedTime:
8241             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8242                 strcat(parseList[boardIndex-1], " ");
8243                 strcat(parseList[boardIndex-1], yy_text);
8244             }
8245             continue;
8246           case Comment:
8247           case PGNTag:
8248           case NAG:
8249           default:
8250             /* ignore */
8251             continue;
8252           case WhiteWins:
8253           case BlackWins:
8254           case GameIsDrawn:
8255           case GameUnfinished:
8256             if (gameMode == IcsExamining) {
8257                 if (boardIndex < backwardMostMove) {
8258                     /* Oops, gap.  How did that happen? */
8259                     return;
8260                 }
8261                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8262                 return;
8263             }
8264             gameInfo.result = moveType;
8265             p = strchr(yy_text, '{');
8266             if (p == NULL) p = strchr(yy_text, '(');
8267             if (p == NULL) {
8268                 p = yy_text;
8269                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8270             } else {
8271                 q = strchr(p, *p == '{' ? '}' : ')');
8272                 if (q != NULL) *q = NULLCHAR;
8273                 p++;
8274             }
8275             gameInfo.resultDetails = StrSave(p);
8276             continue;
8277         }
8278         if (boardIndex >= forwardMostMove &&
8279             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8280             backwardMostMove = blackPlaysFirst ? 1 : 0;
8281             return;
8282         }
8283         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8284                                  fromY, fromX, toY, toX, promoChar,
8285                                  parseList[boardIndex]);
8286         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8287         /* currentMoveString is set as a side-effect of yylex */
8288         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8289         strcat(moveList[boardIndex], "\n");
8290         boardIndex++;
8291         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8292         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8293           case MT_NONE:
8294           case MT_STALEMATE:
8295           default:
8296             break;
8297           case MT_CHECK:
8298             if(gameInfo.variant != VariantShogi)
8299                 strcat(parseList[boardIndex - 1], "+");
8300             break;
8301           case MT_CHECKMATE:
8302           case MT_STAINMATE:
8303             strcat(parseList[boardIndex - 1], "#");
8304             break;
8305         }
8306     }
8307 }
8308
8309
8310 /* Apply a move to the given board  */
8311 void
8312 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8313      int fromX, fromY, toX, toY;
8314      int promoChar;
8315      Board board;
8316 {
8317   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8318   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8319
8320     /* [HGM] compute & store e.p. status and castling rights for new position */
8321     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8322
8323       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8324       oldEP = (signed char)board[EP_STATUS];
8325       board[EP_STATUS] = EP_NONE;
8326
8327       if( board[toY][toX] != EmptySquare )
8328            board[EP_STATUS] = EP_CAPTURE;
8329
8330   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8331   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8332        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8333          
8334   if (fromY == DROP_RANK) {
8335         /* must be first */
8336         piece = board[toY][toX] = (ChessSquare) fromX;
8337   } else {
8338       int i;
8339
8340       if( board[fromY][fromX] == WhitePawn ) {
8341            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8342                board[EP_STATUS] = EP_PAWN_MOVE;
8343            if( toY-fromY==2) {
8344                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8345                         gameInfo.variant != VariantBerolina || toX < fromX)
8346                       board[EP_STATUS] = toX | berolina;
8347                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8348                         gameInfo.variant != VariantBerolina || toX > fromX)
8349                       board[EP_STATUS] = toX;
8350            }
8351       } else
8352       if( board[fromY][fromX] == BlackPawn ) {
8353            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8354                board[EP_STATUS] = EP_PAWN_MOVE;
8355            if( toY-fromY== -2) {
8356                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8357                         gameInfo.variant != VariantBerolina || toX < fromX)
8358                       board[EP_STATUS] = toX | berolina;
8359                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8360                         gameInfo.variant != VariantBerolina || toX > fromX)
8361                       board[EP_STATUS] = toX;
8362            }
8363        }
8364
8365        for(i=0; i<nrCastlingRights; i++) {
8366            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8367               board[CASTLING][i] == toX   && castlingRank[i] == toY
8368              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8369        }
8370
8371      if (fromX == toX && fromY == toY) return;
8372
8373      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8374      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8375      if(gameInfo.variant == VariantKnightmate)
8376          king += (int) WhiteUnicorn - (int) WhiteKing;
8377
8378     /* Code added by Tord: */
8379     /* FRC castling assumed when king captures friendly rook. */
8380     if (board[fromY][fromX] == WhiteKing &&
8381              board[toY][toX] == WhiteRook) {
8382       board[fromY][fromX] = EmptySquare;
8383       board[toY][toX] = EmptySquare;
8384       if(toX > fromX) {
8385         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8386       } else {
8387         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8388       }
8389     } else if (board[fromY][fromX] == BlackKing &&
8390                board[toY][toX] == BlackRook) {
8391       board[fromY][fromX] = EmptySquare;
8392       board[toY][toX] = EmptySquare;
8393       if(toX > fromX) {
8394         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8395       } else {
8396         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8397       }
8398     /* End of code added by Tord */
8399
8400     } else if (board[fromY][fromX] == king
8401         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8402         && toY == fromY && toX > fromX+1) {
8403         board[fromY][fromX] = EmptySquare;
8404         board[toY][toX] = king;
8405         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8406         board[fromY][BOARD_RGHT-1] = EmptySquare;
8407     } else if (board[fromY][fromX] == king
8408         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8409                && toY == fromY && toX < fromX-1) {
8410         board[fromY][fromX] = EmptySquare;
8411         board[toY][toX] = king;
8412         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8413         board[fromY][BOARD_LEFT] = EmptySquare;
8414     } else if (board[fromY][fromX] == WhitePawn
8415                && toY >= BOARD_HEIGHT-promoRank
8416                && gameInfo.variant != VariantXiangqi
8417                ) {
8418         /* white pawn promotion */
8419         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8420         if (board[toY][toX] == EmptySquare) {
8421             board[toY][toX] = WhiteQueen;
8422         }
8423         if(gameInfo.variant==VariantBughouse ||
8424            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8425             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8426         board[fromY][fromX] = EmptySquare;
8427     } else if ((fromY == BOARD_HEIGHT-4)
8428                && (toX != fromX)
8429                && gameInfo.variant != VariantXiangqi
8430                && gameInfo.variant != VariantBerolina
8431                && (board[fromY][fromX] == WhitePawn)
8432                && (board[toY][toX] == EmptySquare)) {
8433         board[fromY][fromX] = EmptySquare;
8434         board[toY][toX] = WhitePawn;
8435         captured = board[toY - 1][toX];
8436         board[toY - 1][toX] = EmptySquare;
8437     } else if ((fromY == BOARD_HEIGHT-4)
8438                && (toX == fromX)
8439                && gameInfo.variant == VariantBerolina
8440                && (board[fromY][fromX] == WhitePawn)
8441                && (board[toY][toX] == EmptySquare)) {
8442         board[fromY][fromX] = EmptySquare;
8443         board[toY][toX] = WhitePawn;
8444         if(oldEP & EP_BEROLIN_A) {
8445                 captured = board[fromY][fromX-1];
8446                 board[fromY][fromX-1] = EmptySquare;
8447         }else{  captured = board[fromY][fromX+1];
8448                 board[fromY][fromX+1] = EmptySquare;
8449         }
8450     } else if (board[fromY][fromX] == king
8451         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8452                && toY == fromY && toX > fromX+1) {
8453         board[fromY][fromX] = EmptySquare;
8454         board[toY][toX] = king;
8455         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8456         board[fromY][BOARD_RGHT-1] = EmptySquare;
8457     } else if (board[fromY][fromX] == king
8458         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8459                && toY == fromY && toX < fromX-1) {
8460         board[fromY][fromX] = EmptySquare;
8461         board[toY][toX] = king;
8462         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8463         board[fromY][BOARD_LEFT] = EmptySquare;
8464     } else if (fromY == 7 && fromX == 3
8465                && board[fromY][fromX] == BlackKing
8466                && toY == 7 && toX == 5) {
8467         board[fromY][fromX] = EmptySquare;
8468         board[toY][toX] = BlackKing;
8469         board[fromY][7] = EmptySquare;
8470         board[toY][4] = BlackRook;
8471     } else if (fromY == 7 && fromX == 3
8472                && board[fromY][fromX] == BlackKing
8473                && toY == 7 && toX == 1) {
8474         board[fromY][fromX] = EmptySquare;
8475         board[toY][toX] = BlackKing;
8476         board[fromY][0] = EmptySquare;
8477         board[toY][2] = BlackRook;
8478     } else if (board[fromY][fromX] == BlackPawn
8479                && toY < promoRank
8480                && gameInfo.variant != VariantXiangqi
8481                ) {
8482         /* black pawn promotion */
8483         board[toY][toX] = CharToPiece(ToLower(promoChar));
8484         if (board[toY][toX] == EmptySquare) {
8485             board[toY][toX] = BlackQueen;
8486         }
8487         if(gameInfo.variant==VariantBughouse ||
8488            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8489             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8490         board[fromY][fromX] = EmptySquare;
8491     } else if ((fromY == 3)
8492                && (toX != fromX)
8493                && gameInfo.variant != VariantXiangqi
8494                && gameInfo.variant != VariantBerolina
8495                && (board[fromY][fromX] == BlackPawn)
8496                && (board[toY][toX] == EmptySquare)) {
8497         board[fromY][fromX] = EmptySquare;
8498         board[toY][toX] = BlackPawn;
8499         captured = board[toY + 1][toX];
8500         board[toY + 1][toX] = EmptySquare;
8501     } else if ((fromY == 3)
8502                && (toX == fromX)
8503                && gameInfo.variant == VariantBerolina
8504                && (board[fromY][fromX] == BlackPawn)
8505                && (board[toY][toX] == EmptySquare)) {
8506         board[fromY][fromX] = EmptySquare;
8507         board[toY][toX] = BlackPawn;
8508         if(oldEP & EP_BEROLIN_A) {
8509                 captured = board[fromY][fromX-1];
8510                 board[fromY][fromX-1] = EmptySquare;
8511         }else{  captured = board[fromY][fromX+1];
8512                 board[fromY][fromX+1] = EmptySquare;
8513         }
8514     } else {
8515         board[toY][toX] = board[fromY][fromX];
8516         board[fromY][fromX] = EmptySquare;
8517     }
8518   }
8519
8520     if (gameInfo.holdingsWidth != 0) {
8521
8522       /* !!A lot more code needs to be written to support holdings  */
8523       /* [HGM] OK, so I have written it. Holdings are stored in the */
8524       /* penultimate board files, so they are automaticlly stored   */
8525       /* in the game history.                                       */
8526       if (fromY == DROP_RANK) {
8527         /* Delete from holdings, by decreasing count */
8528         /* and erasing image if necessary            */
8529         p = (int) fromX;
8530         if(p < (int) BlackPawn) { /* white drop */
8531              p -= (int)WhitePawn;
8532                  p = PieceToNumber((ChessSquare)p);
8533              if(p >= gameInfo.holdingsSize) p = 0;
8534              if(--board[p][BOARD_WIDTH-2] <= 0)
8535                   board[p][BOARD_WIDTH-1] = EmptySquare;
8536              if((int)board[p][BOARD_WIDTH-2] < 0)
8537                         board[p][BOARD_WIDTH-2] = 0;
8538         } else {                  /* black drop */
8539              p -= (int)BlackPawn;
8540                  p = PieceToNumber((ChessSquare)p);
8541              if(p >= gameInfo.holdingsSize) p = 0;
8542              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8543                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8544              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8545                         board[BOARD_HEIGHT-1-p][1] = 0;
8546         }
8547       }
8548       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8549           && gameInfo.variant != VariantBughouse        ) {
8550         /* [HGM] holdings: Add to holdings, if holdings exist */
8551         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8552                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8553                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8554         }
8555         p = (int) captured;
8556         if (p >= (int) BlackPawn) {
8557           p -= (int)BlackPawn;
8558           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8559                   /* in Shogi restore piece to its original  first */
8560                   captured = (ChessSquare) (DEMOTED captured);
8561                   p = DEMOTED p;
8562           }
8563           p = PieceToNumber((ChessSquare)p);
8564           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8565           board[p][BOARD_WIDTH-2]++;
8566           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8567         } else {
8568           p -= (int)WhitePawn;
8569           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8570                   captured = (ChessSquare) (DEMOTED captured);
8571                   p = DEMOTED p;
8572           }
8573           p = PieceToNumber((ChessSquare)p);
8574           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8575           board[BOARD_HEIGHT-1-p][1]++;
8576           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8577         }
8578       }
8579     } else if (gameInfo.variant == VariantAtomic) {
8580       if (captured != EmptySquare) {
8581         int y, x;
8582         for (y = toY-1; y <= toY+1; y++) {
8583           for (x = toX-1; x <= toX+1; x++) {
8584             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8585                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8586               board[y][x] = EmptySquare;
8587             }
8588           }
8589         }
8590         board[toY][toX] = EmptySquare;
8591       }
8592     }
8593     if(promoChar == '+') {
8594         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8595         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8596     } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar
8597         board[toY][toX] = CharToPiece(promoChar);
8598     }
8599     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8600                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8601         // [HGM] superchess: take promotion piece out of holdings
8602         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8603         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8604             if(!--board[k][BOARD_WIDTH-2])
8605                 board[k][BOARD_WIDTH-1] = EmptySquare;
8606         } else {
8607             if(!--board[BOARD_HEIGHT-1-k][1])
8608                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8609         }
8610     }
8611
8612 }
8613
8614 /* Updates forwardMostMove */
8615 void
8616 MakeMove(fromX, fromY, toX, toY, promoChar)
8617      int fromX, fromY, toX, toY;
8618      int promoChar;
8619 {
8620 //    forwardMostMove++; // [HGM] bare: moved downstream
8621
8622     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8623         int timeLeft; static int lastLoadFlag=0; int king, piece;
8624         piece = boards[forwardMostMove][fromY][fromX];
8625         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8626         if(gameInfo.variant == VariantKnightmate)
8627             king += (int) WhiteUnicorn - (int) WhiteKing;
8628         if(forwardMostMove == 0) {
8629             if(blackPlaysFirst)
8630                 fprintf(serverMoves, "%s;", second.tidy);
8631             fprintf(serverMoves, "%s;", first.tidy);
8632             if(!blackPlaysFirst)
8633                 fprintf(serverMoves, "%s;", second.tidy);
8634         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8635         lastLoadFlag = loadFlag;
8636         // print base move
8637         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8638         // print castling suffix
8639         if( toY == fromY && piece == king ) {
8640             if(toX-fromX > 1)
8641                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8642             if(fromX-toX >1)
8643                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8644         }
8645         // e.p. suffix
8646         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8647              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8648              boards[forwardMostMove][toY][toX] == EmptySquare
8649              && fromX != toX && fromY != toY)
8650                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8651         // promotion suffix
8652         if(promoChar != NULLCHAR)
8653                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8654         if(!loadFlag) {
8655             fprintf(serverMoves, "/%d/%d",
8656                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8657             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8658             else                      timeLeft = blackTimeRemaining/1000;
8659             fprintf(serverMoves, "/%d", timeLeft);
8660         }
8661         fflush(serverMoves);
8662     }
8663
8664     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8665       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8666                         0, 1);
8667       return;
8668     }
8669     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8670     if (commentList[forwardMostMove+1] != NULL) {
8671         free(commentList[forwardMostMove+1]);
8672         commentList[forwardMostMove+1] = NULL;
8673     }
8674     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8675     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8676     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8677     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8678     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8679     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8680     gameInfo.result = GameUnfinished;
8681     if (gameInfo.resultDetails != NULL) {
8682         free(gameInfo.resultDetails);
8683         gameInfo.resultDetails = NULL;
8684     }
8685     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8686                               moveList[forwardMostMove - 1]);
8687     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8688                              PosFlags(forwardMostMove - 1),
8689                              fromY, fromX, toY, toX, promoChar,
8690                              parseList[forwardMostMove - 1]);
8691     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8692       case MT_NONE:
8693       case MT_STALEMATE:
8694       default:
8695         break;
8696       case MT_CHECK:
8697         if(gameInfo.variant != VariantShogi)
8698             strcat(parseList[forwardMostMove - 1], "+");
8699         break;
8700       case MT_CHECKMATE:
8701       case MT_STAINMATE:
8702         strcat(parseList[forwardMostMove - 1], "#");
8703         break;
8704     }
8705     if (appData.debugMode) {
8706         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8707     }
8708
8709 }
8710
8711 /* Updates currentMove if not pausing */
8712 void
8713 ShowMove(fromX, fromY, toX, toY)
8714 {
8715     int instant = (gameMode == PlayFromGameFile) ?
8716         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8717     if(appData.noGUI) return;
8718     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8719         if (!instant) {
8720             if (forwardMostMove == currentMove + 1) {
8721                 AnimateMove(boards[forwardMostMove - 1],
8722                             fromX, fromY, toX, toY);
8723             }
8724             if (appData.highlightLastMove) {
8725                 SetHighlights(fromX, fromY, toX, toY);
8726             }
8727         }
8728         currentMove = forwardMostMove;
8729     }
8730
8731     if (instant) return;
8732
8733     DisplayMove(currentMove - 1);
8734     DrawPosition(FALSE, boards[currentMove]);
8735     DisplayBothClocks();
8736     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8737 }
8738
8739 void SendEgtPath(ChessProgramState *cps)
8740 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8741         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8742
8743         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8744
8745         while(*p) {
8746             char c, *q = name+1, *r, *s;
8747
8748             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8749             while(*p && *p != ',') *q++ = *p++;
8750             *q++ = ':'; *q = 0;
8751             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8752                 strcmp(name, ",nalimov:") == 0 ) {
8753                 // take nalimov path from the menu-changeable option first, if it is defined
8754               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8755                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8756             } else
8757             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8758                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8759                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8760                 s = r = StrStr(s, ":") + 1; // beginning of path info
8761                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8762                 c = *r; *r = 0;             // temporarily null-terminate path info
8763                     *--q = 0;               // strip of trailig ':' from name
8764                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8765                 *r = c;
8766                 SendToProgram(buf,cps);     // send egtbpath command for this format
8767             }
8768             if(*p == ',') p++; // read away comma to position for next format name
8769         }
8770 }
8771
8772 void
8773 InitChessProgram(cps, setup)
8774      ChessProgramState *cps;
8775      int setup; /* [HGM] needed to setup FRC opening position */
8776 {
8777     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8778     if (appData.noChessProgram) return;
8779     hintRequested = FALSE;
8780     bookRequested = FALSE;
8781
8782     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8783     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8784     if(cps->memSize) { /* [HGM] memory */
8785       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8786         SendToProgram(buf, cps);
8787     }
8788     SendEgtPath(cps); /* [HGM] EGT */
8789     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8790       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8791         SendToProgram(buf, cps);
8792     }
8793
8794     SendToProgram(cps->initString, cps);
8795     if (gameInfo.variant != VariantNormal &&
8796         gameInfo.variant != VariantLoadable
8797         /* [HGM] also send variant if board size non-standard */
8798         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8799                                             ) {
8800       char *v = VariantName(gameInfo.variant);
8801       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8802         /* [HGM] in protocol 1 we have to assume all variants valid */
8803         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8804         DisplayFatalError(buf, 0, 1);
8805         return;
8806       }
8807
8808       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8809       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8810       if( gameInfo.variant == VariantXiangqi )
8811            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8812       if( gameInfo.variant == VariantShogi )
8813            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8814       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8815            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8816       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8817                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8818            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8819       if( gameInfo.variant == VariantCourier )
8820            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8821       if( gameInfo.variant == VariantSuper )
8822            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8823       if( gameInfo.variant == VariantGreat )
8824            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8825
8826       if(overruled) {
8827         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8828                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8829            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8830            if(StrStr(cps->variants, b) == NULL) {
8831                // specific sized variant not known, check if general sizing allowed
8832                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8833                    if(StrStr(cps->variants, "boardsize") == NULL) {
8834                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8835                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8836                        DisplayFatalError(buf, 0, 1);
8837                        return;
8838                    }
8839                    /* [HGM] here we really should compare with the maximum supported board size */
8840                }
8841            }
8842       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8843       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8844       SendToProgram(buf, cps);
8845     }
8846     currentlyInitializedVariant = gameInfo.variant;
8847
8848     /* [HGM] send opening position in FRC to first engine */
8849     if(setup) {
8850           SendToProgram("force\n", cps);
8851           SendBoard(cps, 0);
8852           /* engine is now in force mode! Set flag to wake it up after first move. */
8853           setboardSpoiledMachineBlack = 1;
8854     }
8855
8856     if (cps->sendICS) {
8857       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8858       SendToProgram(buf, cps);
8859     }
8860     cps->maybeThinking = FALSE;
8861     cps->offeredDraw = 0;
8862     if (!appData.icsActive) {
8863         SendTimeControl(cps, movesPerSession, timeControl,
8864                         timeIncrement, appData.searchDepth,
8865                         searchTime);
8866     }
8867     if (appData.showThinking
8868         // [HGM] thinking: four options require thinking output to be sent
8869         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8870                                 ) {
8871         SendToProgram("post\n", cps);
8872     }
8873     SendToProgram("hard\n", cps);
8874     if (!appData.ponderNextMove) {
8875         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8876            it without being sure what state we are in first.  "hard"
8877            is not a toggle, so that one is OK.
8878          */
8879         SendToProgram("easy\n", cps);
8880     }
8881     if (cps->usePing) {
8882       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8883       SendToProgram(buf, cps);
8884     }
8885     cps->initDone = TRUE;
8886 }
8887
8888
8889 void
8890 StartChessProgram(cps)
8891      ChessProgramState *cps;
8892 {
8893     char buf[MSG_SIZ];
8894     int err;
8895
8896     if (appData.noChessProgram) return;
8897     cps->initDone = FALSE;
8898
8899     if (strcmp(cps->host, "localhost") == 0) {
8900         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8901     } else if (*appData.remoteShell == NULLCHAR) {
8902         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8903     } else {
8904         if (*appData.remoteUser == NULLCHAR) {
8905           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8906                     cps->program);
8907         } else {
8908           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8909                     cps->host, appData.remoteUser, cps->program);
8910         }
8911         err = StartChildProcess(buf, "", &cps->pr);
8912     }
8913
8914     if (err != 0) {
8915       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8916         DisplayFatalError(buf, err, 1);
8917         cps->pr = NoProc;
8918         cps->isr = NULL;
8919         return;
8920     }
8921
8922     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8923     if (cps->protocolVersion > 1) {
8924       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
8925       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8926       cps->comboCnt = 0;  //                and values of combo boxes
8927       SendToProgram(buf, cps);
8928     } else {
8929       SendToProgram("xboard\n", cps);
8930     }
8931 }
8932
8933
8934 void
8935 TwoMachinesEventIfReady P((void))
8936 {
8937   if (first.lastPing != first.lastPong) {
8938     DisplayMessage("", _("Waiting for first chess program"));
8939     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8940     return;
8941   }
8942   if (second.lastPing != second.lastPong) {
8943     DisplayMessage("", _("Waiting for second chess program"));
8944     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8945     return;
8946   }
8947   ThawUI();
8948   TwoMachinesEvent();
8949 }
8950
8951 void
8952 NextMatchGame P((void))
8953 {
8954     int index; /* [HGM] autoinc: step load index during match */
8955     Reset(FALSE, TRUE);
8956     if (*appData.loadGameFile != NULLCHAR) {
8957         index = appData.loadGameIndex;
8958         if(index < 0) { // [HGM] autoinc
8959             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8960             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8961         }
8962         LoadGameFromFile(appData.loadGameFile,
8963                          index,
8964                          appData.loadGameFile, FALSE);
8965     } else if (*appData.loadPositionFile != NULLCHAR) {
8966         index = appData.loadPositionIndex;
8967         if(index < 0) { // [HGM] autoinc
8968             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8969             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8970         }
8971         LoadPositionFromFile(appData.loadPositionFile,
8972                              index,
8973                              appData.loadPositionFile);
8974     }
8975     TwoMachinesEventIfReady();
8976 }
8977
8978 void UserAdjudicationEvent( int result )
8979 {
8980     ChessMove gameResult = GameIsDrawn;
8981
8982     if( result > 0 ) {
8983         gameResult = WhiteWins;
8984     }
8985     else if( result < 0 ) {
8986         gameResult = BlackWins;
8987     }
8988
8989     if( gameMode == TwoMachinesPlay ) {
8990         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8991     }
8992 }
8993
8994
8995 // [HGM] save: calculate checksum of game to make games easily identifiable
8996 int StringCheckSum(char *s)
8997 {
8998         int i = 0;
8999         if(s==NULL) return 0;
9000         while(*s) i = i*259 + *s++;
9001         return i;
9002 }
9003
9004 int GameCheckSum()
9005 {
9006         int i, sum=0;
9007         for(i=backwardMostMove; i<forwardMostMove; i++) {
9008                 sum += pvInfoList[i].depth;
9009                 sum += StringCheckSum(parseList[i]);
9010                 sum += StringCheckSum(commentList[i]);
9011                 sum *= 261;
9012         }
9013         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9014         return sum + StringCheckSum(commentList[i]);
9015 } // end of save patch
9016
9017 void
9018 GameEnds(result, resultDetails, whosays)
9019      ChessMove result;
9020      char *resultDetails;
9021      int whosays;
9022 {
9023     GameMode nextGameMode;
9024     int isIcsGame;
9025     char buf[MSG_SIZ], popupRequested = 0;
9026
9027     if(endingGame) return; /* [HGM] crash: forbid recursion */
9028     endingGame = 1;
9029     if(twoBoards) { // [HGM] dual: switch back to one board
9030         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9031         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9032     }
9033     if (appData.debugMode) {
9034       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9035               result, resultDetails ? resultDetails : "(null)", whosays);
9036     }
9037
9038     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9039
9040     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9041         /* If we are playing on ICS, the server decides when the
9042            game is over, but the engine can offer to draw, claim
9043            a draw, or resign.
9044          */
9045 #if ZIPPY
9046         if (appData.zippyPlay && first.initDone) {
9047             if (result == GameIsDrawn) {
9048                 /* In case draw still needs to be claimed */
9049                 SendToICS(ics_prefix);
9050                 SendToICS("draw\n");
9051             } else if (StrCaseStr(resultDetails, "resign")) {
9052                 SendToICS(ics_prefix);
9053                 SendToICS("resign\n");
9054             }
9055         }
9056 #endif
9057         endingGame = 0; /* [HGM] crash */
9058         return;
9059     }
9060
9061     /* If we're loading the game from a file, stop */
9062     if (whosays == GE_FILE) {
9063       (void) StopLoadGameTimer();
9064       gameFileFP = NULL;
9065     }
9066
9067     /* Cancel draw offers */
9068     first.offeredDraw = second.offeredDraw = 0;
9069
9070     /* If this is an ICS game, only ICS can really say it's done;
9071        if not, anyone can. */
9072     isIcsGame = (gameMode == IcsPlayingWhite ||
9073                  gameMode == IcsPlayingBlack ||
9074                  gameMode == IcsObserving    ||
9075                  gameMode == IcsExamining);
9076
9077     if (!isIcsGame || whosays == GE_ICS) {
9078         /* OK -- not an ICS game, or ICS said it was done */
9079         StopClocks();
9080         if (!isIcsGame && !appData.noChessProgram)
9081           SetUserThinkingEnables();
9082
9083         /* [HGM] if a machine claims the game end we verify this claim */
9084         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9085             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9086                 char claimer;
9087                 ChessMove trueResult = (ChessMove) -1;
9088
9089                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9090                                             first.twoMachinesColor[0] :
9091                                             second.twoMachinesColor[0] ;
9092
9093                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9094                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9095                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9096                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9097                 } else
9098                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9099                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9100                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9101                 } else
9102                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9103                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9104                 }
9105
9106                 // now verify win claims, but not in drop games, as we don't understand those yet
9107                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9108                                                  || gameInfo.variant == VariantGreat) &&
9109                     (result == WhiteWins && claimer == 'w' ||
9110                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9111                       if (appData.debugMode) {
9112                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9113                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9114                       }
9115                       if(result != trueResult) {
9116                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9117                               result = claimer == 'w' ? BlackWins : WhiteWins;
9118                               resultDetails = buf;
9119                       }
9120                 } else
9121                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9122                     && (forwardMostMove <= backwardMostMove ||
9123                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9124                         (claimer=='b')==(forwardMostMove&1))
9125                                                                                   ) {
9126                       /* [HGM] verify: draws that were not flagged are false claims */
9127                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9128                       result = claimer == 'w' ? BlackWins : WhiteWins;
9129                       resultDetails = buf;
9130                 }
9131                 /* (Claiming a loss is accepted no questions asked!) */
9132             }
9133             /* [HGM] bare: don't allow bare King to win */
9134             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9135                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9136                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9137                && result != GameIsDrawn)
9138             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9139                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9140                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9141                         if(p >= 0 && p <= (int)WhiteKing) k++;
9142                 }
9143                 if (appData.debugMode) {
9144                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9145                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9146                 }
9147                 if(k <= 1) {
9148                         result = GameIsDrawn;
9149                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9150                         resultDetails = buf;
9151                 }
9152             }
9153         }
9154
9155
9156         if(serverMoves != NULL && !loadFlag) { char c = '=';
9157             if(result==WhiteWins) c = '+';
9158             if(result==BlackWins) c = '-';
9159             if(resultDetails != NULL)
9160                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9161         }
9162         if (resultDetails != NULL) {
9163             gameInfo.result = result;
9164             gameInfo.resultDetails = StrSave(resultDetails);
9165
9166             /* display last move only if game was not loaded from file */
9167             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9168                 DisplayMove(currentMove - 1);
9169
9170             if (forwardMostMove != 0) {
9171                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9172                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9173                                                                 ) {
9174                     if (*appData.saveGameFile != NULLCHAR) {
9175                         SaveGameToFile(appData.saveGameFile, TRUE);
9176                     } else if (appData.autoSaveGames) {
9177                         AutoSaveGame();
9178                     }
9179                     if (*appData.savePositionFile != NULLCHAR) {
9180                         SavePositionToFile(appData.savePositionFile);
9181                     }
9182                 }
9183             }
9184
9185             /* Tell program how game ended in case it is learning */
9186             /* [HGM] Moved this to after saving the PGN, just in case */
9187             /* engine died and we got here through time loss. In that */
9188             /* case we will get a fatal error writing the pipe, which */
9189             /* would otherwise lose us the PGN.                       */
9190             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9191             /* output during GameEnds should never be fatal anymore   */
9192             if (gameMode == MachinePlaysWhite ||
9193                 gameMode == MachinePlaysBlack ||
9194                 gameMode == TwoMachinesPlay ||
9195                 gameMode == IcsPlayingWhite ||
9196                 gameMode == IcsPlayingBlack ||
9197                 gameMode == BeginningOfGame) {
9198                 char buf[MSG_SIZ];
9199                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9200                         resultDetails);
9201                 if (first.pr != NoProc) {
9202                     SendToProgram(buf, &first);
9203                 }
9204                 if (second.pr != NoProc &&
9205                     gameMode == TwoMachinesPlay) {
9206                     SendToProgram(buf, &second);
9207                 }
9208             }
9209         }
9210
9211         if (appData.icsActive) {
9212             if (appData.quietPlay &&
9213                 (gameMode == IcsPlayingWhite ||
9214                  gameMode == IcsPlayingBlack)) {
9215                 SendToICS(ics_prefix);
9216                 SendToICS("set shout 1\n");
9217             }
9218             nextGameMode = IcsIdle;
9219             ics_user_moved = FALSE;
9220             /* clean up premove.  It's ugly when the game has ended and the
9221              * premove highlights are still on the board.
9222              */
9223             if (gotPremove) {
9224               gotPremove = FALSE;
9225               ClearPremoveHighlights();
9226               DrawPosition(FALSE, boards[currentMove]);
9227             }
9228             if (whosays == GE_ICS) {
9229                 switch (result) {
9230                 case WhiteWins:
9231                     if (gameMode == IcsPlayingWhite)
9232                         PlayIcsWinSound();
9233                     else if(gameMode == IcsPlayingBlack)
9234                         PlayIcsLossSound();
9235                     break;
9236                 case BlackWins:
9237                     if (gameMode == IcsPlayingBlack)
9238                         PlayIcsWinSound();
9239                     else if(gameMode == IcsPlayingWhite)
9240                         PlayIcsLossSound();
9241                     break;
9242                 case GameIsDrawn:
9243                     PlayIcsDrawSound();
9244                     break;
9245                 default:
9246                     PlayIcsUnfinishedSound();
9247                 }
9248             }
9249         } else if (gameMode == EditGame ||
9250                    gameMode == PlayFromGameFile ||
9251                    gameMode == AnalyzeMode ||
9252                    gameMode == AnalyzeFile) {
9253             nextGameMode = gameMode;
9254         } else {
9255             nextGameMode = EndOfGame;
9256         }
9257         pausing = FALSE;
9258         ModeHighlight();
9259     } else {
9260         nextGameMode = gameMode;
9261     }
9262
9263     if (appData.noChessProgram) {
9264         gameMode = nextGameMode;
9265         ModeHighlight();
9266         endingGame = 0; /* [HGM] crash */
9267         return;
9268     }
9269
9270     if (first.reuse) {
9271         /* Put first chess program into idle state */
9272         if (first.pr != NoProc &&
9273             (gameMode == MachinePlaysWhite ||
9274              gameMode == MachinePlaysBlack ||
9275              gameMode == TwoMachinesPlay ||
9276              gameMode == IcsPlayingWhite ||
9277              gameMode == IcsPlayingBlack ||
9278              gameMode == BeginningOfGame)) {
9279             SendToProgram("force\n", &first);
9280             if (first.usePing) {
9281               char buf[MSG_SIZ];
9282               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9283               SendToProgram(buf, &first);
9284             }
9285         }
9286     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9287         /* Kill off first chess program */
9288         if (first.isr != NULL)
9289           RemoveInputSource(first.isr);
9290         first.isr = NULL;
9291
9292         if (first.pr != NoProc) {
9293             ExitAnalyzeMode();
9294             DoSleep( appData.delayBeforeQuit );
9295             SendToProgram("quit\n", &first);
9296             DoSleep( appData.delayAfterQuit );
9297             DestroyChildProcess(first.pr, first.useSigterm);
9298         }
9299         first.pr = NoProc;
9300     }
9301     if (second.reuse) {
9302         /* Put second chess program into idle state */
9303         if (second.pr != NoProc &&
9304             gameMode == TwoMachinesPlay) {
9305             SendToProgram("force\n", &second);
9306             if (second.usePing) {
9307               char buf[MSG_SIZ];
9308               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9309               SendToProgram(buf, &second);
9310             }
9311         }
9312     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9313         /* Kill off second chess program */
9314         if (second.isr != NULL)
9315           RemoveInputSource(second.isr);
9316         second.isr = NULL;
9317
9318         if (second.pr != NoProc) {
9319             DoSleep( appData.delayBeforeQuit );
9320             SendToProgram("quit\n", &second);
9321             DoSleep( appData.delayAfterQuit );
9322             DestroyChildProcess(second.pr, second.useSigterm);
9323         }
9324         second.pr = NoProc;
9325     }
9326
9327     if (matchMode && gameMode == TwoMachinesPlay) {
9328         switch (result) {
9329         case WhiteWins:
9330           if (first.twoMachinesColor[0] == 'w') {
9331             first.matchWins++;
9332           } else {
9333             second.matchWins++;
9334           }
9335           break;
9336         case BlackWins:
9337           if (first.twoMachinesColor[0] == 'b') {
9338             first.matchWins++;
9339           } else {
9340             second.matchWins++;
9341           }
9342           break;
9343         default:
9344           break;
9345         }
9346         if (matchGame < appData.matchGames) {
9347             char *tmp;
9348             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9349                 tmp = first.twoMachinesColor;
9350                 first.twoMachinesColor = second.twoMachinesColor;
9351                 second.twoMachinesColor = tmp;
9352             }
9353             gameMode = nextGameMode;
9354             matchGame++;
9355             if(appData.matchPause>10000 || appData.matchPause<10)
9356                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9357             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9358             endingGame = 0; /* [HGM] crash */
9359             return;
9360         } else {
9361             gameMode = nextGameMode;
9362             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9363                      first.tidy, second.tidy,
9364                      first.matchWins, second.matchWins,
9365                      appData.matchGames - (first.matchWins + second.matchWins));
9366             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9367         }
9368     }
9369     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9370         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9371       ExitAnalyzeMode();
9372     gameMode = nextGameMode;
9373     ModeHighlight();
9374     endingGame = 0;  /* [HGM] crash */
9375     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9376       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9377         matchMode = FALSE; appData.matchGames = matchGame = 0;
9378         DisplayNote(buf);
9379       }
9380     }
9381 }
9382
9383 /* Assumes program was just initialized (initString sent).
9384    Leaves program in force mode. */
9385 void
9386 FeedMovesToProgram(cps, upto)
9387      ChessProgramState *cps;
9388      int upto;
9389 {
9390     int i;
9391
9392     if (appData.debugMode)
9393       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9394               startedFromSetupPosition ? "position and " : "",
9395               backwardMostMove, upto, cps->which);
9396     if(currentlyInitializedVariant != gameInfo.variant) {
9397       char buf[MSG_SIZ];
9398         // [HGM] variantswitch: make engine aware of new variant
9399         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9400                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9401         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9402         SendToProgram(buf, cps);
9403         currentlyInitializedVariant = gameInfo.variant;
9404     }
9405     SendToProgram("force\n", cps);
9406     if (startedFromSetupPosition) {
9407         SendBoard(cps, backwardMostMove);
9408     if (appData.debugMode) {
9409         fprintf(debugFP, "feedMoves\n");
9410     }
9411     }
9412     for (i = backwardMostMove; i < upto; i++) {
9413         SendMoveToProgram(i, cps);
9414     }
9415 }
9416
9417
9418 void
9419 ResurrectChessProgram()
9420 {
9421      /* The chess program may have exited.
9422         If so, restart it and feed it all the moves made so far. */
9423
9424     if (appData.noChessProgram || first.pr != NoProc) return;
9425
9426     StartChessProgram(&first);
9427     InitChessProgram(&first, FALSE);
9428     FeedMovesToProgram(&first, currentMove);
9429
9430     if (!first.sendTime) {
9431         /* can't tell gnuchess what its clock should read,
9432            so we bow to its notion. */
9433         ResetClocks();
9434         timeRemaining[0][currentMove] = whiteTimeRemaining;
9435         timeRemaining[1][currentMove] = blackTimeRemaining;
9436     }
9437
9438     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9439                 appData.icsEngineAnalyze) && first.analysisSupport) {
9440       SendToProgram("analyze\n", &first);
9441       first.analyzing = TRUE;
9442     }
9443 }
9444
9445 /*
9446  * Button procedures
9447  */
9448 void
9449 Reset(redraw, init)
9450      int redraw, init;
9451 {
9452     int i;
9453
9454     if (appData.debugMode) {
9455         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9456                 redraw, init, gameMode);
9457     }
9458     CleanupTail(); // [HGM] vari: delete any stored variations
9459     pausing = pauseExamInvalid = FALSE;
9460     startedFromSetupPosition = blackPlaysFirst = FALSE;
9461     firstMove = TRUE;
9462     whiteFlag = blackFlag = FALSE;
9463     userOfferedDraw = FALSE;
9464     hintRequested = bookRequested = FALSE;
9465     first.maybeThinking = FALSE;
9466     second.maybeThinking = FALSE;
9467     first.bookSuspend = FALSE; // [HGM] book
9468     second.bookSuspend = FALSE;
9469     thinkOutput[0] = NULLCHAR;
9470     lastHint[0] = NULLCHAR;
9471     ClearGameInfo(&gameInfo);
9472     gameInfo.variant = StringToVariant(appData.variant);
9473     ics_user_moved = ics_clock_paused = FALSE;
9474     ics_getting_history = H_FALSE;
9475     ics_gamenum = -1;
9476     white_holding[0] = black_holding[0] = NULLCHAR;
9477     ClearProgramStats();
9478     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9479
9480     ResetFrontEnd();
9481     ClearHighlights();
9482     flipView = appData.flipView;
9483     ClearPremoveHighlights();
9484     gotPremove = FALSE;
9485     alarmSounded = FALSE;
9486
9487     GameEnds(EndOfFile, NULL, GE_PLAYER);
9488     if(appData.serverMovesName != NULL) {
9489         /* [HGM] prepare to make moves file for broadcasting */
9490         clock_t t = clock();
9491         if(serverMoves != NULL) fclose(serverMoves);
9492         serverMoves = fopen(appData.serverMovesName, "r");
9493         if(serverMoves != NULL) {
9494             fclose(serverMoves);
9495             /* delay 15 sec before overwriting, so all clients can see end */
9496             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9497         }
9498         serverMoves = fopen(appData.serverMovesName, "w");
9499     }
9500
9501     ExitAnalyzeMode();
9502     gameMode = BeginningOfGame;
9503     ModeHighlight();
9504     if(appData.icsActive) gameInfo.variant = VariantNormal;
9505     currentMove = forwardMostMove = backwardMostMove = 0;
9506     InitPosition(redraw);
9507     for (i = 0; i < MAX_MOVES; i++) {
9508         if (commentList[i] != NULL) {
9509             free(commentList[i]);
9510             commentList[i] = NULL;
9511         }
9512     }
9513     ResetClocks();
9514     timeRemaining[0][0] = whiteTimeRemaining;
9515     timeRemaining[1][0] = blackTimeRemaining;
9516     if (first.pr == NULL) {
9517         StartChessProgram(&first);
9518     }
9519     if (init) {
9520             InitChessProgram(&first, startedFromSetupPosition);
9521     }
9522     DisplayTitle("");
9523     DisplayMessage("", "");
9524     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9525     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9526 }
9527
9528 void
9529 AutoPlayGameLoop()
9530 {
9531     for (;;) {
9532         if (!AutoPlayOneMove())
9533           return;
9534         if (matchMode || appData.timeDelay == 0)
9535           continue;
9536         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9537           return;
9538         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9539         break;
9540     }
9541 }
9542
9543
9544 int
9545 AutoPlayOneMove()
9546 {
9547     int fromX, fromY, toX, toY;
9548
9549     if (appData.debugMode) {
9550       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9551     }
9552
9553     if (gameMode != PlayFromGameFile)
9554       return FALSE;
9555
9556     if (currentMove >= forwardMostMove) {
9557       gameMode = EditGame;
9558       ModeHighlight();
9559
9560       /* [AS] Clear current move marker at the end of a game */
9561       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9562
9563       return FALSE;
9564     }
9565
9566     toX = moveList[currentMove][2] - AAA;
9567     toY = moveList[currentMove][3] - ONE;
9568
9569     if (moveList[currentMove][1] == '@') {
9570         if (appData.highlightLastMove) {
9571             SetHighlights(-1, -1, toX, toY);
9572         }
9573     } else {
9574         fromX = moveList[currentMove][0] - AAA;
9575         fromY = moveList[currentMove][1] - ONE;
9576
9577         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9578
9579         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9580
9581         if (appData.highlightLastMove) {
9582             SetHighlights(fromX, fromY, toX, toY);
9583         }
9584     }
9585     DisplayMove(currentMove);
9586     SendMoveToProgram(currentMove++, &first);
9587     DisplayBothClocks();
9588     DrawPosition(FALSE, boards[currentMove]);
9589     // [HGM] PV info: always display, routine tests if empty
9590     DisplayComment(currentMove - 1, commentList[currentMove]);
9591     return TRUE;
9592 }
9593
9594
9595 int
9596 LoadGameOneMove(readAhead)
9597      ChessMove readAhead;
9598 {
9599     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9600     char promoChar = NULLCHAR;
9601     ChessMove moveType;
9602     char move[MSG_SIZ];
9603     char *p, *q;
9604
9605     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9606         gameMode != AnalyzeMode && gameMode != Training) {
9607         gameFileFP = NULL;
9608         return FALSE;
9609     }
9610
9611     yyboardindex = forwardMostMove;
9612     if (readAhead != EndOfFile) {
9613       moveType = readAhead;
9614     } else {
9615       if (gameFileFP == NULL)
9616           return FALSE;
9617       moveType = (ChessMove) yylex();
9618     }
9619
9620     done = FALSE;
9621     switch (moveType) {
9622       case Comment:
9623         if (appData.debugMode)
9624           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9625         p = yy_text;
9626
9627         /* append the comment but don't display it */
9628         AppendComment(currentMove, p, FALSE);
9629         return TRUE;
9630
9631       case WhiteCapturesEnPassant:
9632       case BlackCapturesEnPassant:
9633       case WhitePromotion:
9634       case BlackPromotion:
9635       case WhiteNonPromotion:
9636       case BlackNonPromotion:
9637       case NormalMove:
9638       case WhiteKingSideCastle:
9639       case WhiteQueenSideCastle:
9640       case BlackKingSideCastle:
9641       case BlackQueenSideCastle:
9642       case WhiteKingSideCastleWild:
9643       case WhiteQueenSideCastleWild:
9644       case BlackKingSideCastleWild:
9645       case BlackQueenSideCastleWild:
9646       /* PUSH Fabien */
9647       case WhiteHSideCastleFR:
9648       case WhiteASideCastleFR:
9649       case BlackHSideCastleFR:
9650       case BlackASideCastleFR:
9651       /* POP Fabien */
9652         if (appData.debugMode)
9653           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9654         fromX = currentMoveString[0] - AAA;
9655         fromY = currentMoveString[1] - ONE;
9656         toX = currentMoveString[2] - AAA;
9657         toY = currentMoveString[3] - ONE;
9658         promoChar = currentMoveString[4];
9659         break;
9660
9661       case WhiteDrop:
9662       case BlackDrop:
9663         if (appData.debugMode)
9664           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9665         fromX = moveType == WhiteDrop ?
9666           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9667         (int) CharToPiece(ToLower(currentMoveString[0]));
9668         fromY = DROP_RANK;
9669         toX = currentMoveString[2] - AAA;
9670         toY = currentMoveString[3] - ONE;
9671         break;
9672
9673       case WhiteWins:
9674       case BlackWins:
9675       case GameIsDrawn:
9676       case GameUnfinished:
9677         if (appData.debugMode)
9678           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9679         p = strchr(yy_text, '{');
9680         if (p == NULL) p = strchr(yy_text, '(');
9681         if (p == NULL) {
9682             p = yy_text;
9683             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9684         } else {
9685             q = strchr(p, *p == '{' ? '}' : ')');
9686             if (q != NULL) *q = NULLCHAR;
9687             p++;
9688         }
9689         GameEnds(moveType, p, GE_FILE);
9690         done = TRUE;
9691         if (cmailMsgLoaded) {
9692             ClearHighlights();
9693             flipView = WhiteOnMove(currentMove);
9694             if (moveType == GameUnfinished) flipView = !flipView;
9695             if (appData.debugMode)
9696               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9697         }
9698         break;
9699
9700       case EndOfFile:
9701         if (appData.debugMode)
9702           fprintf(debugFP, "Parser hit end of file\n");
9703         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9704           case MT_NONE:
9705           case MT_CHECK:
9706             break;
9707           case MT_CHECKMATE:
9708           case MT_STAINMATE:
9709             if (WhiteOnMove(currentMove)) {
9710                 GameEnds(BlackWins, "Black mates", GE_FILE);
9711             } else {
9712                 GameEnds(WhiteWins, "White mates", GE_FILE);
9713             }
9714             break;
9715           case MT_STALEMATE:
9716             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9717             break;
9718         }
9719         done = TRUE;
9720         break;
9721
9722       case MoveNumberOne:
9723         if (lastLoadGameStart == GNUChessGame) {
9724             /* GNUChessGames have numbers, but they aren't move numbers */
9725             if (appData.debugMode)
9726               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9727                       yy_text, (int) moveType);
9728             return LoadGameOneMove(EndOfFile); /* tail recursion */
9729         }
9730         /* else fall thru */
9731
9732       case XBoardGame:
9733       case GNUChessGame:
9734       case PGNTag:
9735         /* Reached start of next game in file */
9736         if (appData.debugMode)
9737           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9738         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9739           case MT_NONE:
9740           case MT_CHECK:
9741             break;
9742           case MT_CHECKMATE:
9743           case MT_STAINMATE:
9744             if (WhiteOnMove(currentMove)) {
9745                 GameEnds(BlackWins, "Black mates", GE_FILE);
9746             } else {
9747                 GameEnds(WhiteWins, "White mates", GE_FILE);
9748             }
9749             break;
9750           case MT_STALEMATE:
9751             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9752             break;
9753         }
9754         done = TRUE;
9755         break;
9756
9757       case PositionDiagram:     /* should not happen; ignore */
9758       case ElapsedTime:         /* ignore */
9759       case NAG:                 /* ignore */
9760         if (appData.debugMode)
9761           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9762                   yy_text, (int) moveType);
9763         return LoadGameOneMove(EndOfFile); /* tail recursion */
9764
9765       case IllegalMove:
9766         if (appData.testLegality) {
9767             if (appData.debugMode)
9768               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9769             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9770                     (forwardMostMove / 2) + 1,
9771                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9772             DisplayError(move, 0);
9773             done = TRUE;
9774         } else {
9775             if (appData.debugMode)
9776               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9777                       yy_text, currentMoveString);
9778             fromX = currentMoveString[0] - AAA;
9779             fromY = currentMoveString[1] - ONE;
9780             toX = currentMoveString[2] - AAA;
9781             toY = currentMoveString[3] - ONE;
9782             promoChar = currentMoveString[4];
9783         }
9784         break;
9785
9786       case AmbiguousMove:
9787         if (appData.debugMode)
9788           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9789         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9790                 (forwardMostMove / 2) + 1,
9791                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9792         DisplayError(move, 0);
9793         done = TRUE;
9794         break;
9795
9796       default:
9797       case ImpossibleMove:
9798         if (appData.debugMode)
9799           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9800         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9801                 (forwardMostMove / 2) + 1,
9802                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9803         DisplayError(move, 0);
9804         done = TRUE;
9805         break;
9806     }
9807
9808     if (done) {
9809         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9810             DrawPosition(FALSE, boards[currentMove]);
9811             DisplayBothClocks();
9812             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9813               DisplayComment(currentMove - 1, commentList[currentMove]);
9814         }
9815         (void) StopLoadGameTimer();
9816         gameFileFP = NULL;
9817         cmailOldMove = forwardMostMove;
9818         return FALSE;
9819     } else {
9820         /* currentMoveString is set as a side-effect of yylex */
9821         strcat(currentMoveString, "\n");
9822         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9823
9824         thinkOutput[0] = NULLCHAR;
9825         MakeMove(fromX, fromY, toX, toY, promoChar);
9826         currentMove = forwardMostMove;
9827         return TRUE;
9828     }
9829 }
9830
9831 /* Load the nth game from the given file */
9832 int
9833 LoadGameFromFile(filename, n, title, useList)
9834      char *filename;
9835      int n;
9836      char *title;
9837      /*Boolean*/ int useList;
9838 {
9839     FILE *f;
9840     char buf[MSG_SIZ];
9841
9842     if (strcmp(filename, "-") == 0) {
9843         f = stdin;
9844         title = "stdin";
9845     } else {
9846         f = fopen(filename, "rb");
9847         if (f == NULL) {
9848           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9849             DisplayError(buf, errno);
9850             return FALSE;
9851         }
9852     }
9853     if (fseek(f, 0, 0) == -1) {
9854         /* f is not seekable; probably a pipe */
9855         useList = FALSE;
9856     }
9857     if (useList && n == 0) {
9858         int error = GameListBuild(f);
9859         if (error) {
9860             DisplayError(_("Cannot build game list"), error);
9861         } else if (!ListEmpty(&gameList) &&
9862                    ((ListGame *) gameList.tailPred)->number > 1) {
9863             GameListPopUp(f, title);
9864             return TRUE;
9865         }
9866         GameListDestroy();
9867         n = 1;
9868     }
9869     if (n == 0) n = 1;
9870     return LoadGame(f, n, title, FALSE);
9871 }
9872
9873
9874 void
9875 MakeRegisteredMove()
9876 {
9877     int fromX, fromY, toX, toY;
9878     char promoChar;
9879     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9880         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9881           case CMAIL_MOVE:
9882           case CMAIL_DRAW:
9883             if (appData.debugMode)
9884               fprintf(debugFP, "Restoring %s for game %d\n",
9885                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9886
9887             thinkOutput[0] = NULLCHAR;
9888             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9889             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9890             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9891             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9892             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9893             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9894             MakeMove(fromX, fromY, toX, toY, promoChar);
9895             ShowMove(fromX, fromY, toX, toY);
9896
9897             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9898               case MT_NONE:
9899               case MT_CHECK:
9900                 break;
9901
9902               case MT_CHECKMATE:
9903               case MT_STAINMATE:
9904                 if (WhiteOnMove(currentMove)) {
9905                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9906                 } else {
9907                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9908                 }
9909                 break;
9910
9911               case MT_STALEMATE:
9912                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9913                 break;
9914             }
9915
9916             break;
9917
9918           case CMAIL_RESIGN:
9919             if (WhiteOnMove(currentMove)) {
9920                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9921             } else {
9922                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9923             }
9924             break;
9925
9926           case CMAIL_ACCEPT:
9927             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9928             break;
9929
9930           default:
9931             break;
9932         }
9933     }
9934
9935     return;
9936 }
9937
9938 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9939 int
9940 CmailLoadGame(f, gameNumber, title, useList)
9941      FILE *f;
9942      int gameNumber;
9943      char *title;
9944      int useList;
9945 {
9946     int retVal;
9947
9948     if (gameNumber > nCmailGames) {
9949         DisplayError(_("No more games in this message"), 0);
9950         return FALSE;
9951     }
9952     if (f == lastLoadGameFP) {
9953         int offset = gameNumber - lastLoadGameNumber;
9954         if (offset == 0) {
9955             cmailMsg[0] = NULLCHAR;
9956             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9957                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9958                 nCmailMovesRegistered--;
9959             }
9960             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9961             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9962                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9963             }
9964         } else {
9965             if (! RegisterMove()) return FALSE;
9966         }
9967     }
9968
9969     retVal = LoadGame(f, gameNumber, title, useList);
9970
9971     /* Make move registered during previous look at this game, if any */
9972     MakeRegisteredMove();
9973
9974     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9975         commentList[currentMove]
9976           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9977         DisplayComment(currentMove - 1, commentList[currentMove]);
9978     }
9979
9980     return retVal;
9981 }
9982
9983 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9984 int
9985 ReloadGame(offset)
9986      int offset;
9987 {
9988     int gameNumber = lastLoadGameNumber + offset;
9989     if (lastLoadGameFP == NULL) {
9990         DisplayError(_("No game has been loaded yet"), 0);
9991         return FALSE;
9992     }
9993     if (gameNumber <= 0) {
9994         DisplayError(_("Can't back up any further"), 0);
9995         return FALSE;
9996     }
9997     if (cmailMsgLoaded) {
9998         return CmailLoadGame(lastLoadGameFP, gameNumber,
9999                              lastLoadGameTitle, lastLoadGameUseList);
10000     } else {
10001         return LoadGame(lastLoadGameFP, gameNumber,
10002                         lastLoadGameTitle, lastLoadGameUseList);
10003     }
10004 }
10005
10006
10007
10008 /* Load the nth game from open file f */
10009 int
10010 LoadGame(f, gameNumber, title, useList)
10011      FILE *f;
10012      int gameNumber;
10013      char *title;
10014      int useList;
10015 {
10016     ChessMove cm;
10017     char buf[MSG_SIZ];
10018     int gn = gameNumber;
10019     ListGame *lg = NULL;
10020     int numPGNTags = 0;
10021     int err;
10022     GameMode oldGameMode;
10023     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10024
10025     if (appData.debugMode)
10026         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10027
10028     if (gameMode == Training )
10029         SetTrainingModeOff();
10030
10031     oldGameMode = gameMode;
10032     if (gameMode != BeginningOfGame) {
10033       Reset(FALSE, TRUE);
10034     }
10035
10036     gameFileFP = f;
10037     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10038         fclose(lastLoadGameFP);
10039     }
10040
10041     if (useList) {
10042         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10043
10044         if (lg) {
10045             fseek(f, lg->offset, 0);
10046             GameListHighlight(gameNumber);
10047             gn = 1;
10048         }
10049         else {
10050             DisplayError(_("Game number out of range"), 0);
10051             return FALSE;
10052         }
10053     } else {
10054         GameListDestroy();
10055         if (fseek(f, 0, 0) == -1) {
10056             if (f == lastLoadGameFP ?
10057                 gameNumber == lastLoadGameNumber + 1 :
10058                 gameNumber == 1) {
10059                 gn = 1;
10060             } else {
10061                 DisplayError(_("Can't seek on game file"), 0);
10062                 return FALSE;
10063             }
10064         }
10065     }
10066     lastLoadGameFP = f;
10067     lastLoadGameNumber = gameNumber;
10068     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10069     lastLoadGameUseList = useList;
10070
10071     yynewfile(f);
10072
10073     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10074       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10075                 lg->gameInfo.black);
10076             DisplayTitle(buf);
10077     } else if (*title != NULLCHAR) {
10078         if (gameNumber > 1) {
10079           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10080             DisplayTitle(buf);
10081         } else {
10082             DisplayTitle(title);
10083         }
10084     }
10085
10086     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10087         gameMode = PlayFromGameFile;
10088         ModeHighlight();
10089     }
10090
10091     currentMove = forwardMostMove = backwardMostMove = 0;
10092     CopyBoard(boards[0], initialPosition);
10093     StopClocks();
10094
10095     /*
10096      * Skip the first gn-1 games in the file.
10097      * Also skip over anything that precedes an identifiable
10098      * start of game marker, to avoid being confused by
10099      * garbage at the start of the file.  Currently
10100      * recognized start of game markers are the move number "1",
10101      * the pattern "gnuchess .* game", the pattern
10102      * "^[#;%] [^ ]* game file", and a PGN tag block.
10103      * A game that starts with one of the latter two patterns
10104      * will also have a move number 1, possibly
10105      * following a position diagram.
10106      * 5-4-02: Let's try being more lenient and allowing a game to
10107      * start with an unnumbered move.  Does that break anything?
10108      */
10109     cm = lastLoadGameStart = EndOfFile;
10110     while (gn > 0) {
10111         yyboardindex = forwardMostMove;
10112         cm = (ChessMove) yylex();
10113         switch (cm) {
10114           case EndOfFile:
10115             if (cmailMsgLoaded) {
10116                 nCmailGames = CMAIL_MAX_GAMES - gn;
10117             } else {
10118                 Reset(TRUE, TRUE);
10119                 DisplayError(_("Game not found in file"), 0);
10120             }
10121             return FALSE;
10122
10123           case GNUChessGame:
10124           case XBoardGame:
10125             gn--;
10126             lastLoadGameStart = cm;
10127             break;
10128
10129           case MoveNumberOne:
10130             switch (lastLoadGameStart) {
10131               case GNUChessGame:
10132               case XBoardGame:
10133               case PGNTag:
10134                 break;
10135               case MoveNumberOne:
10136               case EndOfFile:
10137                 gn--;           /* count this game */
10138                 lastLoadGameStart = cm;
10139                 break;
10140               default:
10141                 /* impossible */
10142                 break;
10143             }
10144             break;
10145
10146           case PGNTag:
10147             switch (lastLoadGameStart) {
10148               case GNUChessGame:
10149               case PGNTag:
10150               case MoveNumberOne:
10151               case EndOfFile:
10152                 gn--;           /* count this game */
10153                 lastLoadGameStart = cm;
10154                 break;
10155               case XBoardGame:
10156                 lastLoadGameStart = cm; /* game counted already */
10157                 break;
10158               default:
10159                 /* impossible */
10160                 break;
10161             }
10162             if (gn > 0) {
10163                 do {
10164                     yyboardindex = forwardMostMove;
10165                     cm = (ChessMove) yylex();
10166                 } while (cm == PGNTag || cm == Comment);
10167             }
10168             break;
10169
10170           case WhiteWins:
10171           case BlackWins:
10172           case GameIsDrawn:
10173             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10174                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10175                     != CMAIL_OLD_RESULT) {
10176                     nCmailResults ++ ;
10177                     cmailResult[  CMAIL_MAX_GAMES
10178                                 - gn - 1] = CMAIL_OLD_RESULT;
10179                 }
10180             }
10181             break;
10182
10183           case NormalMove:
10184             /* Only a NormalMove can be at the start of a game
10185              * without a position diagram. */
10186             if (lastLoadGameStart == EndOfFile ) {
10187               gn--;
10188               lastLoadGameStart = MoveNumberOne;
10189             }
10190             break;
10191
10192           default:
10193             break;
10194         }
10195     }
10196
10197     if (appData.debugMode)
10198       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10199
10200     if (cm == XBoardGame) {
10201         /* Skip any header junk before position diagram and/or move 1 */
10202         for (;;) {
10203             yyboardindex = forwardMostMove;
10204             cm = (ChessMove) yylex();
10205
10206             if (cm == EndOfFile ||
10207                 cm == GNUChessGame || cm == XBoardGame) {
10208                 /* Empty game; pretend end-of-file and handle later */
10209                 cm = EndOfFile;
10210                 break;
10211             }
10212
10213             if (cm == MoveNumberOne || cm == PositionDiagram ||
10214                 cm == PGNTag || cm == Comment)
10215               break;
10216         }
10217     } else if (cm == GNUChessGame) {
10218         if (gameInfo.event != NULL) {
10219             free(gameInfo.event);
10220         }
10221         gameInfo.event = StrSave(yy_text);
10222     }
10223
10224     startedFromSetupPosition = FALSE;
10225     while (cm == PGNTag) {
10226         if (appData.debugMode)
10227           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10228         err = ParsePGNTag(yy_text, &gameInfo);
10229         if (!err) numPGNTags++;
10230
10231         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10232         if(gameInfo.variant != oldVariant) {
10233             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10234             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10235             InitPosition(TRUE);
10236             oldVariant = gameInfo.variant;
10237             if (appData.debugMode)
10238               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10239         }
10240
10241
10242         if (gameInfo.fen != NULL) {
10243           Board initial_position;
10244           startedFromSetupPosition = TRUE;
10245           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10246             Reset(TRUE, TRUE);
10247             DisplayError(_("Bad FEN position in file"), 0);
10248             return FALSE;
10249           }
10250           CopyBoard(boards[0], initial_position);
10251           if (blackPlaysFirst) {
10252             currentMove = forwardMostMove = backwardMostMove = 1;
10253             CopyBoard(boards[1], initial_position);
10254             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10255             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10256             timeRemaining[0][1] = whiteTimeRemaining;
10257             timeRemaining[1][1] = blackTimeRemaining;
10258             if (commentList[0] != NULL) {
10259               commentList[1] = commentList[0];
10260               commentList[0] = NULL;
10261             }
10262           } else {
10263             currentMove = forwardMostMove = backwardMostMove = 0;
10264           }
10265           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10266           {   int i;
10267               initialRulePlies = FENrulePlies;
10268               for( i=0; i< nrCastlingRights; i++ )
10269                   initialRights[i] = initial_position[CASTLING][i];
10270           }
10271           yyboardindex = forwardMostMove;
10272           free(gameInfo.fen);
10273           gameInfo.fen = NULL;
10274         }
10275
10276         yyboardindex = forwardMostMove;
10277         cm = (ChessMove) yylex();
10278
10279         /* Handle comments interspersed among the tags */
10280         while (cm == Comment) {
10281             char *p;
10282             if (appData.debugMode)
10283               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10284             p = yy_text;
10285             AppendComment(currentMove, p, FALSE);
10286             yyboardindex = forwardMostMove;
10287             cm = (ChessMove) yylex();
10288         }
10289     }
10290
10291     /* don't rely on existence of Event tag since if game was
10292      * pasted from clipboard the Event tag may not exist
10293      */
10294     if (numPGNTags > 0){
10295         char *tags;
10296         if (gameInfo.variant == VariantNormal) {
10297           VariantClass v = StringToVariant(gameInfo.event);
10298           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10299           if(v < VariantShogi) gameInfo.variant = v;
10300         }
10301         if (!matchMode) {
10302           if( appData.autoDisplayTags ) {
10303             tags = PGNTags(&gameInfo);
10304             TagsPopUp(tags, CmailMsg());
10305             free(tags);
10306           }
10307         }
10308     } else {
10309         /* Make something up, but don't display it now */
10310         SetGameInfo();
10311         TagsPopDown();
10312     }
10313
10314     if (cm == PositionDiagram) {
10315         int i, j;
10316         char *p;
10317         Board initial_position;
10318
10319         if (appData.debugMode)
10320           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10321
10322         if (!startedFromSetupPosition) {
10323             p = yy_text;
10324             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10325               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10326                 switch (*p) {
10327                   case '[':
10328                   case '-':
10329                   case ' ':
10330                   case '\t':
10331                   case '\n':
10332                   case '\r':
10333                     break;
10334                   default:
10335                     initial_position[i][j++] = CharToPiece(*p);
10336                     break;
10337                 }
10338             while (*p == ' ' || *p == '\t' ||
10339                    *p == '\n' || *p == '\r') p++;
10340
10341             if (strncmp(p, "black", strlen("black"))==0)
10342               blackPlaysFirst = TRUE;
10343             else
10344               blackPlaysFirst = FALSE;
10345             startedFromSetupPosition = TRUE;
10346
10347             CopyBoard(boards[0], initial_position);
10348             if (blackPlaysFirst) {
10349                 currentMove = forwardMostMove = backwardMostMove = 1;
10350                 CopyBoard(boards[1], initial_position);
10351                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10352                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10353                 timeRemaining[0][1] = whiteTimeRemaining;
10354                 timeRemaining[1][1] = blackTimeRemaining;
10355                 if (commentList[0] != NULL) {
10356                     commentList[1] = commentList[0];
10357                     commentList[0] = NULL;
10358                 }
10359             } else {
10360                 currentMove = forwardMostMove = backwardMostMove = 0;
10361             }
10362         }
10363         yyboardindex = forwardMostMove;
10364         cm = (ChessMove) yylex();
10365     }
10366
10367     if (first.pr == NoProc) {
10368         StartChessProgram(&first);
10369     }
10370     InitChessProgram(&first, FALSE);
10371     SendToProgram("force\n", &first);
10372     if (startedFromSetupPosition) {
10373         SendBoard(&first, forwardMostMove);
10374     if (appData.debugMode) {
10375         fprintf(debugFP, "Load Game\n");
10376     }
10377         DisplayBothClocks();
10378     }
10379
10380     /* [HGM] server: flag to write setup moves in broadcast file as one */
10381     loadFlag = appData.suppressLoadMoves;
10382
10383     while (cm == Comment) {
10384         char *p;
10385         if (appData.debugMode)
10386           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10387         p = yy_text;
10388         AppendComment(currentMove, p, FALSE);
10389         yyboardindex = forwardMostMove;
10390         cm = (ChessMove) yylex();
10391     }
10392
10393     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10394         cm == WhiteWins || cm == BlackWins ||
10395         cm == GameIsDrawn || cm == GameUnfinished) {
10396         DisplayMessage("", _("No moves in game"));
10397         if (cmailMsgLoaded) {
10398             if (appData.debugMode)
10399               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10400             ClearHighlights();
10401             flipView = FALSE;
10402         }
10403         DrawPosition(FALSE, boards[currentMove]);
10404         DisplayBothClocks();
10405         gameMode = EditGame;
10406         ModeHighlight();
10407         gameFileFP = NULL;
10408         cmailOldMove = 0;
10409         return TRUE;
10410     }
10411
10412     // [HGM] PV info: routine tests if comment empty
10413     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10414         DisplayComment(currentMove - 1, commentList[currentMove]);
10415     }
10416     if (!matchMode && appData.timeDelay != 0)
10417       DrawPosition(FALSE, boards[currentMove]);
10418
10419     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10420       programStats.ok_to_send = 1;
10421     }
10422
10423     /* if the first token after the PGN tags is a move
10424      * and not move number 1, retrieve it from the parser
10425      */
10426     if (cm != MoveNumberOne)
10427         LoadGameOneMove(cm);
10428
10429     /* load the remaining moves from the file */
10430     while (LoadGameOneMove(EndOfFile)) {
10431       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10432       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10433     }
10434
10435     /* rewind to the start of the game */
10436     currentMove = backwardMostMove;
10437
10438     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10439
10440     if (oldGameMode == AnalyzeFile ||
10441         oldGameMode == AnalyzeMode) {
10442       AnalyzeFileEvent();
10443     }
10444
10445     if (matchMode || appData.timeDelay == 0) {
10446       ToEndEvent();
10447       gameMode = EditGame;
10448       ModeHighlight();
10449     } else if (appData.timeDelay > 0) {
10450       AutoPlayGameLoop();
10451     }
10452
10453     if (appData.debugMode)
10454         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10455
10456     loadFlag = 0; /* [HGM] true game starts */
10457     return TRUE;
10458 }
10459
10460 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10461 int
10462 ReloadPosition(offset)
10463      int offset;
10464 {
10465     int positionNumber = lastLoadPositionNumber + offset;
10466     if (lastLoadPositionFP == NULL) {
10467         DisplayError(_("No position has been loaded yet"), 0);
10468         return FALSE;
10469     }
10470     if (positionNumber <= 0) {
10471         DisplayError(_("Can't back up any further"), 0);
10472         return FALSE;
10473     }
10474     return LoadPosition(lastLoadPositionFP, positionNumber,
10475                         lastLoadPositionTitle);
10476 }
10477
10478 /* Load the nth position from the given file */
10479 int
10480 LoadPositionFromFile(filename, n, title)
10481      char *filename;
10482      int n;
10483      char *title;
10484 {
10485     FILE *f;
10486     char buf[MSG_SIZ];
10487
10488     if (strcmp(filename, "-") == 0) {
10489         return LoadPosition(stdin, n, "stdin");
10490     } else {
10491         f = fopen(filename, "rb");
10492         if (f == NULL) {
10493             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10494             DisplayError(buf, errno);
10495             return FALSE;
10496         } else {
10497             return LoadPosition(f, n, title);
10498         }
10499     }
10500 }
10501
10502 /* Load the nth position from the given open file, and close it */
10503 int
10504 LoadPosition(f, positionNumber, title)
10505      FILE *f;
10506      int positionNumber;
10507      char *title;
10508 {
10509     char *p, line[MSG_SIZ];
10510     Board initial_position;
10511     int i, j, fenMode, pn;
10512
10513     if (gameMode == Training )
10514         SetTrainingModeOff();
10515
10516     if (gameMode != BeginningOfGame) {
10517         Reset(FALSE, TRUE);
10518     }
10519     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10520         fclose(lastLoadPositionFP);
10521     }
10522     if (positionNumber == 0) positionNumber = 1;
10523     lastLoadPositionFP = f;
10524     lastLoadPositionNumber = positionNumber;
10525     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10526     if (first.pr == NoProc) {
10527       StartChessProgram(&first);
10528       InitChessProgram(&first, FALSE);
10529     }
10530     pn = positionNumber;
10531     if (positionNumber < 0) {
10532         /* Negative position number means to seek to that byte offset */
10533         if (fseek(f, -positionNumber, 0) == -1) {
10534             DisplayError(_("Can't seek on position file"), 0);
10535             return FALSE;
10536         };
10537         pn = 1;
10538     } else {
10539         if (fseek(f, 0, 0) == -1) {
10540             if (f == lastLoadPositionFP ?
10541                 positionNumber == lastLoadPositionNumber + 1 :
10542                 positionNumber == 1) {
10543                 pn = 1;
10544             } else {
10545                 DisplayError(_("Can't seek on position file"), 0);
10546                 return FALSE;
10547             }
10548         }
10549     }
10550     /* See if this file is FEN or old-style xboard */
10551     if (fgets(line, MSG_SIZ, f) == NULL) {
10552         DisplayError(_("Position not found in file"), 0);
10553         return FALSE;
10554     }
10555     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10556     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10557
10558     if (pn >= 2) {
10559         if (fenMode || line[0] == '#') pn--;
10560         while (pn > 0) {
10561             /* skip positions before number pn */
10562             if (fgets(line, MSG_SIZ, f) == NULL) {
10563                 Reset(TRUE, TRUE);
10564                 DisplayError(_("Position not found in file"), 0);
10565                 return FALSE;
10566             }
10567             if (fenMode || line[0] == '#') pn--;
10568         }
10569     }
10570
10571     if (fenMode) {
10572         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10573             DisplayError(_("Bad FEN position in file"), 0);
10574             return FALSE;
10575         }
10576     } else {
10577         (void) fgets(line, MSG_SIZ, f);
10578         (void) fgets(line, MSG_SIZ, f);
10579
10580         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10581             (void) fgets(line, MSG_SIZ, f);
10582             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10583                 if (*p == ' ')
10584                   continue;
10585                 initial_position[i][j++] = CharToPiece(*p);
10586             }
10587         }
10588
10589         blackPlaysFirst = FALSE;
10590         if (!feof(f)) {
10591             (void) fgets(line, MSG_SIZ, f);
10592             if (strncmp(line, "black", strlen("black"))==0)
10593               blackPlaysFirst = TRUE;
10594         }
10595     }
10596     startedFromSetupPosition = TRUE;
10597
10598     SendToProgram("force\n", &first);
10599     CopyBoard(boards[0], initial_position);
10600     if (blackPlaysFirst) {
10601         currentMove = forwardMostMove = backwardMostMove = 1;
10602         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10603         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10604         CopyBoard(boards[1], initial_position);
10605         DisplayMessage("", _("Black to play"));
10606     } else {
10607         currentMove = forwardMostMove = backwardMostMove = 0;
10608         DisplayMessage("", _("White to play"));
10609     }
10610     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10611     SendBoard(&first, forwardMostMove);
10612     if (appData.debugMode) {
10613 int i, j;
10614   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10615   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10616         fprintf(debugFP, "Load Position\n");
10617     }
10618
10619     if (positionNumber > 1) {
10620       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10621         DisplayTitle(line);
10622     } else {
10623         DisplayTitle(title);
10624     }
10625     gameMode = EditGame;
10626     ModeHighlight();
10627     ResetClocks();
10628     timeRemaining[0][1] = whiteTimeRemaining;
10629     timeRemaining[1][1] = blackTimeRemaining;
10630     DrawPosition(FALSE, boards[currentMove]);
10631
10632     return TRUE;
10633 }
10634
10635
10636 void
10637 CopyPlayerNameIntoFileName(dest, src)
10638      char **dest, *src;
10639 {
10640     while (*src != NULLCHAR && *src != ',') {
10641         if (*src == ' ') {
10642             *(*dest)++ = '_';
10643             src++;
10644         } else {
10645             *(*dest)++ = *src++;
10646         }
10647     }
10648 }
10649
10650 char *DefaultFileName(ext)
10651      char *ext;
10652 {
10653     static char def[MSG_SIZ];
10654     char *p;
10655
10656     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10657         p = def;
10658         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10659         *p++ = '-';
10660         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10661         *p++ = '.';
10662         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10663     } else {
10664         def[0] = NULLCHAR;
10665     }
10666     return def;
10667 }
10668
10669 /* Save the current game to the given file */
10670 int
10671 SaveGameToFile(filename, append)
10672      char *filename;
10673      int append;
10674 {
10675     FILE *f;
10676     char buf[MSG_SIZ];
10677
10678     if (strcmp(filename, "-") == 0) {
10679         return SaveGame(stdout, 0, NULL);
10680     } else {
10681         f = fopen(filename, append ? "a" : "w");
10682         if (f == NULL) {
10683             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10684             DisplayError(buf, errno);
10685             return FALSE;
10686         } else {
10687             return SaveGame(f, 0, NULL);
10688         }
10689     }
10690 }
10691
10692 char *
10693 SavePart(str)
10694      char *str;
10695 {
10696     static char buf[MSG_SIZ];
10697     char *p;
10698
10699     p = strchr(str, ' ');
10700     if (p == NULL) return str;
10701     strncpy(buf, str, p - str);
10702     buf[p - str] = NULLCHAR;
10703     return buf;
10704 }
10705
10706 #define PGN_MAX_LINE 75
10707
10708 #define PGN_SIDE_WHITE  0
10709 #define PGN_SIDE_BLACK  1
10710
10711 /* [AS] */
10712 static int FindFirstMoveOutOfBook( int side )
10713 {
10714     int result = -1;
10715
10716     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10717         int index = backwardMostMove;
10718         int has_book_hit = 0;
10719
10720         if( (index % 2) != side ) {
10721             index++;
10722         }
10723
10724         while( index < forwardMostMove ) {
10725             /* Check to see if engine is in book */
10726             int depth = pvInfoList[index].depth;
10727             int score = pvInfoList[index].score;
10728             int in_book = 0;
10729
10730             if( depth <= 2 ) {
10731                 in_book = 1;
10732             }
10733             else if( score == 0 && depth == 63 ) {
10734                 in_book = 1; /* Zappa */
10735             }
10736             else if( score == 2 && depth == 99 ) {
10737                 in_book = 1; /* Abrok */
10738             }
10739
10740             has_book_hit += in_book;
10741
10742             if( ! in_book ) {
10743                 result = index;
10744
10745                 break;
10746             }
10747
10748             index += 2;
10749         }
10750     }
10751
10752     return result;
10753 }
10754
10755 /* [AS] */
10756 void GetOutOfBookInfo( char * buf )
10757 {
10758     int oob[2];
10759     int i;
10760     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10761
10762     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10763     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10764
10765     *buf = '\0';
10766
10767     if( oob[0] >= 0 || oob[1] >= 0 ) {
10768         for( i=0; i<2; i++ ) {
10769             int idx = oob[i];
10770
10771             if( idx >= 0 ) {
10772                 if( i > 0 && oob[0] >= 0 ) {
10773                     strcat( buf, "   " );
10774                 }
10775
10776                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10777                 sprintf( buf+strlen(buf), "%s%.2f",
10778                     pvInfoList[idx].score >= 0 ? "+" : "",
10779                     pvInfoList[idx].score / 100.0 );
10780             }
10781         }
10782     }
10783 }
10784
10785 /* Save game in PGN style and close the file */
10786 int
10787 SaveGamePGN(f)
10788      FILE *f;
10789 {
10790     int i, offset, linelen, newblock;
10791     time_t tm;
10792 //    char *movetext;
10793     char numtext[32];
10794     int movelen, numlen, blank;
10795     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10796
10797     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10798
10799     tm = time((time_t *) NULL);
10800
10801     PrintPGNTags(f, &gameInfo);
10802
10803     if (backwardMostMove > 0 || startedFromSetupPosition) {
10804         char *fen = PositionToFEN(backwardMostMove, NULL);
10805         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10806         fprintf(f, "\n{--------------\n");
10807         PrintPosition(f, backwardMostMove);
10808         fprintf(f, "--------------}\n");
10809         free(fen);
10810     }
10811     else {
10812         /* [AS] Out of book annotation */
10813         if( appData.saveOutOfBookInfo ) {
10814             char buf[64];
10815
10816             GetOutOfBookInfo( buf );
10817
10818             if( buf[0] != '\0' ) {
10819                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10820             }
10821         }
10822
10823         fprintf(f, "\n");
10824     }
10825
10826     i = backwardMostMove;
10827     linelen = 0;
10828     newblock = TRUE;
10829
10830     while (i < forwardMostMove) {
10831         /* Print comments preceding this move */
10832         if (commentList[i] != NULL) {
10833             if (linelen > 0) fprintf(f, "\n");
10834             fprintf(f, "%s", commentList[i]);
10835             linelen = 0;
10836             newblock = TRUE;
10837         }
10838
10839         /* Format move number */
10840         if ((i % 2) == 0)
10841           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10842         else
10843           if (newblock)
10844             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10845           else
10846             numtext[0] = NULLCHAR;
10847
10848         numlen = strlen(numtext);
10849         newblock = FALSE;
10850
10851         /* Print move number */
10852         blank = linelen > 0 && numlen > 0;
10853         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10854             fprintf(f, "\n");
10855             linelen = 0;
10856             blank = 0;
10857         }
10858         if (blank) {
10859             fprintf(f, " ");
10860             linelen++;
10861         }
10862         fprintf(f, "%s", numtext);
10863         linelen += numlen;
10864
10865         /* Get move */
10866         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10867         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10868
10869         /* Print move */
10870         blank = linelen > 0 && movelen > 0;
10871         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10872             fprintf(f, "\n");
10873             linelen = 0;
10874             blank = 0;
10875         }
10876         if (blank) {
10877             fprintf(f, " ");
10878             linelen++;
10879         }
10880         fprintf(f, "%s", move_buffer);
10881         linelen += movelen;
10882
10883         /* [AS] Add PV info if present */
10884         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10885             /* [HGM] add time */
10886             char buf[MSG_SIZ]; int seconds;
10887
10888             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10889
10890             if( seconds <= 0)
10891               buf[0] = 0;
10892             else
10893               if( seconds < 30 )
10894                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10895               else
10896                 {
10897                   seconds = (seconds + 4)/10; // round to full seconds
10898                   if( seconds < 60 )
10899                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10900                   else
10901                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10902                 }
10903
10904             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10905                       pvInfoList[i].score >= 0 ? "+" : "",
10906                       pvInfoList[i].score / 100.0,
10907                       pvInfoList[i].depth,
10908                       buf );
10909
10910             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10911
10912             /* Print score/depth */
10913             blank = linelen > 0 && movelen > 0;
10914             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10915                 fprintf(f, "\n");
10916                 linelen = 0;
10917                 blank = 0;
10918             }
10919             if (blank) {
10920                 fprintf(f, " ");
10921                 linelen++;
10922             }
10923             fprintf(f, "%s", move_buffer);
10924             linelen += movelen;
10925         }
10926
10927         i++;
10928     }
10929
10930     /* Start a new line */
10931     if (linelen > 0) fprintf(f, "\n");
10932
10933     /* Print comments after last move */
10934     if (commentList[i] != NULL) {
10935         fprintf(f, "%s\n", commentList[i]);
10936     }
10937
10938     /* Print result */
10939     if (gameInfo.resultDetails != NULL &&
10940         gameInfo.resultDetails[0] != NULLCHAR) {
10941         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10942                 PGNResult(gameInfo.result));
10943     } else {
10944         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10945     }
10946
10947     fclose(f);
10948     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10949     return TRUE;
10950 }
10951
10952 /* Save game in old style and close the file */
10953 int
10954 SaveGameOldStyle(f)
10955      FILE *f;
10956 {
10957     int i, offset;
10958     time_t tm;
10959
10960     tm = time((time_t *) NULL);
10961
10962     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10963     PrintOpponents(f);
10964
10965     if (backwardMostMove > 0 || startedFromSetupPosition) {
10966         fprintf(f, "\n[--------------\n");
10967         PrintPosition(f, backwardMostMove);
10968         fprintf(f, "--------------]\n");
10969     } else {
10970         fprintf(f, "\n");
10971     }
10972
10973     i = backwardMostMove;
10974     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10975
10976     while (i < forwardMostMove) {
10977         if (commentList[i] != NULL) {
10978             fprintf(f, "[%s]\n", commentList[i]);
10979         }
10980
10981         if ((i % 2) == 1) {
10982             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10983             i++;
10984         } else {
10985             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10986             i++;
10987             if (commentList[i] != NULL) {
10988                 fprintf(f, "\n");
10989                 continue;
10990             }
10991             if (i >= forwardMostMove) {
10992                 fprintf(f, "\n");
10993                 break;
10994             }
10995             fprintf(f, "%s\n", parseList[i]);
10996             i++;
10997         }
10998     }
10999
11000     if (commentList[i] != NULL) {
11001         fprintf(f, "[%s]\n", commentList[i]);
11002     }
11003
11004     /* This isn't really the old style, but it's close enough */
11005     if (gameInfo.resultDetails != NULL &&
11006         gameInfo.resultDetails[0] != NULLCHAR) {
11007         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11008                 gameInfo.resultDetails);
11009     } else {
11010         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11011     }
11012
11013     fclose(f);
11014     return TRUE;
11015 }
11016
11017 /* Save the current game to open file f and close the file */
11018 int
11019 SaveGame(f, dummy, dummy2)
11020      FILE *f;
11021      int dummy;
11022      char *dummy2;
11023 {
11024     if (gameMode == EditPosition) EditPositionDone(TRUE);
11025     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11026     if (appData.oldSaveStyle)
11027       return SaveGameOldStyle(f);
11028     else
11029       return SaveGamePGN(f);
11030 }
11031
11032 /* Save the current position to the given file */
11033 int
11034 SavePositionToFile(filename)
11035      char *filename;
11036 {
11037     FILE *f;
11038     char buf[MSG_SIZ];
11039
11040     if (strcmp(filename, "-") == 0) {
11041         return SavePosition(stdout, 0, NULL);
11042     } else {
11043         f = fopen(filename, "a");
11044         if (f == NULL) {
11045             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11046             DisplayError(buf, errno);
11047             return FALSE;
11048         } else {
11049             SavePosition(f, 0, NULL);
11050             return TRUE;
11051         }
11052     }
11053 }
11054
11055 /* Save the current position to the given open file and close the file */
11056 int
11057 SavePosition(f, dummy, dummy2)
11058      FILE *f;
11059      int dummy;
11060      char *dummy2;
11061 {
11062     time_t tm;
11063     char *fen;
11064
11065     if (gameMode == EditPosition) EditPositionDone(TRUE);
11066     if (appData.oldSaveStyle) {
11067         tm = time((time_t *) NULL);
11068
11069         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11070         PrintOpponents(f);
11071         fprintf(f, "[--------------\n");
11072         PrintPosition(f, currentMove);
11073         fprintf(f, "--------------]\n");
11074     } else {
11075         fen = PositionToFEN(currentMove, NULL);
11076         fprintf(f, "%s\n", fen);
11077         free(fen);
11078     }
11079     fclose(f);
11080     return TRUE;
11081 }
11082
11083 void
11084 ReloadCmailMsgEvent(unregister)
11085      int unregister;
11086 {
11087 #if !WIN32
11088     static char *inFilename = NULL;
11089     static char *outFilename;
11090     int i;
11091     struct stat inbuf, outbuf;
11092     int status;
11093
11094     /* Any registered moves are unregistered if unregister is set, */
11095     /* i.e. invoked by the signal handler */
11096     if (unregister) {
11097         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11098             cmailMoveRegistered[i] = FALSE;
11099             if (cmailCommentList[i] != NULL) {
11100                 free(cmailCommentList[i]);
11101                 cmailCommentList[i] = NULL;
11102             }
11103         }
11104         nCmailMovesRegistered = 0;
11105     }
11106
11107     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11108         cmailResult[i] = CMAIL_NOT_RESULT;
11109     }
11110     nCmailResults = 0;
11111
11112     if (inFilename == NULL) {
11113         /* Because the filenames are static they only get malloced once  */
11114         /* and they never get freed                                      */
11115         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11116         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11117
11118         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11119         sprintf(outFilename, "%s.out", appData.cmailGameName);
11120     }
11121
11122     status = stat(outFilename, &outbuf);
11123     if (status < 0) {
11124         cmailMailedMove = FALSE;
11125     } else {
11126         status = stat(inFilename, &inbuf);
11127         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11128     }
11129
11130     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11131        counts the games, notes how each one terminated, etc.
11132
11133        It would be nice to remove this kludge and instead gather all
11134        the information while building the game list.  (And to keep it
11135        in the game list nodes instead of having a bunch of fixed-size
11136        parallel arrays.)  Note this will require getting each game's
11137        termination from the PGN tags, as the game list builder does
11138        not process the game moves.  --mann
11139        */
11140     cmailMsgLoaded = TRUE;
11141     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11142
11143     /* Load first game in the file or popup game menu */
11144     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11145
11146 #endif /* !WIN32 */
11147     return;
11148 }
11149
11150 int
11151 RegisterMove()
11152 {
11153     FILE *f;
11154     char string[MSG_SIZ];
11155
11156     if (   cmailMailedMove
11157         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11158         return TRUE;            /* Allow free viewing  */
11159     }
11160
11161     /* Unregister move to ensure that we don't leave RegisterMove        */
11162     /* with the move registered when the conditions for registering no   */
11163     /* longer hold                                                       */
11164     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11165         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11166         nCmailMovesRegistered --;
11167
11168         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11169           {
11170               free(cmailCommentList[lastLoadGameNumber - 1]);
11171               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11172           }
11173     }
11174
11175     if (cmailOldMove == -1) {
11176         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11177         return FALSE;
11178     }
11179
11180     if (currentMove > cmailOldMove + 1) {
11181         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11182         return FALSE;
11183     }
11184
11185     if (currentMove < cmailOldMove) {
11186         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11187         return FALSE;
11188     }
11189
11190     if (forwardMostMove > currentMove) {
11191         /* Silently truncate extra moves */
11192         TruncateGame();
11193     }
11194
11195     if (   (currentMove == cmailOldMove + 1)
11196         || (   (currentMove == cmailOldMove)
11197             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11198                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11199         if (gameInfo.result != GameUnfinished) {
11200             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11201         }
11202
11203         if (commentList[currentMove] != NULL) {
11204             cmailCommentList[lastLoadGameNumber - 1]
11205               = StrSave(commentList[currentMove]);
11206         }
11207         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11208
11209         if (appData.debugMode)
11210           fprintf(debugFP, "Saving %s for game %d\n",
11211                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11212
11213         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11214
11215         f = fopen(string, "w");
11216         if (appData.oldSaveStyle) {
11217             SaveGameOldStyle(f); /* also closes the file */
11218
11219             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11220             f = fopen(string, "w");
11221             SavePosition(f, 0, NULL); /* also closes the file */
11222         } else {
11223             fprintf(f, "{--------------\n");
11224             PrintPosition(f, currentMove);
11225             fprintf(f, "--------------}\n\n");
11226
11227             SaveGame(f, 0, NULL); /* also closes the file*/
11228         }
11229
11230         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11231         nCmailMovesRegistered ++;
11232     } else if (nCmailGames == 1) {
11233         DisplayError(_("You have not made a move yet"), 0);
11234         return FALSE;
11235     }
11236
11237     return TRUE;
11238 }
11239
11240 void
11241 MailMoveEvent()
11242 {
11243 #if !WIN32
11244     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11245     FILE *commandOutput;
11246     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11247     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11248     int nBuffers;
11249     int i;
11250     int archived;
11251     char *arcDir;
11252
11253     if (! cmailMsgLoaded) {
11254         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11255         return;
11256     }
11257
11258     if (nCmailGames == nCmailResults) {
11259         DisplayError(_("No unfinished games"), 0);
11260         return;
11261     }
11262
11263 #if CMAIL_PROHIBIT_REMAIL
11264     if (cmailMailedMove) {
11265       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);
11266         DisplayError(msg, 0);
11267         return;
11268     }
11269 #endif
11270
11271     if (! (cmailMailedMove || RegisterMove())) return;
11272
11273     if (   cmailMailedMove
11274         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11275       snprintf(string, MSG_SIZ, partCommandString,
11276                appData.debugMode ? " -v" : "", appData.cmailGameName);
11277         commandOutput = popen(string, "r");
11278
11279         if (commandOutput == NULL) {
11280             DisplayError(_("Failed to invoke cmail"), 0);
11281         } else {
11282             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11283                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11284             }
11285             if (nBuffers > 1) {
11286                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11287                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11288                 nBytes = MSG_SIZ - 1;
11289             } else {
11290                 (void) memcpy(msg, buffer, nBytes);
11291             }
11292             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11293
11294             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11295                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11296
11297                 archived = TRUE;
11298                 for (i = 0; i < nCmailGames; i ++) {
11299                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11300                         archived = FALSE;
11301                     }
11302                 }
11303                 if (   archived
11304                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11305                         != NULL)) {
11306                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11307                            arcDir,
11308                            appData.cmailGameName,
11309                            gameInfo.date);
11310                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11311                     cmailMsgLoaded = FALSE;
11312                 }
11313             }
11314
11315             DisplayInformation(msg);
11316             pclose(commandOutput);
11317         }
11318     } else {
11319         if ((*cmailMsg) != '\0') {
11320             DisplayInformation(cmailMsg);
11321         }
11322     }
11323
11324     return;
11325 #endif /* !WIN32 */
11326 }
11327
11328 char *
11329 CmailMsg()
11330 {
11331 #if WIN32
11332     return NULL;
11333 #else
11334     int  prependComma = 0;
11335     char number[5];
11336     char string[MSG_SIZ];       /* Space for game-list */
11337     int  i;
11338
11339     if (!cmailMsgLoaded) return "";
11340
11341     if (cmailMailedMove) {
11342       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11343     } else {
11344         /* Create a list of games left */
11345       snprintf(string, MSG_SIZ, "[");
11346         for (i = 0; i < nCmailGames; i ++) {
11347             if (! (   cmailMoveRegistered[i]
11348                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11349                 if (prependComma) {
11350                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11351                 } else {
11352                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11353                     prependComma = 1;
11354                 }
11355
11356                 strcat(string, number);
11357             }
11358         }
11359         strcat(string, "]");
11360
11361         if (nCmailMovesRegistered + nCmailResults == 0) {
11362             switch (nCmailGames) {
11363               case 1:
11364                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11365                 break;
11366
11367               case 2:
11368                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11369                 break;
11370
11371               default:
11372                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11373                          nCmailGames);
11374                 break;
11375             }
11376         } else {
11377             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11378               case 1:
11379                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11380                          string);
11381                 break;
11382
11383               case 0:
11384                 if (nCmailResults == nCmailGames) {
11385                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11386                 } else {
11387                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11388                 }
11389                 break;
11390
11391               default:
11392                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11393                          string);
11394             }
11395         }
11396     }
11397     return cmailMsg;
11398 #endif /* WIN32 */
11399 }
11400
11401 void
11402 ResetGameEvent()
11403 {
11404     if (gameMode == Training)
11405       SetTrainingModeOff();
11406
11407     Reset(TRUE, TRUE);
11408     cmailMsgLoaded = FALSE;
11409     if (appData.icsActive) {
11410       SendToICS(ics_prefix);
11411       SendToICS("refresh\n");
11412     }
11413 }
11414
11415 void
11416 ExitEvent(status)
11417      int status;
11418 {
11419     exiting++;
11420     if (exiting > 2) {
11421       /* Give up on clean exit */
11422       exit(status);
11423     }
11424     if (exiting > 1) {
11425       /* Keep trying for clean exit */
11426       return;
11427     }
11428
11429     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11430
11431     if (telnetISR != NULL) {
11432       RemoveInputSource(telnetISR);
11433     }
11434     if (icsPR != NoProc) {
11435       DestroyChildProcess(icsPR, TRUE);
11436     }
11437
11438     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11439     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11440
11441     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11442     /* make sure this other one finishes before killing it!                  */
11443     if(endingGame) { int count = 0;
11444         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11445         while(endingGame && count++ < 10) DoSleep(1);
11446         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11447     }
11448
11449     /* Kill off chess programs */
11450     if (first.pr != NoProc) {
11451         ExitAnalyzeMode();
11452
11453         DoSleep( appData.delayBeforeQuit );
11454         SendToProgram("quit\n", &first);
11455         DoSleep( appData.delayAfterQuit );
11456         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11457     }
11458     if (second.pr != NoProc) {
11459         DoSleep( appData.delayBeforeQuit );
11460         SendToProgram("quit\n", &second);
11461         DoSleep( appData.delayAfterQuit );
11462         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11463     }
11464     if (first.isr != NULL) {
11465         RemoveInputSource(first.isr);
11466     }
11467     if (second.isr != NULL) {
11468         RemoveInputSource(second.isr);
11469     }
11470
11471     ShutDownFrontEnd();
11472     exit(status);
11473 }
11474
11475 void
11476 PauseEvent()
11477 {
11478     if (appData.debugMode)
11479         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11480     if (pausing) {
11481         pausing = FALSE;
11482         ModeHighlight();
11483         if (gameMode == MachinePlaysWhite ||
11484             gameMode == MachinePlaysBlack) {
11485             StartClocks();
11486         } else {
11487             DisplayBothClocks();
11488         }
11489         if (gameMode == PlayFromGameFile) {
11490             if (appData.timeDelay >= 0)
11491                 AutoPlayGameLoop();
11492         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11493             Reset(FALSE, TRUE);
11494             SendToICS(ics_prefix);
11495             SendToICS("refresh\n");
11496         } else if (currentMove < forwardMostMove) {
11497             ForwardInner(forwardMostMove);
11498         }
11499         pauseExamInvalid = FALSE;
11500     } else {
11501         switch (gameMode) {
11502           default:
11503             return;
11504           case IcsExamining:
11505             pauseExamForwardMostMove = forwardMostMove;
11506             pauseExamInvalid = FALSE;
11507             /* fall through */
11508           case IcsObserving:
11509           case IcsPlayingWhite:
11510           case IcsPlayingBlack:
11511             pausing = TRUE;
11512             ModeHighlight();
11513             return;
11514           case PlayFromGameFile:
11515             (void) StopLoadGameTimer();
11516             pausing = TRUE;
11517             ModeHighlight();
11518             break;
11519           case BeginningOfGame:
11520             if (appData.icsActive) return;
11521             /* else fall through */
11522           case MachinePlaysWhite:
11523           case MachinePlaysBlack:
11524           case TwoMachinesPlay:
11525             if (forwardMostMove == 0)
11526               return;           /* don't pause if no one has moved */
11527             if ((gameMode == MachinePlaysWhite &&
11528                  !WhiteOnMove(forwardMostMove)) ||
11529                 (gameMode == MachinePlaysBlack &&
11530                  WhiteOnMove(forwardMostMove))) {
11531                 StopClocks();
11532             }
11533             pausing = TRUE;
11534             ModeHighlight();
11535             break;
11536         }
11537     }
11538 }
11539
11540 void
11541 EditCommentEvent()
11542 {
11543     char title[MSG_SIZ];
11544
11545     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11546       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11547     } else {
11548       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11549                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11550                parseList[currentMove - 1]);
11551     }
11552
11553     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11554 }
11555
11556
11557 void
11558 EditTagsEvent()
11559 {
11560     char *tags = PGNTags(&gameInfo);
11561     EditTagsPopUp(tags);
11562     free(tags);
11563 }
11564
11565 void
11566 AnalyzeModeEvent()
11567 {
11568     if (appData.noChessProgram || gameMode == AnalyzeMode)
11569       return;
11570
11571     if (gameMode != AnalyzeFile) {
11572         if (!appData.icsEngineAnalyze) {
11573                EditGameEvent();
11574                if (gameMode != EditGame) return;
11575         }
11576         ResurrectChessProgram();
11577         SendToProgram("analyze\n", &first);
11578         first.analyzing = TRUE;
11579         /*first.maybeThinking = TRUE;*/
11580         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11581         EngineOutputPopUp();
11582     }
11583     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11584     pausing = FALSE;
11585     ModeHighlight();
11586     SetGameInfo();
11587
11588     StartAnalysisClock();
11589     GetTimeMark(&lastNodeCountTime);
11590     lastNodeCount = 0;
11591 }
11592
11593 void
11594 AnalyzeFileEvent()
11595 {
11596     if (appData.noChessProgram || gameMode == AnalyzeFile)
11597       return;
11598
11599     if (gameMode != AnalyzeMode) {
11600         EditGameEvent();
11601         if (gameMode != EditGame) return;
11602         ResurrectChessProgram();
11603         SendToProgram("analyze\n", &first);
11604         first.analyzing = TRUE;
11605         /*first.maybeThinking = TRUE;*/
11606         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11607         EngineOutputPopUp();
11608     }
11609     gameMode = AnalyzeFile;
11610     pausing = FALSE;
11611     ModeHighlight();
11612     SetGameInfo();
11613
11614     StartAnalysisClock();
11615     GetTimeMark(&lastNodeCountTime);
11616     lastNodeCount = 0;
11617 }
11618
11619 void
11620 MachineWhiteEvent()
11621 {
11622     char buf[MSG_SIZ];
11623     char *bookHit = NULL;
11624
11625     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11626       return;
11627
11628
11629     if (gameMode == PlayFromGameFile ||
11630         gameMode == TwoMachinesPlay  ||
11631         gameMode == Training         ||
11632         gameMode == AnalyzeMode      ||
11633         gameMode == EndOfGame)
11634         EditGameEvent();
11635
11636     if (gameMode == EditPosition)
11637         EditPositionDone(TRUE);
11638
11639     if (!WhiteOnMove(currentMove)) {
11640         DisplayError(_("It is not White's turn"), 0);
11641         return;
11642     }
11643
11644     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11645       ExitAnalyzeMode();
11646
11647     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11648         gameMode == AnalyzeFile)
11649         TruncateGame();
11650
11651     ResurrectChessProgram();    /* in case it isn't running */
11652     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11653         gameMode = MachinePlaysWhite;
11654         ResetClocks();
11655     } else
11656     gameMode = MachinePlaysWhite;
11657     pausing = FALSE;
11658     ModeHighlight();
11659     SetGameInfo();
11660     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11661     DisplayTitle(buf);
11662     if (first.sendName) {
11663       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11664       SendToProgram(buf, &first);
11665     }
11666     if (first.sendTime) {
11667       if (first.useColors) {
11668         SendToProgram("black\n", &first); /*gnu kludge*/
11669       }
11670       SendTimeRemaining(&first, TRUE);
11671     }
11672     if (first.useColors) {
11673       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11674     }
11675     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11676     SetMachineThinkingEnables();
11677     first.maybeThinking = TRUE;
11678     StartClocks();
11679     firstMove = FALSE;
11680
11681     if (appData.autoFlipView && !flipView) {
11682       flipView = !flipView;
11683       DrawPosition(FALSE, NULL);
11684       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11685     }
11686
11687     if(bookHit) { // [HGM] book: simulate book reply
11688         static char bookMove[MSG_SIZ]; // a bit generous?
11689
11690         programStats.nodes = programStats.depth = programStats.time =
11691         programStats.score = programStats.got_only_move = 0;
11692         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11693
11694         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11695         strcat(bookMove, bookHit);
11696         HandleMachineMove(bookMove, &first);
11697     }
11698 }
11699
11700 void
11701 MachineBlackEvent()
11702 {
11703   char buf[MSG_SIZ];
11704   char *bookHit = NULL;
11705
11706     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11707         return;
11708
11709
11710     if (gameMode == PlayFromGameFile ||
11711         gameMode == TwoMachinesPlay  ||
11712         gameMode == Training         ||
11713         gameMode == AnalyzeMode      ||
11714         gameMode == EndOfGame)
11715         EditGameEvent();
11716
11717     if (gameMode == EditPosition)
11718         EditPositionDone(TRUE);
11719
11720     if (WhiteOnMove(currentMove)) {
11721         DisplayError(_("It is not Black's turn"), 0);
11722         return;
11723     }
11724
11725     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11726       ExitAnalyzeMode();
11727
11728     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11729         gameMode == AnalyzeFile)
11730         TruncateGame();
11731
11732     ResurrectChessProgram();    /* in case it isn't running */
11733     gameMode = MachinePlaysBlack;
11734     pausing = FALSE;
11735     ModeHighlight();
11736     SetGameInfo();
11737     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11738     DisplayTitle(buf);
11739     if (first.sendName) {
11740       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11741       SendToProgram(buf, &first);
11742     }
11743     if (first.sendTime) {
11744       if (first.useColors) {
11745         SendToProgram("white\n", &first); /*gnu kludge*/
11746       }
11747       SendTimeRemaining(&first, FALSE);
11748     }
11749     if (first.useColors) {
11750       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11751     }
11752     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11753     SetMachineThinkingEnables();
11754     first.maybeThinking = TRUE;
11755     StartClocks();
11756
11757     if (appData.autoFlipView && flipView) {
11758       flipView = !flipView;
11759       DrawPosition(FALSE, NULL);
11760       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11761     }
11762     if(bookHit) { // [HGM] book: simulate book reply
11763         static char bookMove[MSG_SIZ]; // a bit generous?
11764
11765         programStats.nodes = programStats.depth = programStats.time =
11766         programStats.score = programStats.got_only_move = 0;
11767         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11768
11769         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11770         strcat(bookMove, bookHit);
11771         HandleMachineMove(bookMove, &first);
11772     }
11773 }
11774
11775
11776 void
11777 DisplayTwoMachinesTitle()
11778 {
11779     char buf[MSG_SIZ];
11780     if (appData.matchGames > 0) {
11781         if (first.twoMachinesColor[0] == 'w') {
11782           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11783                    gameInfo.white, gameInfo.black,
11784                    first.matchWins, second.matchWins,
11785                    matchGame - 1 - (first.matchWins + second.matchWins));
11786         } else {
11787           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11788                    gameInfo.white, gameInfo.black,
11789                    second.matchWins, first.matchWins,
11790                    matchGame - 1 - (first.matchWins + second.matchWins));
11791         }
11792     } else {
11793       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11794     }
11795     DisplayTitle(buf);
11796 }
11797
11798 void
11799 TwoMachinesEvent P((void))
11800 {
11801     int i;
11802     char buf[MSG_SIZ];
11803     ChessProgramState *onmove;
11804     char *bookHit = NULL;
11805
11806     if (appData.noChessProgram) return;
11807
11808     switch (gameMode) {
11809       case TwoMachinesPlay:
11810         return;
11811       case MachinePlaysWhite:
11812       case MachinePlaysBlack:
11813         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11814             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11815             return;
11816         }
11817         /* fall through */
11818       case BeginningOfGame:
11819       case PlayFromGameFile:
11820       case EndOfGame:
11821         EditGameEvent();
11822         if (gameMode != EditGame) return;
11823         break;
11824       case EditPosition:
11825         EditPositionDone(TRUE);
11826         break;
11827       case AnalyzeMode:
11828       case AnalyzeFile:
11829         ExitAnalyzeMode();
11830         break;
11831       case EditGame:
11832       default:
11833         break;
11834     }
11835
11836 //    forwardMostMove = currentMove;
11837     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11838     ResurrectChessProgram();    /* in case first program isn't running */
11839
11840     if (second.pr == NULL) {
11841         StartChessProgram(&second);
11842         if (second.protocolVersion == 1) {
11843           TwoMachinesEventIfReady();
11844         } else {
11845           /* kludge: allow timeout for initial "feature" command */
11846           FreezeUI();
11847           DisplayMessage("", _("Starting second chess program"));
11848           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11849         }
11850         return;
11851     }
11852     DisplayMessage("", "");
11853     InitChessProgram(&second, FALSE);
11854     SendToProgram("force\n", &second);
11855     if (startedFromSetupPosition) {
11856         SendBoard(&second, backwardMostMove);
11857     if (appData.debugMode) {
11858         fprintf(debugFP, "Two Machines\n");
11859     }
11860     }
11861     for (i = backwardMostMove; i < forwardMostMove; i++) {
11862         SendMoveToProgram(i, &second);
11863     }
11864
11865     gameMode = TwoMachinesPlay;
11866     pausing = FALSE;
11867     ModeHighlight();
11868     SetGameInfo();
11869     DisplayTwoMachinesTitle();
11870     firstMove = TRUE;
11871     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11872         onmove = &first;
11873     } else {
11874         onmove = &second;
11875     }
11876
11877     SendToProgram(first.computerString, &first);
11878     if (first.sendName) {
11879       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11880       SendToProgram(buf, &first);
11881     }
11882     SendToProgram(second.computerString, &second);
11883     if (second.sendName) {
11884       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11885       SendToProgram(buf, &second);
11886     }
11887
11888     ResetClocks();
11889     if (!first.sendTime || !second.sendTime) {
11890         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11891         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11892     }
11893     if (onmove->sendTime) {
11894       if (onmove->useColors) {
11895         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11896       }
11897       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11898     }
11899     if (onmove->useColors) {
11900       SendToProgram(onmove->twoMachinesColor, onmove);
11901     }
11902     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11903 //    SendToProgram("go\n", onmove);
11904     onmove->maybeThinking = TRUE;
11905     SetMachineThinkingEnables();
11906
11907     StartClocks();
11908
11909     if(bookHit) { // [HGM] book: simulate book reply
11910         static char bookMove[MSG_SIZ]; // a bit generous?
11911
11912         programStats.nodes = programStats.depth = programStats.time =
11913         programStats.score = programStats.got_only_move = 0;
11914         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11915
11916         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11917         strcat(bookMove, bookHit);
11918         savedMessage = bookMove; // args for deferred call
11919         savedState = onmove;
11920         ScheduleDelayedEvent(DeferredBookMove, 1);
11921     }
11922 }
11923
11924 void
11925 TrainingEvent()
11926 {
11927     if (gameMode == Training) {
11928       SetTrainingModeOff();
11929       gameMode = PlayFromGameFile;
11930       DisplayMessage("", _("Training mode off"));
11931     } else {
11932       gameMode = Training;
11933       animateTraining = appData.animate;
11934
11935       /* make sure we are not already at the end of the game */
11936       if (currentMove < forwardMostMove) {
11937         SetTrainingModeOn();
11938         DisplayMessage("", _("Training mode on"));
11939       } else {
11940         gameMode = PlayFromGameFile;
11941         DisplayError(_("Already at end of game"), 0);
11942       }
11943     }
11944     ModeHighlight();
11945 }
11946
11947 void
11948 IcsClientEvent()
11949 {
11950     if (!appData.icsActive) return;
11951     switch (gameMode) {
11952       case IcsPlayingWhite:
11953       case IcsPlayingBlack:
11954       case IcsObserving:
11955       case IcsIdle:
11956       case BeginningOfGame:
11957       case IcsExamining:
11958         return;
11959
11960       case EditGame:
11961         break;
11962
11963       case EditPosition:
11964         EditPositionDone(TRUE);
11965         break;
11966
11967       case AnalyzeMode:
11968       case AnalyzeFile:
11969         ExitAnalyzeMode();
11970         break;
11971
11972       default:
11973         EditGameEvent();
11974         break;
11975     }
11976
11977     gameMode = IcsIdle;
11978     ModeHighlight();
11979     return;
11980 }
11981
11982
11983 void
11984 EditGameEvent()
11985 {
11986     int i;
11987
11988     switch (gameMode) {
11989       case Training:
11990         SetTrainingModeOff();
11991         break;
11992       case MachinePlaysWhite:
11993       case MachinePlaysBlack:
11994       case BeginningOfGame:
11995         SendToProgram("force\n", &first);
11996         SetUserThinkingEnables();
11997         break;
11998       case PlayFromGameFile:
11999         (void) StopLoadGameTimer();
12000         if (gameFileFP != NULL) {
12001             gameFileFP = NULL;
12002         }
12003         break;
12004       case EditPosition:
12005         EditPositionDone(TRUE);
12006         break;
12007       case AnalyzeMode:
12008       case AnalyzeFile:
12009         ExitAnalyzeMode();
12010         SendToProgram("force\n", &first);
12011         break;
12012       case TwoMachinesPlay:
12013         GameEnds(EndOfFile, NULL, GE_PLAYER);
12014         ResurrectChessProgram();
12015         SetUserThinkingEnables();
12016         break;
12017       case EndOfGame:
12018         ResurrectChessProgram();
12019         break;
12020       case IcsPlayingBlack:
12021       case IcsPlayingWhite:
12022         DisplayError(_("Warning: You are still playing a game"), 0);
12023         break;
12024       case IcsObserving:
12025         DisplayError(_("Warning: You are still observing a game"), 0);
12026         break;
12027       case IcsExamining:
12028         DisplayError(_("Warning: You are still examining a game"), 0);
12029         break;
12030       case IcsIdle:
12031         break;
12032       case EditGame:
12033       default:
12034         return;
12035     }
12036
12037     pausing = FALSE;
12038     StopClocks();
12039     first.offeredDraw = second.offeredDraw = 0;
12040
12041     if (gameMode == PlayFromGameFile) {
12042         whiteTimeRemaining = timeRemaining[0][currentMove];
12043         blackTimeRemaining = timeRemaining[1][currentMove];
12044         DisplayTitle("");
12045     }
12046
12047     if (gameMode == MachinePlaysWhite ||
12048         gameMode == MachinePlaysBlack ||
12049         gameMode == TwoMachinesPlay ||
12050         gameMode == EndOfGame) {
12051         i = forwardMostMove;
12052         while (i > currentMove) {
12053             SendToProgram("undo\n", &first);
12054             i--;
12055         }
12056         whiteTimeRemaining = timeRemaining[0][currentMove];
12057         blackTimeRemaining = timeRemaining[1][currentMove];
12058         DisplayBothClocks();
12059         if (whiteFlag || blackFlag) {
12060             whiteFlag = blackFlag = 0;
12061         }
12062         DisplayTitle("");
12063     }
12064
12065     gameMode = EditGame;
12066     ModeHighlight();
12067     SetGameInfo();
12068 }
12069
12070
12071 void
12072 EditPositionEvent()
12073 {
12074     if (gameMode == EditPosition) {
12075         EditGameEvent();
12076         return;
12077     }
12078
12079     EditGameEvent();
12080     if (gameMode != EditGame) return;
12081
12082     gameMode = EditPosition;
12083     ModeHighlight();
12084     SetGameInfo();
12085     if (currentMove > 0)
12086       CopyBoard(boards[0], boards[currentMove]);
12087
12088     blackPlaysFirst = !WhiteOnMove(currentMove);
12089     ResetClocks();
12090     currentMove = forwardMostMove = backwardMostMove = 0;
12091     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12092     DisplayMove(-1);
12093 }
12094
12095 void
12096 ExitAnalyzeMode()
12097 {
12098     /* [DM] icsEngineAnalyze - possible call from other functions */
12099     if (appData.icsEngineAnalyze) {
12100         appData.icsEngineAnalyze = FALSE;
12101
12102         DisplayMessage("",_("Close ICS engine analyze..."));
12103     }
12104     if (first.analysisSupport && first.analyzing) {
12105       SendToProgram("exit\n", &first);
12106       first.analyzing = FALSE;
12107     }
12108     thinkOutput[0] = NULLCHAR;
12109 }
12110
12111 void
12112 EditPositionDone(Boolean fakeRights)
12113 {
12114     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12115
12116     startedFromSetupPosition = TRUE;
12117     InitChessProgram(&first, FALSE);
12118     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12119       boards[0][EP_STATUS] = EP_NONE;
12120       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12121     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12122         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12123         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12124       } else boards[0][CASTLING][2] = NoRights;
12125     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12126         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12127         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12128       } else boards[0][CASTLING][5] = NoRights;
12129     }
12130     SendToProgram("force\n", &first);
12131     if (blackPlaysFirst) {
12132         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12133         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12134         currentMove = forwardMostMove = backwardMostMove = 1;
12135         CopyBoard(boards[1], boards[0]);
12136     } else {
12137         currentMove = forwardMostMove = backwardMostMove = 0;
12138     }
12139     SendBoard(&first, forwardMostMove);
12140     if (appData.debugMode) {
12141         fprintf(debugFP, "EditPosDone\n");
12142     }
12143     DisplayTitle("");
12144     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12145     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12146     gameMode = EditGame;
12147     ModeHighlight();
12148     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12149     ClearHighlights(); /* [AS] */
12150 }
12151
12152 /* Pause for `ms' milliseconds */
12153 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12154 void
12155 TimeDelay(ms)
12156      long ms;
12157 {
12158     TimeMark m1, m2;
12159
12160     GetTimeMark(&m1);
12161     do {
12162         GetTimeMark(&m2);
12163     } while (SubtractTimeMarks(&m2, &m1) < ms);
12164 }
12165
12166 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12167 void
12168 SendMultiLineToICS(buf)
12169      char *buf;
12170 {
12171     char temp[MSG_SIZ+1], *p;
12172     int len;
12173
12174     len = strlen(buf);
12175     if (len > MSG_SIZ)
12176       len = MSG_SIZ;
12177
12178     strncpy(temp, buf, len);
12179     temp[len] = 0;
12180
12181     p = temp;
12182     while (*p) {
12183         if (*p == '\n' || *p == '\r')
12184           *p = ' ';
12185         ++p;
12186     }
12187
12188     strcat(temp, "\n");
12189     SendToICS(temp);
12190     SendToPlayer(temp, strlen(temp));
12191 }
12192
12193 void
12194 SetWhiteToPlayEvent()
12195 {
12196     if (gameMode == EditPosition) {
12197         blackPlaysFirst = FALSE;
12198         DisplayBothClocks();    /* works because currentMove is 0 */
12199     } else if (gameMode == IcsExamining) {
12200         SendToICS(ics_prefix);
12201         SendToICS("tomove white\n");
12202     }
12203 }
12204
12205 void
12206 SetBlackToPlayEvent()
12207 {
12208     if (gameMode == EditPosition) {
12209         blackPlaysFirst = TRUE;
12210         currentMove = 1;        /* kludge */
12211         DisplayBothClocks();
12212         currentMove = 0;
12213     } else if (gameMode == IcsExamining) {
12214         SendToICS(ics_prefix);
12215         SendToICS("tomove black\n");
12216     }
12217 }
12218
12219 void
12220 EditPositionMenuEvent(selection, x, y)
12221      ChessSquare selection;
12222      int x, y;
12223 {
12224     char buf[MSG_SIZ];
12225     ChessSquare piece = boards[0][y][x];
12226
12227     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12228
12229     switch (selection) {
12230       case ClearBoard:
12231         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12232             SendToICS(ics_prefix);
12233             SendToICS("bsetup clear\n");
12234         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12235             SendToICS(ics_prefix);
12236             SendToICS("clearboard\n");
12237         } else {
12238             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12239                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12240                 for (y = 0; y < BOARD_HEIGHT; y++) {
12241                     if (gameMode == IcsExamining) {
12242                         if (boards[currentMove][y][x] != EmptySquare) {
12243                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12244                                     AAA + x, ONE + y);
12245                             SendToICS(buf);
12246                         }
12247                     } else {
12248                         boards[0][y][x] = p;
12249                     }
12250                 }
12251             }
12252         }
12253         if (gameMode == EditPosition) {
12254             DrawPosition(FALSE, boards[0]);
12255         }
12256         break;
12257
12258       case WhitePlay:
12259         SetWhiteToPlayEvent();
12260         break;
12261
12262       case BlackPlay:
12263         SetBlackToPlayEvent();
12264         break;
12265
12266       case EmptySquare:
12267         if (gameMode == IcsExamining) {
12268             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12269             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12270             SendToICS(buf);
12271         } else {
12272             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12273                 if(x == BOARD_LEFT-2) {
12274                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12275                     boards[0][y][1] = 0;
12276                 } else
12277                 if(x == BOARD_RGHT+1) {
12278                     if(y >= gameInfo.holdingsSize) break;
12279                     boards[0][y][BOARD_WIDTH-2] = 0;
12280                 } else break;
12281             }
12282             boards[0][y][x] = EmptySquare;
12283             DrawPosition(FALSE, boards[0]);
12284         }
12285         break;
12286
12287       case PromotePiece:
12288         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12289            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12290             selection = (ChessSquare) (PROMOTED piece);
12291         } else if(piece == EmptySquare) selection = WhiteSilver;
12292         else selection = (ChessSquare)((int)piece - 1);
12293         goto defaultlabel;
12294
12295       case DemotePiece:
12296         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12297            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12298             selection = (ChessSquare) (DEMOTED piece);
12299         } else if(piece == EmptySquare) selection = BlackSilver;
12300         else selection = (ChessSquare)((int)piece + 1);
12301         goto defaultlabel;
12302
12303       case WhiteQueen:
12304       case BlackQueen:
12305         if(gameInfo.variant == VariantShatranj ||
12306            gameInfo.variant == VariantXiangqi  ||
12307            gameInfo.variant == VariantCourier  ||
12308            gameInfo.variant == VariantMakruk     )
12309             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12310         goto defaultlabel;
12311
12312       case WhiteKing:
12313       case BlackKing:
12314         if(gameInfo.variant == VariantXiangqi)
12315             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12316         if(gameInfo.variant == VariantKnightmate)
12317             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12318       default:
12319         defaultlabel:
12320         if (gameMode == IcsExamining) {
12321             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12322             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12323                      PieceToChar(selection), AAA + x, ONE + y);
12324             SendToICS(buf);
12325         } else {
12326             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12327                 int n;
12328                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12329                     n = PieceToNumber(selection - BlackPawn);
12330                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12331                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12332                     boards[0][BOARD_HEIGHT-1-n][1]++;
12333                 } else
12334                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12335                     n = PieceToNumber(selection);
12336                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12337                     boards[0][n][BOARD_WIDTH-1] = selection;
12338                     boards[0][n][BOARD_WIDTH-2]++;
12339                 }
12340             } else
12341             boards[0][y][x] = selection;
12342             DrawPosition(TRUE, boards[0]);
12343         }
12344         break;
12345     }
12346 }
12347
12348
12349 void
12350 DropMenuEvent(selection, x, y)
12351      ChessSquare selection;
12352      int x, y;
12353 {
12354     ChessMove moveType;
12355
12356     switch (gameMode) {
12357       case IcsPlayingWhite:
12358       case MachinePlaysBlack:
12359         if (!WhiteOnMove(currentMove)) {
12360             DisplayMoveError(_("It is Black's turn"));
12361             return;
12362         }
12363         moveType = WhiteDrop;
12364         break;
12365       case IcsPlayingBlack:
12366       case MachinePlaysWhite:
12367         if (WhiteOnMove(currentMove)) {
12368             DisplayMoveError(_("It is White's turn"));
12369             return;
12370         }
12371         moveType = BlackDrop;
12372         break;
12373       case EditGame:
12374         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12375         break;
12376       default:
12377         return;
12378     }
12379
12380     if (moveType == BlackDrop && selection < BlackPawn) {
12381       selection = (ChessSquare) ((int) selection
12382                                  + (int) BlackPawn - (int) WhitePawn);
12383     }
12384     if (boards[currentMove][y][x] != EmptySquare) {
12385         DisplayMoveError(_("That square is occupied"));
12386         return;
12387     }
12388
12389     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12390 }
12391
12392 void
12393 AcceptEvent()
12394 {
12395     /* Accept a pending offer of any kind from opponent */
12396
12397     if (appData.icsActive) {
12398         SendToICS(ics_prefix);
12399         SendToICS("accept\n");
12400     } else if (cmailMsgLoaded) {
12401         if (currentMove == cmailOldMove &&
12402             commentList[cmailOldMove] != NULL &&
12403             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12404                    "Black offers a draw" : "White offers a draw")) {
12405             TruncateGame();
12406             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12407             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12408         } else {
12409             DisplayError(_("There is no pending offer on this move"), 0);
12410             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12411         }
12412     } else {
12413         /* Not used for offers from chess program */
12414     }
12415 }
12416
12417 void
12418 DeclineEvent()
12419 {
12420     /* Decline a pending offer of any kind from opponent */
12421
12422     if (appData.icsActive) {
12423         SendToICS(ics_prefix);
12424         SendToICS("decline\n");
12425     } else if (cmailMsgLoaded) {
12426         if (currentMove == cmailOldMove &&
12427             commentList[cmailOldMove] != NULL &&
12428             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12429                    "Black offers a draw" : "White offers a draw")) {
12430 #ifdef NOTDEF
12431             AppendComment(cmailOldMove, "Draw declined", TRUE);
12432             DisplayComment(cmailOldMove - 1, "Draw declined");
12433 #endif /*NOTDEF*/
12434         } else {
12435             DisplayError(_("There is no pending offer on this move"), 0);
12436         }
12437     } else {
12438         /* Not used for offers from chess program */
12439     }
12440 }
12441
12442 void
12443 RematchEvent()
12444 {
12445     /* Issue ICS rematch command */
12446     if (appData.icsActive) {
12447         SendToICS(ics_prefix);
12448         SendToICS("rematch\n");
12449     }
12450 }
12451
12452 void
12453 CallFlagEvent()
12454 {
12455     /* Call your opponent's flag (claim a win on time) */
12456     if (appData.icsActive) {
12457         SendToICS(ics_prefix);
12458         SendToICS("flag\n");
12459     } else {
12460         switch (gameMode) {
12461           default:
12462             return;
12463           case MachinePlaysWhite:
12464             if (whiteFlag) {
12465                 if (blackFlag)
12466                   GameEnds(GameIsDrawn, "Both players ran out of time",
12467                            GE_PLAYER);
12468                 else
12469                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12470             } else {
12471                 DisplayError(_("Your opponent is not out of time"), 0);
12472             }
12473             break;
12474           case MachinePlaysBlack:
12475             if (blackFlag) {
12476                 if (whiteFlag)
12477                   GameEnds(GameIsDrawn, "Both players ran out of time",
12478                            GE_PLAYER);
12479                 else
12480                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12481             } else {
12482                 DisplayError(_("Your opponent is not out of time"), 0);
12483             }
12484             break;
12485         }
12486     }
12487 }
12488
12489 void
12490 DrawEvent()
12491 {
12492     /* Offer draw or accept pending draw offer from opponent */
12493
12494     if (appData.icsActive) {
12495         /* Note: tournament rules require draw offers to be
12496            made after you make your move but before you punch
12497            your clock.  Currently ICS doesn't let you do that;
12498            instead, you immediately punch your clock after making
12499            a move, but you can offer a draw at any time. */
12500
12501         SendToICS(ics_prefix);
12502         SendToICS("draw\n");
12503         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12504     } else if (cmailMsgLoaded) {
12505         if (currentMove == cmailOldMove &&
12506             commentList[cmailOldMove] != NULL &&
12507             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12508                    "Black offers a draw" : "White offers a draw")) {
12509             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12510             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12511         } else if (currentMove == cmailOldMove + 1) {
12512             char *offer = WhiteOnMove(cmailOldMove) ?
12513               "White offers a draw" : "Black offers a draw";
12514             AppendComment(currentMove, offer, TRUE);
12515             DisplayComment(currentMove - 1, offer);
12516             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12517         } else {
12518             DisplayError(_("You must make your move before offering a draw"), 0);
12519             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12520         }
12521     } else if (first.offeredDraw) {
12522         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12523     } else {
12524         if (first.sendDrawOffers) {
12525             SendToProgram("draw\n", &first);
12526             userOfferedDraw = TRUE;
12527         }
12528     }
12529 }
12530
12531 void
12532 AdjournEvent()
12533 {
12534     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12535
12536     if (appData.icsActive) {
12537         SendToICS(ics_prefix);
12538         SendToICS("adjourn\n");
12539     } else {
12540         /* Currently GNU Chess doesn't offer or accept Adjourns */
12541     }
12542 }
12543
12544
12545 void
12546 AbortEvent()
12547 {
12548     /* Offer Abort or accept pending Abort offer from opponent */
12549
12550     if (appData.icsActive) {
12551         SendToICS(ics_prefix);
12552         SendToICS("abort\n");
12553     } else {
12554         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12555     }
12556 }
12557
12558 void
12559 ResignEvent()
12560 {
12561     /* Resign.  You can do this even if it's not your turn. */
12562
12563     if (appData.icsActive) {
12564         SendToICS(ics_prefix);
12565         SendToICS("resign\n");
12566     } else {
12567         switch (gameMode) {
12568           case MachinePlaysWhite:
12569             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12570             break;
12571           case MachinePlaysBlack:
12572             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12573             break;
12574           case EditGame:
12575             if (cmailMsgLoaded) {
12576                 TruncateGame();
12577                 if (WhiteOnMove(cmailOldMove)) {
12578                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12579                 } else {
12580                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12581                 }
12582                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12583             }
12584             break;
12585           default:
12586             break;
12587         }
12588     }
12589 }
12590
12591
12592 void
12593 StopObservingEvent()
12594 {
12595     /* Stop observing current games */
12596     SendToICS(ics_prefix);
12597     SendToICS("unobserve\n");
12598 }
12599
12600 void
12601 StopExaminingEvent()
12602 {
12603     /* Stop observing current game */
12604     SendToICS(ics_prefix);
12605     SendToICS("unexamine\n");
12606 }
12607
12608 void
12609 ForwardInner(target)
12610      int target;
12611 {
12612     int limit;
12613
12614     if (appData.debugMode)
12615         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12616                 target, currentMove, forwardMostMove);
12617
12618     if (gameMode == EditPosition)
12619       return;
12620
12621     if (gameMode == PlayFromGameFile && !pausing)
12622       PauseEvent();
12623
12624     if (gameMode == IcsExamining && pausing)
12625       limit = pauseExamForwardMostMove;
12626     else
12627       limit = forwardMostMove;
12628
12629     if (target > limit) target = limit;
12630
12631     if (target > 0 && moveList[target - 1][0]) {
12632         int fromX, fromY, toX, toY;
12633         toX = moveList[target - 1][2] - AAA;
12634         toY = moveList[target - 1][3] - ONE;
12635         if (moveList[target - 1][1] == '@') {
12636             if (appData.highlightLastMove) {
12637                 SetHighlights(-1, -1, toX, toY);
12638             }
12639         } else {
12640             fromX = moveList[target - 1][0] - AAA;
12641             fromY = moveList[target - 1][1] - ONE;
12642             if (target == currentMove + 1) {
12643                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12644             }
12645             if (appData.highlightLastMove) {
12646                 SetHighlights(fromX, fromY, toX, toY);
12647             }
12648         }
12649     }
12650     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12651         gameMode == Training || gameMode == PlayFromGameFile ||
12652         gameMode == AnalyzeFile) {
12653         while (currentMove < target) {
12654             SendMoveToProgram(currentMove++, &first);
12655         }
12656     } else {
12657         currentMove = target;
12658     }
12659
12660     if (gameMode == EditGame || gameMode == EndOfGame) {
12661         whiteTimeRemaining = timeRemaining[0][currentMove];
12662         blackTimeRemaining = timeRemaining[1][currentMove];
12663     }
12664     DisplayBothClocks();
12665     DisplayMove(currentMove - 1);
12666     DrawPosition(FALSE, boards[currentMove]);
12667     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12668     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12669         DisplayComment(currentMove - 1, commentList[currentMove]);
12670     }
12671 }
12672
12673
12674 void
12675 ForwardEvent()
12676 {
12677     if (gameMode == IcsExamining && !pausing) {
12678         SendToICS(ics_prefix);
12679         SendToICS("forward\n");
12680     } else {
12681         ForwardInner(currentMove + 1);
12682     }
12683 }
12684
12685 void
12686 ToEndEvent()
12687 {
12688     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12689         /* to optimze, we temporarily turn off analysis mode while we feed
12690          * the remaining moves to the engine. Otherwise we get analysis output
12691          * after each move.
12692          */
12693         if (first.analysisSupport) {
12694           SendToProgram("exit\nforce\n", &first);
12695           first.analyzing = FALSE;
12696         }
12697     }
12698
12699     if (gameMode == IcsExamining && !pausing) {
12700         SendToICS(ics_prefix);
12701         SendToICS("forward 999999\n");
12702     } else {
12703         ForwardInner(forwardMostMove);
12704     }
12705
12706     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12707         /* we have fed all the moves, so reactivate analysis mode */
12708         SendToProgram("analyze\n", &first);
12709         first.analyzing = TRUE;
12710         /*first.maybeThinking = TRUE;*/
12711         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12712     }
12713 }
12714
12715 void
12716 BackwardInner(target)
12717      int target;
12718 {
12719     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12720
12721     if (appData.debugMode)
12722         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12723                 target, currentMove, forwardMostMove);
12724
12725     if (gameMode == EditPosition) return;
12726     if (currentMove <= backwardMostMove) {
12727         ClearHighlights();
12728         DrawPosition(full_redraw, boards[currentMove]);
12729         return;
12730     }
12731     if (gameMode == PlayFromGameFile && !pausing)
12732       PauseEvent();
12733
12734     if (moveList[target][0]) {
12735         int fromX, fromY, toX, toY;
12736         toX = moveList[target][2] - AAA;
12737         toY = moveList[target][3] - ONE;
12738         if (moveList[target][1] == '@') {
12739             if (appData.highlightLastMove) {
12740                 SetHighlights(-1, -1, toX, toY);
12741             }
12742         } else {
12743             fromX = moveList[target][0] - AAA;
12744             fromY = moveList[target][1] - ONE;
12745             if (target == currentMove - 1) {
12746                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12747             }
12748             if (appData.highlightLastMove) {
12749                 SetHighlights(fromX, fromY, toX, toY);
12750             }
12751         }
12752     }
12753     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12754         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12755         while (currentMove > target) {
12756             SendToProgram("undo\n", &first);
12757             currentMove--;
12758         }
12759     } else {
12760         currentMove = target;
12761     }
12762
12763     if (gameMode == EditGame || gameMode == EndOfGame) {
12764         whiteTimeRemaining = timeRemaining[0][currentMove];
12765         blackTimeRemaining = timeRemaining[1][currentMove];
12766     }
12767     DisplayBothClocks();
12768     DisplayMove(currentMove - 1);
12769     DrawPosition(full_redraw, boards[currentMove]);
12770     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12771     // [HGM] PV info: routine tests if comment empty
12772     DisplayComment(currentMove - 1, commentList[currentMove]);
12773 }
12774
12775 void
12776 BackwardEvent()
12777 {
12778     if (gameMode == IcsExamining && !pausing) {
12779         SendToICS(ics_prefix);
12780         SendToICS("backward\n");
12781     } else {
12782         BackwardInner(currentMove - 1);
12783     }
12784 }
12785
12786 void
12787 ToStartEvent()
12788 {
12789     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12790         /* to optimize, we temporarily turn off analysis mode while we undo
12791          * all the moves. Otherwise we get analysis output after each undo.
12792          */
12793         if (first.analysisSupport) {
12794           SendToProgram("exit\nforce\n", &first);
12795           first.analyzing = FALSE;
12796         }
12797     }
12798
12799     if (gameMode == IcsExamining && !pausing) {
12800         SendToICS(ics_prefix);
12801         SendToICS("backward 999999\n");
12802     } else {
12803         BackwardInner(backwardMostMove);
12804     }
12805
12806     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12807         /* we have fed all the moves, so reactivate analysis mode */
12808         SendToProgram("analyze\n", &first);
12809         first.analyzing = TRUE;
12810         /*first.maybeThinking = TRUE;*/
12811         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12812     }
12813 }
12814
12815 void
12816 ToNrEvent(int to)
12817 {
12818   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12819   if (to >= forwardMostMove) to = forwardMostMove;
12820   if (to <= backwardMostMove) to = backwardMostMove;
12821   if (to < currentMove) {
12822     BackwardInner(to);
12823   } else {
12824     ForwardInner(to);
12825   }
12826 }
12827
12828 void
12829 RevertEvent(Boolean annotate)
12830 {
12831     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12832         return;
12833     }
12834     if (gameMode != IcsExamining) {
12835         DisplayError(_("You are not examining a game"), 0);
12836         return;
12837     }
12838     if (pausing) {
12839         DisplayError(_("You can't revert while pausing"), 0);
12840         return;
12841     }
12842     SendToICS(ics_prefix);
12843     SendToICS("revert\n");
12844 }
12845
12846 void
12847 RetractMoveEvent()
12848 {
12849     switch (gameMode) {
12850       case MachinePlaysWhite:
12851       case MachinePlaysBlack:
12852         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12853             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12854             return;
12855         }
12856         if (forwardMostMove < 2) return;
12857         currentMove = forwardMostMove = forwardMostMove - 2;
12858         whiteTimeRemaining = timeRemaining[0][currentMove];
12859         blackTimeRemaining = timeRemaining[1][currentMove];
12860         DisplayBothClocks();
12861         DisplayMove(currentMove - 1);
12862         ClearHighlights();/*!! could figure this out*/
12863         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12864         SendToProgram("remove\n", &first);
12865         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12866         break;
12867
12868       case BeginningOfGame:
12869       default:
12870         break;
12871
12872       case IcsPlayingWhite:
12873       case IcsPlayingBlack:
12874         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12875             SendToICS(ics_prefix);
12876             SendToICS("takeback 2\n");
12877         } else {
12878             SendToICS(ics_prefix);
12879             SendToICS("takeback 1\n");
12880         }
12881         break;
12882     }
12883 }
12884
12885 void
12886 MoveNowEvent()
12887 {
12888     ChessProgramState *cps;
12889
12890     switch (gameMode) {
12891       case MachinePlaysWhite:
12892         if (!WhiteOnMove(forwardMostMove)) {
12893             DisplayError(_("It is your turn"), 0);
12894             return;
12895         }
12896         cps = &first;
12897         break;
12898       case MachinePlaysBlack:
12899         if (WhiteOnMove(forwardMostMove)) {
12900             DisplayError(_("It is your turn"), 0);
12901             return;
12902         }
12903         cps = &first;
12904         break;
12905       case TwoMachinesPlay:
12906         if (WhiteOnMove(forwardMostMove) ==
12907             (first.twoMachinesColor[0] == 'w')) {
12908             cps = &first;
12909         } else {
12910             cps = &second;
12911         }
12912         break;
12913       case BeginningOfGame:
12914       default:
12915         return;
12916     }
12917     SendToProgram("?\n", cps);
12918 }
12919
12920 void
12921 TruncateGameEvent()
12922 {
12923     EditGameEvent();
12924     if (gameMode != EditGame) return;
12925     TruncateGame();
12926 }
12927
12928 void
12929 TruncateGame()
12930 {
12931     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12932     if (forwardMostMove > currentMove) {
12933         if (gameInfo.resultDetails != NULL) {
12934             free(gameInfo.resultDetails);
12935             gameInfo.resultDetails = NULL;
12936             gameInfo.result = GameUnfinished;
12937         }
12938         forwardMostMove = currentMove;
12939         HistorySet(parseList, backwardMostMove, forwardMostMove,
12940                    currentMove-1);
12941     }
12942 }
12943
12944 void
12945 HintEvent()
12946 {
12947     if (appData.noChessProgram) return;
12948     switch (gameMode) {
12949       case MachinePlaysWhite:
12950         if (WhiteOnMove(forwardMostMove)) {
12951             DisplayError(_("Wait until your turn"), 0);
12952             return;
12953         }
12954         break;
12955       case BeginningOfGame:
12956       case MachinePlaysBlack:
12957         if (!WhiteOnMove(forwardMostMove)) {
12958             DisplayError(_("Wait until your turn"), 0);
12959             return;
12960         }
12961         break;
12962       default:
12963         DisplayError(_("No hint available"), 0);
12964         return;
12965     }
12966     SendToProgram("hint\n", &first);
12967     hintRequested = TRUE;
12968 }
12969
12970 void
12971 BookEvent()
12972 {
12973     if (appData.noChessProgram) return;
12974     switch (gameMode) {
12975       case MachinePlaysWhite:
12976         if (WhiteOnMove(forwardMostMove)) {
12977             DisplayError(_("Wait until your turn"), 0);
12978             return;
12979         }
12980         break;
12981       case BeginningOfGame:
12982       case MachinePlaysBlack:
12983         if (!WhiteOnMove(forwardMostMove)) {
12984             DisplayError(_("Wait until your turn"), 0);
12985             return;
12986         }
12987         break;
12988       case EditPosition:
12989         EditPositionDone(TRUE);
12990         break;
12991       case TwoMachinesPlay:
12992         return;
12993       default:
12994         break;
12995     }
12996     SendToProgram("bk\n", &first);
12997     bookOutput[0] = NULLCHAR;
12998     bookRequested = TRUE;
12999 }
13000
13001 void
13002 AboutGameEvent()
13003 {
13004     char *tags = PGNTags(&gameInfo);
13005     TagsPopUp(tags, CmailMsg());
13006     free(tags);
13007 }
13008
13009 /* end button procedures */
13010
13011 void
13012 PrintPosition(fp, move)
13013      FILE *fp;
13014      int move;
13015 {
13016     int i, j;
13017
13018     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13019         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13020             char c = PieceToChar(boards[move][i][j]);
13021             fputc(c == 'x' ? '.' : c, fp);
13022             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13023         }
13024     }
13025     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13026       fprintf(fp, "white to play\n");
13027     else
13028       fprintf(fp, "black to play\n");
13029 }
13030
13031 void
13032 PrintOpponents(fp)
13033      FILE *fp;
13034 {
13035     if (gameInfo.white != NULL) {
13036         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13037     } else {
13038         fprintf(fp, "\n");
13039     }
13040 }
13041
13042 /* Find last component of program's own name, using some heuristics */
13043 void
13044 TidyProgramName(prog, host, buf)
13045      char *prog, *host, buf[MSG_SIZ];
13046 {
13047     char *p, *q;
13048     int local = (strcmp(host, "localhost") == 0);
13049     while (!local && (p = strchr(prog, ';')) != NULL) {
13050         p++;
13051         while (*p == ' ') p++;
13052         prog = p;
13053     }
13054     if (*prog == '"' || *prog == '\'') {
13055         q = strchr(prog + 1, *prog);
13056     } else {
13057         q = strchr(prog, ' ');
13058     }
13059     if (q == NULL) q = prog + strlen(prog);
13060     p = q;
13061     while (p >= prog && *p != '/' && *p != '\\') p--;
13062     p++;
13063     if(p == prog && *p == '"') p++;
13064     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13065     memcpy(buf, p, q - p);
13066     buf[q - p] = NULLCHAR;
13067     if (!local) {
13068         strcat(buf, "@");
13069         strcat(buf, host);
13070     }
13071 }
13072
13073 char *
13074 TimeControlTagValue()
13075 {
13076     char buf[MSG_SIZ];
13077     if (!appData.clockMode) {
13078       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13079     } else if (movesPerSession > 0) {
13080       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13081     } else if (timeIncrement == 0) {
13082       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13083     } else {
13084       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13085     }
13086     return StrSave(buf);
13087 }
13088
13089 void
13090 SetGameInfo()
13091 {
13092     /* This routine is used only for certain modes */
13093     VariantClass v = gameInfo.variant;
13094     ChessMove r = GameUnfinished;
13095     char *p = NULL;
13096
13097     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13098         r = gameInfo.result;
13099         p = gameInfo.resultDetails;
13100         gameInfo.resultDetails = NULL;
13101     }
13102     ClearGameInfo(&gameInfo);
13103     gameInfo.variant = v;
13104
13105     switch (gameMode) {
13106       case MachinePlaysWhite:
13107         gameInfo.event = StrSave( appData.pgnEventHeader );
13108         gameInfo.site = StrSave(HostName());
13109         gameInfo.date = PGNDate();
13110         gameInfo.round = StrSave("-");
13111         gameInfo.white = StrSave(first.tidy);
13112         gameInfo.black = StrSave(UserName());
13113         gameInfo.timeControl = TimeControlTagValue();
13114         break;
13115
13116       case MachinePlaysBlack:
13117         gameInfo.event = StrSave( appData.pgnEventHeader );
13118         gameInfo.site = StrSave(HostName());
13119         gameInfo.date = PGNDate();
13120         gameInfo.round = StrSave("-");
13121         gameInfo.white = StrSave(UserName());
13122         gameInfo.black = StrSave(first.tidy);
13123         gameInfo.timeControl = TimeControlTagValue();
13124         break;
13125
13126       case TwoMachinesPlay:
13127         gameInfo.event = StrSave( appData.pgnEventHeader );
13128         gameInfo.site = StrSave(HostName());
13129         gameInfo.date = PGNDate();
13130         if (matchGame > 0) {
13131             char buf[MSG_SIZ];
13132             snprintf(buf, MSG_SIZ, "%d", matchGame);
13133             gameInfo.round = StrSave(buf);
13134         } else {
13135             gameInfo.round = StrSave("-");
13136         }
13137         if (first.twoMachinesColor[0] == 'w') {
13138             gameInfo.white = StrSave(first.tidy);
13139             gameInfo.black = StrSave(second.tidy);
13140         } else {
13141             gameInfo.white = StrSave(second.tidy);
13142             gameInfo.black = StrSave(first.tidy);
13143         }
13144         gameInfo.timeControl = TimeControlTagValue();
13145         break;
13146
13147       case EditGame:
13148         gameInfo.event = StrSave("Edited game");
13149         gameInfo.site = StrSave(HostName());
13150         gameInfo.date = PGNDate();
13151         gameInfo.round = StrSave("-");
13152         gameInfo.white = StrSave("-");
13153         gameInfo.black = StrSave("-");
13154         gameInfo.result = r;
13155         gameInfo.resultDetails = p;
13156         break;
13157
13158       case EditPosition:
13159         gameInfo.event = StrSave("Edited position");
13160         gameInfo.site = StrSave(HostName());
13161         gameInfo.date = PGNDate();
13162         gameInfo.round = StrSave("-");
13163         gameInfo.white = StrSave("-");
13164         gameInfo.black = StrSave("-");
13165         break;
13166
13167       case IcsPlayingWhite:
13168       case IcsPlayingBlack:
13169       case IcsObserving:
13170       case IcsExamining:
13171         break;
13172
13173       case PlayFromGameFile:
13174         gameInfo.event = StrSave("Game from non-PGN file");
13175         gameInfo.site = StrSave(HostName());
13176         gameInfo.date = PGNDate();
13177         gameInfo.round = StrSave("-");
13178         gameInfo.white = StrSave("?");
13179         gameInfo.black = StrSave("?");
13180         break;
13181
13182       default:
13183         break;
13184     }
13185 }
13186
13187 void
13188 ReplaceComment(index, text)
13189      int index;
13190      char *text;
13191 {
13192     int len;
13193
13194     while (*text == '\n') text++;
13195     len = strlen(text);
13196     while (len > 0 && text[len - 1] == '\n') len--;
13197
13198     if (commentList[index] != NULL)
13199       free(commentList[index]);
13200
13201     if (len == 0) {
13202         commentList[index] = NULL;
13203         return;
13204     }
13205   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13206       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13207       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13208     commentList[index] = (char *) malloc(len + 2);
13209     strncpy(commentList[index], text, len);
13210     commentList[index][len] = '\n';
13211     commentList[index][len + 1] = NULLCHAR;
13212   } else {
13213     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13214     char *p;
13215     commentList[index] = (char *) malloc(len + 7);
13216     safeStrCpy(commentList[index], "{\n", 3);
13217     safeStrCpy(commentList[index]+2, text, len+1);
13218     commentList[index][len+2] = NULLCHAR;
13219     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13220     strcat(commentList[index], "\n}\n");
13221   }
13222 }
13223
13224 void
13225 CrushCRs(text)
13226      char *text;
13227 {
13228   char *p = text;
13229   char *q = text;
13230   char ch;
13231
13232   do {
13233     ch = *p++;
13234     if (ch == '\r') continue;
13235     *q++ = ch;
13236   } while (ch != '\0');
13237 }
13238
13239 void
13240 AppendComment(index, text, addBraces)
13241      int index;
13242      char *text;
13243      Boolean addBraces; // [HGM] braces: tells if we should add {}
13244 {
13245     int oldlen, len;
13246     char *old;
13247
13248 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13249     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13250
13251     CrushCRs(text);
13252     while (*text == '\n') text++;
13253     len = strlen(text);
13254     while (len > 0 && text[len - 1] == '\n') len--;
13255
13256     if (len == 0) return;
13257
13258     if (commentList[index] != NULL) {
13259         old = commentList[index];
13260         oldlen = strlen(old);
13261         while(commentList[index][oldlen-1] ==  '\n')
13262           commentList[index][--oldlen] = NULLCHAR;
13263         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13264         safeStrCpy(commentList[index], old, oldlen);
13265         free(old);
13266         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13267         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13268           if(addBraces) addBraces = FALSE; else { text++; len--; }
13269           while (*text == '\n') { text++; len--; }
13270           commentList[index][--oldlen] = NULLCHAR;
13271       }
13272         if(addBraces) strcat(commentList[index], "\n{\n");
13273         else          strcat(commentList[index], "\n");
13274         strcat(commentList[index], text);
13275         if(addBraces) strcat(commentList[index], "\n}\n");
13276         else          strcat(commentList[index], "\n");
13277     } else {
13278         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13279         if(addBraces)
13280           safeStrCpy(commentList[index], "{\n", sizeof(commentList[index])/sizeof(commentList[index][0]));
13281         else commentList[index][0] = NULLCHAR;
13282         strcat(commentList[index], text);
13283         strcat(commentList[index], "\n");
13284         if(addBraces) strcat(commentList[index], "}\n");
13285     }
13286 }
13287
13288 static char * FindStr( char * text, char * sub_text )
13289 {
13290     char * result = strstr( text, sub_text );
13291
13292     if( result != NULL ) {
13293         result += strlen( sub_text );
13294     }
13295
13296     return result;
13297 }
13298
13299 /* [AS] Try to extract PV info from PGN comment */
13300 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13301 char *GetInfoFromComment( int index, char * text )
13302 {
13303     char * sep = text;
13304
13305     if( text != NULL && index > 0 ) {
13306         int score = 0;
13307         int depth = 0;
13308         int time = -1, sec = 0, deci;
13309         char * s_eval = FindStr( text, "[%eval " );
13310         char * s_emt = FindStr( text, "[%emt " );
13311
13312         if( s_eval != NULL || s_emt != NULL ) {
13313             /* New style */
13314             char delim;
13315
13316             if( s_eval != NULL ) {
13317                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13318                     return text;
13319                 }
13320
13321                 if( delim != ']' ) {
13322                     return text;
13323                 }
13324             }
13325
13326             if( s_emt != NULL ) {
13327             }
13328                 return text;
13329         }
13330         else {
13331             /* We expect something like: [+|-]nnn.nn/dd */
13332             int score_lo = 0;
13333
13334             if(*text != '{') return text; // [HGM] braces: must be normal comment
13335
13336             sep = strchr( text, '/' );
13337             if( sep == NULL || sep < (text+4) ) {
13338                 return text;
13339             }
13340
13341             time = -1; sec = -1; deci = -1;
13342             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13343                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13344                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13345                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13346                 return text;
13347             }
13348
13349             if( score_lo < 0 || score_lo >= 100 ) {
13350                 return text;
13351             }
13352
13353             if(sec >= 0) time = 600*time + 10*sec; else
13354             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13355
13356             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13357
13358             /* [HGM] PV time: now locate end of PV info */
13359             while( *++sep >= '0' && *sep <= '9'); // strip depth
13360             if(time >= 0)
13361             while( *++sep >= '0' && *sep <= '9'); // strip time
13362             if(sec >= 0)
13363             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13364             if(deci >= 0)
13365             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13366             while(*sep == ' ') sep++;
13367         }
13368
13369         if( depth <= 0 ) {
13370             return text;
13371         }
13372
13373         if( time < 0 ) {
13374             time = -1;
13375         }
13376
13377         pvInfoList[index-1].depth = depth;
13378         pvInfoList[index-1].score = score;
13379         pvInfoList[index-1].time  = 10*time; // centi-sec
13380         if(*sep == '}') *sep = 0; else *--sep = '{';
13381     }
13382     return sep;
13383 }
13384
13385 void
13386 SendToProgram(message, cps)
13387      char *message;
13388      ChessProgramState *cps;
13389 {
13390     int count, outCount, error;
13391     char buf[MSG_SIZ];
13392
13393     if (cps->pr == NULL) return;
13394     Attention(cps);
13395
13396     if (appData.debugMode) {
13397         TimeMark now;
13398         GetTimeMark(&now);
13399         fprintf(debugFP, "%ld >%-6s: %s",
13400                 SubtractTimeMarks(&now, &programStartTime),
13401                 cps->which, message);
13402     }
13403
13404     count = strlen(message);
13405     outCount = OutputToProcess(cps->pr, message, count, &error);
13406     if (outCount < count && !exiting
13407                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13408       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13409         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13410             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13411                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13412                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13413             } else {
13414                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13415             }
13416             gameInfo.resultDetails = StrSave(buf);
13417         }
13418         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13419     }
13420 }
13421
13422 void
13423 ReceiveFromProgram(isr, closure, message, count, error)
13424      InputSourceRef isr;
13425      VOIDSTAR closure;
13426      char *message;
13427      int count;
13428      int error;
13429 {
13430     char *end_str;
13431     char buf[MSG_SIZ];
13432     ChessProgramState *cps = (ChessProgramState *)closure;
13433
13434     if (isr != cps->isr) return; /* Killed intentionally */
13435     if (count <= 0) {
13436         if (count == 0) {
13437             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13438                     cps->which, cps->program);
13439         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13440                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13441                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13442                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13443                 } else {
13444                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13445                 }
13446                 gameInfo.resultDetails = StrSave(buf);
13447             }
13448             RemoveInputSource(cps->isr);
13449             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13450         } else {
13451             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13452                     cps->which, cps->program);
13453             RemoveInputSource(cps->isr);
13454
13455             /* [AS] Program is misbehaving badly... kill it */
13456             if( count == -2 ) {
13457                 DestroyChildProcess( cps->pr, 9 );
13458                 cps->pr = NoProc;
13459             }
13460
13461             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13462         }
13463         return;
13464     }
13465
13466     if ((end_str = strchr(message, '\r')) != NULL)
13467       *end_str = NULLCHAR;
13468     if ((end_str = strchr(message, '\n')) != NULL)
13469       *end_str = NULLCHAR;
13470
13471     if (appData.debugMode) {
13472         TimeMark now; int print = 1;
13473         char *quote = ""; char c; int i;
13474
13475         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13476                 char start = message[0];
13477                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13478                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13479                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13480                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13481                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13482                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13483                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13484                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13485                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13486                     print = (appData.engineComments >= 2);
13487                 }
13488                 message[0] = start; // restore original message
13489         }
13490         if(print) {
13491                 GetTimeMark(&now);
13492                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13493                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13494                         quote,
13495                         message);
13496         }
13497     }
13498
13499     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13500     if (appData.icsEngineAnalyze) {
13501         if (strstr(message, "whisper") != NULL ||
13502              strstr(message, "kibitz") != NULL ||
13503             strstr(message, "tellics") != NULL) return;
13504     }
13505
13506     HandleMachineMove(message, cps);
13507 }
13508
13509
13510 void
13511 SendTimeControl(cps, mps, tc, inc, sd, st)
13512      ChessProgramState *cps;
13513      int mps, inc, sd, st;
13514      long tc;
13515 {
13516     char buf[MSG_SIZ];
13517     int seconds;
13518
13519     if( timeControl_2 > 0 ) {
13520         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13521             tc = timeControl_2;
13522         }
13523     }
13524     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13525     inc /= cps->timeOdds;
13526     st  /= cps->timeOdds;
13527
13528     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13529
13530     if (st > 0) {
13531       /* Set exact time per move, normally using st command */
13532       if (cps->stKludge) {
13533         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13534         seconds = st % 60;
13535         if (seconds == 0) {
13536           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13537         } else {
13538           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13539         }
13540       } else {
13541         snprintf(buf, MSG_SIZ, "st %d\n", st);
13542       }
13543     } else {
13544       /* Set conventional or incremental time control, using level command */
13545       if (seconds == 0) {
13546         /* Note old gnuchess bug -- minutes:seconds used to not work.
13547            Fixed in later versions, but still avoid :seconds
13548            when seconds is 0. */
13549         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000);
13550       } else {
13551         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13552                  seconds, inc/1000);
13553       }
13554     }
13555     SendToProgram(buf, cps);
13556
13557     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13558     /* Orthogonally, limit search to given depth */
13559     if (sd > 0) {
13560       if (cps->sdKludge) {
13561         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13562       } else {
13563         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13564       }
13565       SendToProgram(buf, cps);
13566     }
13567
13568     if(cps->nps > 0) { /* [HGM] nps */
13569         if(cps->supportsNPS == FALSE)
13570           cps->nps = -1; // don't use if engine explicitly says not supported!
13571         else {
13572           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13573           SendToProgram(buf, cps);
13574         }
13575     }
13576 }
13577
13578 ChessProgramState *WhitePlayer()
13579 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13580 {
13581     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13582        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13583         return &second;
13584     return &first;
13585 }
13586
13587 void
13588 SendTimeRemaining(cps, machineWhite)
13589      ChessProgramState *cps;
13590      int /*boolean*/ machineWhite;
13591 {
13592     char message[MSG_SIZ];
13593     long time, otime;
13594
13595     /* Note: this routine must be called when the clocks are stopped
13596        or when they have *just* been set or switched; otherwise
13597        it will be off by the time since the current tick started.
13598     */
13599     if (machineWhite) {
13600         time = whiteTimeRemaining / 10;
13601         otime = blackTimeRemaining / 10;
13602     } else {
13603         time = blackTimeRemaining / 10;
13604         otime = whiteTimeRemaining / 10;
13605     }
13606     /* [HGM] translate opponent's time by time-odds factor */
13607     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13608     if (appData.debugMode) {
13609         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13610     }
13611
13612     if (time <= 0) time = 1;
13613     if (otime <= 0) otime = 1;
13614
13615     snprintf(message, MSG_SIZ, "time %ld\n", time);
13616     SendToProgram(message, cps);
13617
13618     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13619     SendToProgram(message, cps);
13620 }
13621
13622 int
13623 BoolFeature(p, name, loc, cps)
13624      char **p;
13625      char *name;
13626      int *loc;
13627      ChessProgramState *cps;
13628 {
13629   char buf[MSG_SIZ];
13630   int len = strlen(name);
13631   int val;
13632
13633   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13634     (*p) += len + 1;
13635     sscanf(*p, "%d", &val);
13636     *loc = (val != 0);
13637     while (**p && **p != ' ')
13638       (*p)++;
13639     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13640     SendToProgram(buf, cps);
13641     return TRUE;
13642   }
13643   return FALSE;
13644 }
13645
13646 int
13647 IntFeature(p, name, loc, cps)
13648      char **p;
13649      char *name;
13650      int *loc;
13651      ChessProgramState *cps;
13652 {
13653   char buf[MSG_SIZ];
13654   int len = strlen(name);
13655   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13656     (*p) += len + 1;
13657     sscanf(*p, "%d", loc);
13658     while (**p && **p != ' ') (*p)++;
13659     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13660     SendToProgram(buf, cps);
13661     return TRUE;
13662   }
13663   return FALSE;
13664 }
13665
13666 int
13667 StringFeature(p, name, loc, cps)
13668      char **p;
13669      char *name;
13670      char loc[];
13671      ChessProgramState *cps;
13672 {
13673   char buf[MSG_SIZ];
13674   int len = strlen(name);
13675   if (strncmp((*p), name, len) == 0
13676       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13677     (*p) += len + 2;
13678     sscanf(*p, "%[^\"]", loc);
13679     while (**p && **p != '\"') (*p)++;
13680     if (**p == '\"') (*p)++;
13681     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13682     SendToProgram(buf, cps);
13683     return TRUE;
13684   }
13685   return FALSE;
13686 }
13687
13688 int
13689 ParseOption(Option *opt, ChessProgramState *cps)
13690 // [HGM] options: process the string that defines an engine option, and determine
13691 // name, type, default value, and allowed value range
13692 {
13693         char *p, *q, buf[MSG_SIZ];
13694         int n, min = (-1)<<31, max = 1<<31, def;
13695
13696         if(p = strstr(opt->name, " -spin ")) {
13697             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13698             if(max < min) max = min; // enforce consistency
13699             if(def < min) def = min;
13700             if(def > max) def = max;
13701             opt->value = def;
13702             opt->min = min;
13703             opt->max = max;
13704             opt->type = Spin;
13705         } else if((p = strstr(opt->name, " -slider "))) {
13706             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13707             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13708             if(max < min) max = min; // enforce consistency
13709             if(def < min) def = min;
13710             if(def > max) def = max;
13711             opt->value = def;
13712             opt->min = min;
13713             opt->max = max;
13714             opt->type = Spin; // Slider;
13715         } else if((p = strstr(opt->name, " -string "))) {
13716             opt->textValue = p+9;
13717             opt->type = TextBox;
13718         } else if((p = strstr(opt->name, " -file "))) {
13719             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13720             opt->textValue = p+7;
13721             opt->type = TextBox; // FileName;
13722         } else if((p = strstr(opt->name, " -path "))) {
13723             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13724             opt->textValue = p+7;
13725             opt->type = TextBox; // PathName;
13726         } else if(p = strstr(opt->name, " -check ")) {
13727             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13728             opt->value = (def != 0);
13729             opt->type = CheckBox;
13730         } else if(p = strstr(opt->name, " -combo ")) {
13731             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13732             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13733             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13734             opt->value = n = 0;
13735             while(q = StrStr(q, " /// ")) {
13736                 n++; *q = 0;    // count choices, and null-terminate each of them
13737                 q += 5;
13738                 if(*q == '*') { // remember default, which is marked with * prefix
13739                     q++;
13740                     opt->value = n;
13741                 }
13742                 cps->comboList[cps->comboCnt++] = q;
13743             }
13744             cps->comboList[cps->comboCnt++] = NULL;
13745             opt->max = n + 1;
13746             opt->type = ComboBox;
13747         } else if(p = strstr(opt->name, " -button")) {
13748             opt->type = Button;
13749         } else if(p = strstr(opt->name, " -save")) {
13750             opt->type = SaveButton;
13751         } else return FALSE;
13752         *p = 0; // terminate option name
13753         // now look if the command-line options define a setting for this engine option.
13754         if(cps->optionSettings && cps->optionSettings[0])
13755             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13756         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13757           snprintf(buf, MSG_SIZ, "option %s", p);
13758                 if(p = strstr(buf, ",")) *p = 0;
13759                 strcat(buf, "\n");
13760                 SendToProgram(buf, cps);
13761         }
13762         return TRUE;
13763 }
13764
13765 void
13766 FeatureDone(cps, val)
13767      ChessProgramState* cps;
13768      int val;
13769 {
13770   DelayedEventCallback cb = GetDelayedEvent();
13771   if ((cb == InitBackEnd3 && cps == &first) ||
13772       (cb == TwoMachinesEventIfReady && cps == &second)) {
13773     CancelDelayedEvent();
13774     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13775   }
13776   cps->initDone = val;
13777 }
13778
13779 /* Parse feature command from engine */
13780 void
13781 ParseFeatures(args, cps)
13782      char* args;
13783      ChessProgramState *cps;
13784 {
13785   char *p = args;
13786   char *q;
13787   int val;
13788   char buf[MSG_SIZ];
13789
13790   for (;;) {
13791     while (*p == ' ') p++;
13792     if (*p == NULLCHAR) return;
13793
13794     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13795     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13796     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13797     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13798     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13799     if (BoolFeature(&p, "reuse", &val, cps)) {
13800       /* Engine can disable reuse, but can't enable it if user said no */
13801       if (!val) cps->reuse = FALSE;
13802       continue;
13803     }
13804     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13805     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13806       if (gameMode == TwoMachinesPlay) {
13807         DisplayTwoMachinesTitle();
13808       } else {
13809         DisplayTitle("");
13810       }
13811       continue;
13812     }
13813     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13814     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13815     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13816     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13817     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13818     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13819     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13820     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13821     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13822     if (IntFeature(&p, "done", &val, cps)) {
13823       FeatureDone(cps, val);
13824       continue;
13825     }
13826     /* Added by Tord: */
13827     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13828     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13829     /* End of additions by Tord */
13830
13831     /* [HGM] added features: */
13832     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13833     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13834     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13835     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13836     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13837     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13838     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13839         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13840           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13841             SendToProgram(buf, cps);
13842             continue;
13843         }
13844         if(cps->nrOptions >= MAX_OPTIONS) {
13845             cps->nrOptions--;
13846             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13847             DisplayError(buf, 0);
13848         }
13849         continue;
13850     }
13851     /* End of additions by HGM */
13852
13853     /* unknown feature: complain and skip */
13854     q = p;
13855     while (*q && *q != '=') q++;
13856     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13857     SendToProgram(buf, cps);
13858     p = q;
13859     if (*p == '=') {
13860       p++;
13861       if (*p == '\"') {
13862         p++;
13863         while (*p && *p != '\"') p++;
13864         if (*p == '\"') p++;
13865       } else {
13866         while (*p && *p != ' ') p++;
13867       }
13868     }
13869   }
13870
13871 }
13872
13873 void
13874 PeriodicUpdatesEvent(newState)
13875      int newState;
13876 {
13877     if (newState == appData.periodicUpdates)
13878       return;
13879
13880     appData.periodicUpdates=newState;
13881
13882     /* Display type changes, so update it now */
13883 //    DisplayAnalysis();
13884
13885     /* Get the ball rolling again... */
13886     if (newState) {
13887         AnalysisPeriodicEvent(1);
13888         StartAnalysisClock();
13889     }
13890 }
13891
13892 void
13893 PonderNextMoveEvent(newState)
13894      int newState;
13895 {
13896     if (newState == appData.ponderNextMove) return;
13897     if (gameMode == EditPosition) EditPositionDone(TRUE);
13898     if (newState) {
13899         SendToProgram("hard\n", &first);
13900         if (gameMode == TwoMachinesPlay) {
13901             SendToProgram("hard\n", &second);
13902         }
13903     } else {
13904         SendToProgram("easy\n", &first);
13905         thinkOutput[0] = NULLCHAR;
13906         if (gameMode == TwoMachinesPlay) {
13907             SendToProgram("easy\n", &second);
13908         }
13909     }
13910     appData.ponderNextMove = newState;
13911 }
13912
13913 void
13914 NewSettingEvent(option, feature, command, value)
13915      char *command;
13916      int option, value, *feature;
13917 {
13918     char buf[MSG_SIZ];
13919
13920     if (gameMode == EditPosition) EditPositionDone(TRUE);
13921     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
13922     if(feature == NULL || *feature) SendToProgram(buf, &first);
13923     if (gameMode == TwoMachinesPlay) {
13924         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13925     }
13926 }
13927
13928 void
13929 ShowThinkingEvent()
13930 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13931 {
13932     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13933     int newState = appData.showThinking
13934         // [HGM] thinking: other features now need thinking output as well
13935         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13936
13937     if (oldState == newState) return;
13938     oldState = newState;
13939     if (gameMode == EditPosition) EditPositionDone(TRUE);
13940     if (oldState) {
13941         SendToProgram("post\n", &first);
13942         if (gameMode == TwoMachinesPlay) {
13943             SendToProgram("post\n", &second);
13944         }
13945     } else {
13946         SendToProgram("nopost\n", &first);
13947         thinkOutput[0] = NULLCHAR;
13948         if (gameMode == TwoMachinesPlay) {
13949             SendToProgram("nopost\n", &second);
13950         }
13951     }
13952 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13953 }
13954
13955 void
13956 AskQuestionEvent(title, question, replyPrefix, which)
13957      char *title; char *question; char *replyPrefix; char *which;
13958 {
13959   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13960   if (pr == NoProc) return;
13961   AskQuestion(title, question, replyPrefix, pr);
13962 }
13963
13964 void
13965 DisplayMove(moveNumber)
13966      int moveNumber;
13967 {
13968     char message[MSG_SIZ];
13969     char res[MSG_SIZ];
13970     char cpThinkOutput[MSG_SIZ];
13971
13972     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13973
13974     if (moveNumber == forwardMostMove - 1 ||
13975         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13976
13977         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
13978
13979         if (strchr(cpThinkOutput, '\n')) {
13980             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13981         }
13982     } else {
13983         *cpThinkOutput = NULLCHAR;
13984     }
13985
13986     /* [AS] Hide thinking from human user */
13987     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13988         *cpThinkOutput = NULLCHAR;
13989         if( thinkOutput[0] != NULLCHAR ) {
13990             int i;
13991
13992             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13993                 cpThinkOutput[i] = '.';
13994             }
13995             cpThinkOutput[i] = NULLCHAR;
13996             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13997         }
13998     }
13999
14000     if (moveNumber == forwardMostMove - 1 &&
14001         gameInfo.resultDetails != NULL) {
14002         if (gameInfo.resultDetails[0] == NULLCHAR) {
14003           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14004         } else {
14005           snprintf(res, MSG_SIZ, " {%s} %s",
14006                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14007         }
14008     } else {
14009         res[0] = NULLCHAR;
14010     }
14011
14012     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14013         DisplayMessage(res, cpThinkOutput);
14014     } else {
14015       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14016                 WhiteOnMove(moveNumber) ? " " : ".. ",
14017                 parseList[moveNumber], res);
14018         DisplayMessage(message, cpThinkOutput);
14019     }
14020 }
14021
14022 void
14023 DisplayComment(moveNumber, text)
14024      int moveNumber;
14025      char *text;
14026 {
14027     char title[MSG_SIZ];
14028     char buf[8000]; // comment can be long!
14029     int score, depth;
14030
14031     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14032       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14033     } else {
14034       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14035               WhiteOnMove(moveNumber) ? " " : ".. ",
14036               parseList[moveNumber]);
14037     }
14038     // [HGM] PV info: display PV info together with (or as) comment
14039     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14040       if(text == NULL) text = "";
14041       score = pvInfoList[moveNumber].score;
14042       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14043               depth, (pvInfoList[moveNumber].time+50)/100, text);
14044       text = buf;
14045     }
14046     if (text != NULL && (appData.autoDisplayComment || commentUp))
14047         CommentPopUp(title, text);
14048 }
14049
14050 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14051  * might be busy thinking or pondering.  It can be omitted if your
14052  * gnuchess is configured to stop thinking immediately on any user
14053  * input.  However, that gnuchess feature depends on the FIONREAD
14054  * ioctl, which does not work properly on some flavors of Unix.
14055  */
14056 void
14057 Attention(cps)
14058      ChessProgramState *cps;
14059 {
14060 #if ATTENTION
14061     if (!cps->useSigint) return;
14062     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14063     switch (gameMode) {
14064       case MachinePlaysWhite:
14065       case MachinePlaysBlack:
14066       case TwoMachinesPlay:
14067       case IcsPlayingWhite:
14068       case IcsPlayingBlack:
14069       case AnalyzeMode:
14070       case AnalyzeFile:
14071         /* Skip if we know it isn't thinking */
14072         if (!cps->maybeThinking) return;
14073         if (appData.debugMode)
14074           fprintf(debugFP, "Interrupting %s\n", cps->which);
14075         InterruptChildProcess(cps->pr);
14076         cps->maybeThinking = FALSE;
14077         break;
14078       default:
14079         break;
14080     }
14081 #endif /*ATTENTION*/
14082 }
14083
14084 int
14085 CheckFlags()
14086 {
14087     if (whiteTimeRemaining <= 0) {
14088         if (!whiteFlag) {
14089             whiteFlag = TRUE;
14090             if (appData.icsActive) {
14091                 if (appData.autoCallFlag &&
14092                     gameMode == IcsPlayingBlack && !blackFlag) {
14093                   SendToICS(ics_prefix);
14094                   SendToICS("flag\n");
14095                 }
14096             } else {
14097                 if (blackFlag) {
14098                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14099                 } else {
14100                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14101                     if (appData.autoCallFlag) {
14102                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14103                         return TRUE;
14104                     }
14105                 }
14106             }
14107         }
14108     }
14109     if (blackTimeRemaining <= 0) {
14110         if (!blackFlag) {
14111             blackFlag = TRUE;
14112             if (appData.icsActive) {
14113                 if (appData.autoCallFlag &&
14114                     gameMode == IcsPlayingWhite && !whiteFlag) {
14115                   SendToICS(ics_prefix);
14116                   SendToICS("flag\n");
14117                 }
14118             } else {
14119                 if (whiteFlag) {
14120                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14121                 } else {
14122                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14123                     if (appData.autoCallFlag) {
14124                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14125                         return TRUE;
14126                     }
14127                 }
14128             }
14129         }
14130     }
14131     return FALSE;
14132 }
14133
14134 void
14135 CheckTimeControl()
14136 {
14137     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14138         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14139
14140     /*
14141      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14142      */
14143     if ( !WhiteOnMove(forwardMostMove) ) {
14144         /* White made time control */
14145         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14146         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14147         /* [HGM] time odds: correct new time quota for time odds! */
14148                                             / WhitePlayer()->timeOdds;
14149         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14150     } else {
14151         lastBlack -= blackTimeRemaining;
14152         /* Black made time control */
14153         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14154                                             / WhitePlayer()->other->timeOdds;
14155         lastWhite = whiteTimeRemaining;
14156     }
14157 }
14158
14159 void
14160 DisplayBothClocks()
14161 {
14162     int wom = gameMode == EditPosition ?
14163       !blackPlaysFirst : WhiteOnMove(currentMove);
14164     DisplayWhiteClock(whiteTimeRemaining, wom);
14165     DisplayBlackClock(blackTimeRemaining, !wom);
14166 }
14167
14168
14169 /* Timekeeping seems to be a portability nightmare.  I think everyone
14170    has ftime(), but I'm really not sure, so I'm including some ifdefs
14171    to use other calls if you don't.  Clocks will be less accurate if
14172    you have neither ftime nor gettimeofday.
14173 */
14174
14175 /* VS 2008 requires the #include outside of the function */
14176 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14177 #include <sys/timeb.h>
14178 #endif
14179
14180 /* Get the current time as a TimeMark */
14181 void
14182 GetTimeMark(tm)
14183      TimeMark *tm;
14184 {
14185 #if HAVE_GETTIMEOFDAY
14186
14187     struct timeval timeVal;
14188     struct timezone timeZone;
14189
14190     gettimeofday(&timeVal, &timeZone);
14191     tm->sec = (long) timeVal.tv_sec;
14192     tm->ms = (int) (timeVal.tv_usec / 1000L);
14193
14194 #else /*!HAVE_GETTIMEOFDAY*/
14195 #if HAVE_FTIME
14196
14197 // include <sys/timeb.h> / moved to just above start of function
14198     struct timeb timeB;
14199
14200     ftime(&timeB);
14201     tm->sec = (long) timeB.time;
14202     tm->ms = (int) timeB.millitm;
14203
14204 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14205     tm->sec = (long) time(NULL);
14206     tm->ms = 0;
14207 #endif
14208 #endif
14209 }
14210
14211 /* Return the difference in milliseconds between two
14212    time marks.  We assume the difference will fit in a long!
14213 */
14214 long
14215 SubtractTimeMarks(tm2, tm1)
14216      TimeMark *tm2, *tm1;
14217 {
14218     return 1000L*(tm2->sec - tm1->sec) +
14219            (long) (tm2->ms - tm1->ms);
14220 }
14221
14222
14223 /*
14224  * Code to manage the game clocks.
14225  *
14226  * In tournament play, black starts the clock and then white makes a move.
14227  * We give the human user a slight advantage if he is playing white---the
14228  * clocks don't run until he makes his first move, so it takes zero time.
14229  * Also, we don't account for network lag, so we could get out of sync
14230  * with GNU Chess's clock -- but then, referees are always right.
14231  */
14232
14233 static TimeMark tickStartTM;
14234 static long intendedTickLength;
14235
14236 long
14237 NextTickLength(timeRemaining)
14238      long timeRemaining;
14239 {
14240     long nominalTickLength, nextTickLength;
14241
14242     if (timeRemaining > 0L && timeRemaining <= 10000L)
14243       nominalTickLength = 100L;
14244     else
14245       nominalTickLength = 1000L;
14246     nextTickLength = timeRemaining % nominalTickLength;
14247     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14248
14249     return nextTickLength;
14250 }
14251
14252 /* Adjust clock one minute up or down */
14253 void
14254 AdjustClock(Boolean which, int dir)
14255 {
14256     if(which) blackTimeRemaining += 60000*dir;
14257     else      whiteTimeRemaining += 60000*dir;
14258     DisplayBothClocks();
14259 }
14260
14261 /* Stop clocks and reset to a fresh time control */
14262 void
14263 ResetClocks()
14264 {
14265     (void) StopClockTimer();
14266     if (appData.icsActive) {
14267         whiteTimeRemaining = blackTimeRemaining = 0;
14268     } else if (searchTime) {
14269         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14270         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14271     } else { /* [HGM] correct new time quote for time odds */
14272         whiteTC = blackTC = fullTimeControlString;
14273         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14274         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14275     }
14276     if (whiteFlag || blackFlag) {
14277         DisplayTitle("");
14278         whiteFlag = blackFlag = FALSE;
14279     }
14280     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14281     DisplayBothClocks();
14282 }
14283
14284 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14285
14286 /* Decrement running clock by amount of time that has passed */
14287 void
14288 DecrementClocks()
14289 {
14290     long timeRemaining;
14291     long lastTickLength, fudge;
14292     TimeMark now;
14293
14294     if (!appData.clockMode) return;
14295     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14296
14297     GetTimeMark(&now);
14298
14299     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14300
14301     /* Fudge if we woke up a little too soon */
14302     fudge = intendedTickLength - lastTickLength;
14303     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14304
14305     if (WhiteOnMove(forwardMostMove)) {
14306         if(whiteNPS >= 0) lastTickLength = 0;
14307         timeRemaining = whiteTimeRemaining -= lastTickLength;
14308         if(timeRemaining < 0) {
14309             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14310             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14311                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14312                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14313             }
14314         }
14315         DisplayWhiteClock(whiteTimeRemaining - fudge,
14316                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14317     } else {
14318         if(blackNPS >= 0) lastTickLength = 0;
14319         timeRemaining = blackTimeRemaining -= lastTickLength;
14320         if(timeRemaining < 0) { // [HGM] if we run out of a non-last incremental session, go to the next
14321             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14322             if(suddenDeath) {
14323                 blackStartMove = forwardMostMove;
14324                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14325             }
14326         }
14327         DisplayBlackClock(blackTimeRemaining - fudge,
14328                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14329     }
14330     if (CheckFlags()) return;
14331
14332     tickStartTM = now;
14333     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14334     StartClockTimer(intendedTickLength);
14335
14336     /* if the time remaining has fallen below the alarm threshold, sound the
14337      * alarm. if the alarm has sounded and (due to a takeback or time control
14338      * with increment) the time remaining has increased to a level above the
14339      * threshold, reset the alarm so it can sound again.
14340      */
14341
14342     if (appData.icsActive && appData.icsAlarm) {
14343
14344         /* make sure we are dealing with the user's clock */
14345         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14346                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14347            )) return;
14348
14349         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14350             alarmSounded = FALSE;
14351         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14352             PlayAlarmSound();
14353             alarmSounded = TRUE;
14354         }
14355     }
14356 }
14357
14358
14359 /* A player has just moved, so stop the previously running
14360    clock and (if in clock mode) start the other one.
14361    We redisplay both clocks in case we're in ICS mode, because
14362    ICS gives us an update to both clocks after every move.
14363    Note that this routine is called *after* forwardMostMove
14364    is updated, so the last fractional tick must be subtracted
14365    from the color that is *not* on move now.
14366 */
14367 void
14368 SwitchClocks(int newMoveNr)
14369 {
14370     long lastTickLength;
14371     TimeMark now;
14372     int flagged = FALSE;
14373
14374     GetTimeMark(&now);
14375
14376     if (StopClockTimer() && appData.clockMode) {
14377         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14378         if (!WhiteOnMove(forwardMostMove)) {
14379             if(blackNPS >= 0) lastTickLength = 0;
14380             blackTimeRemaining -= lastTickLength;
14381            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14382 //         if(pvInfoList[forwardMostMove-1].time == -1)
14383                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14384                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14385         } else {
14386            if(whiteNPS >= 0) lastTickLength = 0;
14387            whiteTimeRemaining -= lastTickLength;
14388            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14389 //         if(pvInfoList[forwardMostMove-1].time == -1)
14390                  pvInfoList[forwardMostMove-1].time =
14391                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14392         }
14393         flagged = CheckFlags();
14394     }
14395     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14396     CheckTimeControl();
14397
14398     if (flagged || !appData.clockMode) return;
14399
14400     switch (gameMode) {
14401       case MachinePlaysBlack:
14402       case MachinePlaysWhite:
14403       case BeginningOfGame:
14404         if (pausing) return;
14405         break;
14406
14407       case EditGame:
14408       case PlayFromGameFile:
14409       case IcsExamining:
14410         return;
14411
14412       default:
14413         break;
14414     }
14415
14416     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14417         if(WhiteOnMove(forwardMostMove))
14418              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14419         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14420     }
14421
14422     tickStartTM = now;
14423     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14424       whiteTimeRemaining : blackTimeRemaining);
14425     StartClockTimer(intendedTickLength);
14426 }
14427
14428
14429 /* Stop both clocks */
14430 void
14431 StopClocks()
14432 {
14433     long lastTickLength;
14434     TimeMark now;
14435
14436     if (!StopClockTimer()) return;
14437     if (!appData.clockMode) return;
14438
14439     GetTimeMark(&now);
14440
14441     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14442     if (WhiteOnMove(forwardMostMove)) {
14443         if(whiteNPS >= 0) lastTickLength = 0;
14444         whiteTimeRemaining -= lastTickLength;
14445         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14446     } else {
14447         if(blackNPS >= 0) lastTickLength = 0;
14448         blackTimeRemaining -= lastTickLength;
14449         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14450     }
14451     CheckFlags();
14452 }
14453
14454 /* Start clock of player on move.  Time may have been reset, so
14455    if clock is already running, stop and restart it. */
14456 void
14457 StartClocks()
14458 {
14459     (void) StopClockTimer(); /* in case it was running already */
14460     DisplayBothClocks();
14461     if (CheckFlags()) return;
14462
14463     if (!appData.clockMode) return;
14464     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14465
14466     GetTimeMark(&tickStartTM);
14467     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14468       whiteTimeRemaining : blackTimeRemaining);
14469
14470    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14471     whiteNPS = blackNPS = -1;
14472     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14473        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14474         whiteNPS = first.nps;
14475     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14476        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14477         blackNPS = first.nps;
14478     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14479         whiteNPS = second.nps;
14480     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14481         blackNPS = second.nps;
14482     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14483
14484     StartClockTimer(intendedTickLength);
14485 }
14486
14487 char *
14488 TimeString(ms)
14489      long ms;
14490 {
14491     long second, minute, hour, day;
14492     char *sign = "";
14493     static char buf[32];
14494
14495     if (ms > 0 && ms <= 9900) {
14496       /* convert milliseconds to tenths, rounding up */
14497       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14498
14499       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14500       return buf;
14501     }
14502
14503     /* convert milliseconds to seconds, rounding up */
14504     /* use floating point to avoid strangeness of integer division
14505        with negative dividends on many machines */
14506     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14507
14508     if (second < 0) {
14509         sign = "-";
14510         second = -second;
14511     }
14512
14513     day = second / (60 * 60 * 24);
14514     second = second % (60 * 60 * 24);
14515     hour = second / (60 * 60);
14516     second = second % (60 * 60);
14517     minute = second / 60;
14518     second = second % 60;
14519
14520     if (day > 0)
14521       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14522               sign, day, hour, minute, second);
14523     else if (hour > 0)
14524       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14525     else
14526       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14527
14528     return buf;
14529 }
14530
14531
14532 /*
14533  * This is necessary because some C libraries aren't ANSI C compliant yet.
14534  */
14535 char *
14536 StrStr(string, match)
14537      char *string, *match;
14538 {
14539     int i, length;
14540
14541     length = strlen(match);
14542
14543     for (i = strlen(string) - length; i >= 0; i--, string++)
14544       if (!strncmp(match, string, length))
14545         return string;
14546
14547     return NULL;
14548 }
14549
14550 char *
14551 StrCaseStr(string, match)
14552      char *string, *match;
14553 {
14554     int i, j, length;
14555
14556     length = strlen(match);
14557
14558     for (i = strlen(string) - length; i >= 0; i--, string++) {
14559         for (j = 0; j < length; j++) {
14560             if (ToLower(match[j]) != ToLower(string[j]))
14561               break;
14562         }
14563         if (j == length) return string;
14564     }
14565
14566     return NULL;
14567 }
14568
14569 #ifndef _amigados
14570 int
14571 StrCaseCmp(s1, s2)
14572      char *s1, *s2;
14573 {
14574     char c1, c2;
14575
14576     for (;;) {
14577         c1 = ToLower(*s1++);
14578         c2 = ToLower(*s2++);
14579         if (c1 > c2) return 1;
14580         if (c1 < c2) return -1;
14581         if (c1 == NULLCHAR) return 0;
14582     }
14583 }
14584
14585
14586 int
14587 ToLower(c)
14588      int c;
14589 {
14590     return isupper(c) ? tolower(c) : c;
14591 }
14592
14593
14594 int
14595 ToUpper(c)
14596      int c;
14597 {
14598     return islower(c) ? toupper(c) : c;
14599 }
14600 #endif /* !_amigados    */
14601
14602 char *
14603 StrSave(s)
14604      char *s;
14605 {
14606   char *ret;
14607
14608   if ((ret = (char *) malloc(strlen(s) + 1)))
14609     {
14610       safeStrCpy(ret, s, strlen(s)+1);
14611     }
14612   return ret;
14613 }
14614
14615 char *
14616 StrSavePtr(s, savePtr)
14617      char *s, **savePtr;
14618 {
14619     if (*savePtr) {
14620         free(*savePtr);
14621     }
14622     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14623       safeStrCpy(*savePtr, s, strlen(s)+1);
14624     }
14625     return(*savePtr);
14626 }
14627
14628 char *
14629 PGNDate()
14630 {
14631     time_t clock;
14632     struct tm *tm;
14633     char buf[MSG_SIZ];
14634
14635     clock = time((time_t *)NULL);
14636     tm = localtime(&clock);
14637     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14638             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14639     return StrSave(buf);
14640 }
14641
14642
14643 char *
14644 PositionToFEN(move, overrideCastling)
14645      int move;
14646      char *overrideCastling;
14647 {
14648     int i, j, fromX, fromY, toX, toY;
14649     int whiteToPlay;
14650     char buf[128];
14651     char *p, *q;
14652     int emptycount;
14653     ChessSquare piece;
14654
14655     whiteToPlay = (gameMode == EditPosition) ?
14656       !blackPlaysFirst : (move % 2 == 0);
14657     p = buf;
14658
14659     /* Piece placement data */
14660     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14661         emptycount = 0;
14662         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14663             if (boards[move][i][j] == EmptySquare) {
14664                 emptycount++;
14665             } else { ChessSquare piece = boards[move][i][j];
14666                 if (emptycount > 0) {
14667                     if(emptycount<10) /* [HGM] can be >= 10 */
14668                         *p++ = '0' + emptycount;
14669                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14670                     emptycount = 0;
14671                 }
14672                 if(PieceToChar(piece) == '+') {
14673                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14674                     *p++ = '+';
14675                     piece = (ChessSquare)(DEMOTED piece);
14676                 }
14677                 *p++ = PieceToChar(piece);
14678                 if(p[-1] == '~') {
14679                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14680                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14681                     *p++ = '~';
14682                 }
14683             }
14684         }
14685         if (emptycount > 0) {
14686             if(emptycount<10) /* [HGM] can be >= 10 */
14687                 *p++ = '0' + emptycount;
14688             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14689             emptycount = 0;
14690         }
14691         *p++ = '/';
14692     }
14693     *(p - 1) = ' ';
14694
14695     /* [HGM] print Crazyhouse or Shogi holdings */
14696     if( gameInfo.holdingsWidth ) {
14697         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14698         q = p;
14699         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14700             piece = boards[move][i][BOARD_WIDTH-1];
14701             if( piece != EmptySquare )
14702               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14703                   *p++ = PieceToChar(piece);
14704         }
14705         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14706             piece = boards[move][BOARD_HEIGHT-i-1][0];
14707             if( piece != EmptySquare )
14708               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14709                   *p++ = PieceToChar(piece);
14710         }
14711
14712         if( q == p ) *p++ = '-';
14713         *p++ = ']';
14714         *p++ = ' ';
14715     }
14716
14717     /* Active color */
14718     *p++ = whiteToPlay ? 'w' : 'b';
14719     *p++ = ' ';
14720
14721   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14722     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14723   } else {
14724   if(nrCastlingRights) {
14725      q = p;
14726      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14727        /* [HGM] write directly from rights */
14728            if(boards[move][CASTLING][2] != NoRights &&
14729               boards[move][CASTLING][0] != NoRights   )
14730                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14731            if(boards[move][CASTLING][2] != NoRights &&
14732               boards[move][CASTLING][1] != NoRights   )
14733                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14734            if(boards[move][CASTLING][5] != NoRights &&
14735               boards[move][CASTLING][3] != NoRights   )
14736                 *p++ = boards[move][CASTLING][3] + AAA;
14737            if(boards[move][CASTLING][5] != NoRights &&
14738               boards[move][CASTLING][4] != NoRights   )
14739                 *p++ = boards[move][CASTLING][4] + AAA;
14740      } else {
14741
14742         /* [HGM] write true castling rights */
14743         if( nrCastlingRights == 6 ) {
14744             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14745                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14746             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14747                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14748             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14749                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14750             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14751                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14752         }
14753      }
14754      if (q == p) *p++ = '-'; /* No castling rights */
14755      *p++ = ' ';
14756   }
14757
14758   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14759      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14760     /* En passant target square */
14761     if (move > backwardMostMove) {
14762         fromX = moveList[move - 1][0] - AAA;
14763         fromY = moveList[move - 1][1] - ONE;
14764         toX = moveList[move - 1][2] - AAA;
14765         toY = moveList[move - 1][3] - ONE;
14766         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14767             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14768             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14769             fromX == toX) {
14770             /* 2-square pawn move just happened */
14771             *p++ = toX + AAA;
14772             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14773         } else {
14774             *p++ = '-';
14775         }
14776     } else if(move == backwardMostMove) {
14777         // [HGM] perhaps we should always do it like this, and forget the above?
14778         if((signed char)boards[move][EP_STATUS] >= 0) {
14779             *p++ = boards[move][EP_STATUS] + AAA;
14780             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14781         } else {
14782             *p++ = '-';
14783         }
14784     } else {
14785         *p++ = '-';
14786     }
14787     *p++ = ' ';
14788   }
14789   }
14790
14791     /* [HGM] find reversible plies */
14792     {   int i = 0, j=move;
14793
14794         if (appData.debugMode) { int k;
14795             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14796             for(k=backwardMostMove; k<=forwardMostMove; k++)
14797                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14798
14799         }
14800
14801         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14802         if( j == backwardMostMove ) i += initialRulePlies;
14803         sprintf(p, "%d ", i);
14804         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14805     }
14806     /* Fullmove number */
14807     sprintf(p, "%d", (move / 2) + 1);
14808
14809     return StrSave(buf);
14810 }
14811
14812 Boolean
14813 ParseFEN(board, blackPlaysFirst, fen)
14814     Board board;
14815      int *blackPlaysFirst;
14816      char *fen;
14817 {
14818     int i, j;
14819     char *p, c;
14820     int emptycount;
14821     ChessSquare piece;
14822
14823     p = fen;
14824
14825     /* [HGM] by default clear Crazyhouse holdings, if present */
14826     if(gameInfo.holdingsWidth) {
14827        for(i=0; i<BOARD_HEIGHT; i++) {
14828            board[i][0]             = EmptySquare; /* black holdings */
14829            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14830            board[i][1]             = (ChessSquare) 0; /* black counts */
14831            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14832        }
14833     }
14834
14835     /* Piece placement data */
14836     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14837         j = 0;
14838         for (;;) {
14839             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14840                 if (*p == '/') p++;
14841                 emptycount = gameInfo.boardWidth - j;
14842                 while (emptycount--)
14843                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14844                 break;
14845 #if(BOARD_FILES >= 10)
14846             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14847                 p++; emptycount=10;
14848                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14849                 while (emptycount--)
14850                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14851 #endif
14852             } else if (isdigit(*p)) {
14853                 emptycount = *p++ - '0';
14854                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14855                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14856                 while (emptycount--)
14857                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14858             } else if (*p == '+' || isalpha(*p)) {
14859                 if (j >= gameInfo.boardWidth) return FALSE;
14860                 if(*p=='+') {
14861                     piece = CharToPiece(*++p);
14862                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14863                     piece = (ChessSquare) (PROMOTED piece ); p++;
14864                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14865                 } else piece = CharToPiece(*p++);
14866
14867                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14868                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14869                     piece = (ChessSquare) (PROMOTED piece);
14870                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14871                     p++;
14872                 }
14873                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14874             } else {
14875                 return FALSE;
14876             }
14877         }
14878     }
14879     while (*p == '/' || *p == ' ') p++;
14880
14881     /* [HGM] look for Crazyhouse holdings here */
14882     while(*p==' ') p++;
14883     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14884         if(*p == '[') p++;
14885         if(*p == '-' ) p++; /* empty holdings */ else {
14886             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14887             /* if we would allow FEN reading to set board size, we would   */
14888             /* have to add holdings and shift the board read so far here   */
14889             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14890                 p++;
14891                 if((int) piece >= (int) BlackPawn ) {
14892                     i = (int)piece - (int)BlackPawn;
14893                     i = PieceToNumber((ChessSquare)i);
14894                     if( i >= gameInfo.holdingsSize ) return FALSE;
14895                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14896                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14897                 } else {
14898                     i = (int)piece - (int)WhitePawn;
14899                     i = PieceToNumber((ChessSquare)i);
14900                     if( i >= gameInfo.holdingsSize ) return FALSE;
14901                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14902                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14903                 }
14904             }
14905         }
14906         if(*p == ']') p++;
14907     }
14908
14909     while(*p == ' ') p++;
14910
14911     /* Active color */
14912     c = *p++;
14913     if(appData.colorNickNames) {
14914       if( c == appData.colorNickNames[0] ) c = 'w'; else
14915       if( c == appData.colorNickNames[1] ) c = 'b';
14916     }
14917     switch (c) {
14918       case 'w':
14919         *blackPlaysFirst = FALSE;
14920         break;
14921       case 'b':
14922         *blackPlaysFirst = TRUE;
14923         break;
14924       default:
14925         return FALSE;
14926     }
14927
14928     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14929     /* return the extra info in global variiables             */
14930
14931     /* set defaults in case FEN is incomplete */
14932     board[EP_STATUS] = EP_UNKNOWN;
14933     for(i=0; i<nrCastlingRights; i++ ) {
14934         board[CASTLING][i] =
14935             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14936     }   /* assume possible unless obviously impossible */
14937     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14938     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14939     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14940                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14941     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14942     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14943     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14944                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14945     FENrulePlies = 0;
14946
14947     while(*p==' ') p++;
14948     if(nrCastlingRights) {
14949       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14950           /* castling indicator present, so default becomes no castlings */
14951           for(i=0; i<nrCastlingRights; i++ ) {
14952                  board[CASTLING][i] = NoRights;
14953           }
14954       }
14955       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14956              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14957              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14958              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14959         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14960
14961         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14962             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14963             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14964         }
14965         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14966             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14967         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14968                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14969         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14970                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14971         switch(c) {
14972           case'K':
14973               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14974               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14975               board[CASTLING][2] = whiteKingFile;
14976               break;
14977           case'Q':
14978               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14979               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14980               board[CASTLING][2] = whiteKingFile;
14981               break;
14982           case'k':
14983               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14984               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14985               board[CASTLING][5] = blackKingFile;
14986               break;
14987           case'q':
14988               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14989               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14990               board[CASTLING][5] = blackKingFile;
14991           case '-':
14992               break;
14993           default: /* FRC castlings */
14994               if(c >= 'a') { /* black rights */
14995                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14996                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14997                   if(i == BOARD_RGHT) break;
14998                   board[CASTLING][5] = i;
14999                   c -= AAA;
15000                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15001                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15002                   if(c > i)
15003                       board[CASTLING][3] = c;
15004                   else
15005                       board[CASTLING][4] = c;
15006               } else { /* white rights */
15007                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15008                     if(board[0][i] == WhiteKing) break;
15009                   if(i == BOARD_RGHT) break;
15010                   board[CASTLING][2] = i;
15011                   c -= AAA - 'a' + 'A';
15012                   if(board[0][c] >= WhiteKing) break;
15013                   if(c > i)
15014                       board[CASTLING][0] = c;
15015                   else
15016                       board[CASTLING][1] = c;
15017               }
15018         }
15019       }
15020       for(i=0; i<nrCastlingRights; i++)
15021         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15022     if (appData.debugMode) {
15023         fprintf(debugFP, "FEN castling rights:");
15024         for(i=0; i<nrCastlingRights; i++)
15025         fprintf(debugFP, " %d", board[CASTLING][i]);
15026         fprintf(debugFP, "\n");
15027     }
15028
15029       while(*p==' ') p++;
15030     }
15031
15032     /* read e.p. field in games that know e.p. capture */
15033     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15034        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15035       if(*p=='-') {
15036         p++; board[EP_STATUS] = EP_NONE;
15037       } else {
15038          char c = *p++ - AAA;
15039
15040          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15041          if(*p >= '0' && *p <='9') p++;
15042          board[EP_STATUS] = c;
15043       }
15044     }
15045
15046
15047     if(sscanf(p, "%d", &i) == 1) {
15048         FENrulePlies = i; /* 50-move ply counter */
15049         /* (The move number is still ignored)    */
15050     }
15051
15052     return TRUE;
15053 }
15054
15055 void
15056 EditPositionPasteFEN(char *fen)
15057 {
15058   if (fen != NULL) {
15059     Board initial_position;
15060
15061     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15062       DisplayError(_("Bad FEN position in clipboard"), 0);
15063       return ;
15064     } else {
15065       int savedBlackPlaysFirst = blackPlaysFirst;
15066       EditPositionEvent();
15067       blackPlaysFirst = savedBlackPlaysFirst;
15068       CopyBoard(boards[0], initial_position);
15069       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15070       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15071       DisplayBothClocks();
15072       DrawPosition(FALSE, boards[currentMove]);
15073     }
15074   }
15075 }
15076
15077 static char cseq[12] = "\\   ";
15078
15079 Boolean set_cont_sequence(char *new_seq)
15080 {
15081     int len;
15082     Boolean ret;
15083
15084     // handle bad attempts to set the sequence
15085         if (!new_seq)
15086                 return 0; // acceptable error - no debug
15087
15088     len = strlen(new_seq);
15089     ret = (len > 0) && (len < sizeof(cseq));
15090     if (ret)
15091       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15092     else if (appData.debugMode)
15093       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15094     return ret;
15095 }
15096
15097 /*
15098     reformat a source message so words don't cross the width boundary.  internal
15099     newlines are not removed.  returns the wrapped size (no null character unless
15100     included in source message).  If dest is NULL, only calculate the size required
15101     for the dest buffer.  lp argument indicats line position upon entry, and it's
15102     passed back upon exit.
15103 */
15104 int wrap(char *dest, char *src, int count, int width, int *lp)
15105 {
15106     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15107
15108     cseq_len = strlen(cseq);
15109     old_line = line = *lp;
15110     ansi = len = clen = 0;
15111
15112     for (i=0; i < count; i++)
15113     {
15114         if (src[i] == '\033')
15115             ansi = 1;
15116
15117         // if we hit the width, back up
15118         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15119         {
15120             // store i & len in case the word is too long
15121             old_i = i, old_len = len;
15122
15123             // find the end of the last word
15124             while (i && src[i] != ' ' && src[i] != '\n')
15125             {
15126                 i--;
15127                 len--;
15128             }
15129
15130             // word too long?  restore i & len before splitting it
15131             if ((old_i-i+clen) >= width)
15132             {
15133                 i = old_i;
15134                 len = old_len;
15135             }
15136
15137             // extra space?
15138             if (i && src[i-1] == ' ')
15139                 len--;
15140
15141             if (src[i] != ' ' && src[i] != '\n')
15142             {
15143                 i--;
15144                 if (len)
15145                     len--;
15146             }
15147
15148             // now append the newline and continuation sequence
15149             if (dest)
15150                 dest[len] = '\n';
15151             len++;
15152             if (dest)
15153                 strncpy(dest+len, cseq, cseq_len);
15154             len += cseq_len;
15155             line = cseq_len;
15156             clen = cseq_len;
15157             continue;
15158         }
15159
15160         if (dest)
15161             dest[len] = src[i];
15162         len++;
15163         if (!ansi)
15164             line++;
15165         if (src[i] == '\n')
15166             line = 0;
15167         if (src[i] == 'm')
15168             ansi = 0;
15169     }
15170     if (dest && appData.debugMode)
15171     {
15172         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15173             count, width, line, len, *lp);
15174         show_bytes(debugFP, src, count);
15175         fprintf(debugFP, "\ndest: ");
15176         show_bytes(debugFP, dest, len);
15177         fprintf(debugFP, "\n");
15178     }
15179     *lp = dest ? line : old_line;
15180
15181     return len;
15182 }
15183
15184 // [HGM] vari: routines for shelving variations
15185
15186 void
15187 PushTail(int firstMove, int lastMove)
15188 {
15189         int i, j, nrMoves = lastMove - firstMove;
15190
15191         if(appData.icsActive) { // only in local mode
15192                 forwardMostMove = currentMove; // mimic old ICS behavior
15193                 return;
15194         }
15195         if(storedGames >= MAX_VARIATIONS-1) return;
15196
15197         // push current tail of game on stack
15198         savedResult[storedGames] = gameInfo.result;
15199         savedDetails[storedGames] = gameInfo.resultDetails;
15200         gameInfo.resultDetails = NULL;
15201         savedFirst[storedGames] = firstMove;
15202         savedLast [storedGames] = lastMove;
15203         savedFramePtr[storedGames] = framePtr;
15204         framePtr -= nrMoves; // reserve space for the boards
15205         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15206             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15207             for(j=0; j<MOVE_LEN; j++)
15208                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15209             for(j=0; j<2*MOVE_LEN; j++)
15210                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15211             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15212             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15213             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15214             pvInfoList[firstMove+i-1].depth = 0;
15215             commentList[framePtr+i] = commentList[firstMove+i];
15216             commentList[firstMove+i] = NULL;
15217         }
15218
15219         storedGames++;
15220         forwardMostMove = firstMove; // truncate game so we can start variation
15221         if(storedGames == 1) GreyRevert(FALSE);
15222 }
15223
15224 Boolean
15225 PopTail(Boolean annotate)
15226 {
15227         int i, j, nrMoves;
15228         char buf[8000], moveBuf[20];
15229
15230         if(appData.icsActive) return FALSE; // only in local mode
15231         if(!storedGames) return FALSE; // sanity
15232         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15233
15234         storedGames--;
15235         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15236         nrMoves = savedLast[storedGames] - currentMove;
15237         if(annotate) {
15238                 int cnt = 10;
15239                 if(!WhiteOnMove(currentMove))
15240                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15241                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15242                 for(i=currentMove; i<forwardMostMove; i++) {
15243                         if(WhiteOnMove(i))
15244                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15245                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15246                         strcat(buf, moveBuf);
15247                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15248                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15249                 }
15250                 strcat(buf, ")");
15251         }
15252         for(i=1; i<=nrMoves; i++) { // copy last variation back
15253             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15254             for(j=0; j<MOVE_LEN; j++)
15255                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15256             for(j=0; j<2*MOVE_LEN; j++)
15257                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15258             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15259             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15260             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15261             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15262             commentList[currentMove+i] = commentList[framePtr+i];
15263             commentList[framePtr+i] = NULL;
15264         }
15265         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15266         framePtr = savedFramePtr[storedGames];
15267         gameInfo.result = savedResult[storedGames];
15268         if(gameInfo.resultDetails != NULL) {
15269             free(gameInfo.resultDetails);
15270       }
15271         gameInfo.resultDetails = savedDetails[storedGames];
15272         forwardMostMove = currentMove + nrMoves;
15273         if(storedGames == 0) GreyRevert(TRUE);
15274         return TRUE;
15275 }
15276
15277 void
15278 CleanupTail()
15279 {       // remove all shelved variations
15280         int i;
15281         for(i=0; i<storedGames; i++) {
15282             if(savedDetails[i])
15283                 free(savedDetails[i]);
15284             savedDetails[i] = NULL;
15285         }
15286         for(i=framePtr; i<MAX_MOVES; i++) {
15287                 if(commentList[i]) free(commentList[i]);
15288                 commentList[i] = NULL;
15289         }
15290         framePtr = MAX_MOVES-1;
15291         storedGames = 0;
15292 }
15293
15294 void
15295 LoadVariation(int index, char *text)
15296 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15297         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15298         int level = 0, move;
15299
15300         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15301         // first find outermost bracketing variation
15302         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15303             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15304                 if(*p == '{') wait = '}'; else
15305                 if(*p == '[') wait = ']'; else
15306                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15307                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15308             }
15309             if(*p == wait) wait = NULLCHAR; // closing ]} found
15310             p++;
15311         }
15312         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15313         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15314         end[1] = NULLCHAR; // clip off comment beyond variation
15315         ToNrEvent(currentMove-1);
15316         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15317         // kludge: use ParsePV() to append variation to game
15318         move = currentMove;
15319         ParsePV(start, TRUE);
15320         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15321         ClearPremoveHighlights();
15322         CommentPopDown();
15323         ToNrEvent(currentMove+1);
15324 }
15325