security fix: replaced sprintf with snprintf
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270
271 /* States for ics_getting_history */
272 #define H_FALSE 0
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
278
279 /* whosays values for GameEnds */
280 #define GE_ICS 0
281 #define GE_ENGINE 1
282 #define GE_PLAYER 2
283 #define GE_FILE 3
284 #define GE_XBOARD 4
285 #define GE_ENGINE1 5
286 #define GE_ENGINE2 6
287
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
290
291 /* Different types of move when calling RegisterMove */
292 #define CMAIL_MOVE   0
293 #define CMAIL_RESIGN 1
294 #define CMAIL_DRAW   2
295 #define CMAIL_ACCEPT 3
296
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
301
302 /* Telnet protocol constants */
303 #define TN_WILL 0373
304 #define TN_WONT 0374
305 #define TN_DO   0375
306 #define TN_DONT 0376
307 #define TN_IAC  0377
308 #define TN_ECHO 0001
309 #define TN_SGA  0003
310 #define TN_PORT 23
311
312 char*
313 safeStrCpy( char *dst, const char *src, size_t count )
314 {
315   /* see for example: https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/854-BSI.html
316    *
317    * usage:   safeStrCpy( stringA, stringB, sizeof(stringA)/sizeof(stringA[0]);
318    */
319
320   assert( dst != NULL );
321   assert( src != NULL );
322   assert( count > 0 );
323
324   strncpy( dst, src, count );
325   if(  dst[ count-1 ] != '\0' )
326     {
327       if(appData.debugMode)
328       printf("safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
329     }
330   dst[ count-1 ] = '\0';
331
332   return dst;
333 }
334
335 /* Some compiler can't cast u64 to double
336  * This function do the job for us:
337
338  * We use the highest bit for cast, this only
339  * works if the highest bit is not
340  * in use (This should not happen)
341  *
342  * We used this for all compiler
343  */
344 double
345 u64ToDouble(u64 value)
346 {
347   double r;
348   u64 tmp = value & u64Const(0x7fffffffffffffff);
349   r = (double)(s64)tmp;
350   if (value & u64Const(0x8000000000000000))
351        r +=  9.2233720368547758080e18; /* 2^63 */
352  return r;
353 }
354
355 /* Fake up flags for now, as we aren't keeping track of castling
356    availability yet. [HGM] Change of logic: the flag now only
357    indicates the type of castlings allowed by the rule of the game.
358    The actual rights themselves are maintained in the array
359    castlingRights, as part of the game history, and are not probed
360    by this function.
361  */
362 int
363 PosFlags(index)
364 {
365   int flags = F_ALL_CASTLE_OK;
366   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
367   switch (gameInfo.variant) {
368   case VariantSuicide:
369     flags &= ~F_ALL_CASTLE_OK;
370   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
371     flags |= F_IGNORE_CHECK;
372   case VariantLosers:
373     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374     break;
375   case VariantAtomic:
376     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377     break;
378   case VariantKriegspiel:
379     flags |= F_KRIEGSPIEL_CAPTURE;
380     break;
381   case VariantCapaRandom:
382   case VariantFischeRandom:
383     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
384   case VariantNoCastle:
385   case VariantShatranj:
386   case VariantCourier:
387   case VariantMakruk:
388     flags &= ~F_ALL_CASTLE_OK;
389     break;
390   default:
391     break;
392   }
393   return flags;
394 }
395
396 FILE *gameFileFP, *debugFP;
397
398 /*
399     [AS] Note: sometimes, the sscanf() function is used to parse the input
400     into a fixed-size buffer. Because of this, we must be prepared to
401     receive strings as long as the size of the input buffer, which is currently
402     set to 4K for Windows and 8K for the rest.
403     So, we must either allocate sufficiently large buffers here, or
404     reduce the size of the input buffer in the input reading part.
405 */
406
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
410
411 ChessProgramState first, second;
412
413 /* premove variables */
414 int premoveToX = 0;
415 int premoveToY = 0;
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
419 int gotPremove = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
422
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int have_sent_ICS_logon = 0;
453 int movesPerSession;
454 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
455 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
456 long timeControl_2; /* [AS] Allow separate time controls */
457 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
458 long timeRemaining[2][MAX_MOVES];
459 int matchGame = 0;
460 TimeMark programStartTime;
461 char ics_handle[MSG_SIZ];
462 int have_set_title = 0;
463
464 /* animateTraining preserves the state of appData.animate
465  * when Training mode is activated. This allows the
466  * response to be animated when appData.animate == TRUE and
467  * appData.animateDragging == TRUE.
468  */
469 Boolean animateTraining;
470
471 GameInfo gameInfo;
472
473 AppData appData;
474
475 Board boards[MAX_MOVES];
476 /* [HGM] Following 7 needed for accurate legality tests: */
477 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
478 signed char  initialRights[BOARD_FILES];
479 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
480 int   initialRulePlies, FENrulePlies;
481 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
482 int loadFlag = 0;
483 int shuffleOpenings;
484 int mute; // mute all sounds
485
486 // [HGM] vari: next 12 to save and restore variations
487 #define MAX_VARIATIONS 10
488 int framePtr = MAX_MOVES-1; // points to free stack entry
489 int storedGames = 0;
490 int savedFirst[MAX_VARIATIONS];
491 int savedLast[MAX_VARIATIONS];
492 int savedFramePtr[MAX_VARIATIONS];
493 char *savedDetails[MAX_VARIATIONS];
494 ChessMove savedResult[MAX_VARIATIONS];
495
496 void PushTail P((int firstMove, int lastMove));
497 Boolean PopTail P((Boolean annotate));
498 void CleanupTail P((void));
499
500 ChessSquare  FIDEArray[2][BOARD_FILES] = {
501     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
502         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
503     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
504         BlackKing, BlackBishop, BlackKnight, BlackRook }
505 };
506
507 ChessSquare twoKingsArray[2][BOARD_FILES] = {
508     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511         BlackKing, BlackKing, BlackKnight, BlackRook }
512 };
513
514 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
516         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
517     { BlackRook, BlackMan, BlackBishop, BlackQueen,
518         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
519 };
520
521 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
522     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
524     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
525         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
526 };
527
528 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
529     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
530         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
531     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
532         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
533 };
534
535 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
536     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
537         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
538     { BlackRook, BlackKnight, BlackMan, BlackFerz,
539         BlackKing, BlackMan, BlackKnight, BlackRook }
540 };
541
542
543 #if (BOARD_FILES>=10)
544 ChessSquare ShogiArray[2][BOARD_FILES] = {
545     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
546         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
547     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
548         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
549 };
550
551 ChessSquare XiangqiArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
553         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
555         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
556 };
557
558 ChessSquare CapablancaArray[2][BOARD_FILES] = {
559     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
560         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
562         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
563 };
564
565 ChessSquare GreatArray[2][BOARD_FILES] = {
566     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
567         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
568     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
569         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
570 };
571
572 ChessSquare JanusArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
574         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
575     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
576         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
577 };
578
579 #ifdef GOTHIC
580 ChessSquare GothicArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
582         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
584         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
585 };
586 #else // !GOTHIC
587 #define GothicArray CapablancaArray
588 #endif // !GOTHIC
589
590 #ifdef FALCON
591 ChessSquare FalconArray[2][BOARD_FILES] = {
592     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
593         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
594     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
595         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
596 };
597 #else // !FALCON
598 #define FalconArray CapablancaArray
599 #endif // !FALCON
600
601 #else // !(BOARD_FILES>=10)
602 #define XiangqiPosition FIDEArray
603 #define CapablancaArray FIDEArray
604 #define GothicArray FIDEArray
605 #define GreatArray FIDEArray
606 #endif // !(BOARD_FILES>=10)
607
608 #if (BOARD_FILES>=12)
609 ChessSquare CourierArray[2][BOARD_FILES] = {
610     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
611         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
612     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
613         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
614 };
615 #else // !(BOARD_FILES>=12)
616 #define CourierArray CapablancaArray
617 #endif // !(BOARD_FILES>=12)
618
619
620 Board initialPosition;
621
622
623 /* Convert str to a rating. Checks for special cases of "----",
624
625    "++++", etc. Also strips ()'s */
626 int
627 string_to_rating(str)
628   char *str;
629 {
630   while(*str && !isdigit(*str)) ++str;
631   if (!*str)
632     return 0;   /* One of the special "no rating" cases */
633   else
634     return atoi(str);
635 }
636
637 void
638 ClearProgramStats()
639 {
640     /* Init programStats */
641     programStats.movelist[0] = 0;
642     programStats.depth = 0;
643     programStats.nr_moves = 0;
644     programStats.moves_left = 0;
645     programStats.nodes = 0;
646     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
647     programStats.score = 0;
648     programStats.got_only_move = 0;
649     programStats.got_fail = 0;
650     programStats.line_is_book = 0;
651 }
652
653 void
654 InitBackEnd1()
655 {
656     int matched, min, sec;
657
658     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
659     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
660
661     GetTimeMark(&programStartTime);
662     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
663
664     ClearProgramStats();
665     programStats.ok_to_send = 1;
666     programStats.seen_stat = 0;
667
668     /*
669      * Initialize game list
670      */
671     ListNew(&gameList);
672
673
674     /*
675      * Internet chess server status
676      */
677     if (appData.icsActive) {
678         appData.matchMode = FALSE;
679         appData.matchGames = 0;
680 #if ZIPPY
681         appData.noChessProgram = !appData.zippyPlay;
682 #else
683         appData.zippyPlay = FALSE;
684         appData.zippyTalk = FALSE;
685         appData.noChessProgram = TRUE;
686 #endif
687         if (*appData.icsHelper != NULLCHAR) {
688             appData.useTelnet = TRUE;
689             appData.telnetProgram = appData.icsHelper;
690         }
691     } else {
692         appData.zippyTalk = appData.zippyPlay = FALSE;
693     }
694
695     /* [AS] Initialize pv info list [HGM] and game state */
696     {
697         int i, j;
698
699         for( i=0; i<=framePtr; i++ ) {
700             pvInfoList[i].depth = -1;
701             boards[i][EP_STATUS] = EP_NONE;
702             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
703         }
704     }
705
706     /*
707      * Parse timeControl resource
708      */
709     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
710                           appData.movesPerSession)) {
711         char buf[MSG_SIZ];
712         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
713         DisplayFatalError(buf, 0, 2);
714     }
715
716     /*
717      * Parse searchTime resource
718      */
719     if (*appData.searchTime != NULLCHAR) {
720         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
721         if (matched == 1) {
722             searchTime = min * 60;
723         } else if (matched == 2) {
724             searchTime = min * 60 + sec;
725         } else {
726             char buf[MSG_SIZ];
727             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
728             DisplayFatalError(buf, 0, 2);
729         }
730     }
731
732     /* [AS] Adjudication threshold */
733     adjudicateLossThreshold = appData.adjudicateLossThreshold;
734
735     first.which = _("first");
736     second.which = _("second");
737     first.maybeThinking = second.maybeThinking = FALSE;
738     first.pr = second.pr = NoProc;
739     first.isr = second.isr = NULL;
740     first.sendTime = second.sendTime = 2;
741     first.sendDrawOffers = 1;
742     if (appData.firstPlaysBlack) {
743         first.twoMachinesColor = "black\n";
744         second.twoMachinesColor = "white\n";
745     } else {
746         first.twoMachinesColor = "white\n";
747         second.twoMachinesColor = "black\n";
748     }
749     first.program = appData.firstChessProgram;
750     second.program = appData.secondChessProgram;
751     first.host = appData.firstHost;
752     second.host = appData.secondHost;
753     first.dir = appData.firstDirectory;
754     second.dir = appData.secondDirectory;
755     first.other = &second;
756     second.other = &first;
757     first.initString = appData.initString;
758     second.initString = appData.secondInitString;
759     first.computerString = appData.firstComputerString;
760     second.computerString = appData.secondComputerString;
761     first.useSigint = second.useSigint = TRUE;
762     first.useSigterm = second.useSigterm = TRUE;
763     first.reuse = appData.reuseFirst;
764     second.reuse = appData.reuseSecond;
765     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
766     second.nps = appData.secondNPS;
767     first.useSetboard = second.useSetboard = FALSE;
768     first.useSAN = second.useSAN = FALSE;
769     first.usePing = second.usePing = FALSE;
770     first.lastPing = second.lastPing = 0;
771     first.lastPong = second.lastPong = 0;
772     first.usePlayother = second.usePlayother = FALSE;
773     first.useColors = second.useColors = TRUE;
774     first.useUsermove = second.useUsermove = FALSE;
775     first.sendICS = second.sendICS = FALSE;
776     first.sendName = second.sendName = appData.icsActive;
777     first.sdKludge = second.sdKludge = FALSE;
778     first.stKludge = second.stKludge = FALSE;
779     TidyProgramName(first.program, first.host, first.tidy);
780     TidyProgramName(second.program, second.host, second.tidy);
781     first.matchWins = second.matchWins = 0;
782     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
783     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
784     first.analysisSupport = second.analysisSupport = 2; /* detect */
785     first.analyzing = second.analyzing = FALSE;
786     first.initDone = second.initDone = FALSE;
787
788     /* New features added by Tord: */
789     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
790     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
791     /* End of new features added by Tord. */
792     first.fenOverride  = appData.fenOverride1;
793     second.fenOverride = appData.fenOverride2;
794
795     /* [HGM] time odds: set factor for each machine */
796     first.timeOdds  = appData.firstTimeOdds;
797     second.timeOdds = appData.secondTimeOdds;
798     { float norm = 1;
799         if(appData.timeOddsMode) {
800             norm = first.timeOdds;
801             if(norm > second.timeOdds) norm = second.timeOdds;
802         }
803         first.timeOdds /= norm;
804         second.timeOdds /= norm;
805     }
806
807     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
808     first.accumulateTC = appData.firstAccumulateTC;
809     second.accumulateTC = appData.secondAccumulateTC;
810     first.maxNrOfSessions = second.maxNrOfSessions = 1;
811
812     /* [HGM] debug */
813     first.debug = second.debug = FALSE;
814     first.supportsNPS = second.supportsNPS = UNKNOWN;
815
816     /* [HGM] options */
817     first.optionSettings  = appData.firstOptions;
818     second.optionSettings = appData.secondOptions;
819
820     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
821     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
822     first.isUCI = appData.firstIsUCI; /* [AS] */
823     second.isUCI = appData.secondIsUCI; /* [AS] */
824     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
825     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
826
827     if (appData.firstProtocolVersion > PROTOVER
828         || appData.firstProtocolVersion < 1)
829       {
830         char buf[MSG_SIZ];
831         int len;
832
833         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
834                        appData.firstProtocolVersion);
835         if( (len > MSG_SIZ) && appData.debugMode )
836           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
837
838         DisplayFatalError(buf, 0, 2);
839       }
840     else
841       {
842         first.protocolVersion = appData.firstProtocolVersion;
843       }
844
845     if (appData.secondProtocolVersion > PROTOVER
846         || appData.secondProtocolVersion < 1)
847       {
848         char buf[MSG_SIZ];
849         int len;
850
851         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
852                        appData.secondProtocolVersion);
853         if( (len > MSG_SIZ) && appData.debugMode )
854           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
855
856         DisplayFatalError(buf, 0, 2);
857       }
858     else
859       {
860         second.protocolVersion = appData.secondProtocolVersion;
861       }
862
863     if (appData.icsActive) {
864         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
865 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
866     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
867         appData.clockMode = FALSE;
868         first.sendTime = second.sendTime = 0;
869     }
870
871 #if ZIPPY
872     /* Override some settings from environment variables, for backward
873        compatibility.  Unfortunately it's not feasible to have the env
874        vars just set defaults, at least in xboard.  Ugh.
875     */
876     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
877       ZippyInit();
878     }
879 #endif
880
881     if (appData.noChessProgram) {
882         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
883         sprintf(programVersion, "%s", PACKAGE_STRING);
884     } else {
885       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
886       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
887       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
888     }
889
890     if (!appData.icsActive) {
891       char buf[MSG_SIZ];
892       int len;
893
894       /* Check for variants that are supported only in ICS mode,
895          or not at all.  Some that are accepted here nevertheless
896          have bugs; see comments below.
897       */
898       VariantClass variant = StringToVariant(appData.variant);
899       switch (variant) {
900       case VariantBughouse:     /* need four players and two boards */
901       case VariantKriegspiel:   /* need to hide pieces and move details */
902         /* case VariantFischeRandom: (Fabien: moved below) */
903         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
904         if( (len > MSG_SIZ) && appData.debugMode )
905           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
906
907         DisplayFatalError(buf, 0, 2);
908         return;
909
910       case VariantUnknown:
911       case VariantLoadable:
912       case Variant29:
913       case Variant30:
914       case Variant31:
915       case Variant32:
916       case Variant33:
917       case Variant34:
918       case Variant35:
919       case Variant36:
920       default:
921         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
922         if( (len > MSG_SIZ) && appData.debugMode )
923           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
924
925         DisplayFatalError(buf, 0, 2);
926         return;
927
928       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
929       case VariantFairy:      /* [HGM] TestLegality definitely off! */
930       case VariantGothic:     /* [HGM] should work */
931       case VariantCapablanca: /* [HGM] should work */
932       case VariantCourier:    /* [HGM] initial forced moves not implemented */
933       case VariantShogi:      /* [HGM] could still mate with pawn drop */
934       case VariantKnightmate: /* [HGM] should work */
935       case VariantCylinder:   /* [HGM] untested */
936       case VariantFalcon:     /* [HGM] untested */
937       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
938                                  offboard interposition not understood */
939       case VariantNormal:     /* definitely works! */
940       case VariantWildCastle: /* pieces not automatically shuffled */
941       case VariantNoCastle:   /* pieces not automatically shuffled */
942       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
943       case VariantLosers:     /* should work except for win condition,
944                                  and doesn't know captures are mandatory */
945       case VariantSuicide:    /* should work except for win condition,
946                                  and doesn't know captures are mandatory */
947       case VariantGiveaway:   /* should work except for win condition,
948                                  and doesn't know captures are mandatory */
949       case VariantTwoKings:   /* should work */
950       case VariantAtomic:     /* should work except for win condition */
951       case Variant3Check:     /* should work except for win condition */
952       case VariantShatranj:   /* should work except for all win conditions */
953       case VariantMakruk:     /* should work except for daw countdown */
954       case VariantBerolina:   /* might work if TestLegality is off */
955       case VariantCapaRandom: /* should work */
956       case VariantJanus:      /* should work */
957       case VariantSuper:      /* experimental */
958       case VariantGreat:      /* experimental, requires legality testing to be off */
959         break;
960       }
961     }
962
963     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
964     InitEngineUCI( installDir, &second );
965 }
966
967 int NextIntegerFromString( char ** str, long * value )
968 {
969     int result = -1;
970     char * s = *str;
971
972     while( *s == ' ' || *s == '\t' ) {
973         s++;
974     }
975
976     *value = 0;
977
978     if( *s >= '0' && *s <= '9' ) {
979         while( *s >= '0' && *s <= '9' ) {
980             *value = *value * 10 + (*s - '0');
981             s++;
982         }
983
984         result = 0;
985     }
986
987     *str = s;
988
989     return result;
990 }
991
992 int NextTimeControlFromString( char ** str, long * value )
993 {
994     long temp;
995     int result = NextIntegerFromString( str, &temp );
996
997     if( result == 0 ) {
998         *value = temp * 60; /* Minutes */
999         if( **str == ':' ) {
1000             (*str)++;
1001             result = NextIntegerFromString( str, &temp );
1002             *value += temp; /* Seconds */
1003         }
1004     }
1005
1006     return result;
1007 }
1008
1009 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1010 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1011     int result = -1, type = 0; long temp, temp2;
1012
1013     if(**str != ':') return -1; // old params remain in force!
1014     (*str)++;
1015     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1016     if( NextIntegerFromString( str, &temp ) ) return -1;
1017     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1018
1019     if(**str != '/') {
1020         /* time only: incremental or sudden-death time control */
1021         if(**str == '+') { /* increment follows; read it */
1022             (*str)++;
1023             if(**str == '!') type = *(*str)++; // Bronstein TC
1024             if(result = NextIntegerFromString( str, &temp2)) return -1;
1025             *inc = temp2 * 1000;
1026         } else *inc = 0;
1027         *moves = 0; *tc = temp * 1000; *incType = type;
1028         return 0;
1029     }
1030
1031     (*str)++; /* classical time control */
1032     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1033
1034     if(result == 0) {
1035         *moves = temp;
1036         *tc    = temp2 * 1000;
1037         *inc   = 0;
1038         *incType = type;
1039     }
1040     return result;
1041 }
1042
1043 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1044 {   /* [HGM] get time to add from the multi-session time-control string */
1045     int incType, moves=1; /* kludge to force reading of first session */
1046     long time, increment;
1047     char *s = tcString;
1048
1049     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1050     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1051     do {
1052         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1053         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1054         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1055         if(movenr == -1) return time;    /* last move before new session     */
1056         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1057         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1058         if(!moves) return increment;     /* current session is incremental   */
1059         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1060     } while(movenr >= -1);               /* try again for next session       */
1061
1062     return 0; // no new time quota on this move
1063 }
1064
1065 int
1066 ParseTimeControl(tc, ti, mps)
1067      char *tc;
1068      int ti;
1069      int mps;
1070 {
1071   long tc1;
1072   long tc2;
1073   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1074   int min, sec=0;
1075   int len;
1076
1077   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1078   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1079       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1080   if(ti > 0) {
1081
1082     if(mps)
1083       snprintf(buf, MSG_SIZ, ":%d/%s+%d", mps, mytc, ti);
1084     else 
1085       snprintf(buf, MSG_SIZ, ":%s+%d", mytc, ti);
1086   } else {
1087     if(mps)
1088       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1089     else 
1090       snprintf(buf, MSG_SIZ, ":%s", mytc);
1091   }
1092   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1093   
1094   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1095     return FALSE;
1096   }
1097
1098   if( *tc == '/' ) {
1099     /* Parse second time control */
1100     tc++;
1101
1102     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1103       return FALSE;
1104     }
1105
1106     if( tc2 == 0 ) {
1107       return FALSE;
1108     }
1109
1110     timeControl_2 = tc2 * 1000;
1111   }
1112   else {
1113     timeControl_2 = 0;
1114   }
1115
1116   if( tc1 == 0 ) {
1117     return FALSE;
1118   }
1119
1120   timeControl = tc1 * 1000;
1121
1122   if (ti >= 0) {
1123     timeIncrement = ti * 1000;  /* convert to ms */
1124     movesPerSession = 0;
1125   } else {
1126     timeIncrement = 0;
1127     movesPerSession = mps;
1128   }
1129   return TRUE;
1130 }
1131
1132 void
1133 InitBackEnd2()
1134 {
1135     if (appData.debugMode) {
1136         fprintf(debugFP, "%s\n", programVersion);
1137     }
1138
1139     set_cont_sequence(appData.wrapContSeq);
1140     if (appData.matchGames > 0) {
1141         appData.matchMode = TRUE;
1142     } else if (appData.matchMode) {
1143         appData.matchGames = 1;
1144     }
1145     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1146         appData.matchGames = appData.sameColorGames;
1147     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1148         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1149         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1150     }
1151     Reset(TRUE, FALSE);
1152     if (appData.noChessProgram || first.protocolVersion == 1) {
1153       InitBackEnd3();
1154     } else {
1155       /* kludge: allow timeout for initial "feature" commands */
1156       FreezeUI();
1157       DisplayMessage("", _("Starting chess program"));
1158       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1159     }
1160 }
1161
1162 void
1163 InitBackEnd3 P((void))
1164 {
1165     GameMode initialMode;
1166     char buf[MSG_SIZ];
1167     int err, len;
1168
1169     InitChessProgram(&first, startedFromSetupPosition);
1170
1171     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1172         free(programVersion);
1173         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1174         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1175     }
1176
1177     if (appData.icsActive) {
1178 #ifdef WIN32
1179         /* [DM] Make a console window if needed [HGM] merged ifs */
1180         ConsoleCreate();
1181 #endif
1182         err = establish();
1183         if (err != 0)
1184           {
1185             if (*appData.icsCommPort != NULLCHAR)
1186               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1187                              appData.icsCommPort);
1188             else
1189               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1190                         appData.icsHost, appData.icsPort);
1191
1192             if( (len > MSG_SIZ) && appData.debugMode )
1193               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1194
1195             DisplayFatalError(buf, err, 1);
1196             return;
1197         }
1198         SetICSMode();
1199         telnetISR =
1200           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1201         fromUserISR =
1202           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1203         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1204             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1205     } else if (appData.noChessProgram) {
1206         SetNCPMode();
1207     } else {
1208         SetGNUMode();
1209     }
1210
1211     if (*appData.cmailGameName != NULLCHAR) {
1212         SetCmailMode();
1213         OpenLoopback(&cmailPR);
1214         cmailISR =
1215           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1216     }
1217
1218     ThawUI();
1219     DisplayMessage("", "");
1220     if (StrCaseCmp(appData.initialMode, "") == 0) {
1221       initialMode = BeginningOfGame;
1222     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1223       initialMode = TwoMachinesPlay;
1224     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1225       initialMode = AnalyzeFile;
1226     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1227       initialMode = AnalyzeMode;
1228     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1229       initialMode = MachinePlaysWhite;
1230     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1231       initialMode = MachinePlaysBlack;
1232     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1233       initialMode = EditGame;
1234     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1235       initialMode = EditPosition;
1236     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1237       initialMode = Training;
1238     } else {
1239       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1240       if( (len > MSG_SIZ) && appData.debugMode )
1241         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1242
1243       DisplayFatalError(buf, 0, 2);
1244       return;
1245     }
1246
1247     if (appData.matchMode) {
1248         /* Set up machine vs. machine match */
1249         if (appData.noChessProgram) {
1250             DisplayFatalError(_("Can't have a match with no chess programs"),
1251                               0, 2);
1252             return;
1253         }
1254         matchMode = TRUE;
1255         matchGame = 1;
1256         if (*appData.loadGameFile != NULLCHAR) {
1257             int index = appData.loadGameIndex; // [HGM] autoinc
1258             if(index<0) lastIndex = index = 1;
1259             if (!LoadGameFromFile(appData.loadGameFile,
1260                                   index,
1261                                   appData.loadGameFile, FALSE)) {
1262                 DisplayFatalError(_("Bad game file"), 0, 1);
1263                 return;
1264             }
1265         } else if (*appData.loadPositionFile != NULLCHAR) {
1266             int index = appData.loadPositionIndex; // [HGM] autoinc
1267             if(index<0) lastIndex = index = 1;
1268             if (!LoadPositionFromFile(appData.loadPositionFile,
1269                                       index,
1270                                       appData.loadPositionFile)) {
1271                 DisplayFatalError(_("Bad position file"), 0, 1);
1272                 return;
1273             }
1274         }
1275         TwoMachinesEvent();
1276     } else if (*appData.cmailGameName != NULLCHAR) {
1277         /* Set up cmail mode */
1278         ReloadCmailMsgEvent(TRUE);
1279     } else {
1280         /* Set up other modes */
1281         if (initialMode == AnalyzeFile) {
1282           if (*appData.loadGameFile == NULLCHAR) {
1283             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1284             return;
1285           }
1286         }
1287         if (*appData.loadGameFile != NULLCHAR) {
1288             (void) LoadGameFromFile(appData.loadGameFile,
1289                                     appData.loadGameIndex,
1290                                     appData.loadGameFile, TRUE);
1291         } else if (*appData.loadPositionFile != NULLCHAR) {
1292             (void) LoadPositionFromFile(appData.loadPositionFile,
1293                                         appData.loadPositionIndex,
1294                                         appData.loadPositionFile);
1295             /* [HGM] try to make self-starting even after FEN load */
1296             /* to allow automatic setup of fairy variants with wtm */
1297             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1298                 gameMode = BeginningOfGame;
1299                 setboardSpoiledMachineBlack = 1;
1300             }
1301             /* [HGM] loadPos: make that every new game uses the setup */
1302             /* from file as long as we do not switch variant          */
1303             if(!blackPlaysFirst) {
1304                 startedFromPositionFile = TRUE;
1305                 CopyBoard(filePosition, boards[0]);
1306             }
1307         }
1308         if (initialMode == AnalyzeMode) {
1309           if (appData.noChessProgram) {
1310             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1311             return;
1312           }
1313           if (appData.icsActive) {
1314             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1315             return;
1316           }
1317           AnalyzeModeEvent();
1318         } else if (initialMode == AnalyzeFile) {
1319           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1320           ShowThinkingEvent();
1321           AnalyzeFileEvent();
1322           AnalysisPeriodicEvent(1);
1323         } else if (initialMode == MachinePlaysWhite) {
1324           if (appData.noChessProgram) {
1325             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1326                               0, 2);
1327             return;
1328           }
1329           if (appData.icsActive) {
1330             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1331                               0, 2);
1332             return;
1333           }
1334           MachineWhiteEvent();
1335         } else if (initialMode == MachinePlaysBlack) {
1336           if (appData.noChessProgram) {
1337             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1338                               0, 2);
1339             return;
1340           }
1341           if (appData.icsActive) {
1342             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1343                               0, 2);
1344             return;
1345           }
1346           MachineBlackEvent();
1347         } else if (initialMode == TwoMachinesPlay) {
1348           if (appData.noChessProgram) {
1349             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1350                               0, 2);
1351             return;
1352           }
1353           if (appData.icsActive) {
1354             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1355                               0, 2);
1356             return;
1357           }
1358           TwoMachinesEvent();
1359         } else if (initialMode == EditGame) {
1360           EditGameEvent();
1361         } else if (initialMode == EditPosition) {
1362           EditPositionEvent();
1363         } else if (initialMode == Training) {
1364           if (*appData.loadGameFile == NULLCHAR) {
1365             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1366             return;
1367           }
1368           TrainingEvent();
1369         }
1370     }
1371 }
1372
1373 /*
1374  * Establish will establish a contact to a remote host.port.
1375  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1376  *  used to talk to the host.
1377  * Returns 0 if okay, error code if not.
1378  */
1379 int
1380 establish()
1381 {
1382     char buf[MSG_SIZ];
1383
1384     if (*appData.icsCommPort != NULLCHAR) {
1385         /* Talk to the host through a serial comm port */
1386         return OpenCommPort(appData.icsCommPort, &icsPR);
1387
1388     } else if (*appData.gateway != NULLCHAR) {
1389         if (*appData.remoteShell == NULLCHAR) {
1390             /* Use the rcmd protocol to run telnet program on a gateway host */
1391             snprintf(buf, sizeof(buf), "%s %s %s",
1392                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1393             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1394
1395         } else {
1396             /* Use the rsh program to run telnet program on a gateway host */
1397             if (*appData.remoteUser == NULLCHAR) {
1398                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1399                         appData.gateway, appData.telnetProgram,
1400                         appData.icsHost, appData.icsPort);
1401             } else {
1402                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1403                         appData.remoteShell, appData.gateway,
1404                         appData.remoteUser, appData.telnetProgram,
1405                         appData.icsHost, appData.icsPort);
1406             }
1407             return StartChildProcess(buf, "", &icsPR);
1408
1409         }
1410     } else if (appData.useTelnet) {
1411         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1412
1413     } else {
1414         /* TCP socket interface differs somewhat between
1415            Unix and NT; handle details in the front end.
1416            */
1417         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1418     }
1419 }
1420
1421 void EscapeExpand(char *p, char *q)
1422 {       // [HGM] initstring: routine to shape up string arguments
1423         while(*p++ = *q++) if(p[-1] == '\\')
1424             switch(*q++) {
1425                 case 'n': p[-1] = '\n'; break;
1426                 case 'r': p[-1] = '\r'; break;
1427                 case 't': p[-1] = '\t'; break;
1428                 case '\\': p[-1] = '\\'; break;
1429                 case 0: *p = 0; return;
1430                 default: p[-1] = q[-1]; break;
1431             }
1432 }
1433
1434 void
1435 show_bytes(fp, buf, count)
1436      FILE *fp;
1437      char *buf;
1438      int count;
1439 {
1440     while (count--) {
1441         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1442             fprintf(fp, "\\%03o", *buf & 0xff);
1443         } else {
1444             putc(*buf, fp);
1445         }
1446         buf++;
1447     }
1448     fflush(fp);
1449 }
1450
1451 /* Returns an errno value */
1452 int
1453 OutputMaybeTelnet(pr, message, count, outError)
1454      ProcRef pr;
1455      char *message;
1456      int count;
1457      int *outError;
1458 {
1459     char buf[8192], *p, *q, *buflim;
1460     int left, newcount, outcount;
1461
1462     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1463         *appData.gateway != NULLCHAR) {
1464         if (appData.debugMode) {
1465             fprintf(debugFP, ">ICS: ");
1466             show_bytes(debugFP, message, count);
1467             fprintf(debugFP, "\n");
1468         }
1469         return OutputToProcess(pr, message, count, outError);
1470     }
1471
1472     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1473     p = message;
1474     q = buf;
1475     left = count;
1476     newcount = 0;
1477     while (left) {
1478         if (q >= buflim) {
1479             if (appData.debugMode) {
1480                 fprintf(debugFP, ">ICS: ");
1481                 show_bytes(debugFP, buf, newcount);
1482                 fprintf(debugFP, "\n");
1483             }
1484             outcount = OutputToProcess(pr, buf, newcount, outError);
1485             if (outcount < newcount) return -1; /* to be sure */
1486             q = buf;
1487             newcount = 0;
1488         }
1489         if (*p == '\n') {
1490             *q++ = '\r';
1491             newcount++;
1492         } else if (((unsigned char) *p) == TN_IAC) {
1493             *q++ = (char) TN_IAC;
1494             newcount ++;
1495         }
1496         *q++ = *p++;
1497         newcount++;
1498         left--;
1499     }
1500     if (appData.debugMode) {
1501         fprintf(debugFP, ">ICS: ");
1502         show_bytes(debugFP, buf, newcount);
1503         fprintf(debugFP, "\n");
1504     }
1505     outcount = OutputToProcess(pr, buf, newcount, outError);
1506     if (outcount < newcount) return -1; /* to be sure */
1507     return count;
1508 }
1509
1510 void
1511 read_from_player(isr, closure, message, count, error)
1512      InputSourceRef isr;
1513      VOIDSTAR closure;
1514      char *message;
1515      int count;
1516      int error;
1517 {
1518     int outError, outCount;
1519     static int gotEof = 0;
1520
1521     /* Pass data read from player on to ICS */
1522     if (count > 0) {
1523         gotEof = 0;
1524         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1525         if (outCount < count) {
1526             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1527         }
1528     } else if (count < 0) {
1529         RemoveInputSource(isr);
1530         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1531     } else if (gotEof++ > 0) {
1532         RemoveInputSource(isr);
1533         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1534     }
1535 }
1536
1537 void
1538 KeepAlive()
1539 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1540     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1541     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1542     SendToICS("date\n");
1543     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1544 }
1545
1546 /* added routine for printf style output to ics */
1547 void ics_printf(char *format, ...)
1548 {
1549     char buffer[MSG_SIZ];
1550     va_list args;
1551
1552     va_start(args, format);
1553     vsnprintf(buffer, sizeof(buffer), format, args);
1554     buffer[sizeof(buffer)-1] = '\0';
1555     SendToICS(buffer);
1556     va_end(args);
1557 }
1558
1559 void
1560 SendToICS(s)
1561      char *s;
1562 {
1563     int count, outCount, outError;
1564
1565     if (icsPR == NULL) return;
1566
1567     count = strlen(s);
1568     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1569     if (outCount < count) {
1570         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1571     }
1572 }
1573
1574 /* This is used for sending logon scripts to the ICS. Sending
1575    without a delay causes problems when using timestamp on ICC
1576    (at least on my machine). */
1577 void
1578 SendToICSDelayed(s,msdelay)
1579      char *s;
1580      long msdelay;
1581 {
1582     int count, outCount, outError;
1583
1584     if (icsPR == NULL) return;
1585
1586     count = strlen(s);
1587     if (appData.debugMode) {
1588         fprintf(debugFP, ">ICS: ");
1589         show_bytes(debugFP, s, count);
1590         fprintf(debugFP, "\n");
1591     }
1592     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1593                                       msdelay);
1594     if (outCount < count) {
1595         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1596     }
1597 }
1598
1599
1600 /* Remove all highlighting escape sequences in s
1601    Also deletes any suffix starting with '('
1602    */
1603 char *
1604 StripHighlightAndTitle(s)
1605      char *s;
1606 {
1607     static char retbuf[MSG_SIZ];
1608     char *p = retbuf;
1609
1610     while (*s != NULLCHAR) {
1611         while (*s == '\033') {
1612             while (*s != NULLCHAR && !isalpha(*s)) s++;
1613             if (*s != NULLCHAR) s++;
1614         }
1615         while (*s != NULLCHAR && *s != '\033') {
1616             if (*s == '(' || *s == '[') {
1617                 *p = NULLCHAR;
1618                 return retbuf;
1619             }
1620             *p++ = *s++;
1621         }
1622     }
1623     *p = NULLCHAR;
1624     return retbuf;
1625 }
1626
1627 /* Remove all highlighting escape sequences in s */
1628 char *
1629 StripHighlight(s)
1630      char *s;
1631 {
1632     static char retbuf[MSG_SIZ];
1633     char *p = retbuf;
1634
1635     while (*s != NULLCHAR) {
1636         while (*s == '\033') {
1637             while (*s != NULLCHAR && !isalpha(*s)) s++;
1638             if (*s != NULLCHAR) s++;
1639         }
1640         while (*s != NULLCHAR && *s != '\033') {
1641             *p++ = *s++;
1642         }
1643     }
1644     *p = NULLCHAR;
1645     return retbuf;
1646 }
1647
1648 char *variantNames[] = VARIANT_NAMES;
1649 char *
1650 VariantName(v)
1651      VariantClass v;
1652 {
1653     return variantNames[v];
1654 }
1655
1656
1657 /* Identify a variant from the strings the chess servers use or the
1658    PGN Variant tag names we use. */
1659 VariantClass
1660 StringToVariant(e)
1661      char *e;
1662 {
1663     char *p;
1664     int wnum = -1;
1665     VariantClass v = VariantNormal;
1666     int i, found = FALSE;
1667     char buf[MSG_SIZ];
1668     int len;
1669
1670     if (!e) return v;
1671
1672     /* [HGM] skip over optional board-size prefixes */
1673     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1674         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1675         while( *e++ != '_');
1676     }
1677
1678     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1679         v = VariantNormal;
1680         found = TRUE;
1681     } else
1682     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1683       if (StrCaseStr(e, variantNames[i])) {
1684         v = (VariantClass) i;
1685         found = TRUE;
1686         break;
1687       }
1688     }
1689
1690     if (!found) {
1691       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1692           || StrCaseStr(e, "wild/fr")
1693           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1694         v = VariantFischeRandom;
1695       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1696                  (i = 1, p = StrCaseStr(e, "w"))) {
1697         p += i;
1698         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1699         if (isdigit(*p)) {
1700           wnum = atoi(p);
1701         } else {
1702           wnum = -1;
1703         }
1704         switch (wnum) {
1705         case 0: /* FICS only, actually */
1706         case 1:
1707           /* Castling legal even if K starts on d-file */
1708           v = VariantWildCastle;
1709           break;
1710         case 2:
1711         case 3:
1712         case 4:
1713           /* Castling illegal even if K & R happen to start in
1714              normal positions. */
1715           v = VariantNoCastle;
1716           break;
1717         case 5:
1718         case 7:
1719         case 8:
1720         case 10:
1721         case 11:
1722         case 12:
1723         case 13:
1724         case 14:
1725         case 15:
1726         case 18:
1727         case 19:
1728           /* Castling legal iff K & R start in normal positions */
1729           v = VariantNormal;
1730           break;
1731         case 6:
1732         case 20:
1733         case 21:
1734           /* Special wilds for position setup; unclear what to do here */
1735           v = VariantLoadable;
1736           break;
1737         case 9:
1738           /* Bizarre ICC game */
1739           v = VariantTwoKings;
1740           break;
1741         case 16:
1742           v = VariantKriegspiel;
1743           break;
1744         case 17:
1745           v = VariantLosers;
1746           break;
1747         case 22:
1748           v = VariantFischeRandom;
1749           break;
1750         case 23:
1751           v = VariantCrazyhouse;
1752           break;
1753         case 24:
1754           v = VariantBughouse;
1755           break;
1756         case 25:
1757           v = Variant3Check;
1758           break;
1759         case 26:
1760           /* Not quite the same as FICS suicide! */
1761           v = VariantGiveaway;
1762           break;
1763         case 27:
1764           v = VariantAtomic;
1765           break;
1766         case 28:
1767           v = VariantShatranj;
1768           break;
1769
1770         /* Temporary names for future ICC types.  The name *will* change in
1771            the next xboard/WinBoard release after ICC defines it. */
1772         case 29:
1773           v = Variant29;
1774           break;
1775         case 30:
1776           v = Variant30;
1777           break;
1778         case 31:
1779           v = Variant31;
1780           break;
1781         case 32:
1782           v = Variant32;
1783           break;
1784         case 33:
1785           v = Variant33;
1786           break;
1787         case 34:
1788           v = Variant34;
1789           break;
1790         case 35:
1791           v = Variant35;
1792           break;
1793         case 36:
1794           v = Variant36;
1795           break;
1796         case 37:
1797           v = VariantShogi;
1798           break;
1799         case 38:
1800           v = VariantXiangqi;
1801           break;
1802         case 39:
1803           v = VariantCourier;
1804           break;
1805         case 40:
1806           v = VariantGothic;
1807           break;
1808         case 41:
1809           v = VariantCapablanca;
1810           break;
1811         case 42:
1812           v = VariantKnightmate;
1813           break;
1814         case 43:
1815           v = VariantFairy;
1816           break;
1817         case 44:
1818           v = VariantCylinder;
1819           break;
1820         case 45:
1821           v = VariantFalcon;
1822           break;
1823         case 46:
1824           v = VariantCapaRandom;
1825           break;
1826         case 47:
1827           v = VariantBerolina;
1828           break;
1829         case 48:
1830           v = VariantJanus;
1831           break;
1832         case 49:
1833           v = VariantSuper;
1834           break;
1835         case 50:
1836           v = VariantGreat;
1837           break;
1838         case -1:
1839           /* Found "wild" or "w" in the string but no number;
1840              must assume it's normal chess. */
1841           v = VariantNormal;
1842           break;
1843         default:
1844           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1845           if( (len > MSG_SIZ) && appData.debugMode )
1846             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1847
1848           DisplayError(buf, 0);
1849           v = VariantUnknown;
1850           break;
1851         }
1852       }
1853     }
1854     if (appData.debugMode) {
1855       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1856               e, wnum, VariantName(v));
1857     }
1858     return v;
1859 }
1860
1861 static int leftover_start = 0, leftover_len = 0;
1862 char star_match[STAR_MATCH_N][MSG_SIZ];
1863
1864 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1865    advance *index beyond it, and set leftover_start to the new value of
1866    *index; else return FALSE.  If pattern contains the character '*', it
1867    matches any sequence of characters not containing '\r', '\n', or the
1868    character following the '*' (if any), and the matched sequence(s) are
1869    copied into star_match.
1870    */
1871 int
1872 looking_at(buf, index, pattern)
1873      char *buf;
1874      int *index;
1875      char *pattern;
1876 {
1877     char *bufp = &buf[*index], *patternp = pattern;
1878     int star_count = 0;
1879     char *matchp = star_match[0];
1880
1881     for (;;) {
1882         if (*patternp == NULLCHAR) {
1883             *index = leftover_start = bufp - buf;
1884             *matchp = NULLCHAR;
1885             return TRUE;
1886         }
1887         if (*bufp == NULLCHAR) return FALSE;
1888         if (*patternp == '*') {
1889             if (*bufp == *(patternp + 1)) {
1890                 *matchp = NULLCHAR;
1891                 matchp = star_match[++star_count];
1892                 patternp += 2;
1893                 bufp++;
1894                 continue;
1895             } else if (*bufp == '\n' || *bufp == '\r') {
1896                 patternp++;
1897                 if (*patternp == NULLCHAR)
1898                   continue;
1899                 else
1900                   return FALSE;
1901             } else {
1902                 *matchp++ = *bufp++;
1903                 continue;
1904             }
1905         }
1906         if (*patternp != *bufp) return FALSE;
1907         patternp++;
1908         bufp++;
1909     }
1910 }
1911
1912 void
1913 SendToPlayer(data, length)
1914      char *data;
1915      int length;
1916 {
1917     int error, outCount;
1918     outCount = OutputToProcess(NoProc, data, length, &error);
1919     if (outCount < length) {
1920         DisplayFatalError(_("Error writing to display"), error, 1);
1921     }
1922 }
1923
1924 void
1925 PackHolding(packed, holding)
1926      char packed[];
1927      char *holding;
1928 {
1929     char *p = holding;
1930     char *q = packed;
1931     int runlength = 0;
1932     int curr = 9999;
1933     do {
1934         if (*p == curr) {
1935             runlength++;
1936         } else {
1937             switch (runlength) {
1938               case 0:
1939                 break;
1940               case 1:
1941                 *q++ = curr;
1942                 break;
1943               case 2:
1944                 *q++ = curr;
1945                 *q++ = curr;
1946                 break;
1947               default:
1948                 sprintf(q, "%d", runlength);
1949                 while (*q) q++;
1950                 *q++ = curr;
1951                 break;
1952             }
1953             runlength = 1;
1954             curr = *p;
1955         }
1956     } while (*p++);
1957     *q = NULLCHAR;
1958 }
1959
1960 /* Telnet protocol requests from the front end */
1961 void
1962 TelnetRequest(ddww, option)
1963      unsigned char ddww, option;
1964 {
1965     unsigned char msg[3];
1966     int outCount, outError;
1967
1968     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1969
1970     if (appData.debugMode) {
1971         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1972         switch (ddww) {
1973           case TN_DO:
1974             ddwwStr = "DO";
1975             break;
1976           case TN_DONT:
1977             ddwwStr = "DONT";
1978             break;
1979           case TN_WILL:
1980             ddwwStr = "WILL";
1981             break;
1982           case TN_WONT:
1983             ddwwStr = "WONT";
1984             break;
1985           default:
1986             ddwwStr = buf1;
1987             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
1988             break;
1989         }
1990         switch (option) {
1991           case TN_ECHO:
1992             optionStr = "ECHO";
1993             break;
1994           default:
1995             optionStr = buf2;
1996             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
1997             break;
1998         }
1999         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2000     }
2001     msg[0] = TN_IAC;
2002     msg[1] = ddww;
2003     msg[2] = option;
2004     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2005     if (outCount < 3) {
2006         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2007     }
2008 }
2009
2010 void
2011 DoEcho()
2012 {
2013     if (!appData.icsActive) return;
2014     TelnetRequest(TN_DO, TN_ECHO);
2015 }
2016
2017 void
2018 DontEcho()
2019 {
2020     if (!appData.icsActive) return;
2021     TelnetRequest(TN_DONT, TN_ECHO);
2022 }
2023
2024 void
2025 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2026 {
2027     /* put the holdings sent to us by the server on the board holdings area */
2028     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2029     char p;
2030     ChessSquare piece;
2031
2032     if(gameInfo.holdingsWidth < 2)  return;
2033     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2034         return; // prevent overwriting by pre-board holdings
2035
2036     if( (int)lowestPiece >= BlackPawn ) {
2037         holdingsColumn = 0;
2038         countsColumn = 1;
2039         holdingsStartRow = BOARD_HEIGHT-1;
2040         direction = -1;
2041     } else {
2042         holdingsColumn = BOARD_WIDTH-1;
2043         countsColumn = BOARD_WIDTH-2;
2044         holdingsStartRow = 0;
2045         direction = 1;
2046     }
2047
2048     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2049         board[i][holdingsColumn] = EmptySquare;
2050         board[i][countsColumn]   = (ChessSquare) 0;
2051     }
2052     while( (p=*holdings++) != NULLCHAR ) {
2053         piece = CharToPiece( ToUpper(p) );
2054         if(piece == EmptySquare) continue;
2055         /*j = (int) piece - (int) WhitePawn;*/
2056         j = PieceToNumber(piece);
2057         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2058         if(j < 0) continue;               /* should not happen */
2059         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2060         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2061         board[holdingsStartRow+j*direction][countsColumn]++;
2062     }
2063 }
2064
2065
2066 void
2067 VariantSwitch(Board board, VariantClass newVariant)
2068 {
2069    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2070    static Board oldBoard;
2071
2072    startedFromPositionFile = FALSE;
2073    if(gameInfo.variant == newVariant) return;
2074
2075    /* [HGM] This routine is called each time an assignment is made to
2076     * gameInfo.variant during a game, to make sure the board sizes
2077     * are set to match the new variant. If that means adding or deleting
2078     * holdings, we shift the playing board accordingly
2079     * This kludge is needed because in ICS observe mode, we get boards
2080     * of an ongoing game without knowing the variant, and learn about the
2081     * latter only later. This can be because of the move list we requested,
2082     * in which case the game history is refilled from the beginning anyway,
2083     * but also when receiving holdings of a crazyhouse game. In the latter
2084     * case we want to add those holdings to the already received position.
2085     */
2086
2087
2088    if (appData.debugMode) {
2089      fprintf(debugFP, "Switch board from %s to %s\n",
2090              VariantName(gameInfo.variant), VariantName(newVariant));
2091      setbuf(debugFP, NULL);
2092    }
2093    shuffleOpenings = 0;       /* [HGM] shuffle */
2094    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2095    switch(newVariant)
2096      {
2097      case VariantShogi:
2098        newWidth = 9;  newHeight = 9;
2099        gameInfo.holdingsSize = 7;
2100      case VariantBughouse:
2101      case VariantCrazyhouse:
2102        newHoldingsWidth = 2; break;
2103      case VariantGreat:
2104        newWidth = 10;
2105      case VariantSuper:
2106        newHoldingsWidth = 2;
2107        gameInfo.holdingsSize = 8;
2108        break;
2109      case VariantGothic:
2110      case VariantCapablanca:
2111      case VariantCapaRandom:
2112        newWidth = 10;
2113      default:
2114        newHoldingsWidth = gameInfo.holdingsSize = 0;
2115      };
2116
2117    if(newWidth  != gameInfo.boardWidth  ||
2118       newHeight != gameInfo.boardHeight ||
2119       newHoldingsWidth != gameInfo.holdingsWidth ) {
2120
2121      /* shift position to new playing area, if needed */
2122      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2123        for(i=0; i<BOARD_HEIGHT; i++)
2124          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2125            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2126              board[i][j];
2127        for(i=0; i<newHeight; i++) {
2128          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2129          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2130        }
2131      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2132        for(i=0; i<BOARD_HEIGHT; i++)
2133          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2134            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2135              board[i][j];
2136      }
2137      gameInfo.boardWidth  = newWidth;
2138      gameInfo.boardHeight = newHeight;
2139      gameInfo.holdingsWidth = newHoldingsWidth;
2140      gameInfo.variant = newVariant;
2141      InitDrawingSizes(-2, 0);
2142    } else gameInfo.variant = newVariant;
2143    CopyBoard(oldBoard, board);   // remember correctly formatted board
2144      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2145    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2146 }
2147
2148 static int loggedOn = FALSE;
2149
2150 /*-- Game start info cache: --*/
2151 int gs_gamenum;
2152 char gs_kind[MSG_SIZ];
2153 static char player1Name[128] = "";
2154 static char player2Name[128] = "";
2155 static char cont_seq[] = "\n\\   ";
2156 static int player1Rating = -1;
2157 static int player2Rating = -1;
2158 /*----------------------------*/
2159
2160 ColorClass curColor = ColorNormal;
2161 int suppressKibitz = 0;
2162
2163 // [HGM] seekgraph
2164 Boolean soughtPending = FALSE;
2165 Boolean seekGraphUp;
2166 #define MAX_SEEK_ADS 200
2167 #define SQUARE 0x80
2168 char *seekAdList[MAX_SEEK_ADS];
2169 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2170 float tcList[MAX_SEEK_ADS];
2171 char colorList[MAX_SEEK_ADS];
2172 int nrOfSeekAds = 0;
2173 int minRating = 1010, maxRating = 2800;
2174 int hMargin = 10, vMargin = 20, h, w;
2175 extern int squareSize, lineGap;
2176
2177 void
2178 PlotSeekAd(int i)
2179 {
2180         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2181         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2182         if(r < minRating+100 && r >=0 ) r = minRating+100;
2183         if(r > maxRating) r = maxRating;
2184         if(tc < 1.) tc = 1.;
2185         if(tc > 95.) tc = 95.;
2186         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2187         y = ((double)r - minRating)/(maxRating - minRating)
2188             * (h-vMargin-squareSize/8-1) + vMargin;
2189         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2190         if(strstr(seekAdList[i], " u ")) color = 1;
2191         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2192            !strstr(seekAdList[i], "bullet") &&
2193            !strstr(seekAdList[i], "blitz") &&
2194            !strstr(seekAdList[i], "standard") ) color = 2;
2195         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2196         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2197 }
2198
2199 void
2200 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2201 {
2202         char buf[MSG_SIZ], *ext = "";
2203         VariantClass v = StringToVariant(type);
2204         if(strstr(type, "wild")) {
2205             ext = type + 4; // append wild number
2206             if(v == VariantFischeRandom) type = "chess960"; else
2207             if(v == VariantLoadable) type = "setup"; else
2208             type = VariantName(v);
2209         }
2210         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2211         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2212             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2213             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2214             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2215             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2216             seekNrList[nrOfSeekAds] = nr;
2217             zList[nrOfSeekAds] = 0;
2218             seekAdList[nrOfSeekAds++] = StrSave(buf);
2219             if(plot) PlotSeekAd(nrOfSeekAds-1);
2220         }
2221 }
2222
2223 void
2224 EraseSeekDot(int i)
2225 {
2226     int x = xList[i], y = yList[i], d=squareSize/4, k;
2227     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2228     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2229     // now replot every dot that overlapped
2230     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2231         int xx = xList[k], yy = yList[k];
2232         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2233             DrawSeekDot(xx, yy, colorList[k]);
2234     }
2235 }
2236
2237 void
2238 RemoveSeekAd(int nr)
2239 {
2240         int i;
2241         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2242             EraseSeekDot(i);
2243             if(seekAdList[i]) free(seekAdList[i]);
2244             seekAdList[i] = seekAdList[--nrOfSeekAds];
2245             seekNrList[i] = seekNrList[nrOfSeekAds];
2246             ratingList[i] = ratingList[nrOfSeekAds];
2247             colorList[i]  = colorList[nrOfSeekAds];
2248             tcList[i] = tcList[nrOfSeekAds];
2249             xList[i]  = xList[nrOfSeekAds];
2250             yList[i]  = yList[nrOfSeekAds];
2251             zList[i]  = zList[nrOfSeekAds];
2252             seekAdList[nrOfSeekAds] = NULL;
2253             break;
2254         }
2255 }
2256
2257 Boolean
2258 MatchSoughtLine(char *line)
2259 {
2260     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2261     int nr, base, inc, u=0; char dummy;
2262
2263     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2264        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2265        (u=1) &&
2266        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2267         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2268         // match: compact and save the line
2269         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2270         return TRUE;
2271     }
2272     return FALSE;
2273 }
2274
2275 int
2276 DrawSeekGraph()
2277 {
2278     int i;
2279     if(!seekGraphUp) return FALSE;
2280     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2281     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2282
2283     DrawSeekBackground(0, 0, w, h);
2284     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2285     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2286     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2287         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2288         yy = h-1-yy;
2289         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2290         if(i%500 == 0) {
2291             char buf[MSG_SIZ];
2292             snprintf(buf, MSG_SIZ, "%d", i);
2293             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2294         }
2295     }
2296     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2297     for(i=1; i<100; i+=(i<10?1:5)) {
2298         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2299         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2300         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2301             char buf[MSG_SIZ];
2302             snprintf(buf, MSG_SIZ, "%d", i);
2303             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2304         }
2305     }
2306     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2307     return TRUE;
2308 }
2309
2310 int SeekGraphClick(ClickType click, int x, int y, int moving)
2311 {
2312     static int lastDown = 0, displayed = 0, lastSecond;
2313     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2314         if(click == Release || moving) return FALSE;
2315         nrOfSeekAds = 0;
2316         soughtPending = TRUE;
2317         SendToICS(ics_prefix);
2318         SendToICS("sought\n"); // should this be "sought all"?
2319     } else { // issue challenge based on clicked ad
2320         int dist = 10000; int i, closest = 0, second = 0;
2321         for(i=0; i<nrOfSeekAds; i++) {
2322             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2323             if(d < dist) { dist = d; closest = i; }
2324             second += (d - zList[i] < 120); // count in-range ads
2325             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2326         }
2327         if(dist < 120) {
2328             char buf[MSG_SIZ];
2329             second = (second > 1);
2330             if(displayed != closest || second != lastSecond) {
2331                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2332                 lastSecond = second; displayed = closest;
2333             }
2334             if(click == Press) {
2335                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2336                 lastDown = closest;
2337                 return TRUE;
2338             } // on press 'hit', only show info
2339             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2340             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2341             SendToICS(ics_prefix);
2342             SendToICS(buf);
2343             return TRUE; // let incoming board of started game pop down the graph
2344         } else if(click == Release) { // release 'miss' is ignored
2345             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2346             if(moving == 2) { // right up-click
2347                 nrOfSeekAds = 0; // refresh graph
2348                 soughtPending = TRUE;
2349                 SendToICS(ics_prefix);
2350                 SendToICS("sought\n"); // should this be "sought all"?
2351             }
2352             return TRUE;
2353         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2354         // press miss or release hit 'pop down' seek graph
2355         seekGraphUp = FALSE;
2356         DrawPosition(TRUE, NULL);
2357     }
2358     return TRUE;
2359 }
2360
2361 void
2362 read_from_ics(isr, closure, data, count, error)
2363      InputSourceRef isr;
2364      VOIDSTAR closure;
2365      char *data;
2366      int count;
2367      int error;
2368 {
2369 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2370 #define STARTED_NONE 0
2371 #define STARTED_MOVES 1
2372 #define STARTED_BOARD 2
2373 #define STARTED_OBSERVE 3
2374 #define STARTED_HOLDINGS 4
2375 #define STARTED_CHATTER 5
2376 #define STARTED_COMMENT 6
2377 #define STARTED_MOVES_NOHIDE 7
2378
2379     static int started = STARTED_NONE;
2380     static char parse[20000];
2381     static int parse_pos = 0;
2382     static char buf[BUF_SIZE + 1];
2383     static int firstTime = TRUE, intfSet = FALSE;
2384     static ColorClass prevColor = ColorNormal;
2385     static int savingComment = FALSE;
2386     static int cmatch = 0; // continuation sequence match
2387     char *bp;
2388     char str[MSG_SIZ];
2389     int i, oldi;
2390     int buf_len;
2391     int next_out;
2392     int tkind;
2393     int backup;    /* [DM] For zippy color lines */
2394     char *p;
2395     char talker[MSG_SIZ]; // [HGM] chat
2396     int channel;
2397
2398     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2399
2400     if (appData.debugMode) {
2401       if (!error) {
2402         fprintf(debugFP, "<ICS: ");
2403         show_bytes(debugFP, data, count);
2404         fprintf(debugFP, "\n");
2405       }
2406     }
2407
2408     if (appData.debugMode) { int f = forwardMostMove;
2409         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2410                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2411                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2412     }
2413     if (count > 0) {
2414         /* If last read ended with a partial line that we couldn't parse,
2415            prepend it to the new read and try again. */
2416         if (leftover_len > 0) {
2417             for (i=0; i<leftover_len; i++)
2418               buf[i] = buf[leftover_start + i];
2419         }
2420
2421     /* copy new characters into the buffer */
2422     bp = buf + leftover_len;
2423     buf_len=leftover_len;
2424     for (i=0; i<count; i++)
2425     {
2426         // ignore these
2427         if (data[i] == '\r')
2428             continue;
2429
2430         // join lines split by ICS?
2431         if (!appData.noJoin)
2432         {
2433             /*
2434                 Joining just consists of finding matches against the
2435                 continuation sequence, and discarding that sequence
2436                 if found instead of copying it.  So, until a match
2437                 fails, there's nothing to do since it might be the
2438                 complete sequence, and thus, something we don't want
2439                 copied.
2440             */
2441             if (data[i] == cont_seq[cmatch])
2442             {
2443                 cmatch++;
2444                 if (cmatch == strlen(cont_seq))
2445                 {
2446                     cmatch = 0; // complete match.  just reset the counter
2447
2448                     /*
2449                         it's possible for the ICS to not include the space
2450                         at the end of the last word, making our [correct]
2451                         join operation fuse two separate words.  the server
2452                         does this when the space occurs at the width setting.
2453                     */
2454                     if (!buf_len || buf[buf_len-1] != ' ')
2455                     {
2456                         *bp++ = ' ';
2457                         buf_len++;
2458                     }
2459                 }
2460                 continue;
2461             }
2462             else if (cmatch)
2463             {
2464                 /*
2465                     match failed, so we have to copy what matched before
2466                     falling through and copying this character.  In reality,
2467                     this will only ever be just the newline character, but
2468                     it doesn't hurt to be precise.
2469                 */
2470                 strncpy(bp, cont_seq, cmatch);
2471                 bp += cmatch;
2472                 buf_len += cmatch;
2473                 cmatch = 0;
2474             }
2475         }
2476
2477         // copy this char
2478         *bp++ = data[i];
2479         buf_len++;
2480     }
2481
2482         buf[buf_len] = NULLCHAR;
2483 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2484         next_out = 0;
2485         leftover_start = 0;
2486
2487         i = 0;
2488         while (i < buf_len) {
2489             /* Deal with part of the TELNET option negotiation
2490                protocol.  We refuse to do anything beyond the
2491                defaults, except that we allow the WILL ECHO option,
2492                which ICS uses to turn off password echoing when we are
2493                directly connected to it.  We reject this option
2494                if localLineEditing mode is on (always on in xboard)
2495                and we are talking to port 23, which might be a real
2496                telnet server that will try to keep WILL ECHO on permanently.
2497              */
2498             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2499                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2500                 unsigned char option;
2501                 oldi = i;
2502                 switch ((unsigned char) buf[++i]) {
2503                   case TN_WILL:
2504                     if (appData.debugMode)
2505                       fprintf(debugFP, "\n<WILL ");
2506                     switch (option = (unsigned char) buf[++i]) {
2507                       case TN_ECHO:
2508                         if (appData.debugMode)
2509                           fprintf(debugFP, "ECHO ");
2510                         /* Reply only if this is a change, according
2511                            to the protocol rules. */
2512                         if (remoteEchoOption) break;
2513                         if (appData.localLineEditing &&
2514                             atoi(appData.icsPort) == TN_PORT) {
2515                             TelnetRequest(TN_DONT, TN_ECHO);
2516                         } else {
2517                             EchoOff();
2518                             TelnetRequest(TN_DO, TN_ECHO);
2519                             remoteEchoOption = TRUE;
2520                         }
2521                         break;
2522                       default:
2523                         if (appData.debugMode)
2524                           fprintf(debugFP, "%d ", option);
2525                         /* Whatever this is, we don't want it. */
2526                         TelnetRequest(TN_DONT, option);
2527                         break;
2528                     }
2529                     break;
2530                   case TN_WONT:
2531                     if (appData.debugMode)
2532                       fprintf(debugFP, "\n<WONT ");
2533                     switch (option = (unsigned char) buf[++i]) {
2534                       case TN_ECHO:
2535                         if (appData.debugMode)
2536                           fprintf(debugFP, "ECHO ");
2537                         /* Reply only if this is a change, according
2538                            to the protocol rules. */
2539                         if (!remoteEchoOption) break;
2540                         EchoOn();
2541                         TelnetRequest(TN_DONT, TN_ECHO);
2542                         remoteEchoOption = FALSE;
2543                         break;
2544                       default:
2545                         if (appData.debugMode)
2546                           fprintf(debugFP, "%d ", (unsigned char) option);
2547                         /* Whatever this is, it must already be turned
2548                            off, because we never agree to turn on
2549                            anything non-default, so according to the
2550                            protocol rules, we don't reply. */
2551                         break;
2552                     }
2553                     break;
2554                   case TN_DO:
2555                     if (appData.debugMode)
2556                       fprintf(debugFP, "\n<DO ");
2557                     switch (option = (unsigned char) buf[++i]) {
2558                       default:
2559                         /* Whatever this is, we refuse to do it. */
2560                         if (appData.debugMode)
2561                           fprintf(debugFP, "%d ", option);
2562                         TelnetRequest(TN_WONT, option);
2563                         break;
2564                     }
2565                     break;
2566                   case TN_DONT:
2567                     if (appData.debugMode)
2568                       fprintf(debugFP, "\n<DONT ");
2569                     switch (option = (unsigned char) buf[++i]) {
2570                       default:
2571                         if (appData.debugMode)
2572                           fprintf(debugFP, "%d ", option);
2573                         /* Whatever this is, we are already not doing
2574                            it, because we never agree to do anything
2575                            non-default, so according to the protocol
2576                            rules, we don't reply. */
2577                         break;
2578                     }
2579                     break;
2580                   case TN_IAC:
2581                     if (appData.debugMode)
2582                       fprintf(debugFP, "\n<IAC ");
2583                     /* Doubled IAC; pass it through */
2584                     i--;
2585                     break;
2586                   default:
2587                     if (appData.debugMode)
2588                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2589                     /* Drop all other telnet commands on the floor */
2590                     break;
2591                 }
2592                 if (oldi > next_out)
2593                   SendToPlayer(&buf[next_out], oldi - next_out);
2594                 if (++i > next_out)
2595                   next_out = i;
2596                 continue;
2597             }
2598
2599             /* OK, this at least will *usually* work */
2600             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2601                 loggedOn = TRUE;
2602             }
2603
2604             if (loggedOn && !intfSet) {
2605                 if (ics_type == ICS_ICC) {
2606                   snprintf(str, MSG_SIZ,
2607                           "/set-quietly interface %s\n/set-quietly style 12\n",
2608                           programVersion);
2609                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2610                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2611                 } else if (ics_type == ICS_CHESSNET) {
2612                   snprintf(str, MSG_SIZ, "/style 12\n");
2613                 } else {
2614                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2615                   strcat(str, programVersion);
2616                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2617                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2618                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2619 #ifdef WIN32
2620                   strcat(str, "$iset nohighlight 1\n");
2621 #endif
2622                   strcat(str, "$iset lock 1\n$style 12\n");
2623                 }
2624                 SendToICS(str);
2625                 NotifyFrontendLogin();
2626                 intfSet = TRUE;
2627             }
2628
2629             if (started == STARTED_COMMENT) {
2630                 /* Accumulate characters in comment */
2631                 parse[parse_pos++] = buf[i];
2632                 if (buf[i] == '\n') {
2633                     parse[parse_pos] = NULLCHAR;
2634                     if(chattingPartner>=0) {
2635                         char mess[MSG_SIZ];
2636                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2637                         OutputChatMessage(chattingPartner, mess);
2638                         chattingPartner = -1;
2639                         next_out = i+1; // [HGM] suppress printing in ICS window
2640                     } else
2641                     if(!suppressKibitz) // [HGM] kibitz
2642                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2643                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2644                         int nrDigit = 0, nrAlph = 0, j;
2645                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2646                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2647                         parse[parse_pos] = NULLCHAR;
2648                         // try to be smart: if it does not look like search info, it should go to
2649                         // ICS interaction window after all, not to engine-output window.
2650                         for(j=0; j<parse_pos; j++) { // count letters and digits
2651                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2652                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2653                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2654                         }
2655                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2656                             int depth=0; float score;
2657                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2658                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2659                                 pvInfoList[forwardMostMove-1].depth = depth;
2660                                 pvInfoList[forwardMostMove-1].score = 100*score;
2661                             }
2662                             OutputKibitz(suppressKibitz, parse);
2663                         } else {
2664                             char tmp[MSG_SIZ];
2665                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2666                             SendToPlayer(tmp, strlen(tmp));
2667                         }
2668                         next_out = i+1; // [HGM] suppress printing in ICS window
2669                     }
2670                     started = STARTED_NONE;
2671                 } else {
2672                     /* Don't match patterns against characters in comment */
2673                     i++;
2674                     continue;
2675                 }
2676             }
2677             if (started == STARTED_CHATTER) {
2678                 if (buf[i] != '\n') {
2679                     /* Don't match patterns against characters in chatter */
2680                     i++;
2681                     continue;
2682                 }
2683                 started = STARTED_NONE;
2684                 if(suppressKibitz) next_out = i+1;
2685             }
2686
2687             /* Kludge to deal with rcmd protocol */
2688             if (firstTime && looking_at(buf, &i, "\001*")) {
2689                 DisplayFatalError(&buf[1], 0, 1);
2690                 continue;
2691             } else {
2692                 firstTime = FALSE;
2693             }
2694
2695             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2696                 ics_type = ICS_ICC;
2697                 ics_prefix = "/";
2698                 if (appData.debugMode)
2699                   fprintf(debugFP, "ics_type %d\n", ics_type);
2700                 continue;
2701             }
2702             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2703                 ics_type = ICS_FICS;
2704                 ics_prefix = "$";
2705                 if (appData.debugMode)
2706                   fprintf(debugFP, "ics_type %d\n", ics_type);
2707                 continue;
2708             }
2709             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2710                 ics_type = ICS_CHESSNET;
2711                 ics_prefix = "/";
2712                 if (appData.debugMode)
2713                   fprintf(debugFP, "ics_type %d\n", ics_type);
2714                 continue;
2715             }
2716
2717             if (!loggedOn &&
2718                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2719                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2720                  looking_at(buf, &i, "will be \"*\""))) {
2721               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2722               continue;
2723             }
2724
2725             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2726               char buf[MSG_SIZ];
2727               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2728               DisplayIcsInteractionTitle(buf);
2729               have_set_title = TRUE;
2730             }
2731
2732             /* skip finger notes */
2733             if (started == STARTED_NONE &&
2734                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2735                  (buf[i] == '1' && buf[i+1] == '0')) &&
2736                 buf[i+2] == ':' && buf[i+3] == ' ') {
2737               started = STARTED_CHATTER;
2738               i += 3;
2739               continue;
2740             }
2741
2742             oldi = i;
2743             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2744             if(appData.seekGraph) {
2745                 if(soughtPending && MatchSoughtLine(buf+i)) {
2746                     i = strstr(buf+i, "rated") - buf;
2747                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2748                     next_out = leftover_start = i;
2749                     started = STARTED_CHATTER;
2750                     suppressKibitz = TRUE;
2751                     continue;
2752                 }
2753                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2754                         && looking_at(buf, &i, "* ads displayed")) {
2755                     soughtPending = FALSE;
2756                     seekGraphUp = TRUE;
2757                     DrawSeekGraph();
2758                     continue;
2759                 }
2760                 if(appData.autoRefresh) {
2761                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2762                         int s = (ics_type == ICS_ICC); // ICC format differs
2763                         if(seekGraphUp)
2764                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2765                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2766                         looking_at(buf, &i, "*% "); // eat prompt
2767                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2768                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2769                         next_out = i; // suppress
2770                         continue;
2771                     }
2772                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2773                         char *p = star_match[0];
2774                         while(*p) {
2775                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2776                             while(*p && *p++ != ' '); // next
2777                         }
2778                         looking_at(buf, &i, "*% "); // eat prompt
2779                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2780                         next_out = i;
2781                         continue;
2782                     }
2783                 }
2784             }
2785
2786             /* skip formula vars */
2787             if (started == STARTED_NONE &&
2788                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2789               started = STARTED_CHATTER;
2790               i += 3;
2791               continue;
2792             }
2793
2794             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2795             if (appData.autoKibitz && started == STARTED_NONE &&
2796                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2797                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2798                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2799                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2800                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2801                         suppressKibitz = TRUE;
2802                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2803                         next_out = i;
2804                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2805                                 && (gameMode == IcsPlayingWhite)) ||
2806                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2807                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2808                             started = STARTED_CHATTER; // own kibitz we simply discard
2809                         else {
2810                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2811                             parse_pos = 0; parse[0] = NULLCHAR;
2812                             savingComment = TRUE;
2813                             suppressKibitz = gameMode != IcsObserving ? 2 :
2814                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2815                         }
2816                         continue;
2817                 } else
2818                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2819                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2820                          && atoi(star_match[0])) {
2821                     // suppress the acknowledgements of our own autoKibitz
2822                     char *p;
2823                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2824                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2825                     SendToPlayer(star_match[0], strlen(star_match[0]));
2826                     if(looking_at(buf, &i, "*% ")) // eat prompt
2827                         suppressKibitz = FALSE;
2828                     next_out = i;
2829                     continue;
2830                 }
2831             } // [HGM] kibitz: end of patch
2832
2833             // [HGM] chat: intercept tells by users for which we have an open chat window
2834             channel = -1;
2835             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2836                                            looking_at(buf, &i, "* whispers:") ||
2837                                            looking_at(buf, &i, "* kibitzes:") ||
2838                                            looking_at(buf, &i, "* shouts:") ||
2839                                            looking_at(buf, &i, "* c-shouts:") ||
2840                                            looking_at(buf, &i, "--> * ") ||
2841                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2842                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2843                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2844                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2845                 int p;
2846                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2847                 chattingPartner = -1;
2848
2849                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2850                 for(p=0; p<MAX_CHAT; p++) {
2851                     if(channel == atoi(chatPartner[p])) {
2852                     talker[0] = '['; strcat(talker, "] ");
2853                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2854                     chattingPartner = p; break;
2855                     }
2856                 } else
2857                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2858                 for(p=0; p<MAX_CHAT; p++) {
2859                     if(!strcmp("kibitzes", chatPartner[p])) {
2860                         talker[0] = '['; strcat(talker, "] ");
2861                         chattingPartner = p; break;
2862                     }
2863                 } else
2864                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2865                 for(p=0; p<MAX_CHAT; p++) {
2866                     if(!strcmp("whispers", chatPartner[p])) {
2867                         talker[0] = '['; strcat(talker, "] ");
2868                         chattingPartner = p; break;
2869                     }
2870                 } else
2871                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2872                   if(buf[i-8] == '-' && buf[i-3] == 't')
2873                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2874                     if(!strcmp("c-shouts", chatPartner[p])) {
2875                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2876                         chattingPartner = p; break;
2877                     }
2878                   }
2879                   if(chattingPartner < 0)
2880                   for(p=0; p<MAX_CHAT; p++) {
2881                     if(!strcmp("shouts", chatPartner[p])) {
2882                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2883                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2884                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2885                         chattingPartner = p; break;
2886                     }
2887                   }
2888                 }
2889                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2890                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2891                     talker[0] = 0; Colorize(ColorTell, FALSE);
2892                     chattingPartner = p; break;
2893                 }
2894                 if(chattingPartner<0) i = oldi; else {
2895                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2896                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2897                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2898                     started = STARTED_COMMENT;
2899                     parse_pos = 0; parse[0] = NULLCHAR;
2900                     savingComment = 3 + chattingPartner; // counts as TRUE
2901                     suppressKibitz = TRUE;
2902                     continue;
2903                 }
2904             } // [HGM] chat: end of patch
2905
2906             if (appData.zippyTalk || appData.zippyPlay) {
2907                 /* [DM] Backup address for color zippy lines */
2908                 backup = i;
2909 #if ZIPPY
2910                if (loggedOn == TRUE)
2911                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2912                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2913 #endif
2914             } // [DM] 'else { ' deleted
2915                 if (
2916                     /* Regular tells and says */
2917                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2918                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2919                     looking_at(buf, &i, "* says: ") ||
2920                     /* Don't color "message" or "messages" output */
2921                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2922                     looking_at(buf, &i, "*. * at *:*: ") ||
2923                     looking_at(buf, &i, "--* (*:*): ") ||
2924                     /* Message notifications (same color as tells) */
2925                     looking_at(buf, &i, "* has left a message ") ||
2926                     looking_at(buf, &i, "* just sent you a message:\n") ||
2927                     /* Whispers and kibitzes */
2928                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2929                     looking_at(buf, &i, "* kibitzes: ") ||
2930                     /* Channel tells */
2931                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2932
2933                   if (tkind == 1 && strchr(star_match[0], ':')) {
2934                       /* Avoid "tells you:" spoofs in channels */
2935                      tkind = 3;
2936                   }
2937                   if (star_match[0][0] == NULLCHAR ||
2938                       strchr(star_match[0], ' ') ||
2939                       (tkind == 3 && strchr(star_match[1], ' '))) {
2940                     /* Reject bogus matches */
2941                     i = oldi;
2942                   } else {
2943                     if (appData.colorize) {
2944                       if (oldi > next_out) {
2945                         SendToPlayer(&buf[next_out], oldi - next_out);
2946                         next_out = oldi;
2947                       }
2948                       switch (tkind) {
2949                       case 1:
2950                         Colorize(ColorTell, FALSE);
2951                         curColor = ColorTell;
2952                         break;
2953                       case 2:
2954                         Colorize(ColorKibitz, FALSE);
2955                         curColor = ColorKibitz;
2956                         break;
2957                       case 3:
2958                         p = strrchr(star_match[1], '(');
2959                         if (p == NULL) {
2960                           p = star_match[1];
2961                         } else {
2962                           p++;
2963                         }
2964                         if (atoi(p) == 1) {
2965                           Colorize(ColorChannel1, FALSE);
2966                           curColor = ColorChannel1;
2967                         } else {
2968                           Colorize(ColorChannel, FALSE);
2969                           curColor = ColorChannel;
2970                         }
2971                         break;
2972                       case 5:
2973                         curColor = ColorNormal;
2974                         break;
2975                       }
2976                     }
2977                     if (started == STARTED_NONE && appData.autoComment &&
2978                         (gameMode == IcsObserving ||
2979                          gameMode == IcsPlayingWhite ||
2980                          gameMode == IcsPlayingBlack)) {
2981                       parse_pos = i - oldi;
2982                       memcpy(parse, &buf[oldi], parse_pos);
2983                       parse[parse_pos] = NULLCHAR;
2984                       started = STARTED_COMMENT;
2985                       savingComment = TRUE;
2986                     } else {
2987                       started = STARTED_CHATTER;
2988                       savingComment = FALSE;
2989                     }
2990                     loggedOn = TRUE;
2991                     continue;
2992                   }
2993                 }
2994
2995                 if (looking_at(buf, &i, "* s-shouts: ") ||
2996                     looking_at(buf, &i, "* c-shouts: ")) {
2997                     if (appData.colorize) {
2998                         if (oldi > next_out) {
2999                             SendToPlayer(&buf[next_out], oldi - next_out);
3000                             next_out = oldi;
3001                         }
3002                         Colorize(ColorSShout, FALSE);
3003                         curColor = ColorSShout;
3004                     }
3005                     loggedOn = TRUE;
3006                     started = STARTED_CHATTER;
3007                     continue;
3008                 }
3009
3010                 if (looking_at(buf, &i, "--->")) {
3011                     loggedOn = TRUE;
3012                     continue;
3013                 }
3014
3015                 if (looking_at(buf, &i, "* shouts: ") ||
3016                     looking_at(buf, &i, "--> ")) {
3017                     if (appData.colorize) {
3018                         if (oldi > next_out) {
3019                             SendToPlayer(&buf[next_out], oldi - next_out);
3020                             next_out = oldi;
3021                         }
3022                         Colorize(ColorShout, FALSE);
3023                         curColor = ColorShout;
3024                     }
3025                     loggedOn = TRUE;
3026                     started = STARTED_CHATTER;
3027                     continue;
3028                 }
3029
3030                 if (looking_at( buf, &i, "Challenge:")) {
3031                     if (appData.colorize) {
3032                         if (oldi > next_out) {
3033                             SendToPlayer(&buf[next_out], oldi - next_out);
3034                             next_out = oldi;
3035                         }
3036                         Colorize(ColorChallenge, FALSE);
3037                         curColor = ColorChallenge;
3038                     }
3039                     loggedOn = TRUE;
3040                     continue;
3041                 }
3042
3043                 if (looking_at(buf, &i, "* offers you") ||
3044                     looking_at(buf, &i, "* offers to be") ||
3045                     looking_at(buf, &i, "* would like to") ||
3046                     looking_at(buf, &i, "* requests to") ||
3047                     looking_at(buf, &i, "Your opponent offers") ||
3048                     looking_at(buf, &i, "Your opponent requests")) {
3049
3050                     if (appData.colorize) {
3051                         if (oldi > next_out) {
3052                             SendToPlayer(&buf[next_out], oldi - next_out);
3053                             next_out = oldi;
3054                         }
3055                         Colorize(ColorRequest, FALSE);
3056                         curColor = ColorRequest;
3057                     }
3058                     continue;
3059                 }
3060
3061                 if (looking_at(buf, &i, "* (*) seeking")) {
3062                     if (appData.colorize) {
3063                         if (oldi > next_out) {
3064                             SendToPlayer(&buf[next_out], oldi - next_out);
3065                             next_out = oldi;
3066                         }
3067                         Colorize(ColorSeek, FALSE);
3068                         curColor = ColorSeek;
3069                     }
3070                     continue;
3071             }
3072
3073             if (looking_at(buf, &i, "\\   ")) {
3074                 if (prevColor != ColorNormal) {
3075                     if (oldi > next_out) {
3076                         SendToPlayer(&buf[next_out], oldi - next_out);
3077                         next_out = oldi;
3078                     }
3079                     Colorize(prevColor, TRUE);
3080                     curColor = prevColor;
3081                 }
3082                 if (savingComment) {
3083                     parse_pos = i - oldi;
3084                     memcpy(parse, &buf[oldi], parse_pos);
3085                     parse[parse_pos] = NULLCHAR;
3086                     started = STARTED_COMMENT;
3087                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3088                         chattingPartner = savingComment - 3; // kludge to remember the box
3089                 } else {
3090                     started = STARTED_CHATTER;
3091                 }
3092                 continue;
3093             }
3094
3095             if (looking_at(buf, &i, "Black Strength :") ||
3096                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3097                 looking_at(buf, &i, "<10>") ||
3098                 looking_at(buf, &i, "#@#")) {
3099                 /* Wrong board style */
3100                 loggedOn = TRUE;
3101                 SendToICS(ics_prefix);
3102                 SendToICS("set style 12\n");
3103                 SendToICS(ics_prefix);
3104                 SendToICS("refresh\n");
3105                 continue;
3106             }
3107
3108             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3109                 ICSInitScript();
3110                 have_sent_ICS_logon = 1;
3111                 continue;
3112             }
3113
3114             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3115                 (looking_at(buf, &i, "\n<12> ") ||
3116                  looking_at(buf, &i, "<12> "))) {
3117                 loggedOn = TRUE;
3118                 if (oldi > next_out) {
3119                     SendToPlayer(&buf[next_out], oldi - next_out);
3120                 }
3121                 next_out = i;
3122                 started = STARTED_BOARD;
3123                 parse_pos = 0;
3124                 continue;
3125             }
3126
3127             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3128                 looking_at(buf, &i, "<b1> ")) {
3129                 if (oldi > next_out) {
3130                     SendToPlayer(&buf[next_out], oldi - next_out);
3131                 }
3132                 next_out = i;
3133                 started = STARTED_HOLDINGS;
3134                 parse_pos = 0;
3135                 continue;
3136             }
3137
3138             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3139                 loggedOn = TRUE;
3140                 /* Header for a move list -- first line */
3141
3142                 switch (ics_getting_history) {
3143                   case H_FALSE:
3144                     switch (gameMode) {
3145                       case IcsIdle:
3146                       case BeginningOfGame:
3147                         /* User typed "moves" or "oldmoves" while we
3148                            were idle.  Pretend we asked for these
3149                            moves and soak them up so user can step
3150                            through them and/or save them.
3151                            */
3152                         Reset(FALSE, TRUE);
3153                         gameMode = IcsObserving;
3154                         ModeHighlight();
3155                         ics_gamenum = -1;
3156                         ics_getting_history = H_GOT_UNREQ_HEADER;
3157                         break;
3158                       case EditGame: /*?*/
3159                       case EditPosition: /*?*/
3160                         /* Should above feature work in these modes too? */
3161                         /* For now it doesn't */
3162                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3163                         break;
3164                       default:
3165                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3166                         break;
3167                     }
3168                     break;
3169                   case H_REQUESTED:
3170                     /* Is this the right one? */
3171                     if (gameInfo.white && gameInfo.black &&
3172                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3173                         strcmp(gameInfo.black, star_match[2]) == 0) {
3174                         /* All is well */
3175                         ics_getting_history = H_GOT_REQ_HEADER;
3176                     }
3177                     break;
3178                   case H_GOT_REQ_HEADER:
3179                   case H_GOT_UNREQ_HEADER:
3180                   case H_GOT_UNWANTED_HEADER:
3181                   case H_GETTING_MOVES:
3182                     /* Should not happen */
3183                     DisplayError(_("Error gathering move list: two headers"), 0);
3184                     ics_getting_history = H_FALSE;
3185                     break;
3186                 }
3187
3188                 /* Save player ratings into gameInfo if needed */
3189                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3190                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3191                     (gameInfo.whiteRating == -1 ||
3192                      gameInfo.blackRating == -1)) {
3193
3194                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3195                     gameInfo.blackRating = string_to_rating(star_match[3]);
3196                     if (appData.debugMode)
3197                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3198                               gameInfo.whiteRating, gameInfo.blackRating);
3199                 }
3200                 continue;
3201             }
3202
3203             if (looking_at(buf, &i,
3204               "* * match, initial time: * minute*, increment: * second")) {
3205                 /* Header for a move list -- second line */
3206                 /* Initial board will follow if this is a wild game */
3207                 if (gameInfo.event != NULL) free(gameInfo.event);
3208                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3209                 gameInfo.event = StrSave(str);
3210                 /* [HGM] we switched variant. Translate boards if needed. */
3211                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3212                 continue;
3213             }
3214
3215             if (looking_at(buf, &i, "Move  ")) {
3216                 /* Beginning of a move list */
3217                 switch (ics_getting_history) {
3218                   case H_FALSE:
3219                     /* Normally should not happen */
3220                     /* Maybe user hit reset while we were parsing */
3221                     break;
3222                   case H_REQUESTED:
3223                     /* Happens if we are ignoring a move list that is not
3224                      * the one we just requested.  Common if the user
3225                      * tries to observe two games without turning off
3226                      * getMoveList */
3227                     break;
3228                   case H_GETTING_MOVES:
3229                     /* Should not happen */
3230                     DisplayError(_("Error gathering move list: nested"), 0);
3231                     ics_getting_history = H_FALSE;
3232                     break;
3233                   case H_GOT_REQ_HEADER:
3234                     ics_getting_history = H_GETTING_MOVES;
3235                     started = STARTED_MOVES;
3236                     parse_pos = 0;
3237                     if (oldi > next_out) {
3238                         SendToPlayer(&buf[next_out], oldi - next_out);
3239                     }
3240                     break;
3241                   case H_GOT_UNREQ_HEADER:
3242                     ics_getting_history = H_GETTING_MOVES;
3243                     started = STARTED_MOVES_NOHIDE;
3244                     parse_pos = 0;
3245                     break;
3246                   case H_GOT_UNWANTED_HEADER:
3247                     ics_getting_history = H_FALSE;
3248                     break;
3249                 }
3250                 continue;
3251             }
3252
3253             if (looking_at(buf, &i, "% ") ||
3254                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3255                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3256                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3257                     soughtPending = FALSE;
3258                     seekGraphUp = TRUE;
3259                     DrawSeekGraph();
3260                 }
3261                 if(suppressKibitz) next_out = i;
3262                 savingComment = FALSE;
3263                 suppressKibitz = 0;
3264                 switch (started) {
3265                   case STARTED_MOVES:
3266                   case STARTED_MOVES_NOHIDE:
3267                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3268                     parse[parse_pos + i - oldi] = NULLCHAR;
3269                     ParseGameHistory(parse);
3270 #if ZIPPY
3271                     if (appData.zippyPlay && first.initDone) {
3272                         FeedMovesToProgram(&first, forwardMostMove);
3273                         if (gameMode == IcsPlayingWhite) {
3274                             if (WhiteOnMove(forwardMostMove)) {
3275                                 if (first.sendTime) {
3276                                   if (first.useColors) {
3277                                     SendToProgram("black\n", &first);
3278                                   }
3279                                   SendTimeRemaining(&first, TRUE);
3280                                 }
3281                                 if (first.useColors) {
3282                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3283                                 }
3284                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3285                                 first.maybeThinking = TRUE;
3286                             } else {
3287                                 if (first.usePlayother) {
3288                                   if (first.sendTime) {
3289                                     SendTimeRemaining(&first, TRUE);
3290                                   }
3291                                   SendToProgram("playother\n", &first);
3292                                   firstMove = FALSE;
3293                                 } else {
3294                                   firstMove = TRUE;
3295                                 }
3296                             }
3297                         } else if (gameMode == IcsPlayingBlack) {
3298                             if (!WhiteOnMove(forwardMostMove)) {
3299                                 if (first.sendTime) {
3300                                   if (first.useColors) {
3301                                     SendToProgram("white\n", &first);
3302                                   }
3303                                   SendTimeRemaining(&first, FALSE);
3304                                 }
3305                                 if (first.useColors) {
3306                                   SendToProgram("black\n", &first);
3307                                 }
3308                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3309                                 first.maybeThinking = TRUE;
3310                             } else {
3311                                 if (first.usePlayother) {
3312                                   if (first.sendTime) {
3313                                     SendTimeRemaining(&first, FALSE);
3314                                   }
3315                                   SendToProgram("playother\n", &first);
3316                                   firstMove = FALSE;
3317                                 } else {
3318                                   firstMove = TRUE;
3319                                 }
3320                             }
3321                         }
3322                     }
3323 #endif
3324                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3325                         /* Moves came from oldmoves or moves command
3326                            while we weren't doing anything else.
3327                            */
3328                         currentMove = forwardMostMove;
3329                         ClearHighlights();/*!!could figure this out*/
3330                         flipView = appData.flipView;
3331                         DrawPosition(TRUE, boards[currentMove]);
3332                         DisplayBothClocks();
3333                         snprintf(str, MSG_SIZ, "%s vs. %s",
3334                                 gameInfo.white, gameInfo.black);
3335                         DisplayTitle(str);
3336                         gameMode = IcsIdle;
3337                     } else {
3338                         /* Moves were history of an active game */
3339                         if (gameInfo.resultDetails != NULL) {
3340                             free(gameInfo.resultDetails);
3341                             gameInfo.resultDetails = NULL;
3342                         }
3343                     }
3344                     HistorySet(parseList, backwardMostMove,
3345                                forwardMostMove, currentMove-1);
3346                     DisplayMove(currentMove - 1);
3347                     if (started == STARTED_MOVES) next_out = i;
3348                     started = STARTED_NONE;
3349                     ics_getting_history = H_FALSE;
3350                     break;
3351
3352                   case STARTED_OBSERVE:
3353                     started = STARTED_NONE;
3354                     SendToICS(ics_prefix);
3355                     SendToICS("refresh\n");
3356                     break;
3357
3358                   default:
3359                     break;
3360                 }
3361                 if(bookHit) { // [HGM] book: simulate book reply
3362                     static char bookMove[MSG_SIZ]; // a bit generous?
3363
3364                     programStats.nodes = programStats.depth = programStats.time =
3365                     programStats.score = programStats.got_only_move = 0;
3366                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3367
3368                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3369                     strcat(bookMove, bookHit);
3370                     HandleMachineMove(bookMove, &first);
3371                 }
3372                 continue;
3373             }
3374
3375             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3376                  started == STARTED_HOLDINGS ||
3377                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3378                 /* Accumulate characters in move list or board */
3379                 parse[parse_pos++] = buf[i];
3380             }
3381
3382             /* Start of game messages.  Mostly we detect start of game
3383                when the first board image arrives.  On some versions
3384                of the ICS, though, we need to do a "refresh" after starting
3385                to observe in order to get the current board right away. */
3386             if (looking_at(buf, &i, "Adding game * to observation list")) {
3387                 started = STARTED_OBSERVE;
3388                 continue;
3389             }
3390
3391             /* Handle auto-observe */
3392             if (appData.autoObserve &&
3393                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3394                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3395                 char *player;
3396                 /* Choose the player that was highlighted, if any. */
3397                 if (star_match[0][0] == '\033' ||
3398                     star_match[1][0] != '\033') {
3399                     player = star_match[0];
3400                 } else {
3401                     player = star_match[2];
3402                 }
3403                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3404                         ics_prefix, StripHighlightAndTitle(player));
3405                 SendToICS(str);
3406
3407                 /* Save ratings from notify string */
3408                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3409                 player1Rating = string_to_rating(star_match[1]);
3410                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3411                 player2Rating = string_to_rating(star_match[3]);
3412
3413                 if (appData.debugMode)
3414                   fprintf(debugFP,
3415                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3416                           player1Name, player1Rating,
3417                           player2Name, player2Rating);
3418
3419                 continue;
3420             }
3421
3422             /* Deal with automatic examine mode after a game,
3423                and with IcsObserving -> IcsExamining transition */
3424             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3425                 looking_at(buf, &i, "has made you an examiner of game *")) {
3426
3427                 int gamenum = atoi(star_match[0]);
3428                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3429                     gamenum == ics_gamenum) {
3430                     /* We were already playing or observing this game;
3431                        no need to refetch history */
3432                     gameMode = IcsExamining;
3433                     if (pausing) {
3434                         pauseExamForwardMostMove = forwardMostMove;
3435                     } else if (currentMove < forwardMostMove) {
3436                         ForwardInner(forwardMostMove);
3437                     }
3438                 } else {
3439                     /* I don't think this case really can happen */
3440                     SendToICS(ics_prefix);
3441                     SendToICS("refresh\n");
3442                 }
3443                 continue;
3444             }
3445
3446             /* Error messages */
3447 //          if (ics_user_moved) {
3448             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3449                 if (looking_at(buf, &i, "Illegal move") ||
3450                     looking_at(buf, &i, "Not a legal move") ||
3451                     looking_at(buf, &i, "Your king is in check") ||
3452                     looking_at(buf, &i, "It isn't your turn") ||
3453                     looking_at(buf, &i, "It is not your move")) {
3454                     /* Illegal move */
3455                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3456                         currentMove = forwardMostMove-1;
3457                         DisplayMove(currentMove - 1); /* before DMError */
3458                         DrawPosition(FALSE, boards[currentMove]);
3459                         SwitchClocks(forwardMostMove-1); // [HGM] race
3460                         DisplayBothClocks();
3461                     }
3462                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3463                     ics_user_moved = 0;
3464                     continue;
3465                 }
3466             }
3467
3468             if (looking_at(buf, &i, "still have time") ||
3469                 looking_at(buf, &i, "not out of time") ||
3470                 looking_at(buf, &i, "either player is out of time") ||
3471                 looking_at(buf, &i, "has timeseal; checking")) {
3472                 /* We must have called his flag a little too soon */
3473                 whiteFlag = blackFlag = FALSE;
3474                 continue;
3475             }
3476
3477             if (looking_at(buf, &i, "added * seconds to") ||
3478                 looking_at(buf, &i, "seconds were added to")) {
3479                 /* Update the clocks */
3480                 SendToICS(ics_prefix);
3481                 SendToICS("refresh\n");
3482                 continue;
3483             }
3484
3485             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3486                 ics_clock_paused = TRUE;
3487                 StopClocks();
3488                 continue;
3489             }
3490
3491             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3492                 ics_clock_paused = FALSE;
3493                 StartClocks();
3494                 continue;
3495             }
3496
3497             /* Grab player ratings from the Creating: message.
3498                Note we have to check for the special case when
3499                the ICS inserts things like [white] or [black]. */
3500             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3501                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3502                 /* star_matches:
3503                    0    player 1 name (not necessarily white)
3504                    1    player 1 rating
3505                    2    empty, white, or black (IGNORED)
3506                    3    player 2 name (not necessarily black)
3507                    4    player 2 rating
3508
3509                    The names/ratings are sorted out when the game
3510                    actually starts (below).
3511                 */
3512                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3513                 player1Rating = string_to_rating(star_match[1]);
3514                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3515                 player2Rating = string_to_rating(star_match[4]);
3516
3517                 if (appData.debugMode)
3518                   fprintf(debugFP,
3519                           "Ratings from 'Creating:' %s %d, %s %d\n",
3520                           player1Name, player1Rating,
3521                           player2Name, player2Rating);
3522
3523                 continue;
3524             }
3525
3526             /* Improved generic start/end-of-game messages */
3527             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3528                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3529                 /* If tkind == 0: */
3530                 /* star_match[0] is the game number */
3531                 /*           [1] is the white player's name */
3532                 /*           [2] is the black player's name */
3533                 /* For end-of-game: */
3534                 /*           [3] is the reason for the game end */
3535                 /*           [4] is a PGN end game-token, preceded by " " */
3536                 /* For start-of-game: */
3537                 /*           [3] begins with "Creating" or "Continuing" */
3538                 /*           [4] is " *" or empty (don't care). */
3539                 int gamenum = atoi(star_match[0]);
3540                 char *whitename, *blackname, *why, *endtoken;
3541                 ChessMove endtype = (ChessMove) 0;
3542
3543                 if (tkind == 0) {
3544                   whitename = star_match[1];
3545                   blackname = star_match[2];
3546                   why = star_match[3];
3547                   endtoken = star_match[4];
3548                 } else {
3549                   whitename = star_match[1];
3550                   blackname = star_match[3];
3551                   why = star_match[5];
3552                   endtoken = star_match[6];
3553                 }
3554
3555                 /* Game start messages */
3556                 if (strncmp(why, "Creating ", 9) == 0 ||
3557                     strncmp(why, "Continuing ", 11) == 0) {
3558                     gs_gamenum = gamenum;
3559                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3560                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3561 #if ZIPPY
3562                     if (appData.zippyPlay) {
3563                         ZippyGameStart(whitename, blackname);
3564                     }
3565 #endif /*ZIPPY*/
3566                     partnerBoardValid = FALSE; // [HGM] bughouse
3567                     continue;
3568                 }
3569
3570                 /* Game end messages */
3571                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3572                     ics_gamenum != gamenum) {
3573                     continue;
3574                 }
3575                 while (endtoken[0] == ' ') endtoken++;
3576                 switch (endtoken[0]) {
3577                   case '*':
3578                   default:
3579                     endtype = GameUnfinished;
3580                     break;
3581                   case '0':
3582                     endtype = BlackWins;
3583                     break;
3584                   case '1':
3585                     if (endtoken[1] == '/')
3586                       endtype = GameIsDrawn;
3587                     else
3588                       endtype = WhiteWins;
3589                     break;
3590                 }
3591                 GameEnds(endtype, why, GE_ICS);
3592 #if ZIPPY
3593                 if (appData.zippyPlay && first.initDone) {
3594                     ZippyGameEnd(endtype, why);
3595                     if (first.pr == NULL) {
3596                       /* Start the next process early so that we'll
3597                          be ready for the next challenge */
3598                       StartChessProgram(&first);
3599                     }
3600                     /* Send "new" early, in case this command takes
3601                        a long time to finish, so that we'll be ready
3602                        for the next challenge. */
3603                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3604                     Reset(TRUE, TRUE);
3605                 }
3606 #endif /*ZIPPY*/
3607                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3608                 continue;
3609             }
3610
3611             if (looking_at(buf, &i, "Removing game * from observation") ||
3612                 looking_at(buf, &i, "no longer observing game *") ||
3613                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3614                 if (gameMode == IcsObserving &&
3615                     atoi(star_match[0]) == ics_gamenum)
3616                   {
3617                       /* icsEngineAnalyze */
3618                       if (appData.icsEngineAnalyze) {
3619                             ExitAnalyzeMode();
3620                             ModeHighlight();
3621                       }
3622                       StopClocks();
3623                       gameMode = IcsIdle;
3624                       ics_gamenum = -1;
3625                       ics_user_moved = FALSE;
3626                   }
3627                 continue;
3628             }
3629
3630             if (looking_at(buf, &i, "no longer examining game *")) {
3631                 if (gameMode == IcsExamining &&
3632                     atoi(star_match[0]) == ics_gamenum)
3633                   {
3634                       gameMode = IcsIdle;
3635                       ics_gamenum = -1;
3636                       ics_user_moved = FALSE;
3637                   }
3638                 continue;
3639             }
3640
3641             /* Advance leftover_start past any newlines we find,
3642                so only partial lines can get reparsed */
3643             if (looking_at(buf, &i, "\n")) {
3644                 prevColor = curColor;
3645                 if (curColor != ColorNormal) {
3646                     if (oldi > next_out) {
3647                         SendToPlayer(&buf[next_out], oldi - next_out);
3648                         next_out = oldi;
3649                     }
3650                     Colorize(ColorNormal, FALSE);
3651                     curColor = ColorNormal;
3652                 }
3653                 if (started == STARTED_BOARD) {
3654                     started = STARTED_NONE;
3655                     parse[parse_pos] = NULLCHAR;
3656                     ParseBoard12(parse);
3657                     ics_user_moved = 0;
3658
3659                     /* Send premove here */
3660                     if (appData.premove) {
3661                       char str[MSG_SIZ];
3662                       if (currentMove == 0 &&
3663                           gameMode == IcsPlayingWhite &&
3664                           appData.premoveWhite) {
3665                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3666                         if (appData.debugMode)
3667                           fprintf(debugFP, "Sending premove:\n");
3668                         SendToICS(str);
3669                       } else if (currentMove == 1 &&
3670                                  gameMode == IcsPlayingBlack &&
3671                                  appData.premoveBlack) {
3672                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3673                         if (appData.debugMode)
3674                           fprintf(debugFP, "Sending premove:\n");
3675                         SendToICS(str);
3676                       } else if (gotPremove) {
3677                         gotPremove = 0;
3678                         ClearPremoveHighlights();
3679                         if (appData.debugMode)
3680                           fprintf(debugFP, "Sending premove:\n");
3681                           UserMoveEvent(premoveFromX, premoveFromY,
3682                                         premoveToX, premoveToY,
3683                                         premovePromoChar);
3684                       }
3685                     }
3686
3687                     /* Usually suppress following prompt */
3688                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3689                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3690                         if (looking_at(buf, &i, "*% ")) {
3691                             savingComment = FALSE;
3692                             suppressKibitz = 0;
3693                         }
3694                     }
3695                     next_out = i;
3696                 } else if (started == STARTED_HOLDINGS) {
3697                     int gamenum;
3698                     char new_piece[MSG_SIZ];
3699                     started = STARTED_NONE;
3700                     parse[parse_pos] = NULLCHAR;
3701                     if (appData.debugMode)
3702                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3703                                                         parse, currentMove);
3704                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3705                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3706                         if (gameInfo.variant == VariantNormal) {
3707                           /* [HGM] We seem to switch variant during a game!
3708                            * Presumably no holdings were displayed, so we have
3709                            * to move the position two files to the right to
3710                            * create room for them!
3711                            */
3712                           VariantClass newVariant;
3713                           switch(gameInfo.boardWidth) { // base guess on board width
3714                                 case 9:  newVariant = VariantShogi; break;
3715                                 case 10: newVariant = VariantGreat; break;
3716                                 default: newVariant = VariantCrazyhouse; break;
3717                           }
3718                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3719                           /* Get a move list just to see the header, which
3720                              will tell us whether this is really bug or zh */
3721                           if (ics_getting_history == H_FALSE) {
3722                             ics_getting_history = H_REQUESTED;
3723                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3724                             SendToICS(str);
3725                           }
3726                         }
3727                         new_piece[0] = NULLCHAR;
3728                         sscanf(parse, "game %d white [%s black [%s <- %s",
3729                                &gamenum, white_holding, black_holding,
3730                                new_piece);
3731                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3732                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3733                         /* [HGM] copy holdings to board holdings area */
3734                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3735                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3736                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3737 #if ZIPPY
3738                         if (appData.zippyPlay && first.initDone) {
3739                             ZippyHoldings(white_holding, black_holding,
3740                                           new_piece);
3741                         }
3742 #endif /*ZIPPY*/
3743                         if (tinyLayout || smallLayout) {
3744                             char wh[16], bh[16];
3745                             PackHolding(wh, white_holding);
3746                             PackHolding(bh, black_holding);
3747                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3748                                     gameInfo.white, gameInfo.black);
3749                         } else {
3750                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3751                                     gameInfo.white, white_holding,
3752                                     gameInfo.black, black_holding);
3753                         }
3754                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3755                         DrawPosition(FALSE, boards[currentMove]);
3756                         DisplayTitle(str);
3757                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3758                         sscanf(parse, "game %d white [%s black [%s <- %s",
3759                                &gamenum, white_holding, black_holding,
3760                                new_piece);
3761                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3762                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3763                         /* [HGM] copy holdings to partner-board holdings area */
3764                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3765                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3766                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3767                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3768                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3769                       }
3770                     }
3771                     /* Suppress following prompt */
3772                     if (looking_at(buf, &i, "*% ")) {
3773                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3774                         savingComment = FALSE;
3775                         suppressKibitz = 0;
3776                     }
3777                     next_out = i;
3778                 }
3779                 continue;
3780             }
3781
3782             i++;                /* skip unparsed character and loop back */
3783         }
3784
3785         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3786 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3787 //          SendToPlayer(&buf[next_out], i - next_out);
3788             started != STARTED_HOLDINGS && leftover_start > next_out) {
3789             SendToPlayer(&buf[next_out], leftover_start - next_out);
3790             next_out = i;
3791         }
3792
3793         leftover_len = buf_len - leftover_start;
3794         /* if buffer ends with something we couldn't parse,
3795            reparse it after appending the next read */
3796
3797     } else if (count == 0) {
3798         RemoveInputSource(isr);
3799         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3800     } else {
3801         DisplayFatalError(_("Error reading from ICS"), error, 1);
3802     }
3803 }
3804
3805
3806 /* Board style 12 looks like this:
3807
3808    <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
3809
3810  * The "<12> " is stripped before it gets to this routine.  The two
3811  * trailing 0's (flip state and clock ticking) are later addition, and
3812  * some chess servers may not have them, or may have only the first.
3813  * Additional trailing fields may be added in the future.
3814  */
3815
3816 #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"
3817
3818 #define RELATION_OBSERVING_PLAYED    0
3819 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3820 #define RELATION_PLAYING_MYMOVE      1
3821 #define RELATION_PLAYING_NOTMYMOVE  -1
3822 #define RELATION_EXAMINING           2
3823 #define RELATION_ISOLATED_BOARD     -3
3824 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3825
3826 void
3827 ParseBoard12(string)
3828      char *string;
3829 {
3830     GameMode newGameMode;
3831     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3832     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3833     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3834     char to_play, board_chars[200];
3835     char move_str[500], str[500], elapsed_time[500];
3836     char black[32], white[32];
3837     Board board;
3838     int prevMove = currentMove;
3839     int ticking = 2;
3840     ChessMove moveType;
3841     int fromX, fromY, toX, toY;
3842     char promoChar;
3843     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3844     char *bookHit = NULL; // [HGM] book
3845     Boolean weird = FALSE, reqFlag = FALSE;
3846
3847     fromX = fromY = toX = toY = -1;
3848
3849     newGame = FALSE;
3850
3851     if (appData.debugMode)
3852       fprintf(debugFP, _("Parsing board: %s\n"), string);
3853
3854     move_str[0] = NULLCHAR;
3855     elapsed_time[0] = NULLCHAR;
3856     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3857         int  i = 0, j;
3858         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3859             if(string[i] == ' ') { ranks++; files = 0; }
3860             else files++;
3861             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3862             i++;
3863         }
3864         for(j = 0; j <i; j++) board_chars[j] = string[j];
3865         board_chars[i] = '\0';
3866         string += i + 1;
3867     }
3868     n = sscanf(string, PATTERN, &to_play, &double_push,
3869                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3870                &gamenum, white, black, &relation, &basetime, &increment,
3871                &white_stren, &black_stren, &white_time, &black_time,
3872                &moveNum, str, elapsed_time, move_str, &ics_flip,
3873                &ticking);
3874
3875     if (n < 21) {
3876         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3877         DisplayError(str, 0);
3878         return;
3879     }
3880
3881     /* Convert the move number to internal form */
3882     moveNum = (moveNum - 1) * 2;
3883     if (to_play == 'B') moveNum++;
3884     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3885       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3886                         0, 1);
3887       return;
3888     }
3889
3890     switch (relation) {
3891       case RELATION_OBSERVING_PLAYED:
3892       case RELATION_OBSERVING_STATIC:
3893         if (gamenum == -1) {
3894             /* Old ICC buglet */
3895             relation = RELATION_OBSERVING_STATIC;
3896         }
3897         newGameMode = IcsObserving;
3898         break;
3899       case RELATION_PLAYING_MYMOVE:
3900       case RELATION_PLAYING_NOTMYMOVE:
3901         newGameMode =
3902           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3903             IcsPlayingWhite : IcsPlayingBlack;
3904         break;
3905       case RELATION_EXAMINING:
3906         newGameMode = IcsExamining;
3907         break;
3908       case RELATION_ISOLATED_BOARD:
3909       default:
3910         /* Just display this board.  If user was doing something else,
3911            we will forget about it until the next board comes. */
3912         newGameMode = IcsIdle;
3913         break;
3914       case RELATION_STARTING_POSITION:
3915         newGameMode = gameMode;
3916         break;
3917     }
3918
3919     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3920          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3921       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3922       char *toSqr;
3923       for (k = 0; k < ranks; k++) {
3924         for (j = 0; j < files; j++)
3925           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3926         if(gameInfo.holdingsWidth > 1) {
3927              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3928              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3929         }
3930       }
3931       CopyBoard(partnerBoard, board);
3932       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3933         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3934         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3935       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3936       if(toSqr = strchr(str, '-')) {
3937         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3938         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3939       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3940       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3941       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3942       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3943       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3944       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3945                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3946       DisplayMessage(partnerStatus, "");
3947         partnerBoardValid = TRUE;
3948       return;
3949     }
3950
3951     /* Modify behavior for initial board display on move listing
3952        of wild games.
3953        */
3954     switch (ics_getting_history) {
3955       case H_FALSE:
3956       case H_REQUESTED:
3957         break;
3958       case H_GOT_REQ_HEADER:
3959       case H_GOT_UNREQ_HEADER:
3960         /* This is the initial position of the current game */
3961         gamenum = ics_gamenum;
3962         moveNum = 0;            /* old ICS bug workaround */
3963         if (to_play == 'B') {
3964           startedFromSetupPosition = TRUE;
3965           blackPlaysFirst = TRUE;
3966           moveNum = 1;
3967           if (forwardMostMove == 0) forwardMostMove = 1;
3968           if (backwardMostMove == 0) backwardMostMove = 1;
3969           if (currentMove == 0) currentMove = 1;
3970         }
3971         newGameMode = gameMode;
3972         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3973         break;
3974       case H_GOT_UNWANTED_HEADER:
3975         /* This is an initial board that we don't want */
3976         return;
3977       case H_GETTING_MOVES:
3978         /* Should not happen */
3979         DisplayError(_("Error gathering move list: extra board"), 0);
3980         ics_getting_history = H_FALSE;
3981         return;
3982     }
3983
3984    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3985                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3986      /* [HGM] We seem to have switched variant unexpectedly
3987       * Try to guess new variant from board size
3988       */
3989           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3990           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3991           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3992           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3993           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3994           if(!weird) newVariant = VariantNormal;
3995           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3996           /* Get a move list just to see the header, which
3997              will tell us whether this is really bug or zh */
3998           if (ics_getting_history == H_FALSE) {
3999             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4000             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4001             SendToICS(str);
4002           }
4003     }
4004
4005     /* Take action if this is the first board of a new game, or of a
4006        different game than is currently being displayed.  */
4007     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4008         relation == RELATION_ISOLATED_BOARD) {
4009
4010         /* Forget the old game and get the history (if any) of the new one */
4011         if (gameMode != BeginningOfGame) {
4012           Reset(TRUE, TRUE);
4013         }
4014         newGame = TRUE;
4015         if (appData.autoRaiseBoard) BoardToTop();
4016         prevMove = -3;
4017         if (gamenum == -1) {
4018             newGameMode = IcsIdle;
4019         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4020                    appData.getMoveList && !reqFlag) {
4021             /* Need to get game history */
4022             ics_getting_history = H_REQUESTED;
4023             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4024             SendToICS(str);
4025         }
4026
4027         /* Initially flip the board to have black on the bottom if playing
4028            black or if the ICS flip flag is set, but let the user change
4029            it with the Flip View button. */
4030         flipView = appData.autoFlipView ?
4031           (newGameMode == IcsPlayingBlack) || ics_flip :
4032           appData.flipView;
4033
4034         /* Done with values from previous mode; copy in new ones */
4035         gameMode = newGameMode;
4036         ModeHighlight();
4037         ics_gamenum = gamenum;
4038         if (gamenum == gs_gamenum) {
4039             int klen = strlen(gs_kind);
4040             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4041             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4042             gameInfo.event = StrSave(str);
4043         } else {
4044             gameInfo.event = StrSave("ICS game");
4045         }
4046         gameInfo.site = StrSave(appData.icsHost);
4047         gameInfo.date = PGNDate();
4048         gameInfo.round = StrSave("-");
4049         gameInfo.white = StrSave(white);
4050         gameInfo.black = StrSave(black);
4051         timeControl = basetime * 60 * 1000;
4052         timeControl_2 = 0;
4053         timeIncrement = increment * 1000;
4054         movesPerSession = 0;
4055         gameInfo.timeControl = TimeControlTagValue();
4056         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4057   if (appData.debugMode) {
4058     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4059     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4060     setbuf(debugFP, NULL);
4061   }
4062
4063         gameInfo.outOfBook = NULL;
4064
4065         /* Do we have the ratings? */
4066         if (strcmp(player1Name, white) == 0 &&
4067             strcmp(player2Name, black) == 0) {
4068             if (appData.debugMode)
4069               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4070                       player1Rating, player2Rating);
4071             gameInfo.whiteRating = player1Rating;
4072             gameInfo.blackRating = player2Rating;
4073         } else if (strcmp(player2Name, white) == 0 &&
4074                    strcmp(player1Name, black) == 0) {
4075             if (appData.debugMode)
4076               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4077                       player2Rating, player1Rating);
4078             gameInfo.whiteRating = player2Rating;
4079             gameInfo.blackRating = player1Rating;
4080         }
4081         player1Name[0] = player2Name[0] = NULLCHAR;
4082
4083         /* Silence shouts if requested */
4084         if (appData.quietPlay &&
4085             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4086             SendToICS(ics_prefix);
4087             SendToICS("set shout 0\n");
4088         }
4089     }
4090
4091     /* Deal with midgame name changes */
4092     if (!newGame) {
4093         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4094             if (gameInfo.white) free(gameInfo.white);
4095             gameInfo.white = StrSave(white);
4096         }
4097         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4098             if (gameInfo.black) free(gameInfo.black);
4099             gameInfo.black = StrSave(black);
4100         }
4101     }
4102
4103     /* Throw away game result if anything actually changes in examine mode */
4104     if (gameMode == IcsExamining && !newGame) {
4105         gameInfo.result = GameUnfinished;
4106         if (gameInfo.resultDetails != NULL) {
4107             free(gameInfo.resultDetails);
4108             gameInfo.resultDetails = NULL;
4109         }
4110     }
4111
4112     /* In pausing && IcsExamining mode, we ignore boards coming
4113        in if they are in a different variation than we are. */
4114     if (pauseExamInvalid) return;
4115     if (pausing && gameMode == IcsExamining) {
4116         if (moveNum <= pauseExamForwardMostMove) {
4117             pauseExamInvalid = TRUE;
4118             forwardMostMove = pauseExamForwardMostMove;
4119             return;
4120         }
4121     }
4122
4123   if (appData.debugMode) {
4124     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4125   }
4126     /* Parse the board */
4127     for (k = 0; k < ranks; k++) {
4128       for (j = 0; j < files; j++)
4129         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4130       if(gameInfo.holdingsWidth > 1) {
4131            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4132            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4133       }
4134     }
4135     CopyBoard(boards[moveNum], board);
4136     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4137     if (moveNum == 0) {
4138         startedFromSetupPosition =
4139           !CompareBoards(board, initialPosition);
4140         if(startedFromSetupPosition)
4141             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4142     }
4143
4144     /* [HGM] Set castling rights. Take the outermost Rooks,
4145        to make it also work for FRC opening positions. Note that board12
4146        is really defective for later FRC positions, as it has no way to
4147        indicate which Rook can castle if they are on the same side of King.
4148        For the initial position we grant rights to the outermost Rooks,
4149        and remember thos rights, and we then copy them on positions
4150        later in an FRC game. This means WB might not recognize castlings with
4151        Rooks that have moved back to their original position as illegal,
4152        but in ICS mode that is not its job anyway.
4153     */
4154     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4155     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4156
4157         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4158             if(board[0][i] == WhiteRook) j = i;
4159         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4160         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4161             if(board[0][i] == WhiteRook) j = i;
4162         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4163         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4164             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4165         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4166         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4167             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4168         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4169
4170         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4171         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4172             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4173         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4174             if(board[BOARD_HEIGHT-1][k] == bKing)
4175                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4176         if(gameInfo.variant == VariantTwoKings) {
4177             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4178             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4179             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4180         }
4181     } else { int r;
4182         r = boards[moveNum][CASTLING][0] = initialRights[0];
4183         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4184         r = boards[moveNum][CASTLING][1] = initialRights[1];
4185         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4186         r = boards[moveNum][CASTLING][3] = initialRights[3];
4187         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4188         r = boards[moveNum][CASTLING][4] = initialRights[4];
4189         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4190         /* wildcastle kludge: always assume King has rights */
4191         r = boards[moveNum][CASTLING][2] = initialRights[2];
4192         r = boards[moveNum][CASTLING][5] = initialRights[5];
4193     }
4194     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4195     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4196
4197
4198     if (ics_getting_history == H_GOT_REQ_HEADER ||
4199         ics_getting_history == H_GOT_UNREQ_HEADER) {
4200         /* This was an initial position from a move list, not
4201            the current position */
4202         return;
4203     }
4204
4205     /* Update currentMove and known move number limits */
4206     newMove = newGame || moveNum > forwardMostMove;
4207
4208     if (newGame) {
4209         forwardMostMove = backwardMostMove = currentMove = moveNum;
4210         if (gameMode == IcsExamining && moveNum == 0) {
4211           /* Workaround for ICS limitation: we are not told the wild
4212              type when starting to examine a game.  But if we ask for
4213              the move list, the move list header will tell us */
4214             ics_getting_history = H_REQUESTED;
4215             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4216             SendToICS(str);
4217         }
4218     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4219                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4220 #if ZIPPY
4221         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4222         /* [HGM] applied this also to an engine that is silently watching        */
4223         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4224             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4225             gameInfo.variant == currentlyInitializedVariant) {
4226           takeback = forwardMostMove - moveNum;
4227           for (i = 0; i < takeback; i++) {
4228             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4229             SendToProgram("undo\n", &first);
4230           }
4231         }
4232 #endif
4233
4234         forwardMostMove = moveNum;
4235         if (!pausing || currentMove > forwardMostMove)
4236           currentMove = forwardMostMove;
4237     } else {
4238         /* New part of history that is not contiguous with old part */
4239         if (pausing && gameMode == IcsExamining) {
4240             pauseExamInvalid = TRUE;
4241             forwardMostMove = pauseExamForwardMostMove;
4242             return;
4243         }
4244         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4245 #if ZIPPY
4246             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4247                 // [HGM] when we will receive the move list we now request, it will be
4248                 // fed to the engine from the first move on. So if the engine is not
4249                 // in the initial position now, bring it there.
4250                 InitChessProgram(&first, 0);
4251             }
4252 #endif
4253             ics_getting_history = H_REQUESTED;
4254             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4255             SendToICS(str);
4256         }
4257         forwardMostMove = backwardMostMove = currentMove = moveNum;
4258     }
4259
4260     /* Update the clocks */
4261     if (strchr(elapsed_time, '.')) {
4262       /* Time is in ms */
4263       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4264       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4265     } else {
4266       /* Time is in seconds */
4267       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4268       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4269     }
4270
4271
4272 #if ZIPPY
4273     if (appData.zippyPlay && newGame &&
4274         gameMode != IcsObserving && gameMode != IcsIdle &&
4275         gameMode != IcsExamining)
4276       ZippyFirstBoard(moveNum, basetime, increment);
4277 #endif
4278
4279     /* Put the move on the move list, first converting
4280        to canonical algebraic form. */
4281     if (moveNum > 0) {
4282   if (appData.debugMode) {
4283     if (appData.debugMode) { int f = forwardMostMove;
4284         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4285                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4286                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4287     }
4288     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4289     fprintf(debugFP, "moveNum = %d\n", moveNum);
4290     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4291     setbuf(debugFP, NULL);
4292   }
4293         if (moveNum <= backwardMostMove) {
4294             /* We don't know what the board looked like before
4295                this move.  Punt. */
4296           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4297             strcat(parseList[moveNum - 1], " ");
4298             strcat(parseList[moveNum - 1], elapsed_time);
4299             moveList[moveNum - 1][0] = NULLCHAR;
4300         } else if (strcmp(move_str, "none") == 0) {
4301             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4302             /* Again, we don't know what the board looked like;
4303                this is really the start of the game. */
4304             parseList[moveNum - 1][0] = NULLCHAR;
4305             moveList[moveNum - 1][0] = NULLCHAR;
4306             backwardMostMove = moveNum;
4307             startedFromSetupPosition = TRUE;
4308             fromX = fromY = toX = toY = -1;
4309         } else {
4310           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4311           //                 So we parse the long-algebraic move string in stead of the SAN move
4312           int valid; char buf[MSG_SIZ], *prom;
4313
4314           // str looks something like "Q/a1-a2"; kill the slash
4315           if(str[1] == '/')
4316             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4317           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4318           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4319                 strcat(buf, prom); // long move lacks promo specification!
4320           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4321                 if(appData.debugMode)
4322                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4323                 safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0]));
4324           }
4325           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4326                                 &fromX, &fromY, &toX, &toY, &promoChar)
4327                || ParseOneMove(buf, moveNum - 1, &moveType,
4328                                 &fromX, &fromY, &toX, &toY, &promoChar);
4329           // end of long SAN patch
4330           if (valid) {
4331             (void) CoordsToAlgebraic(boards[moveNum - 1],
4332                                      PosFlags(moveNum - 1),
4333                                      fromY, fromX, toY, toX, promoChar,
4334                                      parseList[moveNum-1]);
4335             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4336               case MT_NONE:
4337               case MT_STALEMATE:
4338               default:
4339                 break;
4340               case MT_CHECK:
4341                 if(gameInfo.variant != VariantShogi)
4342                     strcat(parseList[moveNum - 1], "+");
4343                 break;
4344               case MT_CHECKMATE:
4345               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4346                 strcat(parseList[moveNum - 1], "#");
4347                 break;
4348             }
4349             strcat(parseList[moveNum - 1], " ");
4350             strcat(parseList[moveNum - 1], elapsed_time);
4351             /* currentMoveString is set as a side-effect of ParseOneMove */
4352             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4353             strcat(moveList[moveNum - 1], "\n");
4354           } else {
4355             /* Move from ICS was illegal!?  Punt. */
4356             if (appData.debugMode) {
4357               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4358               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4359             }
4360             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4361             strcat(parseList[moveNum - 1], " ");
4362             strcat(parseList[moveNum - 1], elapsed_time);
4363             moveList[moveNum - 1][0] = NULLCHAR;
4364             fromX = fromY = toX = toY = -1;
4365           }
4366         }
4367   if (appData.debugMode) {
4368     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4369     setbuf(debugFP, NULL);
4370   }
4371
4372 #if ZIPPY
4373         /* Send move to chess program (BEFORE animating it). */
4374         if (appData.zippyPlay && !newGame && newMove &&
4375            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4376
4377             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4378                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4379                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4380                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4381                             move_str);
4382                     DisplayError(str, 0);
4383                 } else {
4384                     if (first.sendTime) {
4385                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4386                     }
4387                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4388                     if (firstMove && !bookHit) {
4389                         firstMove = FALSE;
4390                         if (first.useColors) {
4391                           SendToProgram(gameMode == IcsPlayingWhite ?
4392                                         "white\ngo\n" :
4393                                         "black\ngo\n", &first);
4394                         } else {
4395                           SendToProgram("go\n", &first);
4396                         }
4397                         first.maybeThinking = TRUE;
4398                     }
4399                 }
4400             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4401               if (moveList[moveNum - 1][0] == NULLCHAR) {
4402                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4403                 DisplayError(str, 0);
4404               } else {
4405                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4406                 SendMoveToProgram(moveNum - 1, &first);
4407               }
4408             }
4409         }
4410 #endif
4411     }
4412
4413     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4414         /* If move comes from a remote source, animate it.  If it
4415            isn't remote, it will have already been animated. */
4416         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4417             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4418         }
4419         if (!pausing && appData.highlightLastMove) {
4420             SetHighlights(fromX, fromY, toX, toY);
4421         }
4422     }
4423
4424     /* Start the clocks */
4425     whiteFlag = blackFlag = FALSE;
4426     appData.clockMode = !(basetime == 0 && increment == 0);
4427     if (ticking == 0) {
4428       ics_clock_paused = TRUE;
4429       StopClocks();
4430     } else if (ticking == 1) {
4431       ics_clock_paused = FALSE;
4432     }
4433     if (gameMode == IcsIdle ||
4434         relation == RELATION_OBSERVING_STATIC ||
4435         relation == RELATION_EXAMINING ||
4436         ics_clock_paused)
4437       DisplayBothClocks();
4438     else
4439       StartClocks();
4440
4441     /* Display opponents and material strengths */
4442     if (gameInfo.variant != VariantBughouse &&
4443         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4444         if (tinyLayout || smallLayout) {
4445             if(gameInfo.variant == VariantNormal)
4446               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4447                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4448                     basetime, increment);
4449             else
4450               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4451                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4452                     basetime, increment, (int) gameInfo.variant);
4453         } else {
4454             if(gameInfo.variant == VariantNormal)
4455               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4456                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4457                     basetime, increment);
4458             else
4459               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4460                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4461                     basetime, increment, VariantName(gameInfo.variant));
4462         }
4463         DisplayTitle(str);
4464   if (appData.debugMode) {
4465     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4466   }
4467     }
4468
4469
4470     /* Display the board */
4471     if (!pausing && !appData.noGUI) {
4472
4473       if (appData.premove)
4474           if (!gotPremove ||
4475              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4476              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4477               ClearPremoveHighlights();
4478
4479       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4480         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4481       DrawPosition(j, boards[currentMove]);
4482
4483       DisplayMove(moveNum - 1);
4484       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4485             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4486               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4487         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4488       }
4489     }
4490
4491     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4492 #if ZIPPY
4493     if(bookHit) { // [HGM] book: simulate book reply
4494         static char bookMove[MSG_SIZ]; // a bit generous?
4495
4496         programStats.nodes = programStats.depth = programStats.time =
4497         programStats.score = programStats.got_only_move = 0;
4498         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4499
4500         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4501         strcat(bookMove, bookHit);
4502         HandleMachineMove(bookMove, &first);
4503     }
4504 #endif
4505 }
4506
4507 void
4508 GetMoveListEvent()
4509 {
4510     char buf[MSG_SIZ];
4511     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4512         ics_getting_history = H_REQUESTED;
4513         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4514         SendToICS(buf);
4515     }
4516 }
4517
4518 void
4519 AnalysisPeriodicEvent(force)
4520      int force;
4521 {
4522     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4523          && !force) || !appData.periodicUpdates)
4524       return;
4525
4526     /* Send . command to Crafty to collect stats */
4527     SendToProgram(".\n", &first);
4528
4529     /* Don't send another until we get a response (this makes
4530        us stop sending to old Crafty's which don't understand
4531        the "." command (sending illegal cmds resets node count & time,
4532        which looks bad)) */
4533     programStats.ok_to_send = 0;
4534 }
4535
4536 void ics_update_width(new_width)
4537         int new_width;
4538 {
4539         ics_printf("set width %d\n", new_width);
4540 }
4541
4542 void
4543 SendMoveToProgram(moveNum, cps)
4544      int moveNum;
4545      ChessProgramState *cps;
4546 {
4547     char buf[MSG_SIZ];
4548
4549     if (cps->useUsermove) {
4550       SendToProgram("usermove ", cps);
4551     }
4552     if (cps->useSAN) {
4553       char *space;
4554       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4555         int len = space - parseList[moveNum];
4556         memcpy(buf, parseList[moveNum], len);
4557         buf[len++] = '\n';
4558         buf[len] = NULLCHAR;
4559       } else {
4560         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4561       }
4562       SendToProgram(buf, cps);
4563     } else {
4564       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4565         AlphaRank(moveList[moveNum], 4);
4566         SendToProgram(moveList[moveNum], cps);
4567         AlphaRank(moveList[moveNum], 4); // and back
4568       } else
4569       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4570        * the engine. It would be nice to have a better way to identify castle
4571        * moves here. */
4572       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4573                                                                          && cps->useOOCastle) {
4574         int fromX = moveList[moveNum][0] - AAA;
4575         int fromY = moveList[moveNum][1] - ONE;
4576         int toX = moveList[moveNum][2] - AAA;
4577         int toY = moveList[moveNum][3] - ONE;
4578         if((boards[moveNum][fromY][fromX] == WhiteKing
4579             && boards[moveNum][toY][toX] == WhiteRook)
4580            || (boards[moveNum][fromY][fromX] == BlackKing
4581                && boards[moveNum][toY][toX] == BlackRook)) {
4582           if(toX > fromX) SendToProgram("O-O\n", cps);
4583           else SendToProgram("O-O-O\n", cps);
4584         }
4585         else SendToProgram(moveList[moveNum], cps);
4586       }
4587       else SendToProgram(moveList[moveNum], cps);
4588       /* End of additions by Tord */
4589     }
4590
4591     /* [HGM] setting up the opening has brought engine in force mode! */
4592     /*       Send 'go' if we are in a mode where machine should play. */
4593     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4594         (gameMode == TwoMachinesPlay   ||
4595 #if ZIPPY
4596          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4597 #endif
4598          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4599         SendToProgram("go\n", cps);
4600   if (appData.debugMode) {
4601     fprintf(debugFP, "(extra)\n");
4602   }
4603     }
4604     setboardSpoiledMachineBlack = 0;
4605 }
4606
4607 void
4608 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4609      ChessMove moveType;
4610      int fromX, fromY, toX, toY;
4611      char promoChar;
4612 {
4613     char user_move[MSG_SIZ];
4614
4615     switch (moveType) {
4616       default:
4617         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4618                 (int)moveType, fromX, fromY, toX, toY);
4619         DisplayError(user_move + strlen("say "), 0);
4620         break;
4621       case WhiteKingSideCastle:
4622       case BlackKingSideCastle:
4623       case WhiteQueenSideCastleWild:
4624       case BlackQueenSideCastleWild:
4625       /* PUSH Fabien */
4626       case WhiteHSideCastleFR:
4627       case BlackHSideCastleFR:
4628       /* POP Fabien */
4629         snprintf(user_move, MSG_SIZ, "o-o\n");
4630         break;
4631       case WhiteQueenSideCastle:
4632       case BlackQueenSideCastle:
4633       case WhiteKingSideCastleWild:
4634       case BlackKingSideCastleWild:
4635       /* PUSH Fabien */
4636       case WhiteASideCastleFR:
4637       case BlackASideCastleFR:
4638       /* POP Fabien */
4639         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4640         break;
4641       case WhiteNonPromotion:
4642       case BlackNonPromotion:
4643         sprintf(user_move, "%c%c%c%c=\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4644         break;
4645       case WhitePromotion:
4646       case BlackPromotion:
4647         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4648           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4649                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4650                 PieceToChar(WhiteFerz));
4651         else if(gameInfo.variant == VariantGreat)
4652           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4653                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4654                 PieceToChar(WhiteMan));
4655         else
4656           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4657                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4658                 promoChar);
4659         break;
4660       case WhiteDrop:
4661       case BlackDrop:
4662         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4663                 ToUpper(PieceToChar((ChessSquare) fromX)),
4664                 AAA + toX, ONE + toY);
4665         break;
4666       case NormalMove:
4667       case WhiteCapturesEnPassant:
4668       case BlackCapturesEnPassant:
4669       case IllegalMove:  /* could be a variant we don't quite understand */
4670         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4671                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4672         break;
4673     }
4674     SendToICS(user_move);
4675     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4676         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4677 }
4678
4679 void
4680 UploadGameEvent()
4681 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4682     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4683     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4684     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4685         DisplayError("You cannot do this while you are playing or observing", 0);
4686         return;
4687     }
4688     if(gameMode != IcsExamining) { // is this ever not the case?
4689         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4690
4691         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4692           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4693         } else { // on FICS we must first go to general examine mode
4694           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4695         }
4696         if(gameInfo.variant != VariantNormal) {
4697             // try figure out wild number, as xboard names are not always valid on ICS
4698             for(i=1; i<=36; i++) {
4699               snprintf(buf, MSG_SIZ, "wild/%d", i);
4700                 if(StringToVariant(buf) == gameInfo.variant) break;
4701             }
4702             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4703             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4704             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4705         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4706         SendToICS(ics_prefix);
4707         SendToICS(buf);
4708         if(startedFromSetupPosition || backwardMostMove != 0) {
4709           fen = PositionToFEN(backwardMostMove, NULL);
4710           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4711             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4712             SendToICS(buf);
4713           } else { // FICS: everything has to set by separate bsetup commands
4714             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4715             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4716             SendToICS(buf);
4717             if(!WhiteOnMove(backwardMostMove)) {
4718                 SendToICS("bsetup tomove black\n");
4719             }
4720             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4721             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4722             SendToICS(buf);
4723             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4724             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4725             SendToICS(buf);
4726             i = boards[backwardMostMove][EP_STATUS];
4727             if(i >= 0) { // set e.p.
4728               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4729                 SendToICS(buf);
4730             }
4731             bsetup++;
4732           }
4733         }
4734       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4735             SendToICS("bsetup done\n"); // switch to normal examining.
4736     }
4737     for(i = backwardMostMove; i<last; i++) {
4738         char buf[20];
4739         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4740         SendToICS(buf);
4741     }
4742     SendToICS(ics_prefix);
4743     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4744 }
4745
4746 void
4747 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4748      int rf, ff, rt, ft;
4749      char promoChar;
4750      char move[7];
4751 {
4752     if (rf == DROP_RANK) {
4753       sprintf(move, "%c@%c%c\n",
4754                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4755     } else {
4756         if (promoChar == 'x' || promoChar == NULLCHAR) {
4757           sprintf(move, "%c%c%c%c\n",
4758                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4759         } else {
4760             sprintf(move, "%c%c%c%c%c\n",
4761                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4762         }
4763     }
4764 }
4765
4766 void
4767 ProcessICSInitScript(f)
4768      FILE *f;
4769 {
4770     char buf[MSG_SIZ];
4771
4772     while (fgets(buf, MSG_SIZ, f)) {
4773         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4774     }
4775
4776     fclose(f);
4777 }
4778
4779
4780 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4781 void
4782 AlphaRank(char *move, int n)
4783 {
4784 //    char *p = move, c; int x, y;
4785
4786     if (appData.debugMode) {
4787         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4788     }
4789
4790     if(move[1]=='*' &&
4791        move[2]>='0' && move[2]<='9' &&
4792        move[3]>='a' && move[3]<='x'    ) {
4793         move[1] = '@';
4794         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4795         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4796     } else
4797     if(move[0]>='0' && move[0]<='9' &&
4798        move[1]>='a' && move[1]<='x' &&
4799        move[2]>='0' && move[2]<='9' &&
4800        move[3]>='a' && move[3]<='x'    ) {
4801         /* input move, Shogi -> normal */
4802         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4803         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4804         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4805         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4806     } else
4807     if(move[1]=='@' &&
4808        move[3]>='0' && move[3]<='9' &&
4809        move[2]>='a' && move[2]<='x'    ) {
4810         move[1] = '*';
4811         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4812         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4813     } else
4814     if(
4815        move[0]>='a' && move[0]<='x' &&
4816        move[3]>='0' && move[3]<='9' &&
4817        move[2]>='a' && move[2]<='x'    ) {
4818          /* output move, normal -> Shogi */
4819         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4820         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4821         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4822         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4823         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4824     }
4825     if (appData.debugMode) {
4826         fprintf(debugFP, "   out = '%s'\n", move);
4827     }
4828 }
4829
4830 char yy_textstr[8000];
4831
4832 /* Parser for moves from gnuchess, ICS, or user typein box */
4833 Boolean
4834 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4835      char *move;
4836      int moveNum;
4837      ChessMove *moveType;
4838      int *fromX, *fromY, *toX, *toY;
4839      char *promoChar;
4840 {
4841     if (appData.debugMode) {
4842         fprintf(debugFP, "move to parse: %s\n", move);
4843     }
4844     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4845
4846     switch (*moveType) {
4847       case WhitePromotion:
4848       case BlackPromotion:
4849       case WhiteNonPromotion:
4850       case BlackNonPromotion:
4851       case NormalMove:
4852       case WhiteCapturesEnPassant:
4853       case BlackCapturesEnPassant:
4854       case WhiteKingSideCastle:
4855       case WhiteQueenSideCastle:
4856       case BlackKingSideCastle:
4857       case BlackQueenSideCastle:
4858       case WhiteKingSideCastleWild:
4859       case WhiteQueenSideCastleWild:
4860       case BlackKingSideCastleWild:
4861       case BlackQueenSideCastleWild:
4862       /* Code added by Tord: */
4863       case WhiteHSideCastleFR:
4864       case WhiteASideCastleFR:
4865       case BlackHSideCastleFR:
4866       case BlackASideCastleFR:
4867       /* End of code added by Tord */
4868       case IllegalMove:         /* bug or odd chess variant */
4869         *fromX = currentMoveString[0] - AAA;
4870         *fromY = currentMoveString[1] - ONE;
4871         *toX = currentMoveString[2] - AAA;
4872         *toY = currentMoveString[3] - ONE;
4873         *promoChar = currentMoveString[4];
4874         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4875             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4876     if (appData.debugMode) {
4877         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4878     }
4879             *fromX = *fromY = *toX = *toY = 0;
4880             return FALSE;
4881         }
4882         if (appData.testLegality) {
4883           return (*moveType != IllegalMove);
4884         } else {
4885           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4886                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4887         }
4888
4889       case WhiteDrop:
4890       case BlackDrop:
4891         *fromX = *moveType == WhiteDrop ?
4892           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4893           (int) CharToPiece(ToLower(currentMoveString[0]));
4894         *fromY = DROP_RANK;
4895         *toX = currentMoveString[2] - AAA;
4896         *toY = currentMoveString[3] - ONE;
4897         *promoChar = NULLCHAR;
4898         return TRUE;
4899
4900       case AmbiguousMove:
4901       case ImpossibleMove:
4902       case (ChessMove) 0:       /* end of file */
4903       case ElapsedTime:
4904       case Comment:
4905       case PGNTag:
4906       case NAG:
4907       case WhiteWins:
4908       case BlackWins:
4909       case GameIsDrawn:
4910       default:
4911     if (appData.debugMode) {
4912         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4913     }
4914         /* bug? */
4915         *fromX = *fromY = *toX = *toY = 0;
4916         *promoChar = NULLCHAR;
4917         return FALSE;
4918     }
4919 }
4920
4921
4922 void
4923 ParsePV(char *pv, Boolean storeComments)
4924 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4925   int fromX, fromY, toX, toY; char promoChar;
4926   ChessMove moveType;
4927   Boolean valid;
4928   int nr = 0;
4929
4930   endPV = forwardMostMove;
4931   do {
4932     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4933     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4934     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4935 if(appData.debugMode){
4936 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);
4937 }
4938     if(!valid && nr == 0 &&
4939        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4940         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4941         // Hande case where played move is different from leading PV move
4942         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4943         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4944         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4945         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4946           endPV += 2; // if position different, keep this
4947           moveList[endPV-1][0] = fromX + AAA;
4948           moveList[endPV-1][1] = fromY + ONE;
4949           moveList[endPV-1][2] = toX + AAA;
4950           moveList[endPV-1][3] = toY + ONE;
4951           parseList[endPV-1][0] = NULLCHAR;
4952           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4953         }
4954       }
4955     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4956     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4957     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4958     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4959         valid++; // allow comments in PV
4960         continue;
4961     }
4962     nr++;
4963     if(endPV+1 > framePtr) break; // no space, truncate
4964     if(!valid) break;
4965     endPV++;
4966     CopyBoard(boards[endPV], boards[endPV-1]);
4967     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4968     moveList[endPV-1][0] = fromX + AAA;
4969     moveList[endPV-1][1] = fromY + ONE;
4970     moveList[endPV-1][2] = toX + AAA;
4971     moveList[endPV-1][3] = toY + ONE;
4972     if(storeComments)
4973         CoordsToAlgebraic(boards[endPV - 1],
4974                              PosFlags(endPV - 1),
4975                              fromY, fromX, toY, toX, promoChar,
4976                              parseList[endPV - 1]);
4977     else
4978         parseList[endPV-1][0] = NULLCHAR;
4979   } while(valid);
4980   currentMove = endPV;
4981   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4982   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4983                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4984   DrawPosition(TRUE, boards[currentMove]);
4985 }
4986
4987 static int lastX, lastY;
4988
4989 Boolean
4990 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4991 {
4992         int startPV;
4993         char *p;
4994
4995         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4996         lastX = x; lastY = y;
4997         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4998         startPV = index;
4999         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5000         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5001         index = startPV;
5002         do{ while(buf[index] && buf[index] != '\n') index++;
5003         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5004         buf[index] = 0;
5005         ParsePV(buf+startPV, FALSE);
5006         *start = startPV; *end = index-1;
5007         return TRUE;
5008 }
5009
5010 Boolean
5011 LoadPV(int x, int y)
5012 { // called on right mouse click to load PV
5013   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5014   lastX = x; lastY = y;
5015   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5016   return TRUE;
5017 }
5018
5019 void
5020 UnLoadPV()
5021 {
5022   if(endPV < 0) return;
5023   endPV = -1;
5024   currentMove = forwardMostMove;
5025   ClearPremoveHighlights();
5026   DrawPosition(TRUE, boards[currentMove]);
5027 }
5028
5029 void
5030 MovePV(int x, int y, int h)
5031 { // step through PV based on mouse coordinates (called on mouse move)
5032   int margin = h>>3, step = 0;
5033
5034   if(endPV < 0) return;
5035   // we must somehow check if right button is still down (might be released off board!)
5036   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5037   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5038   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5039   if(!step) return;
5040   lastX = x; lastY = y;
5041   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5042   currentMove += step;
5043   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5044   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5045                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5046   DrawPosition(FALSE, boards[currentMove]);
5047 }
5048
5049
5050 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5051 // All positions will have equal probability, but the current method will not provide a unique
5052 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5053 #define DARK 1
5054 #define LITE 2
5055 #define ANY 3
5056
5057 int squaresLeft[4];
5058 int piecesLeft[(int)BlackPawn];
5059 int seed, nrOfShuffles;
5060
5061 void GetPositionNumber()
5062 {       // sets global variable seed
5063         int i;
5064
5065         seed = appData.defaultFrcPosition;
5066         if(seed < 0) { // randomize based on time for negative FRC position numbers
5067                 for(i=0; i<50; i++) seed += random();
5068                 seed = random() ^ random() >> 8 ^ random() << 8;
5069                 if(seed<0) seed = -seed;
5070         }
5071 }
5072
5073 int put(Board board, int pieceType, int rank, int n, int shade)
5074 // put the piece on the (n-1)-th empty squares of the given shade
5075 {
5076         int i;
5077
5078         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5079                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5080                         board[rank][i] = (ChessSquare) pieceType;
5081                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5082                         squaresLeft[ANY]--;
5083                         piecesLeft[pieceType]--;
5084                         return i;
5085                 }
5086         }
5087         return -1;
5088 }
5089
5090
5091 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5092 // calculate where the next piece goes, (any empty square), and put it there
5093 {
5094         int i;
5095
5096         i = seed % squaresLeft[shade];
5097         nrOfShuffles *= squaresLeft[shade];
5098         seed /= squaresLeft[shade];
5099         put(board, pieceType, rank, i, shade);
5100 }
5101
5102 void AddTwoPieces(Board board, int pieceType, int rank)
5103 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5104 {
5105         int i, n=squaresLeft[ANY], j=n-1, k;
5106
5107         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5108         i = seed % k;  // pick one
5109         nrOfShuffles *= k;
5110         seed /= k;
5111         while(i >= j) i -= j--;
5112         j = n - 1 - j; i += j;
5113         put(board, pieceType, rank, j, ANY);
5114         put(board, pieceType, rank, i, ANY);
5115 }
5116
5117 void SetUpShuffle(Board board, int number)
5118 {
5119         int i, p, first=1;
5120
5121         GetPositionNumber(); nrOfShuffles = 1;
5122
5123         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5124         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5125         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5126
5127         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5128
5129         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5130             p = (int) board[0][i];
5131             if(p < (int) BlackPawn) piecesLeft[p] ++;
5132             board[0][i] = EmptySquare;
5133         }
5134
5135         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5136             // shuffles restricted to allow normal castling put KRR first
5137             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5138                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5139             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5140                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5141             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5142                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5143             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5144                 put(board, WhiteRook, 0, 0, ANY);
5145             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5146         }
5147
5148         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5149             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5150             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5151                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5152                 while(piecesLeft[p] >= 2) {
5153                     AddOnePiece(board, p, 0, LITE);
5154                     AddOnePiece(board, p, 0, DARK);
5155                 }
5156                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5157             }
5158
5159         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5160             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5161             // but we leave King and Rooks for last, to possibly obey FRC restriction
5162             if(p == (int)WhiteRook) continue;
5163             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5164             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5165         }
5166
5167         // now everything is placed, except perhaps King (Unicorn) and Rooks
5168
5169         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5170             // Last King gets castling rights
5171             while(piecesLeft[(int)WhiteUnicorn]) {
5172                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5173                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5174             }
5175
5176             while(piecesLeft[(int)WhiteKing]) {
5177                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5178                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5179             }
5180
5181
5182         } else {
5183             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5184             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5185         }
5186
5187         // Only Rooks can be left; simply place them all
5188         while(piecesLeft[(int)WhiteRook]) {
5189                 i = put(board, WhiteRook, 0, 0, ANY);
5190                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5191                         if(first) {
5192                                 first=0;
5193                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5194                         }
5195                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5196                 }
5197         }
5198         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5199             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5200         }
5201
5202         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5203 }
5204
5205 int SetCharTable( char *table, const char * map )
5206 /* [HGM] moved here from winboard.c because of its general usefulness */
5207 /*       Basically a safe strcpy that uses the last character as King */
5208 {
5209     int result = FALSE; int NrPieces;
5210
5211     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5212                     && NrPieces >= 12 && !(NrPieces&1)) {
5213         int i; /* [HGM] Accept even length from 12 to 34 */
5214
5215         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5216         for( i=0; i<NrPieces/2-1; i++ ) {
5217             table[i] = map[i];
5218             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5219         }
5220         table[(int) WhiteKing]  = map[NrPieces/2-1];
5221         table[(int) BlackKing]  = map[NrPieces-1];
5222
5223         result = TRUE;
5224     }
5225
5226     return result;
5227 }
5228
5229 void Prelude(Board board)
5230 {       // [HGM] superchess: random selection of exo-pieces
5231         int i, j, k; ChessSquare p;
5232         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5233
5234         GetPositionNumber(); // use FRC position number
5235
5236         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5237             SetCharTable(pieceToChar, appData.pieceToCharTable);
5238             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5239                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5240         }
5241
5242         j = seed%4;                 seed /= 4;
5243         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5244         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5245         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5246         j = seed%3 + (seed%3 >= j); seed /= 3;
5247         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5248         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5249         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5250         j = seed%3;                 seed /= 3;
5251         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5252         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5253         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5254         j = seed%2 + (seed%2 >= j); seed /= 2;
5255         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5256         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5257         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5258         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5259         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5260         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5261         put(board, exoPieces[0],    0, 0, ANY);
5262         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5263 }
5264
5265 void
5266 InitPosition(redraw)
5267      int redraw;
5268 {
5269     ChessSquare (* pieces)[BOARD_FILES];
5270     int i, j, pawnRow, overrule,
5271     oldx = gameInfo.boardWidth,
5272     oldy = gameInfo.boardHeight,
5273     oldh = gameInfo.holdingsWidth,
5274     oldv = gameInfo.variant;
5275
5276     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5277
5278     /* [AS] Initialize pv info list [HGM] and game status */
5279     {
5280         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5281             pvInfoList[i].depth = 0;
5282             boards[i][EP_STATUS] = EP_NONE;
5283             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5284         }
5285
5286         initialRulePlies = 0; /* 50-move counter start */
5287
5288         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5289         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5290     }
5291
5292
5293     /* [HGM] logic here is completely changed. In stead of full positions */
5294     /* the initialized data only consist of the two backranks. The switch */
5295     /* selects which one we will use, which is than copied to the Board   */
5296     /* initialPosition, which for the rest is initialized by Pawns and    */
5297     /* empty squares. This initial position is then copied to boards[0],  */
5298     /* possibly after shuffling, so that it remains available.            */
5299
5300     gameInfo.holdingsWidth = 0; /* default board sizes */
5301     gameInfo.boardWidth    = 8;
5302     gameInfo.boardHeight   = 8;
5303     gameInfo.holdingsSize  = 0;
5304     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5305     for(i=0; i<BOARD_FILES-2; i++)
5306       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5307     initialPosition[EP_STATUS] = EP_NONE;
5308     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5309     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5310          SetCharTable(pieceNickName, appData.pieceNickNames);
5311     else SetCharTable(pieceNickName, "............");
5312
5313     switch (gameInfo.variant) {
5314     case VariantFischeRandom:
5315       shuffleOpenings = TRUE;
5316     default:
5317       pieces = FIDEArray;
5318       break;
5319     case VariantShatranj:
5320       pieces = ShatranjArray;
5321       nrCastlingRights = 0;
5322       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5323       break;
5324     case VariantMakruk:
5325       pieces = makrukArray;
5326       nrCastlingRights = 0;
5327       startedFromSetupPosition = TRUE;
5328       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5329       break;
5330     case VariantTwoKings:
5331       pieces = twoKingsArray;
5332       break;
5333     case VariantCapaRandom:
5334       shuffleOpenings = TRUE;
5335     case VariantCapablanca:
5336       pieces = CapablancaArray;
5337       gameInfo.boardWidth = 10;
5338       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5339       break;
5340     case VariantGothic:
5341       pieces = GothicArray;
5342       gameInfo.boardWidth = 10;
5343       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5344       break;
5345     case VariantJanus:
5346       pieces = JanusArray;
5347       gameInfo.boardWidth = 10;
5348       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5349       nrCastlingRights = 6;
5350         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5351         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5352         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5353         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5354         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5355         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5356       break;
5357     case VariantFalcon:
5358       pieces = FalconArray;
5359       gameInfo.boardWidth = 10;
5360       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5361       break;
5362     case VariantXiangqi:
5363       pieces = XiangqiArray;
5364       gameInfo.boardWidth  = 9;
5365       gameInfo.boardHeight = 10;
5366       nrCastlingRights = 0;
5367       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5368       break;
5369     case VariantShogi:
5370       pieces = ShogiArray;
5371       gameInfo.boardWidth  = 9;
5372       gameInfo.boardHeight = 9;
5373       gameInfo.holdingsSize = 7;
5374       nrCastlingRights = 0;
5375       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5376       break;
5377     case VariantCourier:
5378       pieces = CourierArray;
5379       gameInfo.boardWidth  = 12;
5380       nrCastlingRights = 0;
5381       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5382       break;
5383     case VariantKnightmate:
5384       pieces = KnightmateArray;
5385       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5386       break;
5387     case VariantFairy:
5388       pieces = fairyArray;
5389       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5390       break;
5391     case VariantGreat:
5392       pieces = GreatArray;
5393       gameInfo.boardWidth = 10;
5394       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5395       gameInfo.holdingsSize = 8;
5396       break;
5397     case VariantSuper:
5398       pieces = FIDEArray;
5399       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5400       gameInfo.holdingsSize = 8;
5401       startedFromSetupPosition = TRUE;
5402       break;
5403     case VariantCrazyhouse:
5404     case VariantBughouse:
5405       pieces = FIDEArray;
5406       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5407       gameInfo.holdingsSize = 5;
5408       break;
5409     case VariantWildCastle:
5410       pieces = FIDEArray;
5411       /* !!?shuffle with kings guaranteed to be on d or e file */
5412       shuffleOpenings = 1;
5413       break;
5414     case VariantNoCastle:
5415       pieces = FIDEArray;
5416       nrCastlingRights = 0;
5417       /* !!?unconstrained back-rank shuffle */
5418       shuffleOpenings = 1;
5419       break;
5420     }
5421
5422     overrule = 0;
5423     if(appData.NrFiles >= 0) {
5424         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5425         gameInfo.boardWidth = appData.NrFiles;
5426     }
5427     if(appData.NrRanks >= 0) {
5428         gameInfo.boardHeight = appData.NrRanks;
5429     }
5430     if(appData.holdingsSize >= 0) {
5431         i = appData.holdingsSize;
5432         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5433         gameInfo.holdingsSize = i;
5434     }
5435     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5436     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5437         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5438
5439     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5440     if(pawnRow < 1) pawnRow = 1;
5441     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5442
5443     /* User pieceToChar list overrules defaults */
5444     if(appData.pieceToCharTable != NULL)
5445         SetCharTable(pieceToChar, appData.pieceToCharTable);
5446
5447     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5448
5449         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5450             s = (ChessSquare) 0; /* account holding counts in guard band */
5451         for( i=0; i<BOARD_HEIGHT; i++ )
5452             initialPosition[i][j] = s;
5453
5454         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5455         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5456         initialPosition[pawnRow][j] = WhitePawn;
5457         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5458         if(gameInfo.variant == VariantXiangqi) {
5459             if(j&1) {
5460                 initialPosition[pawnRow][j] =
5461                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5462                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5463                    initialPosition[2][j] = WhiteCannon;
5464                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5465                 }
5466             }
5467         }
5468         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5469     }
5470     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5471
5472             j=BOARD_LEFT+1;
5473             initialPosition[1][j] = WhiteBishop;
5474             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5475             j=BOARD_RGHT-2;
5476             initialPosition[1][j] = WhiteRook;
5477             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5478     }
5479
5480     if( nrCastlingRights == -1) {
5481         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5482         /*       This sets default castling rights from none to normal corners   */
5483         /* Variants with other castling rights must set them themselves above    */
5484         nrCastlingRights = 6;
5485
5486         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5487         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5488         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5489         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5490         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5491         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5492      }
5493
5494      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5495      if(gameInfo.variant == VariantGreat) { // promotion commoners
5496         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5497         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5498         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5499         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5500      }
5501   if (appData.debugMode) {
5502     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5503   }
5504     if(shuffleOpenings) {
5505         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5506         startedFromSetupPosition = TRUE;
5507     }
5508     if(startedFromPositionFile) {
5509       /* [HGM] loadPos: use PositionFile for every new game */
5510       CopyBoard(initialPosition, filePosition);
5511       for(i=0; i<nrCastlingRights; i++)
5512           initialRights[i] = filePosition[CASTLING][i];
5513       startedFromSetupPosition = TRUE;
5514     }
5515
5516     CopyBoard(boards[0], initialPosition);
5517
5518     if(oldx != gameInfo.boardWidth ||
5519        oldy != gameInfo.boardHeight ||
5520        oldh != gameInfo.holdingsWidth
5521 #ifdef GOTHIC
5522        || oldv == VariantGothic ||        // For licensing popups
5523        gameInfo.variant == VariantGothic
5524 #endif
5525 #ifdef FALCON
5526        || oldv == VariantFalcon ||
5527        gameInfo.variant == VariantFalcon
5528 #endif
5529                                          )
5530             InitDrawingSizes(-2 ,0);
5531
5532     if (redraw)
5533       DrawPosition(TRUE, boards[currentMove]);
5534 }
5535
5536 void
5537 SendBoard(cps, moveNum)
5538      ChessProgramState *cps;
5539      int moveNum;
5540 {
5541     char message[MSG_SIZ];
5542
5543     if (cps->useSetboard) {
5544       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5545       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5546       SendToProgram(message, cps);
5547       free(fen);
5548
5549     } else {
5550       ChessSquare *bp;
5551       int i, j;
5552       /* Kludge to set black to move, avoiding the troublesome and now
5553        * deprecated "black" command.
5554        */
5555       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5556
5557       SendToProgram("edit\n", cps);
5558       SendToProgram("#\n", cps);
5559       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5560         bp = &boards[moveNum][i][BOARD_LEFT];
5561         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5562           if ((int) *bp < (int) BlackPawn) {
5563             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5564                     AAA + j, ONE + i);
5565             if(message[0] == '+' || message[0] == '~') {
5566               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5567                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5568                         AAA + j, ONE + i);
5569             }
5570             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5571                 message[1] = BOARD_RGHT   - 1 - j + '1';
5572                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5573             }
5574             SendToProgram(message, cps);
5575           }
5576         }
5577       }
5578
5579       SendToProgram("c\n", cps);
5580       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5581         bp = &boards[moveNum][i][BOARD_LEFT];
5582         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5583           if (((int) *bp != (int) EmptySquare)
5584               && ((int) *bp >= (int) BlackPawn)) {
5585             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5586                     AAA + j, ONE + i);
5587             if(message[0] == '+' || message[0] == '~') {
5588               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5589                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5590                         AAA + j, ONE + i);
5591             }
5592             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5593                 message[1] = BOARD_RGHT   - 1 - j + '1';
5594                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5595             }
5596             SendToProgram(message, cps);
5597           }
5598         }
5599       }
5600
5601       SendToProgram(".\n", cps);
5602     }
5603     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5604 }
5605
5606 static int autoQueen; // [HGM] oneclick
5607
5608 int
5609 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5610 {
5611     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5612     /* [HGM] add Shogi promotions */
5613     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5614     ChessSquare piece;
5615     ChessMove moveType;
5616     Boolean premove;
5617
5618     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5619     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5620
5621     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5622       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5623         return FALSE;
5624
5625     piece = boards[currentMove][fromY][fromX];
5626     if(gameInfo.variant == VariantShogi) {
5627         promotionZoneSize = BOARD_HEIGHT/3;
5628         highestPromotingPiece = (int)WhiteFerz;
5629     } else if(gameInfo.variant == VariantMakruk) {
5630         promotionZoneSize = 3;
5631     }
5632
5633     // next weed out all moves that do not touch the promotion zone at all
5634     if((int)piece >= BlackPawn) {
5635         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5636              return FALSE;
5637         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5638     } else {
5639         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5640            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5641     }
5642
5643     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5644
5645     // weed out mandatory Shogi promotions
5646     if(gameInfo.variant == VariantShogi) {
5647         if(piece >= BlackPawn) {
5648             if(toY == 0 && piece == BlackPawn ||
5649                toY == 0 && piece == BlackQueen ||
5650                toY <= 1 && piece == BlackKnight) {
5651                 *promoChoice = '+';
5652                 return FALSE;
5653             }
5654         } else {
5655             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5656                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5657                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5658                 *promoChoice = '+';
5659                 return FALSE;
5660             }
5661         }
5662     }
5663
5664     // weed out obviously illegal Pawn moves
5665     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5666         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5667         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5668         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5669         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5670         // note we are not allowed to test for valid (non-)capture, due to premove
5671     }
5672
5673     // we either have a choice what to promote to, or (in Shogi) whether to promote
5674     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5675         *promoChoice = PieceToChar(BlackFerz);  // no choice
5676         return FALSE;
5677     }
5678     if(autoQueen) { // predetermined
5679         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5680              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5681         else *promoChoice = PieceToChar(BlackQueen);
5682         return FALSE;
5683     }
5684
5685     // suppress promotion popup on illegal moves that are not premoves
5686     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5687               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5688     if(appData.testLegality && !premove) {
5689         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5690                         fromY, fromX, toY, toX, NULLCHAR);
5691         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5692             return FALSE;
5693     }
5694
5695     return TRUE;
5696 }
5697
5698 int
5699 InPalace(row, column)
5700      int row, column;
5701 {   /* [HGM] for Xiangqi */
5702     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5703          column < (BOARD_WIDTH + 4)/2 &&
5704          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5705     return FALSE;
5706 }
5707
5708 int
5709 PieceForSquare (x, y)
5710      int x;
5711      int y;
5712 {
5713   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5714      return -1;
5715   else
5716      return boards[currentMove][y][x];
5717 }
5718
5719 int
5720 OKToStartUserMove(x, y)
5721      int x, y;
5722 {
5723     ChessSquare from_piece;
5724     int white_piece;
5725
5726     if (matchMode) return FALSE;
5727     if (gameMode == EditPosition) return TRUE;
5728
5729     if (x >= 0 && y >= 0)
5730       from_piece = boards[currentMove][y][x];
5731     else
5732       from_piece = EmptySquare;
5733
5734     if (from_piece == EmptySquare) return FALSE;
5735
5736     white_piece = (int)from_piece >= (int)WhitePawn &&
5737       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5738
5739     switch (gameMode) {
5740       case PlayFromGameFile:
5741       case AnalyzeFile:
5742       case TwoMachinesPlay:
5743       case EndOfGame:
5744         return FALSE;
5745
5746       case IcsObserving:
5747       case IcsIdle:
5748         return FALSE;
5749
5750       case MachinePlaysWhite:
5751       case IcsPlayingBlack:
5752         if (appData.zippyPlay) return FALSE;
5753         if (white_piece) {
5754             DisplayMoveError(_("You are playing Black"));
5755             return FALSE;
5756         }
5757         break;
5758
5759       case MachinePlaysBlack:
5760       case IcsPlayingWhite:
5761         if (appData.zippyPlay) return FALSE;
5762         if (!white_piece) {
5763             DisplayMoveError(_("You are playing White"));
5764             return FALSE;
5765         }
5766         break;
5767
5768       case EditGame:
5769         if (!white_piece && WhiteOnMove(currentMove)) {
5770             DisplayMoveError(_("It is White's turn"));
5771             return FALSE;
5772         }
5773         if (white_piece && !WhiteOnMove(currentMove)) {
5774             DisplayMoveError(_("It is Black's turn"));
5775             return FALSE;
5776         }
5777         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5778             /* Editing correspondence game history */
5779             /* Could disallow this or prompt for confirmation */
5780             cmailOldMove = -1;
5781         }
5782         break;
5783
5784       case BeginningOfGame:
5785         if (appData.icsActive) return FALSE;
5786         if (!appData.noChessProgram) {
5787             if (!white_piece) {
5788                 DisplayMoveError(_("You are playing White"));
5789                 return FALSE;
5790             }
5791         }
5792         break;
5793
5794       case Training:
5795         if (!white_piece && WhiteOnMove(currentMove)) {
5796             DisplayMoveError(_("It is White's turn"));
5797             return FALSE;
5798         }
5799         if (white_piece && !WhiteOnMove(currentMove)) {
5800             DisplayMoveError(_("It is Black's turn"));
5801             return FALSE;
5802         }
5803         break;
5804
5805       default:
5806       case IcsExamining:
5807         break;
5808     }
5809     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5810         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5811         && gameMode != AnalyzeFile && gameMode != Training) {
5812         DisplayMoveError(_("Displayed position is not current"));
5813         return FALSE;
5814     }
5815     return TRUE;
5816 }
5817
5818 Boolean
5819 OnlyMove(int *x, int *y, Boolean captures) {
5820     DisambiguateClosure cl;
5821     if (appData.zippyPlay) return FALSE;
5822     switch(gameMode) {
5823       case MachinePlaysBlack:
5824       case IcsPlayingWhite:
5825       case BeginningOfGame:
5826         if(!WhiteOnMove(currentMove)) return FALSE;
5827         break;
5828       case MachinePlaysWhite:
5829       case IcsPlayingBlack:
5830         if(WhiteOnMove(currentMove)) return FALSE;
5831         break;
5832       default:
5833         return FALSE;
5834     }
5835     cl.pieceIn = EmptySquare;
5836     cl.rfIn = *y;
5837     cl.ffIn = *x;
5838     cl.rtIn = -1;
5839     cl.ftIn = -1;
5840     cl.promoCharIn = NULLCHAR;
5841     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5842     if( cl.kind == NormalMove ||
5843         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5844         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5845         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5846       fromX = cl.ff;
5847       fromY = cl.rf;
5848       *x = cl.ft;
5849       *y = cl.rt;
5850       return TRUE;
5851     }
5852     if(cl.kind != ImpossibleMove) return FALSE;
5853     cl.pieceIn = EmptySquare;
5854     cl.rfIn = -1;
5855     cl.ffIn = -1;
5856     cl.rtIn = *y;
5857     cl.ftIn = *x;
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       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5869       return TRUE;
5870     }
5871     return FALSE;
5872 }
5873
5874 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5875 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5876 int lastLoadGameUseList = FALSE;
5877 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5878 ChessMove lastLoadGameStart = (ChessMove) 0;
5879
5880 void
5881 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5882      int fromX, fromY, toX, toY;
5883      int promoChar;
5884 {
5885     ChessMove moveType;
5886     ChessSquare pdown, pup;
5887
5888     /* Check if the user is playing in turn.  This is complicated because we
5889        let the user "pick up" a piece before it is his turn.  So the piece he
5890        tried to pick up may have been captured by the time he puts it down!
5891        Therefore we use the color the user is supposed to be playing in this
5892        test, not the color of the piece that is currently on the starting
5893        square---except in EditGame mode, where the user is playing both
5894        sides; fortunately there the capture race can't happen.  (It can
5895        now happen in IcsExamining mode, but that's just too bad.  The user
5896        will get a somewhat confusing message in that case.)
5897        */
5898
5899     switch (gameMode) {
5900       case PlayFromGameFile:
5901       case AnalyzeFile:
5902       case TwoMachinesPlay:
5903       case EndOfGame:
5904       case IcsObserving:
5905       case IcsIdle:
5906         /* We switched into a game mode where moves are not accepted,
5907            perhaps while the mouse button was down. */
5908         return;
5909
5910       case MachinePlaysWhite:
5911         /* User is moving for Black */
5912         if (WhiteOnMove(currentMove)) {
5913             DisplayMoveError(_("It is White's turn"));
5914             return;
5915         }
5916         break;
5917
5918       case MachinePlaysBlack:
5919         /* User is moving for White */
5920         if (!WhiteOnMove(currentMove)) {
5921             DisplayMoveError(_("It is Black's turn"));
5922             return;
5923         }
5924         break;
5925
5926       case EditGame:
5927       case IcsExamining:
5928       case BeginningOfGame:
5929       case AnalyzeMode:
5930       case Training:
5931         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5932             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5933             /* User is moving for Black */
5934             if (WhiteOnMove(currentMove)) {
5935                 DisplayMoveError(_("It is White's turn"));
5936                 return;
5937             }
5938         } else {
5939             /* User is moving for White */
5940             if (!WhiteOnMove(currentMove)) {
5941                 DisplayMoveError(_("It is Black's turn"));
5942                 return;
5943             }
5944         }
5945         break;
5946
5947       case IcsPlayingBlack:
5948         /* User is moving for Black */
5949         if (WhiteOnMove(currentMove)) {
5950             if (!appData.premove) {
5951                 DisplayMoveError(_("It is White's turn"));
5952             } else if (toX >= 0 && toY >= 0) {
5953                 premoveToX = toX;
5954                 premoveToY = toY;
5955                 premoveFromX = fromX;
5956                 premoveFromY = fromY;
5957                 premovePromoChar = promoChar;
5958                 gotPremove = 1;
5959                 if (appData.debugMode)
5960                     fprintf(debugFP, "Got premove: fromX %d,"
5961                             "fromY %d, toX %d, toY %d\n",
5962                             fromX, fromY, toX, toY);
5963             }
5964             return;
5965         }
5966         break;
5967
5968       case IcsPlayingWhite:
5969         /* User is moving for White */
5970         if (!WhiteOnMove(currentMove)) {
5971             if (!appData.premove) {
5972                 DisplayMoveError(_("It is Black's turn"));
5973             } else if (toX >= 0 && toY >= 0) {
5974                 premoveToX = toX;
5975                 premoveToY = toY;
5976                 premoveFromX = fromX;
5977                 premoveFromY = fromY;
5978                 premovePromoChar = promoChar;
5979                 gotPremove = 1;
5980                 if (appData.debugMode)
5981                     fprintf(debugFP, "Got premove: fromX %d,"
5982                             "fromY %d, toX %d, toY %d\n",
5983                             fromX, fromY, toX, toY);
5984             }
5985             return;
5986         }
5987         break;
5988
5989       default:
5990         break;
5991
5992       case EditPosition:
5993         /* EditPosition, empty square, or different color piece;
5994            click-click move is possible */
5995         if (toX == -2 || toY == -2) {
5996             boards[0][fromY][fromX] = EmptySquare;
5997             DrawPosition(FALSE, boards[currentMove]);
5998             return;
5999         } else if (toX >= 0 && toY >= 0) {
6000             boards[0][toY][toX] = boards[0][fromY][fromX];
6001             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6002                 if(boards[0][fromY][0] != EmptySquare) {
6003                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6004                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6005                 }
6006             } else
6007             if(fromX == BOARD_RGHT+1) {
6008                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6009                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6010                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6011                 }
6012             } else
6013             boards[0][fromY][fromX] = EmptySquare;
6014             DrawPosition(FALSE, boards[currentMove]);
6015             return;
6016         }
6017         return;
6018     }
6019
6020     if(toX < 0 || toY < 0) return;
6021     pdown = boards[currentMove][fromY][fromX];
6022     pup = boards[currentMove][toY][toX];
6023
6024     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6025     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
6026          if( pup != EmptySquare ) return;
6027          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6028            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6029                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6030            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6031            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6032            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6033            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6034          fromY = DROP_RANK;
6035     }
6036
6037     /* [HGM] always test for legality, to get promotion info */
6038     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6039                                          fromY, fromX, toY, toX, promoChar);
6040     /* [HGM] but possibly ignore an IllegalMove result */
6041     if (appData.testLegality) {
6042         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6043             DisplayMoveError(_("Illegal move"));
6044             return;
6045         }
6046     }
6047
6048     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6049 }
6050
6051 /* Common tail of UserMoveEvent and DropMenuEvent */
6052 int
6053 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6054      ChessMove moveType;
6055      int fromX, fromY, toX, toY;
6056      /*char*/int promoChar;
6057 {
6058     char *bookHit = 0;
6059
6060     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6061         // [HGM] superchess: suppress promotions to non-available piece
6062         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6063         if(WhiteOnMove(currentMove)) {
6064             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6065         } else {
6066             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6067         }
6068     }
6069
6070     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6071        move type in caller when we know the move is a legal promotion */
6072     if(moveType == NormalMove && promoChar)
6073         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6074
6075     /* [HGM] <popupFix> The following if has been moved here from
6076        UserMoveEvent(). Because it seemed to belong here (why not allow
6077        piece drops in training games?), and because it can only be
6078        performed after it is known to what we promote. */
6079     if (gameMode == Training) {
6080       /* compare the move played on the board to the next move in the
6081        * game. If they match, display the move and the opponent's response.
6082        * If they don't match, display an error message.
6083        */
6084       int saveAnimate;
6085       Board testBoard;
6086       CopyBoard(testBoard, boards[currentMove]);
6087       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6088
6089       if (CompareBoards(testBoard, boards[currentMove+1])) {
6090         ForwardInner(currentMove+1);
6091
6092         /* Autoplay the opponent's response.
6093          * if appData.animate was TRUE when Training mode was entered,
6094          * the response will be animated.
6095          */
6096         saveAnimate = appData.animate;
6097         appData.animate = animateTraining;
6098         ForwardInner(currentMove+1);
6099         appData.animate = saveAnimate;
6100
6101         /* check for the end of the game */
6102         if (currentMove >= forwardMostMove) {
6103           gameMode = PlayFromGameFile;
6104           ModeHighlight();
6105           SetTrainingModeOff();
6106           DisplayInformation(_("End of game"));
6107         }
6108       } else {
6109         DisplayError(_("Incorrect move"), 0);
6110       }
6111       return 1;
6112     }
6113
6114   /* Ok, now we know that the move is good, so we can kill
6115      the previous line in Analysis Mode */
6116   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6117                                 && currentMove < forwardMostMove) {
6118     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6119   }
6120
6121   /* If we need the chess program but it's dead, restart it */
6122   ResurrectChessProgram();
6123
6124   /* A user move restarts a paused game*/
6125   if (pausing)
6126     PauseEvent();
6127
6128   thinkOutput[0] = NULLCHAR;
6129
6130   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6131
6132   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6133
6134   if (gameMode == BeginningOfGame) {
6135     if (appData.noChessProgram) {
6136       gameMode = EditGame;
6137       SetGameInfo();
6138     } else {
6139       char buf[MSG_SIZ];
6140       gameMode = MachinePlaysBlack;
6141       StartClocks();
6142       SetGameInfo();
6143       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6144       DisplayTitle(buf);
6145       if (first.sendName) {
6146         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6147         SendToProgram(buf, &first);
6148       }
6149       StartClocks();
6150     }
6151     ModeHighlight();
6152   }
6153
6154   /* Relay move to ICS or chess engine */
6155   if (appData.icsActive) {
6156     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6157         gameMode == IcsExamining) {
6158       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6159         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6160         SendToICS("draw ");
6161         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6162       }
6163       // also send plain move, in case ICS does not understand atomic claims
6164       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6165       ics_user_moved = 1;
6166     }
6167   } else {
6168     if (first.sendTime && (gameMode == BeginningOfGame ||
6169                            gameMode == MachinePlaysWhite ||
6170                            gameMode == MachinePlaysBlack)) {
6171       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6172     }
6173     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6174          // [HGM] book: if program might be playing, let it use book
6175         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6176         first.maybeThinking = TRUE;
6177     } else SendMoveToProgram(forwardMostMove-1, &first);
6178     if (currentMove == cmailOldMove + 1) {
6179       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6180     }
6181   }
6182
6183   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6184
6185   switch (gameMode) {
6186   case EditGame:
6187     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6188     case MT_NONE:
6189     case MT_CHECK:
6190       break;
6191     case MT_CHECKMATE:
6192     case MT_STAINMATE:
6193       if (WhiteOnMove(currentMove)) {
6194         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6195       } else {
6196         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6197       }
6198       break;
6199     case MT_STALEMATE:
6200       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6201       break;
6202     }
6203     break;
6204
6205   case MachinePlaysBlack:
6206   case MachinePlaysWhite:
6207     /* disable certain menu options while machine is thinking */
6208     SetMachineThinkingEnables();
6209     break;
6210
6211   default:
6212     break;
6213   }
6214
6215   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6216
6217   if(bookHit) { // [HGM] book: simulate book reply
6218         static char bookMove[MSG_SIZ]; // a bit generous?
6219
6220         programStats.nodes = programStats.depth = programStats.time =
6221         programStats.score = programStats.got_only_move = 0;
6222         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6223
6224         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6225         strcat(bookMove, bookHit);
6226         HandleMachineMove(bookMove, &first);
6227   }
6228   return 1;
6229 }
6230
6231 void
6232 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6233      Board board;
6234      int flags;
6235      ChessMove kind;
6236      int rf, ff, rt, ft;
6237      VOIDSTAR closure;
6238 {
6239     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6240     Markers *m = (Markers *) closure;
6241     if(rf == fromY && ff == fromX)
6242         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6243                          || kind == WhiteCapturesEnPassant
6244                          || kind == BlackCapturesEnPassant);
6245     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6246 }
6247
6248 void
6249 MarkTargetSquares(int clear)
6250 {
6251   int x, y;
6252   if(!appData.markers || !appData.highlightDragging ||
6253      !appData.testLegality || gameMode == EditPosition) return;
6254   if(clear) {
6255     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6256   } else {
6257     int capt = 0;
6258     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6259     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6260       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6261       if(capt)
6262       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6263     }
6264   }
6265   DrawPosition(TRUE, NULL);
6266 }
6267
6268 void LeftClick(ClickType clickType, int xPix, int yPix)
6269 {
6270     int x, y;
6271     Boolean saveAnimate;
6272     static int second = 0, promotionChoice = 0, dragging = 0;
6273     char promoChoice = NULLCHAR;
6274
6275     if(appData.seekGraph && appData.icsActive && loggedOn &&
6276         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6277         SeekGraphClick(clickType, xPix, yPix, 0);
6278         return;
6279     }
6280
6281     if (clickType == Press) ErrorPopDown();
6282     MarkTargetSquares(1);
6283
6284     x = EventToSquare(xPix, BOARD_WIDTH);
6285     y = EventToSquare(yPix, BOARD_HEIGHT);
6286     if (!flipView && y >= 0) {
6287         y = BOARD_HEIGHT - 1 - y;
6288     }
6289     if (flipView && x >= 0) {
6290         x = BOARD_WIDTH - 1 - x;
6291     }
6292
6293     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6294         if(clickType == Release) return; // ignore upclick of click-click destination
6295         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6296         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6297         if(gameInfo.holdingsWidth &&
6298                 (WhiteOnMove(currentMove)
6299                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6300                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6301             // click in right holdings, for determining promotion piece
6302             ChessSquare p = boards[currentMove][y][x];
6303             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6304             if(p != EmptySquare) {
6305                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6306                 fromX = fromY = -1;
6307                 return;
6308             }
6309         }
6310         DrawPosition(FALSE, boards[currentMove]);
6311         return;
6312     }
6313
6314     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6315     if(clickType == Press
6316             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6317               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6318               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6319         return;
6320
6321     autoQueen = appData.alwaysPromoteToQueen;
6322
6323     if (fromX == -1) {
6324       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6325         if (clickType == Press) {
6326             /* First square */
6327             if (OKToStartUserMove(x, y)) {
6328                 fromX = x;
6329                 fromY = y;
6330                 second = 0;
6331                 MarkTargetSquares(0);
6332                 DragPieceBegin(xPix, yPix); dragging = 1;
6333                 if (appData.highlightDragging) {
6334                     SetHighlights(x, y, -1, -1);
6335                 }
6336             }
6337         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6338             DragPieceEnd(xPix, yPix); dragging = 0;
6339             DrawPosition(FALSE, NULL);
6340         }
6341         return;
6342       }
6343     }
6344
6345     /* fromX != -1 */
6346     if (clickType == Press && gameMode != EditPosition) {
6347         ChessSquare fromP;
6348         ChessSquare toP;
6349         int frc;
6350
6351         // ignore off-board to clicks
6352         if(y < 0 || x < 0) return;
6353
6354         /* Check if clicking again on the same color piece */
6355         fromP = boards[currentMove][fromY][fromX];
6356         toP = boards[currentMove][y][x];
6357         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6358         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6359              WhitePawn <= toP && toP <= WhiteKing &&
6360              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6361              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6362             (BlackPawn <= fromP && fromP <= BlackKing &&
6363              BlackPawn <= toP && toP <= BlackKing &&
6364              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6365              !(fromP == BlackKing && toP == BlackRook && frc))) {
6366             /* Clicked again on same color piece -- changed his mind */
6367             second = (x == fromX && y == fromY);
6368            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6369             if (appData.highlightDragging) {
6370                 SetHighlights(x, y, -1, -1);
6371             } else {
6372                 ClearHighlights();
6373             }
6374             if (OKToStartUserMove(x, y)) {
6375                 fromX = x;
6376                 fromY = y; dragging = 1;
6377                 MarkTargetSquares(0);
6378                 DragPieceBegin(xPix, yPix);
6379             }
6380             return;
6381            }
6382         }
6383         // ignore clicks on holdings
6384         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6385     }
6386
6387     if (clickType == Release && x == fromX && y == fromY) {
6388         DragPieceEnd(xPix, yPix); dragging = 0;
6389         if (appData.animateDragging) {
6390             /* Undo animation damage if any */
6391             DrawPosition(FALSE, NULL);
6392         }
6393         if (second) {
6394             /* Second up/down in same square; just abort move */
6395             second = 0;
6396             fromX = fromY = -1;
6397             ClearHighlights();
6398             gotPremove = 0;
6399             ClearPremoveHighlights();
6400         } else {
6401             /* First upclick in same square; start click-click mode */
6402             SetHighlights(x, y, -1, -1);
6403         }
6404         return;
6405     }
6406
6407     /* we now have a different from- and (possibly off-board) to-square */
6408     /* Completed move */
6409     toX = x;
6410     toY = y;
6411     saveAnimate = appData.animate;
6412     if (clickType == Press) {
6413         /* Finish clickclick move */
6414         if (appData.animate || appData.highlightLastMove) {
6415             SetHighlights(fromX, fromY, toX, toY);
6416         } else {
6417             ClearHighlights();
6418         }
6419     } else {
6420         /* Finish drag move */
6421         if (appData.highlightLastMove) {
6422             SetHighlights(fromX, fromY, toX, toY);
6423         } else {
6424             ClearHighlights();
6425         }
6426         DragPieceEnd(xPix, yPix); dragging = 0;
6427         /* Don't animate move and drag both */
6428         appData.animate = FALSE;
6429     }
6430
6431     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6432     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6433         ChessSquare piece = boards[currentMove][fromY][fromX];
6434         if(gameMode == EditPosition && piece != EmptySquare &&
6435            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6436             int n;
6437
6438             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6439                 n = PieceToNumber(piece - (int)BlackPawn);
6440                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6441                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6442                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6443             } else
6444             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6445                 n = PieceToNumber(piece);
6446                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6447                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6448                 boards[currentMove][n][BOARD_WIDTH-2]++;
6449             }
6450             boards[currentMove][fromY][fromX] = EmptySquare;
6451         }
6452         ClearHighlights();
6453         fromX = fromY = -1;
6454         DrawPosition(TRUE, boards[currentMove]);
6455         return;
6456     }
6457
6458     // off-board moves should not be highlighted
6459     if(x < 0 || x < 0) ClearHighlights();
6460
6461     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6462         SetHighlights(fromX, fromY, toX, toY);
6463         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6464             // [HGM] super: promotion to captured piece selected from holdings
6465             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6466             promotionChoice = TRUE;
6467             // kludge follows to temporarily execute move on display, without promoting yet
6468             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6469             boards[currentMove][toY][toX] = p;
6470             DrawPosition(FALSE, boards[currentMove]);
6471             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6472             boards[currentMove][toY][toX] = q;
6473             DisplayMessage("Click in holdings to choose piece", "");
6474             return;
6475         }
6476         PromotionPopUp();
6477     } else {
6478         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6479         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6480         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6481         fromX = fromY = -1;
6482     }
6483     appData.animate = saveAnimate;
6484     if (appData.animate || appData.animateDragging) {
6485         /* Undo animation damage if needed */
6486         DrawPosition(FALSE, NULL);
6487     }
6488 }
6489
6490 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6491 {   // front-end-free part taken out of PieceMenuPopup
6492     int whichMenu; int xSqr, ySqr;
6493
6494     if(seekGraphUp) { // [HGM] seekgraph
6495         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6496         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6497         return -2;
6498     }
6499
6500     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6501          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6502         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6503         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6504         if(action == Press)   {
6505             originalFlip = flipView;
6506             flipView = !flipView; // temporarily flip board to see game from partners perspective
6507             DrawPosition(TRUE, partnerBoard);
6508             DisplayMessage(partnerStatus, "");
6509             partnerUp = TRUE;
6510         } else if(action == Release) {
6511             flipView = originalFlip;
6512             DrawPosition(TRUE, boards[currentMove]);
6513             partnerUp = FALSE;
6514         }
6515         return -2;
6516     }
6517
6518     xSqr = EventToSquare(x, BOARD_WIDTH);
6519     ySqr = EventToSquare(y, BOARD_HEIGHT);
6520     if (action == Release) UnLoadPV(); // [HGM] pv
6521     if (action != Press) return -2; // return code to be ignored
6522     switch (gameMode) {
6523       case IcsExamining:
6524         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6525       case EditPosition:
6526         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6527         if (xSqr < 0 || ySqr < 0) return -1;\r
6528         whichMenu = 0; // edit-position menu
6529         break;
6530       case IcsObserving:
6531         if(!appData.icsEngineAnalyze) return -1;
6532       case IcsPlayingWhite:
6533       case IcsPlayingBlack:
6534         if(!appData.zippyPlay) goto noZip;
6535       case AnalyzeMode:
6536       case AnalyzeFile:
6537       case MachinePlaysWhite:
6538       case MachinePlaysBlack:
6539       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6540         if (!appData.dropMenu) {
6541           LoadPV(x, y);
6542           return 2; // flag front-end to grab mouse events
6543         }
6544         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6545            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6546       case EditGame:
6547       noZip:
6548         if (xSqr < 0 || ySqr < 0) return -1;
6549         if (!appData.dropMenu || appData.testLegality &&
6550             gameInfo.variant != VariantBughouse &&
6551             gameInfo.variant != VariantCrazyhouse) return -1;
6552         whichMenu = 1; // drop menu
6553         break;
6554       default:
6555         return -1;
6556     }
6557
6558     if (((*fromX = xSqr) < 0) ||
6559         ((*fromY = ySqr) < 0)) {
6560         *fromX = *fromY = -1;
6561         return -1;
6562     }
6563     if (flipView)
6564       *fromX = BOARD_WIDTH - 1 - *fromX;
6565     else
6566       *fromY = BOARD_HEIGHT - 1 - *fromY;
6567
6568     return whichMenu;
6569 }
6570
6571 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6572 {
6573 //    char * hint = lastHint;
6574     FrontEndProgramStats stats;
6575
6576     stats.which = cps == &first ? 0 : 1;
6577     stats.depth = cpstats->depth;
6578     stats.nodes = cpstats->nodes;
6579     stats.score = cpstats->score;
6580     stats.time = cpstats->time;
6581     stats.pv = cpstats->movelist;
6582     stats.hint = lastHint;
6583     stats.an_move_index = 0;
6584     stats.an_move_count = 0;
6585
6586     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6587         stats.hint = cpstats->move_name;
6588         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6589         stats.an_move_count = cpstats->nr_moves;
6590     }
6591
6592     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
6593
6594     SetProgramStats( &stats );
6595 }
6596
6597 void
6598 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6599 {       // count all piece types
6600         int p, f, r;
6601         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6602         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6603         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6604                 p = board[r][f];
6605                 pCnt[p]++;
6606                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6607                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6608                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6609                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6610                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6611                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6612         }
6613 }
6614
6615 int
6616 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6617 {
6618         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6619         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6620
6621         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6622         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6623         if(myPawns == 2 && nMine == 3) // KPP
6624             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6625         if(myPawns == 1 && nMine == 2) // KP
6626             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6627         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6628             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6629         if(myPawns) return FALSE;
6630         if(pCnt[WhiteRook+side])
6631             return pCnt[BlackRook-side] ||
6632                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6633                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6634                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6635         if(pCnt[WhiteCannon+side]) {
6636             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6637             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6638         }
6639         if(pCnt[WhiteKnight+side])
6640             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6641         return FALSE;
6642 }
6643
6644 int
6645 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6646 {
6647         VariantClass v = gameInfo.variant;
6648
6649         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6650         if(v == VariantShatranj) return TRUE; // always winnable through baring
6651         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6652         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6653
6654         if(v == VariantXiangqi) {
6655                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6656
6657                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6658                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6659                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6660                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6661                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6662                 if(stale) // we have at least one last-rank P plus perhaps C
6663                     return majors // KPKX
6664                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6665                 else // KCA*E*
6666                     return pCnt[WhiteFerz+side] // KCAK
6667                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6668                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6669                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6670
6671         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6672                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6673
6674                 if(nMine == 1) return FALSE; // bare King
6675                 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
6676                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6677                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6678                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6679                 if(pCnt[WhiteKnight+side])
6680                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6681                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6682                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6683                 if(nBishops)
6684                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6685                 if(pCnt[WhiteAlfil+side])
6686                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6687                 if(pCnt[WhiteWazir+side])
6688                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6689         }
6690
6691         return TRUE;
6692 }
6693
6694 int
6695 Adjudicate(ChessProgramState *cps)
6696 {       // [HGM] some adjudications useful with buggy engines
6697         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6698         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6699         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6700         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6701         int k, count = 0; static int bare = 1;
6702         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6703         Boolean canAdjudicate = !appData.icsActive;
6704
6705         // most tests only when we understand the game, i.e. legality-checking on
6706             if( appData.testLegality )
6707             {   /* [HGM] Some more adjudications for obstinate engines */
6708                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6709                 static int moveCount = 6;
6710                 ChessMove result;
6711                 char *reason = NULL;
6712
6713                 /* Count what is on board. */
6714                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6715
6716                 /* Some material-based adjudications that have to be made before stalemate test */
6717                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6718                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6719                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6720                      if(canAdjudicate && appData.checkMates) {
6721                          if(engineOpponent)
6722                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6723                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6724                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6725                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6726                          return 1;
6727                      }
6728                 }
6729
6730                 /* Bare King in Shatranj (loses) or Losers (wins) */
6731                 if( nrW == 1 || nrB == 1) {
6732                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6733                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6734                      if(canAdjudicate && appData.checkMates) {
6735                          if(engineOpponent)
6736                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6737                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6738                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6739                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6740                          return 1;
6741                      }
6742                   } else
6743                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6744                   {    /* bare King */
6745                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6746                         if(canAdjudicate && appData.checkMates) {
6747                             /* but only adjudicate if adjudication enabled */
6748                             if(engineOpponent)
6749                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6750                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6751                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6752                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6753                             return 1;
6754                         }
6755                   }
6756                 } else bare = 1;
6757
6758
6759             // don't wait for engine to announce game end if we can judge ourselves
6760             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6761               case MT_CHECK:
6762                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6763                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6764                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6765                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6766                             checkCnt++;
6767                         if(checkCnt >= 2) {
6768                             reason = "Xboard adjudication: 3rd check";
6769                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6770                             break;
6771                         }
6772                     }
6773                 }
6774               case MT_NONE:
6775               default:
6776                 break;
6777               case MT_STALEMATE:
6778               case MT_STAINMATE:
6779                 reason = "Xboard adjudication: Stalemate";
6780                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6781                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6782                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6783                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6784                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6785                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6786                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6787                                                                         EP_CHECKMATE : EP_WINS);
6788                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6789                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6790                 }
6791                 break;
6792               case MT_CHECKMATE:
6793                 reason = "Xboard adjudication: Checkmate";
6794                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6795                 break;
6796             }
6797
6798                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6799                     case EP_STALEMATE:
6800                         result = GameIsDrawn; break;
6801                     case EP_CHECKMATE:
6802                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6803                     case EP_WINS:
6804                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6805                     default:
6806                         result = (ChessMove) 0;
6807                 }
6808                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6809                     if(engineOpponent)
6810                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6811                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6812                     GameEnds( result, reason, GE_XBOARD );
6813                     return 1;
6814                 }
6815
6816                 /* Next absolutely insufficient mating material. */
6817                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6818                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6819                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6820
6821                      /* always flag draws, for judging claims */
6822                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6823
6824                      if(canAdjudicate && appData.materialDraws) {
6825                          /* but only adjudicate them if adjudication enabled */
6826                          if(engineOpponent) {
6827                            SendToProgram("force\n", engineOpponent); // suppress reply
6828                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6829                          }
6830                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6831                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6832                          return 1;
6833                      }
6834                 }
6835
6836                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6837                 if(gameInfo.variant == VariantXiangqi ?
6838                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6839                  : nrW + nrB == 4 &&
6840                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6841                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6842                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6843                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6844                    ) ) {
6845                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6846                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6847                           if(engineOpponent) {
6848                             SendToProgram("force\n", engineOpponent); // suppress reply
6849                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6850                           }
6851                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6852                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6853                           return 1;
6854                      }
6855                 } else moveCount = 6;
6856             }
6857         if (appData.debugMode) { int i;
6858             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6859                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6860                     appData.drawRepeats);
6861             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6862               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6863
6864         }
6865
6866         // Repetition draws and 50-move rule can be applied independently of legality testing
6867
6868                 /* Check for rep-draws */
6869                 count = 0;
6870                 for(k = forwardMostMove-2;
6871                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6872                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6873                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6874                     k-=2)
6875                 {   int rights=0;
6876                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6877                         /* compare castling rights */
6878                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6879                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6880                                 rights++; /* King lost rights, while rook still had them */
6881                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6882                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6883                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6884                                    rights++; /* but at least one rook lost them */
6885                         }
6886                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6887                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6888                                 rights++;
6889                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6890                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6891                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6892                                    rights++;
6893                         }
6894                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6895                             && appData.drawRepeats > 1) {
6896                              /* adjudicate after user-specified nr of repeats */
6897                              int result = GameIsDrawn;
6898                              char *details = "XBoard adjudication: repetition draw";
6899                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6900                                 // [HGM] xiangqi: check for forbidden perpetuals
6901                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6902                                 for(m=forwardMostMove; m>k; m-=2) {
6903                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6904                                         ourPerpetual = 0; // the current mover did not always check
6905                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6906                                         hisPerpetual = 0; // the opponent did not always check
6907                                 }
6908                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6909                                                                         ourPerpetual, hisPerpetual);
6910                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6911                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6912                                     details = "Xboard adjudication: perpetual checking";
6913                                 } else
6914                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6915                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6916                                 } else
6917                                 // Now check for perpetual chases
6918                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6919                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6920                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6921                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6922                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6923                                         details = "Xboard adjudication: perpetual chasing";
6924                                     } else
6925                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6926                                         break; // Abort repetition-checking loop.
6927                                 }
6928                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6929                              }
6930                              if(engineOpponent) {
6931                                SendToProgram("force\n", engineOpponent); // suppress reply
6932                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6933                              }
6934                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6935                              GameEnds( result, details, GE_XBOARD );
6936                              return 1;
6937                         }
6938                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6939                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6940                     }
6941                 }
6942
6943                 /* Now we test for 50-move draws. Determine ply count */
6944                 count = forwardMostMove;
6945                 /* look for last irreversble move */
6946                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6947                     count--;
6948                 /* if we hit starting position, add initial plies */
6949                 if( count == backwardMostMove )
6950                     count -= initialRulePlies;
6951                 count = forwardMostMove - count;
6952                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6953                         // adjust reversible move counter for checks in Xiangqi
6954                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6955                         if(i < backwardMostMove) i = backwardMostMove;
6956                         while(i <= forwardMostMove) {
6957                                 lastCheck = inCheck; // check evasion does not count
6958                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6959                                 if(inCheck || lastCheck) count--; // check does not count
6960                                 i++;
6961                         }
6962                 }
6963                 if( count >= 100)
6964                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6965                          /* this is used to judge if draw claims are legal */
6966                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6967                          if(engineOpponent) {
6968                            SendToProgram("force\n", engineOpponent); // suppress reply
6969                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6970                          }
6971                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6972                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6973                          return 1;
6974                 }
6975
6976                 /* if draw offer is pending, treat it as a draw claim
6977                  * when draw condition present, to allow engines a way to
6978                  * claim draws before making their move to avoid a race
6979                  * condition occurring after their move
6980                  */
6981                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6982                          char *p = NULL;
6983                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6984                              p = "Draw claim: 50-move rule";
6985                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6986                              p = "Draw claim: 3-fold repetition";
6987                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6988                              p = "Draw claim: insufficient mating material";
6989                          if( p != NULL && canAdjudicate) {
6990                              if(engineOpponent) {
6991                                SendToProgram("force\n", engineOpponent); // suppress reply
6992                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6993                              }
6994                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6995                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6996                              return 1;
6997                          }
6998                 }
6999
7000                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7001                     if(engineOpponent) {
7002                       SendToProgram("force\n", engineOpponent); // suppress reply
7003                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7004                     }
7005                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7006                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7007                     return 1;
7008                 }
7009         return 0;
7010 }
7011
7012 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7013 {   // [HGM] book: this routine intercepts moves to simulate book replies
7014     char *bookHit = NULL;
7015
7016     //first determine if the incoming move brings opponent into his book
7017     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7018         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7019     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7020     if(bookHit != NULL && !cps->bookSuspend) {
7021         // make sure opponent is not going to reply after receiving move to book position
7022         SendToProgram("force\n", cps);
7023         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7024     }
7025     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7026     // now arrange restart after book miss
7027     if(bookHit) {
7028         // after a book hit we never send 'go', and the code after the call to this routine
7029         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7030         char buf[MSG_SIZ];
7031         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7032         SendToProgram(buf, cps);
7033         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7034     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7035         SendToProgram("go\n", cps);
7036         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7037     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7038         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7039             SendToProgram("go\n", cps);
7040         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7041     }
7042     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7043 }
7044
7045 char *savedMessage;
7046 ChessProgramState *savedState;
7047 void DeferredBookMove(void)
7048 {
7049         if(savedState->lastPing != savedState->lastPong)
7050                     ScheduleDelayedEvent(DeferredBookMove, 10);
7051         else
7052         HandleMachineMove(savedMessage, savedState);
7053 }
7054
7055 void
7056 HandleMachineMove(message, cps)
7057      char *message;
7058      ChessProgramState *cps;
7059 {
7060     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7061     char realname[MSG_SIZ];
7062     int fromX, fromY, toX, toY;
7063     ChessMove moveType;
7064     char promoChar;
7065     char *p;
7066     int machineWhite;
7067     char *bookHit;
7068
7069     cps->userError = 0;
7070
7071 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7072     /*
7073      * Kludge to ignore BEL characters
7074      */
7075     while (*message == '\007') message++;
7076
7077     /*
7078      * [HGM] engine debug message: ignore lines starting with '#' character
7079      */
7080     if(cps->debug && *message == '#') return;
7081
7082     /*
7083      * Look for book output
7084      */
7085     if (cps == &first && bookRequested) {
7086         if (message[0] == '\t' || message[0] == ' ') {
7087             /* Part of the book output is here; append it */
7088             strcat(bookOutput, message);
7089             strcat(bookOutput, "  \n");
7090             return;
7091         } else if (bookOutput[0] != NULLCHAR) {
7092             /* All of book output has arrived; display it */
7093             char *p = bookOutput;
7094             while (*p != NULLCHAR) {
7095                 if (*p == '\t') *p = ' ';
7096                 p++;
7097             }
7098             DisplayInformation(bookOutput);
7099             bookRequested = FALSE;
7100             /* Fall through to parse the current output */
7101         }
7102     }
7103
7104     /*
7105      * Look for machine move.
7106      */
7107     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7108         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7109     {
7110         /* This method is only useful on engines that support ping */
7111         if (cps->lastPing != cps->lastPong) {
7112           if (gameMode == BeginningOfGame) {
7113             /* Extra move from before last new; ignore */
7114             if (appData.debugMode) {
7115                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7116             }
7117           } else {
7118             if (appData.debugMode) {
7119                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7120                         cps->which, gameMode);
7121             }
7122
7123             SendToProgram("undo\n", cps);
7124           }
7125           return;
7126         }
7127
7128         switch (gameMode) {
7129           case BeginningOfGame:
7130             /* Extra move from before last reset; ignore */
7131             if (appData.debugMode) {
7132                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7133             }
7134             return;
7135
7136           case EndOfGame:
7137           case IcsIdle:
7138           default:
7139             /* Extra move after we tried to stop.  The mode test is
7140                not a reliable way of detecting this problem, but it's
7141                the best we can do on engines that don't support ping.
7142             */
7143             if (appData.debugMode) {
7144                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7145                         cps->which, gameMode);
7146             }
7147             SendToProgram("undo\n", cps);
7148             return;
7149
7150           case MachinePlaysWhite:
7151           case IcsPlayingWhite:
7152             machineWhite = TRUE;
7153             break;
7154
7155           case MachinePlaysBlack:
7156           case IcsPlayingBlack:
7157             machineWhite = FALSE;
7158             break;
7159
7160           case TwoMachinesPlay:
7161             machineWhite = (cps->twoMachinesColor[0] == 'w');
7162             break;
7163         }
7164         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7165             if (appData.debugMode) {
7166                 fprintf(debugFP,
7167                         "Ignoring move out of turn by %s, gameMode %d"
7168                         ", forwardMost %d\n",
7169                         cps->which, gameMode, forwardMostMove);
7170             }
7171             return;
7172         }
7173
7174     if (appData.debugMode) { int f = forwardMostMove;
7175         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7176                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7177                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7178     }
7179         if(cps->alphaRank) AlphaRank(machineMove, 4);
7180         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7181                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7182             /* Machine move could not be parsed; ignore it. */
7183           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7184                     machineMove, cps->which);
7185             DisplayError(buf1, 0);
7186             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7187                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7188             if (gameMode == TwoMachinesPlay) {
7189               GameEnds(machineWhite ? BlackWins : WhiteWins,
7190                        buf1, GE_XBOARD);
7191             }
7192             return;
7193         }
7194
7195         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7196         /* So we have to redo legality test with true e.p. status here,  */
7197         /* to make sure an illegal e.p. capture does not slip through,   */
7198         /* to cause a forfeit on a justified illegal-move complaint      */
7199         /* of the opponent.                                              */
7200         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7201            ChessMove moveType;
7202            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7203                              fromY, fromX, toY, toX, promoChar);
7204             if (appData.debugMode) {
7205                 int i;
7206                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7207                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7208                 fprintf(debugFP, "castling rights\n");
7209             }
7210             if(moveType == IllegalMove) {
7211               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7212                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7213                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7214                            buf1, GE_XBOARD);
7215                 return;
7216            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7217            /* [HGM] Kludge to handle engines that send FRC-style castling
7218               when they shouldn't (like TSCP-Gothic) */
7219            switch(moveType) {
7220              case WhiteASideCastleFR:
7221              case BlackASideCastleFR:
7222                toX+=2;
7223                currentMoveString[2]++;
7224                break;
7225              case WhiteHSideCastleFR:
7226              case BlackHSideCastleFR:
7227                toX--;
7228                currentMoveString[2]--;
7229                break;
7230              default: ; // nothing to do, but suppresses warning of pedantic compilers
7231            }
7232         }
7233         hintRequested = FALSE;
7234         lastHint[0] = NULLCHAR;
7235         bookRequested = FALSE;
7236         /* Program may be pondering now */
7237         cps->maybeThinking = TRUE;
7238         if (cps->sendTime == 2) cps->sendTime = 1;
7239         if (cps->offeredDraw) cps->offeredDraw--;
7240
7241         /* currentMoveString is set as a side-effect of ParseOneMove */
7242         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7243         strcat(machineMove, "\n");
7244         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7245
7246         /* [AS] Save move info*/
7247         pvInfoList[ forwardMostMove ].score = programStats.score;
7248         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7249         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7250
7251         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7252
7253         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7254         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7255             int count = 0;
7256
7257             while( count < adjudicateLossPlies ) {
7258                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7259
7260                 if( count & 1 ) {
7261                     score = -score; /* Flip score for winning side */
7262                 }
7263
7264                 if( score > adjudicateLossThreshold ) {
7265                     break;
7266                 }
7267
7268                 count++;
7269             }
7270
7271             if( count >= adjudicateLossPlies ) {
7272                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7273
7274                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7275                     "Xboard adjudication",
7276                     GE_XBOARD );
7277
7278                 return;
7279             }
7280         }
7281
7282         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7283
7284 #if ZIPPY
7285         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7286             first.initDone) {
7287           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7288                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7289                 SendToICS("draw ");
7290                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7291           }
7292           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7293           ics_user_moved = 1;
7294           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7295                 char buf[3*MSG_SIZ];
7296
7297                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7298                         programStats.score / 100.,
7299                         programStats.depth,
7300                         programStats.time / 100.,
7301                         (unsigned int)programStats.nodes,
7302                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7303                         programStats.movelist);
7304                 SendToICS(buf);
7305 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7306           }
7307         }
7308 #endif
7309
7310         /* [AS] Clear stats for next move */
7311         ClearProgramStats();
7312         thinkOutput[0] = NULLCHAR;
7313         hiddenThinkOutputState = 0;
7314
7315         bookHit = NULL;
7316         if (gameMode == TwoMachinesPlay) {
7317             /* [HGM] relaying draw offers moved to after reception of move */
7318             /* and interpreting offer as claim if it brings draw condition */
7319             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7320                 SendToProgram("draw\n", cps->other);
7321             }
7322             if (cps->other->sendTime) {
7323                 SendTimeRemaining(cps->other,
7324                                   cps->other->twoMachinesColor[0] == 'w');
7325             }
7326             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7327             if (firstMove && !bookHit) {
7328                 firstMove = FALSE;
7329                 if (cps->other->useColors) {
7330                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7331                 }
7332                 SendToProgram("go\n", cps->other);
7333             }
7334             cps->other->maybeThinking = TRUE;
7335         }
7336
7337         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7338
7339         if (!pausing && appData.ringBellAfterMoves) {
7340             RingBell();
7341         }
7342
7343         /*
7344          * Reenable menu items that were disabled while
7345          * machine was thinking
7346          */
7347         if (gameMode != TwoMachinesPlay)
7348             SetUserThinkingEnables();
7349
7350         // [HGM] book: after book hit opponent has received move and is now in force mode
7351         // force the book reply into it, and then fake that it outputted this move by jumping
7352         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7353         if(bookHit) {
7354                 static char bookMove[MSG_SIZ]; // a bit generous?
7355
7356                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7357                 strcat(bookMove, bookHit);
7358                 message = bookMove;
7359                 cps = cps->other;
7360                 programStats.nodes = programStats.depth = programStats.time =
7361                 programStats.score = programStats.got_only_move = 0;
7362                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7363
7364                 if(cps->lastPing != cps->lastPong) {
7365                     savedMessage = message; // args for deferred call
7366                     savedState = cps;
7367                     ScheduleDelayedEvent(DeferredBookMove, 10);
7368                     return;
7369                 }
7370                 goto FakeBookMove;
7371         }
7372
7373         return;
7374     }
7375
7376     /* Set special modes for chess engines.  Later something general
7377      *  could be added here; for now there is just one kludge feature,
7378      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7379      *  when "xboard" is given as an interactive command.
7380      */
7381     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7382         cps->useSigint = FALSE;
7383         cps->useSigterm = FALSE;
7384     }
7385     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7386       ParseFeatures(message+8, cps);
7387       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7388     }
7389
7390     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7391      * want this, I was asked to put it in, and obliged.
7392      */
7393     if (!strncmp(message, "setboard ", 9)) {
7394         Board initial_position;
7395
7396         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7397
7398         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7399             DisplayError(_("Bad FEN received from engine"), 0);
7400             return ;
7401         } else {
7402            Reset(TRUE, FALSE);
7403            CopyBoard(boards[0], initial_position);
7404            initialRulePlies = FENrulePlies;
7405            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7406            else gameMode = MachinePlaysBlack;
7407            DrawPosition(FALSE, boards[currentMove]);
7408         }
7409         return;
7410     }
7411
7412     /*
7413      * Look for communication commands
7414      */
7415     if (!strncmp(message, "telluser ", 9)) {
7416         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7417         DisplayNote(message + 9);
7418         return;
7419     }
7420     if (!strncmp(message, "tellusererror ", 14)) {
7421         cps->userError = 1;
7422         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7423         DisplayError(message + 14, 0);
7424         return;
7425     }
7426     if (!strncmp(message, "tellopponent ", 13)) {
7427       if (appData.icsActive) {
7428         if (loggedOn) {
7429           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7430           SendToICS(buf1);
7431         }
7432       } else {
7433         DisplayNote(message + 13);
7434       }
7435       return;
7436     }
7437     if (!strncmp(message, "tellothers ", 11)) {
7438       if (appData.icsActive) {
7439         if (loggedOn) {
7440           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7441           SendToICS(buf1);
7442         }
7443       }
7444       return;
7445     }
7446     if (!strncmp(message, "tellall ", 8)) {
7447       if (appData.icsActive) {
7448         if (loggedOn) {
7449           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7450           SendToICS(buf1);
7451         }
7452       } else {
7453         DisplayNote(message + 8);
7454       }
7455       return;
7456     }
7457     if (strncmp(message, "warning", 7) == 0) {
7458         /* Undocumented feature, use tellusererror in new code */
7459         DisplayError(message, 0);
7460         return;
7461     }
7462     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7463         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7464         strcat(realname, " query");
7465         AskQuestion(realname, buf2, buf1, cps->pr);
7466         return;
7467     }
7468     /* Commands from the engine directly to ICS.  We don't allow these to be
7469      *  sent until we are logged on. Crafty kibitzes have been known to
7470      *  interfere with the login process.
7471      */
7472     if (loggedOn) {
7473         if (!strncmp(message, "tellics ", 8)) {
7474             SendToICS(message + 8);
7475             SendToICS("\n");
7476             return;
7477         }
7478         if (!strncmp(message, "tellicsnoalias ", 15)) {
7479             SendToICS(ics_prefix);
7480             SendToICS(message + 15);
7481             SendToICS("\n");
7482             return;
7483         }
7484         /* The following are for backward compatibility only */
7485         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7486             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7487             SendToICS(ics_prefix);
7488             SendToICS(message);
7489             SendToICS("\n");
7490             return;
7491         }
7492     }
7493     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7494         return;
7495     }
7496     /*
7497      * If the move is illegal, cancel it and redraw the board.
7498      * Also deal with other error cases.  Matching is rather loose
7499      * here to accommodate engines written before the spec.
7500      */
7501     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7502         strncmp(message, "Error", 5) == 0) {
7503         if (StrStr(message, "name") ||
7504             StrStr(message, "rating") || StrStr(message, "?") ||
7505             StrStr(message, "result") || StrStr(message, "board") ||
7506             StrStr(message, "bk") || StrStr(message, "computer") ||
7507             StrStr(message, "variant") || StrStr(message, "hint") ||
7508             StrStr(message, "random") || StrStr(message, "depth") ||
7509             StrStr(message, "accepted")) {
7510             return;
7511         }
7512         if (StrStr(message, "protover")) {
7513           /* Program is responding to input, so it's apparently done
7514              initializing, and this error message indicates it is
7515              protocol version 1.  So we don't need to wait any longer
7516              for it to initialize and send feature commands. */
7517           FeatureDone(cps, 1);
7518           cps->protocolVersion = 1;
7519           return;
7520         }
7521         cps->maybeThinking = FALSE;
7522
7523         if (StrStr(message, "draw")) {
7524             /* Program doesn't have "draw" command */
7525             cps->sendDrawOffers = 0;
7526             return;
7527         }
7528         if (cps->sendTime != 1 &&
7529             (StrStr(message, "time") || StrStr(message, "otim"))) {
7530           /* Program apparently doesn't have "time" or "otim" command */
7531           cps->sendTime = 0;
7532           return;
7533         }
7534         if (StrStr(message, "analyze")) {
7535             cps->analysisSupport = FALSE;
7536             cps->analyzing = FALSE;
7537             Reset(FALSE, TRUE);
7538             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7539             DisplayError(buf2, 0);
7540             return;
7541         }
7542         if (StrStr(message, "(no matching move)st")) {
7543           /* Special kludge for GNU Chess 4 only */
7544           cps->stKludge = TRUE;
7545           SendTimeControl(cps, movesPerSession, timeControl,
7546                           timeIncrement, appData.searchDepth,
7547                           searchTime);
7548           return;
7549         }
7550         if (StrStr(message, "(no matching move)sd")) {
7551           /* Special kludge for GNU Chess 4 only */
7552           cps->sdKludge = TRUE;
7553           SendTimeControl(cps, movesPerSession, timeControl,
7554                           timeIncrement, appData.searchDepth,
7555                           searchTime);
7556           return;
7557         }
7558         if (!StrStr(message, "llegal")) {
7559             return;
7560         }
7561         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7562             gameMode == IcsIdle) return;
7563         if (forwardMostMove <= backwardMostMove) return;
7564         if (pausing) PauseEvent();
7565       if(appData.forceIllegal) {
7566             // [HGM] illegal: machine refused move; force position after move into it
7567           SendToProgram("force\n", cps);
7568           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7569                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7570                 // when black is to move, while there might be nothing on a2 or black
7571                 // might already have the move. So send the board as if white has the move.
7572                 // But first we must change the stm of the engine, as it refused the last move
7573                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7574                 if(WhiteOnMove(forwardMostMove)) {
7575                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7576                     SendBoard(cps, forwardMostMove); // kludgeless board
7577                 } else {
7578                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7579                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7580                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7581                 }
7582           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7583             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7584                  gameMode == TwoMachinesPlay)
7585               SendToProgram("go\n", cps);
7586             return;
7587       } else
7588         if (gameMode == PlayFromGameFile) {
7589             /* Stop reading this game file */
7590             gameMode = EditGame;
7591             ModeHighlight();
7592         }
7593         currentMove = forwardMostMove-1;
7594         DisplayMove(currentMove-1); /* before DisplayMoveError */
7595         SwitchClocks(forwardMostMove-1); // [HGM] race
7596         DisplayBothClocks();
7597         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7598                 parseList[currentMove], cps->which);
7599         DisplayMoveError(buf1);
7600         DrawPosition(FALSE, boards[currentMove]);
7601
7602         /* [HGM] illegal-move claim should forfeit game when Xboard */
7603         /* only passes fully legal moves                            */
7604         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7605             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7606                                 "False illegal-move claim", GE_XBOARD );
7607         }
7608         return;
7609     }
7610     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7611         /* Program has a broken "time" command that
7612            outputs a string not ending in newline.
7613            Don't use it. */
7614         cps->sendTime = 0;
7615     }
7616
7617     /*
7618      * If chess program startup fails, exit with an error message.
7619      * Attempts to recover here are futile.
7620      */
7621     if ((StrStr(message, "unknown host") != NULL)
7622         || (StrStr(message, "No remote directory") != NULL)
7623         || (StrStr(message, "not found") != NULL)
7624         || (StrStr(message, "No such file") != NULL)
7625         || (StrStr(message, "can't alloc") != NULL)
7626         || (StrStr(message, "Permission denied") != NULL)) {
7627
7628         cps->maybeThinking = FALSE;
7629         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7630                 cps->which, cps->program, cps->host, message);
7631         RemoveInputSource(cps->isr);
7632         DisplayFatalError(buf1, 0, 1);
7633         return;
7634     }
7635
7636     /*
7637      * Look for hint output
7638      */
7639     if (sscanf(message, "Hint: %s", buf1) == 1) {
7640         if (cps == &first && hintRequested) {
7641             hintRequested = FALSE;
7642             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7643                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7644                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7645                                     PosFlags(forwardMostMove),
7646                                     fromY, fromX, toY, toX, promoChar, buf1);
7647                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7648                 DisplayInformation(buf2);
7649             } else {
7650                 /* Hint move could not be parsed!? */
7651               snprintf(buf2, sizeof(buf2),
7652                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7653                         buf1, cps->which);
7654                 DisplayError(buf2, 0);
7655             }
7656         } else {
7657           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7658         }
7659         return;
7660     }
7661
7662     /*
7663      * Ignore other messages if game is not in progress
7664      */
7665     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7666         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7667
7668     /*
7669      * look for win, lose, draw, or draw offer
7670      */
7671     if (strncmp(message, "1-0", 3) == 0) {
7672         char *p, *q, *r = "";
7673         p = strchr(message, '{');
7674         if (p) {
7675             q = strchr(p, '}');
7676             if (q) {
7677                 *q = NULLCHAR;
7678                 r = p + 1;
7679             }
7680         }
7681         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7682         return;
7683     } else if (strncmp(message, "0-1", 3) == 0) {
7684         char *p, *q, *r = "";
7685         p = strchr(message, '{');
7686         if (p) {
7687             q = strchr(p, '}');
7688             if (q) {
7689                 *q = NULLCHAR;
7690                 r = p + 1;
7691             }
7692         }
7693         /* Kludge for Arasan 4.1 bug */
7694         if (strcmp(r, "Black resigns") == 0) {
7695             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7696             return;
7697         }
7698         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7699         return;
7700     } else if (strncmp(message, "1/2", 3) == 0) {
7701         char *p, *q, *r = "";
7702         p = strchr(message, '{');
7703         if (p) {
7704             q = strchr(p, '}');
7705             if (q) {
7706                 *q = NULLCHAR;
7707                 r = p + 1;
7708             }
7709         }
7710
7711         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7712         return;
7713
7714     } else if (strncmp(message, "White resign", 12) == 0) {
7715         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7716         return;
7717     } else if (strncmp(message, "Black resign", 12) == 0) {
7718         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7719         return;
7720     } else if (strncmp(message, "White matches", 13) == 0 ||
7721                strncmp(message, "Black matches", 13) == 0   ) {
7722         /* [HGM] ignore GNUShogi noises */
7723         return;
7724     } else if (strncmp(message, "White", 5) == 0 &&
7725                message[5] != '(' &&
7726                StrStr(message, "Black") == NULL) {
7727         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7728         return;
7729     } else if (strncmp(message, "Black", 5) == 0 &&
7730                message[5] != '(') {
7731         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7732         return;
7733     } else if (strcmp(message, "resign") == 0 ||
7734                strcmp(message, "computer resigns") == 0) {
7735         switch (gameMode) {
7736           case MachinePlaysBlack:
7737           case IcsPlayingBlack:
7738             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7739             break;
7740           case MachinePlaysWhite:
7741           case IcsPlayingWhite:
7742             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7743             break;
7744           case TwoMachinesPlay:
7745             if (cps->twoMachinesColor[0] == 'w')
7746               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7747             else
7748               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7749             break;
7750           default:
7751             /* can't happen */
7752             break;
7753         }
7754         return;
7755     } else if (strncmp(message, "opponent mates", 14) == 0) {
7756         switch (gameMode) {
7757           case MachinePlaysBlack:
7758           case IcsPlayingBlack:
7759             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7760             break;
7761           case MachinePlaysWhite:
7762           case IcsPlayingWhite:
7763             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7764             break;
7765           case TwoMachinesPlay:
7766             if (cps->twoMachinesColor[0] == 'w')
7767               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7768             else
7769               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7770             break;
7771           default:
7772             /* can't happen */
7773             break;
7774         }
7775         return;
7776     } else if (strncmp(message, "computer mates", 14) == 0) {
7777         switch (gameMode) {
7778           case MachinePlaysBlack:
7779           case IcsPlayingBlack:
7780             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7781             break;
7782           case MachinePlaysWhite:
7783           case IcsPlayingWhite:
7784             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7785             break;
7786           case TwoMachinesPlay:
7787             if (cps->twoMachinesColor[0] == 'w')
7788               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7789             else
7790               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7791             break;
7792           default:
7793             /* can't happen */
7794             break;
7795         }
7796         return;
7797     } else if (strncmp(message, "checkmate", 9) == 0) {
7798         if (WhiteOnMove(forwardMostMove)) {
7799             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7800         } else {
7801             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7802         }
7803         return;
7804     } else if (strstr(message, "Draw") != NULL ||
7805                strstr(message, "game is a draw") != NULL) {
7806         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7807         return;
7808     } else if (strstr(message, "offer") != NULL &&
7809                strstr(message, "draw") != NULL) {
7810 #if ZIPPY
7811         if (appData.zippyPlay && first.initDone) {
7812             /* Relay offer to ICS */
7813             SendToICS(ics_prefix);
7814             SendToICS("draw\n");
7815         }
7816 #endif
7817         cps->offeredDraw = 2; /* valid until this engine moves twice */
7818         if (gameMode == TwoMachinesPlay) {
7819             if (cps->other->offeredDraw) {
7820                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7821             /* [HGM] in two-machine mode we delay relaying draw offer      */
7822             /* until after we also have move, to see if it is really claim */
7823             }
7824         } else if (gameMode == MachinePlaysWhite ||
7825                    gameMode == MachinePlaysBlack) {
7826           if (userOfferedDraw) {
7827             DisplayInformation(_("Machine accepts your draw offer"));
7828             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7829           } else {
7830             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7831           }
7832         }
7833     }
7834
7835
7836     /*
7837      * Look for thinking output
7838      */
7839     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7840           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7841                                 ) {
7842         int plylev, mvleft, mvtot, curscore, time;
7843         char mvname[MOVE_LEN];
7844         u64 nodes; // [DM]
7845         char plyext;
7846         int ignore = FALSE;
7847         int prefixHint = FALSE;
7848         mvname[0] = NULLCHAR;
7849
7850         switch (gameMode) {
7851           case MachinePlaysBlack:
7852           case IcsPlayingBlack:
7853             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7854             break;
7855           case MachinePlaysWhite:
7856           case IcsPlayingWhite:
7857             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7858             break;
7859           case AnalyzeMode:
7860           case AnalyzeFile:
7861             break;
7862           case IcsObserving: /* [DM] icsEngineAnalyze */
7863             if (!appData.icsEngineAnalyze) ignore = TRUE;
7864             break;
7865           case TwoMachinesPlay:
7866             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7867                 ignore = TRUE;
7868             }
7869             break;
7870           default:
7871             ignore = TRUE;
7872             break;
7873         }
7874
7875         if (!ignore) {
7876             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7877             buf1[0] = NULLCHAR;
7878             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7879                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7880
7881                 if (plyext != ' ' && plyext != '\t') {
7882                     time *= 100;
7883                 }
7884
7885                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7886                 if( cps->scoreIsAbsolute &&
7887                     ( gameMode == MachinePlaysBlack ||
7888                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7889                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7890                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7891                      !WhiteOnMove(currentMove)
7892                     ) )
7893                 {
7894                     curscore = -curscore;
7895                 }
7896
7897
7898                 tempStats.depth = plylev;
7899                 tempStats.nodes = nodes;
7900                 tempStats.time = time;
7901                 tempStats.score = curscore;
7902                 tempStats.got_only_move = 0;
7903
7904                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7905                         int ticklen;
7906
7907                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7908                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7909                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7910                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7911                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7912                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7913                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7914                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7915                 }
7916
7917                 /* Buffer overflow protection */
7918                 if (buf1[0] != NULLCHAR) {
7919                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7920                         && appData.debugMode) {
7921                         fprintf(debugFP,
7922                                 "PV is too long; using the first %u bytes.\n",
7923                                 (unsigned) sizeof(tempStats.movelist) - 1);
7924                     }
7925
7926                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
7927                 } else {
7928                     sprintf(tempStats.movelist, " no PV\n");
7929                 }
7930
7931                 if (tempStats.seen_stat) {
7932                     tempStats.ok_to_send = 1;
7933                 }
7934
7935                 if (strchr(tempStats.movelist, '(') != NULL) {
7936                     tempStats.line_is_book = 1;
7937                     tempStats.nr_moves = 0;
7938                     tempStats.moves_left = 0;
7939                 } else {
7940                     tempStats.line_is_book = 0;
7941                 }
7942
7943                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7944                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7945
7946                 SendProgramStatsToFrontend( cps, &tempStats );
7947
7948                 /*
7949                     [AS] Protect the thinkOutput buffer from overflow... this
7950                     is only useful if buf1 hasn't overflowed first!
7951                 */
7952                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
7953                          plylev,
7954                          (gameMode == TwoMachinesPlay ?
7955                           ToUpper(cps->twoMachinesColor[0]) : ' '),
7956                          ((double) curscore) / 100.0,
7957                          prefixHint ? lastHint : "",
7958                          prefixHint ? " " : "" );
7959
7960                 if( buf1[0] != NULLCHAR ) {
7961                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7962
7963                     if( strlen(buf1) > max_len ) {
7964                         if( appData.debugMode) {
7965                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7966                         }
7967                         buf1[max_len+1] = '\0';
7968                     }
7969
7970                     strcat( thinkOutput, buf1 );
7971                 }
7972
7973                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7974                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7975                     DisplayMove(currentMove - 1);
7976                 }
7977                 return;
7978
7979             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7980                 /* crafty (9.25+) says "(only move) <move>"
7981                  * if there is only 1 legal move
7982                  */
7983                 sscanf(p, "(only move) %s", buf1);
7984                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
7985                 sprintf(programStats.movelist, "%s (only move)", buf1);
7986                 programStats.depth = 1;
7987                 programStats.nr_moves = 1;
7988                 programStats.moves_left = 1;
7989                 programStats.nodes = 1;
7990                 programStats.time = 1;
7991                 programStats.got_only_move = 1;
7992
7993                 /* Not really, but we also use this member to
7994                    mean "line isn't going to change" (Crafty
7995                    isn't searching, so stats won't change) */
7996                 programStats.line_is_book = 1;
7997
7998                 SendProgramStatsToFrontend( cps, &programStats );
7999
8000                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8001                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8002                     DisplayMove(currentMove - 1);
8003                 }
8004                 return;
8005             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8006                               &time, &nodes, &plylev, &mvleft,
8007                               &mvtot, mvname) >= 5) {
8008                 /* The stat01: line is from Crafty (9.29+) in response
8009                    to the "." command */
8010                 programStats.seen_stat = 1;
8011                 cps->maybeThinking = TRUE;
8012
8013                 if (programStats.got_only_move || !appData.periodicUpdates)
8014                   return;
8015
8016                 programStats.depth = plylev;
8017                 programStats.time = time;
8018                 programStats.nodes = nodes;
8019                 programStats.moves_left = mvleft;
8020                 programStats.nr_moves = mvtot;
8021                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8022                 programStats.ok_to_send = 1;
8023                 programStats.movelist[0] = '\0';
8024
8025                 SendProgramStatsToFrontend( cps, &programStats );
8026
8027                 return;
8028
8029             } else if (strncmp(message,"++",2) == 0) {
8030                 /* Crafty 9.29+ outputs this */
8031                 programStats.got_fail = 2;
8032                 return;
8033
8034             } else if (strncmp(message,"--",2) == 0) {
8035                 /* Crafty 9.29+ outputs this */
8036                 programStats.got_fail = 1;
8037                 return;
8038
8039             } else if (thinkOutput[0] != NULLCHAR &&
8040                        strncmp(message, "    ", 4) == 0) {
8041                 unsigned message_len;
8042
8043                 p = message;
8044                 while (*p && *p == ' ') p++;
8045
8046                 message_len = strlen( p );
8047
8048                 /* [AS] Avoid buffer overflow */
8049                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8050                     strcat(thinkOutput, " ");
8051                     strcat(thinkOutput, p);
8052                 }
8053
8054                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8055                     strcat(programStats.movelist, " ");
8056                     strcat(programStats.movelist, p);
8057                 }
8058
8059                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8060                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8061                     DisplayMove(currentMove - 1);
8062                 }
8063                 return;
8064             }
8065         }
8066         else {
8067             buf1[0] = NULLCHAR;
8068
8069             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8070                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8071             {
8072                 ChessProgramStats cpstats;
8073
8074                 if (plyext != ' ' && plyext != '\t') {
8075                     time *= 100;
8076                 }
8077
8078                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8079                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8080                     curscore = -curscore;
8081                 }
8082
8083                 cpstats.depth = plylev;
8084                 cpstats.nodes = nodes;
8085                 cpstats.time = time;
8086                 cpstats.score = curscore;
8087                 cpstats.got_only_move = 0;
8088                 cpstats.movelist[0] = '\0';
8089
8090                 if (buf1[0] != NULLCHAR) {
8091                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8092                 }
8093
8094                 cpstats.ok_to_send = 0;
8095                 cpstats.line_is_book = 0;
8096                 cpstats.nr_moves = 0;
8097                 cpstats.moves_left = 0;
8098
8099                 SendProgramStatsToFrontend( cps, &cpstats );
8100             }
8101         }
8102     }
8103 }
8104
8105
8106 /* Parse a game score from the character string "game", and
8107    record it as the history of the current game.  The game
8108    score is NOT assumed to start from the standard position.
8109    The display is not updated in any way.
8110    */
8111 void
8112 ParseGameHistory(game)
8113      char *game;
8114 {
8115     ChessMove moveType;
8116     int fromX, fromY, toX, toY, boardIndex;
8117     char promoChar;
8118     char *p, *q;
8119     char buf[MSG_SIZ];
8120
8121     if (appData.debugMode)
8122       fprintf(debugFP, "Parsing game history: %s\n", game);
8123
8124     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8125     gameInfo.site = StrSave(appData.icsHost);
8126     gameInfo.date = PGNDate();
8127     gameInfo.round = StrSave("-");
8128
8129     /* Parse out names of players */
8130     while (*game == ' ') game++;
8131     p = buf;
8132     while (*game != ' ') *p++ = *game++;
8133     *p = NULLCHAR;
8134     gameInfo.white = StrSave(buf);
8135     while (*game == ' ') game++;
8136     p = buf;
8137     while (*game != ' ' && *game != '\n') *p++ = *game++;
8138     *p = NULLCHAR;
8139     gameInfo.black = StrSave(buf);
8140
8141     /* Parse moves */
8142     boardIndex = blackPlaysFirst ? 1 : 0;
8143     yynewstr(game);
8144     for (;;) {
8145         yyboardindex = boardIndex;
8146         moveType = (ChessMove) yylex();
8147         switch (moveType) {
8148           case IllegalMove:             /* maybe suicide chess, etc. */
8149   if (appData.debugMode) {
8150     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8151     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8152     setbuf(debugFP, NULL);
8153   }
8154           case WhitePromotion:
8155           case BlackPromotion:
8156           case WhiteNonPromotion:
8157           case BlackNonPromotion:
8158           case NormalMove:
8159           case WhiteCapturesEnPassant:
8160           case BlackCapturesEnPassant:
8161           case WhiteKingSideCastle:
8162           case WhiteQueenSideCastle:
8163           case BlackKingSideCastle:
8164           case BlackQueenSideCastle:
8165           case WhiteKingSideCastleWild:
8166           case WhiteQueenSideCastleWild:
8167           case BlackKingSideCastleWild:
8168           case BlackQueenSideCastleWild:
8169           /* PUSH Fabien */
8170           case WhiteHSideCastleFR:
8171           case WhiteASideCastleFR:
8172           case BlackHSideCastleFR:
8173           case BlackASideCastleFR:
8174           /* POP Fabien */
8175             fromX = currentMoveString[0] - AAA;
8176             fromY = currentMoveString[1] - ONE;
8177             toX = currentMoveString[2] - AAA;
8178             toY = currentMoveString[3] - ONE;
8179             promoChar = currentMoveString[4];
8180             break;
8181           case WhiteDrop:
8182           case BlackDrop:
8183             fromX = moveType == WhiteDrop ?
8184               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8185             (int) CharToPiece(ToLower(currentMoveString[0]));
8186             fromY = DROP_RANK;
8187             toX = currentMoveString[2] - AAA;
8188             toY = currentMoveString[3] - ONE;
8189             promoChar = NULLCHAR;
8190             break;
8191           case AmbiguousMove:
8192             /* bug? */
8193             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8194   if (appData.debugMode) {
8195     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8196     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8197     setbuf(debugFP, NULL);
8198   }
8199             DisplayError(buf, 0);
8200             return;
8201           case ImpossibleMove:
8202             /* bug? */
8203             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8204   if (appData.debugMode) {
8205     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8206     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8207     setbuf(debugFP, NULL);
8208   }
8209             DisplayError(buf, 0);
8210             return;
8211           case (ChessMove) 0:   /* end of file */
8212             if (boardIndex < backwardMostMove) {
8213                 /* Oops, gap.  How did that happen? */
8214                 DisplayError(_("Gap in move list"), 0);
8215                 return;
8216             }
8217             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8218             if (boardIndex > forwardMostMove) {
8219                 forwardMostMove = boardIndex;
8220             }
8221             return;
8222           case ElapsedTime:
8223             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8224                 strcat(parseList[boardIndex-1], " ");
8225                 strcat(parseList[boardIndex-1], yy_text);
8226             }
8227             continue;
8228           case Comment:
8229           case PGNTag:
8230           case NAG:
8231           default:
8232             /* ignore */
8233             continue;
8234           case WhiteWins:
8235           case BlackWins:
8236           case GameIsDrawn:
8237           case GameUnfinished:
8238             if (gameMode == IcsExamining) {
8239                 if (boardIndex < backwardMostMove) {
8240                     /* Oops, gap.  How did that happen? */
8241                     return;
8242                 }
8243                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8244                 return;
8245             }
8246             gameInfo.result = moveType;
8247             p = strchr(yy_text, '{');
8248             if (p == NULL) p = strchr(yy_text, '(');
8249             if (p == NULL) {
8250                 p = yy_text;
8251                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8252             } else {
8253                 q = strchr(p, *p == '{' ? '}' : ')');
8254                 if (q != NULL) *q = NULLCHAR;
8255                 p++;
8256             }
8257             gameInfo.resultDetails = StrSave(p);
8258             continue;
8259         }
8260         if (boardIndex >= forwardMostMove &&
8261             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8262             backwardMostMove = blackPlaysFirst ? 1 : 0;
8263             return;
8264         }
8265         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8266                                  fromY, fromX, toY, toX, promoChar,
8267                                  parseList[boardIndex]);
8268         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8269         /* currentMoveString is set as a side-effect of yylex */
8270         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8271         strcat(moveList[boardIndex], "\n");
8272         boardIndex++;
8273         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8274         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8275           case MT_NONE:
8276           case MT_STALEMATE:
8277           default:
8278             break;
8279           case MT_CHECK:
8280             if(gameInfo.variant != VariantShogi)
8281                 strcat(parseList[boardIndex - 1], "+");
8282             break;
8283           case MT_CHECKMATE:
8284           case MT_STAINMATE:
8285             strcat(parseList[boardIndex - 1], "#");
8286             break;
8287         }
8288     }
8289 }
8290
8291
8292 /* Apply a move to the given board  */
8293 void
8294 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8295      int fromX, fromY, toX, toY;
8296      int promoChar;
8297      Board board;
8298 {
8299   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8300   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8301
8302     /* [HGM] compute & store e.p. status and castling rights for new position */
8303     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8304
8305       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8306       oldEP = (signed char)board[EP_STATUS];
8307       board[EP_STATUS] = EP_NONE;
8308
8309       if( board[toY][toX] != EmptySquare )
8310            board[EP_STATUS] = EP_CAPTURE;
8311
8312   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8313   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8314        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8315          
8316   if (fromY == DROP_RANK) {
8317         /* must be first */
8318         piece = board[toY][toX] = (ChessSquare) fromX;
8319   } else {
8320       int i;
8321
8322       if( board[fromY][fromX] == WhitePawn ) {
8323            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8324                board[EP_STATUS] = EP_PAWN_MOVE;
8325            if( toY-fromY==2) {
8326                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8327                         gameInfo.variant != VariantBerolina || toX < fromX)
8328                       board[EP_STATUS] = toX | berolina;
8329                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8330                         gameInfo.variant != VariantBerolina || toX > fromX)
8331                       board[EP_STATUS] = toX;
8332            }
8333       } else
8334       if( board[fromY][fromX] == BlackPawn ) {
8335            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8336                board[EP_STATUS] = EP_PAWN_MOVE;
8337            if( toY-fromY== -2) {
8338                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8339                         gameInfo.variant != VariantBerolina || toX < fromX)
8340                       board[EP_STATUS] = toX | berolina;
8341                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8342                         gameInfo.variant != VariantBerolina || toX > fromX)
8343                       board[EP_STATUS] = toX;
8344            }
8345        }
8346
8347        for(i=0; i<nrCastlingRights; i++) {
8348            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8349               board[CASTLING][i] == toX   && castlingRank[i] == toY
8350              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8351        }
8352
8353      if (fromX == toX && fromY == toY) return;
8354
8355      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8356      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8357      if(gameInfo.variant == VariantKnightmate)
8358          king += (int) WhiteUnicorn - (int) WhiteKing;
8359
8360     /* Code added by Tord: */
8361     /* FRC castling assumed when king captures friendly rook. */
8362     if (board[fromY][fromX] == WhiteKing &&
8363              board[toY][toX] == WhiteRook) {
8364       board[fromY][fromX] = EmptySquare;
8365       board[toY][toX] = EmptySquare;
8366       if(toX > fromX) {
8367         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8368       } else {
8369         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8370       }
8371     } else if (board[fromY][fromX] == BlackKing &&
8372                board[toY][toX] == BlackRook) {
8373       board[fromY][fromX] = EmptySquare;
8374       board[toY][toX] = EmptySquare;
8375       if(toX > fromX) {
8376         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8377       } else {
8378         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8379       }
8380     /* End of code added by Tord */
8381
8382     } else if (board[fromY][fromX] == king
8383         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8384         && toY == fromY && toX > fromX+1) {
8385         board[fromY][fromX] = EmptySquare;
8386         board[toY][toX] = king;
8387         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8388         board[fromY][BOARD_RGHT-1] = EmptySquare;
8389     } else if (board[fromY][fromX] == king
8390         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8391                && toY == fromY && toX < fromX-1) {
8392         board[fromY][fromX] = EmptySquare;
8393         board[toY][toX] = king;
8394         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8395         board[fromY][BOARD_LEFT] = EmptySquare;
8396     } else if (board[fromY][fromX] == WhitePawn
8397                && toY >= BOARD_HEIGHT-promoRank
8398                && gameInfo.variant != VariantXiangqi
8399                ) {
8400         /* white pawn promotion */
8401         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8402         if (board[toY][toX] == EmptySquare) {
8403             board[toY][toX] = WhiteQueen;
8404         }
8405         if(gameInfo.variant==VariantBughouse ||
8406            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8407             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8408         board[fromY][fromX] = EmptySquare;
8409     } else if ((fromY == BOARD_HEIGHT-4)
8410                && (toX != fromX)
8411                && gameInfo.variant != VariantXiangqi
8412                && gameInfo.variant != VariantBerolina
8413                && (board[fromY][fromX] == WhitePawn)
8414                && (board[toY][toX] == EmptySquare)) {
8415         board[fromY][fromX] = EmptySquare;
8416         board[toY][toX] = WhitePawn;
8417         captured = board[toY - 1][toX];
8418         board[toY - 1][toX] = EmptySquare;
8419     } else if ((fromY == BOARD_HEIGHT-4)
8420                && (toX == fromX)
8421                && gameInfo.variant == VariantBerolina
8422                && (board[fromY][fromX] == WhitePawn)
8423                && (board[toY][toX] == EmptySquare)) {
8424         board[fromY][fromX] = EmptySquare;
8425         board[toY][toX] = WhitePawn;
8426         if(oldEP & EP_BEROLIN_A) {
8427                 captured = board[fromY][fromX-1];
8428                 board[fromY][fromX-1] = EmptySquare;
8429         }else{  captured = board[fromY][fromX+1];
8430                 board[fromY][fromX+1] = EmptySquare;
8431         }
8432     } else if (board[fromY][fromX] == king
8433         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8434                && toY == fromY && toX > fromX+1) {
8435         board[fromY][fromX] = EmptySquare;
8436         board[toY][toX] = king;
8437         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8438         board[fromY][BOARD_RGHT-1] = EmptySquare;
8439     } else if (board[fromY][fromX] == king
8440         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8441                && toY == fromY && toX < fromX-1) {
8442         board[fromY][fromX] = EmptySquare;
8443         board[toY][toX] = king;
8444         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8445         board[fromY][BOARD_LEFT] = EmptySquare;
8446     } else if (fromY == 7 && fromX == 3
8447                && board[fromY][fromX] == BlackKing
8448                && toY == 7 && toX == 5) {
8449         board[fromY][fromX] = EmptySquare;
8450         board[toY][toX] = BlackKing;
8451         board[fromY][7] = EmptySquare;
8452         board[toY][4] = BlackRook;
8453     } else if (fromY == 7 && fromX == 3
8454                && board[fromY][fromX] == BlackKing
8455                && toY == 7 && toX == 1) {
8456         board[fromY][fromX] = EmptySquare;
8457         board[toY][toX] = BlackKing;
8458         board[fromY][0] = EmptySquare;
8459         board[toY][2] = BlackRook;
8460     } else if (board[fromY][fromX] == BlackPawn
8461                && toY < promoRank
8462                && gameInfo.variant != VariantXiangqi
8463                ) {
8464         /* black pawn promotion */
8465         board[toY][toX] = CharToPiece(ToLower(promoChar));
8466         if (board[toY][toX] == EmptySquare) {
8467             board[toY][toX] = BlackQueen;
8468         }
8469         if(gameInfo.variant==VariantBughouse ||
8470            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8471             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8472         board[fromY][fromX] = EmptySquare;
8473     } else if ((fromY == 3)
8474                && (toX != fromX)
8475                && gameInfo.variant != VariantXiangqi
8476                && gameInfo.variant != VariantBerolina
8477                && (board[fromY][fromX] == BlackPawn)
8478                && (board[toY][toX] == EmptySquare)) {
8479         board[fromY][fromX] = EmptySquare;
8480         board[toY][toX] = BlackPawn;
8481         captured = board[toY + 1][toX];
8482         board[toY + 1][toX] = EmptySquare;
8483     } else if ((fromY == 3)
8484                && (toX == fromX)
8485                && gameInfo.variant == VariantBerolina
8486                && (board[fromY][fromX] == BlackPawn)
8487                && (board[toY][toX] == EmptySquare)) {
8488         board[fromY][fromX] = EmptySquare;
8489         board[toY][toX] = BlackPawn;
8490         if(oldEP & EP_BEROLIN_A) {
8491                 captured = board[fromY][fromX-1];
8492                 board[fromY][fromX-1] = EmptySquare;
8493         }else{  captured = board[fromY][fromX+1];
8494                 board[fromY][fromX+1] = EmptySquare;
8495         }
8496     } else {
8497         board[toY][toX] = board[fromY][fromX];
8498         board[fromY][fromX] = EmptySquare;
8499     }
8500   }
8501
8502     if (gameInfo.holdingsWidth != 0) {
8503
8504       /* !!A lot more code needs to be written to support holdings  */
8505       /* [HGM] OK, so I have written it. Holdings are stored in the */
8506       /* penultimate board files, so they are automaticlly stored   */
8507       /* in the game history.                                       */
8508       if (fromY == DROP_RANK) {
8509         /* Delete from holdings, by decreasing count */
8510         /* and erasing image if necessary            */
8511         p = (int) fromX;
8512         if(p < (int) BlackPawn) { /* white drop */
8513              p -= (int)WhitePawn;
8514                  p = PieceToNumber((ChessSquare)p);
8515              if(p >= gameInfo.holdingsSize) p = 0;
8516              if(--board[p][BOARD_WIDTH-2] <= 0)
8517                   board[p][BOARD_WIDTH-1] = EmptySquare;
8518              if((int)board[p][BOARD_WIDTH-2] < 0)
8519                         board[p][BOARD_WIDTH-2] = 0;
8520         } else {                  /* black drop */
8521              p -= (int)BlackPawn;
8522                  p = PieceToNumber((ChessSquare)p);
8523              if(p >= gameInfo.holdingsSize) p = 0;
8524              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8525                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8526              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8527                         board[BOARD_HEIGHT-1-p][1] = 0;
8528         }
8529       }
8530       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8531           && gameInfo.variant != VariantBughouse        ) {
8532         /* [HGM] holdings: Add to holdings, if holdings exist */
8533         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8534                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8535                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8536         }
8537         p = (int) captured;
8538         if (p >= (int) BlackPawn) {
8539           p -= (int)BlackPawn;
8540           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8541                   /* in Shogi restore piece to its original  first */
8542                   captured = (ChessSquare) (DEMOTED captured);
8543                   p = DEMOTED p;
8544           }
8545           p = PieceToNumber((ChessSquare)p);
8546           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8547           board[p][BOARD_WIDTH-2]++;
8548           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8549         } else {
8550           p -= (int)WhitePawn;
8551           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8552                   captured = (ChessSquare) (DEMOTED captured);
8553                   p = DEMOTED p;
8554           }
8555           p = PieceToNumber((ChessSquare)p);
8556           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8557           board[BOARD_HEIGHT-1-p][1]++;
8558           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8559         }
8560       }
8561     } else if (gameInfo.variant == VariantAtomic) {
8562       if (captured != EmptySquare) {
8563         int y, x;
8564         for (y = toY-1; y <= toY+1; y++) {
8565           for (x = toX-1; x <= toX+1; x++) {
8566             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8567                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8568               board[y][x] = EmptySquare;
8569             }
8570           }
8571         }
8572         board[toY][toX] = EmptySquare;
8573       }
8574     }
8575     if(promoChar == '+') {
8576         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8577         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8578     } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar
8579         board[toY][toX] = CharToPiece(promoChar);
8580     }
8581     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8582                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8583         // [HGM] superchess: take promotion piece out of holdings
8584         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8585         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8586             if(!--board[k][BOARD_WIDTH-2])
8587                 board[k][BOARD_WIDTH-1] = EmptySquare;
8588         } else {
8589             if(!--board[BOARD_HEIGHT-1-k][1])
8590                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8591         }
8592     }
8593
8594 }
8595
8596 /* Updates forwardMostMove */
8597 void
8598 MakeMove(fromX, fromY, toX, toY, promoChar)
8599      int fromX, fromY, toX, toY;
8600      int promoChar;
8601 {
8602 //    forwardMostMove++; // [HGM] bare: moved downstream
8603
8604     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8605         int timeLeft; static int lastLoadFlag=0; int king, piece;
8606         piece = boards[forwardMostMove][fromY][fromX];
8607         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8608         if(gameInfo.variant == VariantKnightmate)
8609             king += (int) WhiteUnicorn - (int) WhiteKing;
8610         if(forwardMostMove == 0) {
8611             if(blackPlaysFirst)
8612                 fprintf(serverMoves, "%s;", second.tidy);
8613             fprintf(serverMoves, "%s;", first.tidy);
8614             if(!blackPlaysFirst)
8615                 fprintf(serverMoves, "%s;", second.tidy);
8616         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8617         lastLoadFlag = loadFlag;
8618         // print base move
8619         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8620         // print castling suffix
8621         if( toY == fromY && piece == king ) {
8622             if(toX-fromX > 1)
8623                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8624             if(fromX-toX >1)
8625                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8626         }
8627         // e.p. suffix
8628         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8629              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8630              boards[forwardMostMove][toY][toX] == EmptySquare
8631              && fromX != toX && fromY != toY)
8632                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8633         // promotion suffix
8634         if(promoChar != NULLCHAR)
8635                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8636         if(!loadFlag) {
8637             fprintf(serverMoves, "/%d/%d",
8638                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8639             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8640             else                      timeLeft = blackTimeRemaining/1000;
8641             fprintf(serverMoves, "/%d", timeLeft);
8642         }
8643         fflush(serverMoves);
8644     }
8645
8646     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8647       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8648                         0, 1);
8649       return;
8650     }
8651     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8652     if (commentList[forwardMostMove+1] != NULL) {
8653         free(commentList[forwardMostMove+1]);
8654         commentList[forwardMostMove+1] = NULL;
8655     }
8656     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8657     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8658     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8659     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8660     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8661     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8662     gameInfo.result = GameUnfinished;
8663     if (gameInfo.resultDetails != NULL) {
8664         free(gameInfo.resultDetails);
8665         gameInfo.resultDetails = NULL;
8666     }
8667     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8668                               moveList[forwardMostMove - 1]);
8669     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8670                              PosFlags(forwardMostMove - 1),
8671                              fromY, fromX, toY, toX, promoChar,
8672                              parseList[forwardMostMove - 1]);
8673     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8674       case MT_NONE:
8675       case MT_STALEMATE:
8676       default:
8677         break;
8678       case MT_CHECK:
8679         if(gameInfo.variant != VariantShogi)
8680             strcat(parseList[forwardMostMove - 1], "+");
8681         break;
8682       case MT_CHECKMATE:
8683       case MT_STAINMATE:
8684         strcat(parseList[forwardMostMove - 1], "#");
8685         break;
8686     }
8687     if (appData.debugMode) {
8688         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8689     }
8690
8691 }
8692
8693 /* Updates currentMove if not pausing */
8694 void
8695 ShowMove(fromX, fromY, toX, toY)
8696 {
8697     int instant = (gameMode == PlayFromGameFile) ?
8698         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8699     if(appData.noGUI) return;
8700     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8701         if (!instant) {
8702             if (forwardMostMove == currentMove + 1) {
8703                 AnimateMove(boards[forwardMostMove - 1],
8704                             fromX, fromY, toX, toY);
8705             }
8706             if (appData.highlightLastMove) {
8707                 SetHighlights(fromX, fromY, toX, toY);
8708             }
8709         }
8710         currentMove = forwardMostMove;
8711     }
8712
8713     if (instant) return;
8714
8715     DisplayMove(currentMove - 1);
8716     DrawPosition(FALSE, boards[currentMove]);
8717     DisplayBothClocks();
8718     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8719 }
8720
8721 void SendEgtPath(ChessProgramState *cps)
8722 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8723         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8724
8725         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8726
8727         while(*p) {
8728             char c, *q = name+1, *r, *s;
8729
8730             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8731             while(*p && *p != ',') *q++ = *p++;
8732             *q++ = ':'; *q = 0;
8733             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8734                 strcmp(name, ",nalimov:") == 0 ) {
8735                 // take nalimov path from the menu-changeable option first, if it is defined
8736               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8737                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8738             } else
8739             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8740                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8741                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8742                 s = r = StrStr(s, ":") + 1; // beginning of path info
8743                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8744                 c = *r; *r = 0;             // temporarily null-terminate path info
8745                     *--q = 0;               // strip of trailig ':' from name
8746                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8747                 *r = c;
8748                 SendToProgram(buf,cps);     // send egtbpath command for this format
8749             }
8750             if(*p == ',') p++; // read away comma to position for next format name
8751         }
8752 }
8753
8754 void
8755 InitChessProgram(cps, setup)
8756      ChessProgramState *cps;
8757      int setup; /* [HGM] needed to setup FRC opening position */
8758 {
8759     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8760     if (appData.noChessProgram) return;
8761     hintRequested = FALSE;
8762     bookRequested = FALSE;
8763
8764     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8765     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8766     if(cps->memSize) { /* [HGM] memory */
8767       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8768         SendToProgram(buf, cps);
8769     }
8770     SendEgtPath(cps); /* [HGM] EGT */
8771     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8772       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8773         SendToProgram(buf, cps);
8774     }
8775
8776     SendToProgram(cps->initString, cps);
8777     if (gameInfo.variant != VariantNormal &&
8778         gameInfo.variant != VariantLoadable
8779         /* [HGM] also send variant if board size non-standard */
8780         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8781                                             ) {
8782       char *v = VariantName(gameInfo.variant);
8783       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8784         /* [HGM] in protocol 1 we have to assume all variants valid */
8785         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8786         DisplayFatalError(buf, 0, 1);
8787         return;
8788       }
8789
8790       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8791       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8792       if( gameInfo.variant == VariantXiangqi )
8793            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8794       if( gameInfo.variant == VariantShogi )
8795            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8796       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8797            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8798       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8799                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8800            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8801       if( gameInfo.variant == VariantCourier )
8802            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8803       if( gameInfo.variant == VariantSuper )
8804            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8805       if( gameInfo.variant == VariantGreat )
8806            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8807
8808       if(overruled) {
8809         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8810                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8811            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8812            if(StrStr(cps->variants, b) == NULL) {
8813                // specific sized variant not known, check if general sizing allowed
8814                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8815                    if(StrStr(cps->variants, "boardsize") == NULL) {
8816                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8817                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8818                        DisplayFatalError(buf, 0, 1);
8819                        return;
8820                    }
8821                    /* [HGM] here we really should compare with the maximum supported board size */
8822                }
8823            }
8824       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8825       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8826       SendToProgram(buf, cps);
8827     }
8828     currentlyInitializedVariant = gameInfo.variant;
8829
8830     /* [HGM] send opening position in FRC to first engine */
8831     if(setup) {
8832           SendToProgram("force\n", cps);
8833           SendBoard(cps, 0);
8834           /* engine is now in force mode! Set flag to wake it up after first move. */
8835           setboardSpoiledMachineBlack = 1;
8836     }
8837
8838     if (cps->sendICS) {
8839       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8840       SendToProgram(buf, cps);
8841     }
8842     cps->maybeThinking = FALSE;
8843     cps->offeredDraw = 0;
8844     if (!appData.icsActive) {
8845         SendTimeControl(cps, movesPerSession, timeControl,
8846                         timeIncrement, appData.searchDepth,
8847                         searchTime);
8848     }
8849     if (appData.showThinking
8850         // [HGM] thinking: four options require thinking output to be sent
8851         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8852                                 ) {
8853         SendToProgram("post\n", cps);
8854     }
8855     SendToProgram("hard\n", cps);
8856     if (!appData.ponderNextMove) {
8857         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8858            it without being sure what state we are in first.  "hard"
8859            is not a toggle, so that one is OK.
8860          */
8861         SendToProgram("easy\n", cps);
8862     }
8863     if (cps->usePing) {
8864       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8865       SendToProgram(buf, cps);
8866     }
8867     cps->initDone = TRUE;
8868 }
8869
8870
8871 void
8872 StartChessProgram(cps)
8873      ChessProgramState *cps;
8874 {
8875     char buf[MSG_SIZ];
8876     int err;
8877
8878     if (appData.noChessProgram) return;
8879     cps->initDone = FALSE;
8880
8881     if (strcmp(cps->host, "localhost") == 0) {
8882         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8883     } else if (*appData.remoteShell == NULLCHAR) {
8884         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8885     } else {
8886         if (*appData.remoteUser == NULLCHAR) {
8887           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8888                     cps->program);
8889         } else {
8890           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8891                     cps->host, appData.remoteUser, cps->program);
8892         }
8893         err = StartChildProcess(buf, "", &cps->pr);
8894     }
8895
8896     if (err != 0) {
8897       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8898         DisplayFatalError(buf, err, 1);
8899         cps->pr = NoProc;
8900         cps->isr = NULL;
8901         return;
8902     }
8903
8904     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8905     if (cps->protocolVersion > 1) {
8906       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
8907       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8908       cps->comboCnt = 0;  //                and values of combo boxes
8909       SendToProgram(buf, cps);
8910     } else {
8911       SendToProgram("xboard\n", cps);
8912     }
8913 }
8914
8915
8916 void
8917 TwoMachinesEventIfReady P((void))
8918 {
8919   if (first.lastPing != first.lastPong) {
8920     DisplayMessage("", _("Waiting for first chess program"));
8921     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8922     return;
8923   }
8924   if (second.lastPing != second.lastPong) {
8925     DisplayMessage("", _("Waiting for second chess program"));
8926     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8927     return;
8928   }
8929   ThawUI();
8930   TwoMachinesEvent();
8931 }
8932
8933 void
8934 NextMatchGame P((void))
8935 {
8936     int index; /* [HGM] autoinc: step load index during match */
8937     Reset(FALSE, TRUE);
8938     if (*appData.loadGameFile != NULLCHAR) {
8939         index = appData.loadGameIndex;
8940         if(index < 0) { // [HGM] autoinc
8941             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8942             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8943         }
8944         LoadGameFromFile(appData.loadGameFile,
8945                          index,
8946                          appData.loadGameFile, FALSE);
8947     } else if (*appData.loadPositionFile != NULLCHAR) {
8948         index = appData.loadPositionIndex;
8949         if(index < 0) { // [HGM] autoinc
8950             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8951             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8952         }
8953         LoadPositionFromFile(appData.loadPositionFile,
8954                              index,
8955                              appData.loadPositionFile);
8956     }
8957     TwoMachinesEventIfReady();
8958 }
8959
8960 void UserAdjudicationEvent( int result )
8961 {
8962     ChessMove gameResult = GameIsDrawn;
8963
8964     if( result > 0 ) {
8965         gameResult = WhiteWins;
8966     }
8967     else if( result < 0 ) {
8968         gameResult = BlackWins;
8969     }
8970
8971     if( gameMode == TwoMachinesPlay ) {
8972         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8973     }
8974 }
8975
8976
8977 // [HGM] save: calculate checksum of game to make games easily identifiable
8978 int StringCheckSum(char *s)
8979 {
8980         int i = 0;
8981         if(s==NULL) return 0;
8982         while(*s) i = i*259 + *s++;
8983         return i;
8984 }
8985
8986 int GameCheckSum()
8987 {
8988         int i, sum=0;
8989         for(i=backwardMostMove; i<forwardMostMove; i++) {
8990                 sum += pvInfoList[i].depth;
8991                 sum += StringCheckSum(parseList[i]);
8992                 sum += StringCheckSum(commentList[i]);
8993                 sum *= 261;
8994         }
8995         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8996         return sum + StringCheckSum(commentList[i]);
8997 } // end of save patch
8998
8999 void
9000 GameEnds(result, resultDetails, whosays)
9001      ChessMove result;
9002      char *resultDetails;
9003      int whosays;
9004 {
9005     GameMode nextGameMode;
9006     int isIcsGame;
9007     char buf[MSG_SIZ], popupRequested = 0;
9008
9009     if(endingGame) return; /* [HGM] crash: forbid recursion */
9010     endingGame = 1;
9011     if(twoBoards) { // [HGM] dual: switch back to one board
9012         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9013         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9014     }
9015     if (appData.debugMode) {
9016       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9017               result, resultDetails ? resultDetails : "(null)", whosays);
9018     }
9019
9020     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9021
9022     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9023         /* If we are playing on ICS, the server decides when the
9024            game is over, but the engine can offer to draw, claim
9025            a draw, or resign.
9026          */
9027 #if ZIPPY
9028         if (appData.zippyPlay && first.initDone) {
9029             if (result == GameIsDrawn) {
9030                 /* In case draw still needs to be claimed */
9031                 SendToICS(ics_prefix);
9032                 SendToICS("draw\n");
9033             } else if (StrCaseStr(resultDetails, "resign")) {
9034                 SendToICS(ics_prefix);
9035                 SendToICS("resign\n");
9036             }
9037         }
9038 #endif
9039         endingGame = 0; /* [HGM] crash */
9040         return;
9041     }
9042
9043     /* If we're loading the game from a file, stop */
9044     if (whosays == GE_FILE) {
9045       (void) StopLoadGameTimer();
9046       gameFileFP = NULL;
9047     }
9048
9049     /* Cancel draw offers */
9050     first.offeredDraw = second.offeredDraw = 0;
9051
9052     /* If this is an ICS game, only ICS can really say it's done;
9053        if not, anyone can. */
9054     isIcsGame = (gameMode == IcsPlayingWhite ||
9055                  gameMode == IcsPlayingBlack ||
9056                  gameMode == IcsObserving    ||
9057                  gameMode == IcsExamining);
9058
9059     if (!isIcsGame || whosays == GE_ICS) {
9060         /* OK -- not an ICS game, or ICS said it was done */
9061         StopClocks();
9062         if (!isIcsGame && !appData.noChessProgram)
9063           SetUserThinkingEnables();
9064
9065         /* [HGM] if a machine claims the game end we verify this claim */
9066         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9067             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9068                 char claimer;
9069                 ChessMove trueResult = (ChessMove) -1;
9070
9071                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9072                                             first.twoMachinesColor[0] :
9073                                             second.twoMachinesColor[0] ;
9074
9075                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9076                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9077                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9078                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9079                 } else
9080                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9081                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9082                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9083                 } else
9084                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9085                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9086                 }
9087
9088                 // now verify win claims, but not in drop games, as we don't understand those yet
9089                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9090                                                  || gameInfo.variant == VariantGreat) &&
9091                     (result == WhiteWins && claimer == 'w' ||
9092                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9093                       if (appData.debugMode) {
9094                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9095                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9096                       }
9097                       if(result != trueResult) {
9098                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9099                               result = claimer == 'w' ? BlackWins : WhiteWins;
9100                               resultDetails = buf;
9101                       }
9102                 } else
9103                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9104                     && (forwardMostMove <= backwardMostMove ||
9105                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9106                         (claimer=='b')==(forwardMostMove&1))
9107                                                                                   ) {
9108                       /* [HGM] verify: draws that were not flagged are false claims */
9109                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9110                       result = claimer == 'w' ? BlackWins : WhiteWins;
9111                       resultDetails = buf;
9112                 }
9113                 /* (Claiming a loss is accepted no questions asked!) */
9114             }
9115             /* [HGM] bare: don't allow bare King to win */
9116             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9117                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9118                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9119                && result != GameIsDrawn)
9120             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9121                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9122                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9123                         if(p >= 0 && p <= (int)WhiteKing) k++;
9124                 }
9125                 if (appData.debugMode) {
9126                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9127                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9128                 }
9129                 if(k <= 1) {
9130                         result = GameIsDrawn;
9131                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9132                         resultDetails = buf;
9133                 }
9134             }
9135         }
9136
9137
9138         if(serverMoves != NULL && !loadFlag) { char c = '=';
9139             if(result==WhiteWins) c = '+';
9140             if(result==BlackWins) c = '-';
9141             if(resultDetails != NULL)
9142                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9143         }
9144         if (resultDetails != NULL) {
9145             gameInfo.result = result;
9146             gameInfo.resultDetails = StrSave(resultDetails);
9147
9148             /* display last move only if game was not loaded from file */
9149             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9150                 DisplayMove(currentMove - 1);
9151
9152             if (forwardMostMove != 0) {
9153                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9154                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9155                                                                 ) {
9156                     if (*appData.saveGameFile != NULLCHAR) {
9157                         SaveGameToFile(appData.saveGameFile, TRUE);
9158                     } else if (appData.autoSaveGames) {
9159                         AutoSaveGame();
9160                     }
9161                     if (*appData.savePositionFile != NULLCHAR) {
9162                         SavePositionToFile(appData.savePositionFile);
9163                     }
9164                 }
9165             }
9166
9167             /* Tell program how game ended in case it is learning */
9168             /* [HGM] Moved this to after saving the PGN, just in case */
9169             /* engine died and we got here through time loss. In that */
9170             /* case we will get a fatal error writing the pipe, which */
9171             /* would otherwise lose us the PGN.                       */
9172             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9173             /* output during GameEnds should never be fatal anymore   */
9174             if (gameMode == MachinePlaysWhite ||
9175                 gameMode == MachinePlaysBlack ||
9176                 gameMode == TwoMachinesPlay ||
9177                 gameMode == IcsPlayingWhite ||
9178                 gameMode == IcsPlayingBlack ||
9179                 gameMode == BeginningOfGame) {
9180                 char buf[MSG_SIZ];
9181                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9182                         resultDetails);
9183                 if (first.pr != NoProc) {
9184                     SendToProgram(buf, &first);
9185                 }
9186                 if (second.pr != NoProc &&
9187                     gameMode == TwoMachinesPlay) {
9188                     SendToProgram(buf, &second);
9189                 }
9190             }
9191         }
9192
9193         if (appData.icsActive) {
9194             if (appData.quietPlay &&
9195                 (gameMode == IcsPlayingWhite ||
9196                  gameMode == IcsPlayingBlack)) {
9197                 SendToICS(ics_prefix);
9198                 SendToICS("set shout 1\n");
9199             }
9200             nextGameMode = IcsIdle;
9201             ics_user_moved = FALSE;
9202             /* clean up premove.  It's ugly when the game has ended and the
9203              * premove highlights are still on the board.
9204              */
9205             if (gotPremove) {
9206               gotPremove = FALSE;
9207               ClearPremoveHighlights();
9208               DrawPosition(FALSE, boards[currentMove]);
9209             }
9210             if (whosays == GE_ICS) {
9211                 switch (result) {
9212                 case WhiteWins:
9213                     if (gameMode == IcsPlayingWhite)
9214                         PlayIcsWinSound();
9215                     else if(gameMode == IcsPlayingBlack)
9216                         PlayIcsLossSound();
9217                     break;
9218                 case BlackWins:
9219                     if (gameMode == IcsPlayingBlack)
9220                         PlayIcsWinSound();
9221                     else if(gameMode == IcsPlayingWhite)
9222                         PlayIcsLossSound();
9223                     break;
9224                 case GameIsDrawn:
9225                     PlayIcsDrawSound();
9226                     break;
9227                 default:
9228                     PlayIcsUnfinishedSound();
9229                 }
9230             }
9231         } else if (gameMode == EditGame ||
9232                    gameMode == PlayFromGameFile ||
9233                    gameMode == AnalyzeMode ||
9234                    gameMode == AnalyzeFile) {
9235             nextGameMode = gameMode;
9236         } else {
9237             nextGameMode = EndOfGame;
9238         }
9239         pausing = FALSE;
9240         ModeHighlight();
9241     } else {
9242         nextGameMode = gameMode;
9243     }
9244
9245     if (appData.noChessProgram) {
9246         gameMode = nextGameMode;
9247         ModeHighlight();
9248         endingGame = 0; /* [HGM] crash */
9249         return;
9250     }
9251
9252     if (first.reuse) {
9253         /* Put first chess program into idle state */
9254         if (first.pr != NoProc &&
9255             (gameMode == MachinePlaysWhite ||
9256              gameMode == MachinePlaysBlack ||
9257              gameMode == TwoMachinesPlay ||
9258              gameMode == IcsPlayingWhite ||
9259              gameMode == IcsPlayingBlack ||
9260              gameMode == BeginningOfGame)) {
9261             SendToProgram("force\n", &first);
9262             if (first.usePing) {
9263               char buf[MSG_SIZ];
9264               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9265               SendToProgram(buf, &first);
9266             }
9267         }
9268     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9269         /* Kill off first chess program */
9270         if (first.isr != NULL)
9271           RemoveInputSource(first.isr);
9272         first.isr = NULL;
9273
9274         if (first.pr != NoProc) {
9275             ExitAnalyzeMode();
9276             DoSleep( appData.delayBeforeQuit );
9277             SendToProgram("quit\n", &first);
9278             DoSleep( appData.delayAfterQuit );
9279             DestroyChildProcess(first.pr, first.useSigterm);
9280         }
9281         first.pr = NoProc;
9282     }
9283     if (second.reuse) {
9284         /* Put second chess program into idle state */
9285         if (second.pr != NoProc &&
9286             gameMode == TwoMachinesPlay) {
9287             SendToProgram("force\n", &second);
9288             if (second.usePing) {
9289               char buf[MSG_SIZ];
9290               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9291               SendToProgram(buf, &second);
9292             }
9293         }
9294     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9295         /* Kill off second chess program */
9296         if (second.isr != NULL)
9297           RemoveInputSource(second.isr);
9298         second.isr = NULL;
9299
9300         if (second.pr != NoProc) {
9301             DoSleep( appData.delayBeforeQuit );
9302             SendToProgram("quit\n", &second);
9303             DoSleep( appData.delayAfterQuit );
9304             DestroyChildProcess(second.pr, second.useSigterm);
9305         }
9306         second.pr = NoProc;
9307     }
9308
9309     if (matchMode && gameMode == TwoMachinesPlay) {
9310         switch (result) {
9311         case WhiteWins:
9312           if (first.twoMachinesColor[0] == 'w') {
9313             first.matchWins++;
9314           } else {
9315             second.matchWins++;
9316           }
9317           break;
9318         case BlackWins:
9319           if (first.twoMachinesColor[0] == 'b') {
9320             first.matchWins++;
9321           } else {
9322             second.matchWins++;
9323           }
9324           break;
9325         default:
9326           break;
9327         }
9328         if (matchGame < appData.matchGames) {
9329             char *tmp;
9330             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9331                 tmp = first.twoMachinesColor;
9332                 first.twoMachinesColor = second.twoMachinesColor;
9333                 second.twoMachinesColor = tmp;
9334             }
9335             gameMode = nextGameMode;
9336             matchGame++;
9337             if(appData.matchPause>10000 || appData.matchPause<10)
9338                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9339             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9340             endingGame = 0; /* [HGM] crash */
9341             return;
9342         } else {
9343             gameMode = nextGameMode;
9344             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9345                      first.tidy, second.tidy,
9346                      first.matchWins, second.matchWins,
9347                      appData.matchGames - (first.matchWins + second.matchWins));
9348             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9349         }
9350     }
9351     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9352         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9353       ExitAnalyzeMode();
9354     gameMode = nextGameMode;
9355     ModeHighlight();
9356     endingGame = 0;  /* [HGM] crash */
9357     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9358       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9359         matchMode = FALSE; appData.matchGames = matchGame = 0;
9360         DisplayNote(buf);
9361       }
9362     }
9363 }
9364
9365 /* Assumes program was just initialized (initString sent).
9366    Leaves program in force mode. */
9367 void
9368 FeedMovesToProgram(cps, upto)
9369      ChessProgramState *cps;
9370      int upto;
9371 {
9372     int i;
9373
9374     if (appData.debugMode)
9375       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9376               startedFromSetupPosition ? "position and " : "",
9377               backwardMostMove, upto, cps->which);
9378     if(currentlyInitializedVariant != gameInfo.variant) {
9379       char buf[MSG_SIZ];
9380         // [HGM] variantswitch: make engine aware of new variant
9381         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9382                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9383         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9384         SendToProgram(buf, cps);
9385         currentlyInitializedVariant = gameInfo.variant;
9386     }
9387     SendToProgram("force\n", cps);
9388     if (startedFromSetupPosition) {
9389         SendBoard(cps, backwardMostMove);
9390     if (appData.debugMode) {
9391         fprintf(debugFP, "feedMoves\n");
9392     }
9393     }
9394     for (i = backwardMostMove; i < upto; i++) {
9395         SendMoveToProgram(i, cps);
9396     }
9397 }
9398
9399
9400 void
9401 ResurrectChessProgram()
9402 {
9403      /* The chess program may have exited.
9404         If so, restart it and feed it all the moves made so far. */
9405
9406     if (appData.noChessProgram || first.pr != NoProc) return;
9407
9408     StartChessProgram(&first);
9409     InitChessProgram(&first, FALSE);
9410     FeedMovesToProgram(&first, currentMove);
9411
9412     if (!first.sendTime) {
9413         /* can't tell gnuchess what its clock should read,
9414            so we bow to its notion. */
9415         ResetClocks();
9416         timeRemaining[0][currentMove] = whiteTimeRemaining;
9417         timeRemaining[1][currentMove] = blackTimeRemaining;
9418     }
9419
9420     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9421                 appData.icsEngineAnalyze) && first.analysisSupport) {
9422       SendToProgram("analyze\n", &first);
9423       first.analyzing = TRUE;
9424     }
9425 }
9426
9427 /*
9428  * Button procedures
9429  */
9430 void
9431 Reset(redraw, init)
9432      int redraw, init;
9433 {
9434     int i;
9435
9436     if (appData.debugMode) {
9437         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9438                 redraw, init, gameMode);
9439     }
9440     CleanupTail(); // [HGM] vari: delete any stored variations
9441     pausing = pauseExamInvalid = FALSE;
9442     startedFromSetupPosition = blackPlaysFirst = FALSE;
9443     firstMove = TRUE;
9444     whiteFlag = blackFlag = FALSE;
9445     userOfferedDraw = FALSE;
9446     hintRequested = bookRequested = FALSE;
9447     first.maybeThinking = FALSE;
9448     second.maybeThinking = FALSE;
9449     first.bookSuspend = FALSE; // [HGM] book
9450     second.bookSuspend = FALSE;
9451     thinkOutput[0] = NULLCHAR;
9452     lastHint[0] = NULLCHAR;
9453     ClearGameInfo(&gameInfo);
9454     gameInfo.variant = StringToVariant(appData.variant);
9455     ics_user_moved = ics_clock_paused = FALSE;
9456     ics_getting_history = H_FALSE;
9457     ics_gamenum = -1;
9458     white_holding[0] = black_holding[0] = NULLCHAR;
9459     ClearProgramStats();
9460     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9461
9462     ResetFrontEnd();
9463     ClearHighlights();
9464     flipView = appData.flipView;
9465     ClearPremoveHighlights();
9466     gotPremove = FALSE;
9467     alarmSounded = FALSE;
9468
9469     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9470     if(appData.serverMovesName != NULL) {
9471         /* [HGM] prepare to make moves file for broadcasting */
9472         clock_t t = clock();
9473         if(serverMoves != NULL) fclose(serverMoves);
9474         serverMoves = fopen(appData.serverMovesName, "r");
9475         if(serverMoves != NULL) {
9476             fclose(serverMoves);
9477             /* delay 15 sec before overwriting, so all clients can see end */
9478             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9479         }
9480         serverMoves = fopen(appData.serverMovesName, "w");
9481     }
9482
9483     ExitAnalyzeMode();
9484     gameMode = BeginningOfGame;
9485     ModeHighlight();
9486     if(appData.icsActive) gameInfo.variant = VariantNormal;
9487     currentMove = forwardMostMove = backwardMostMove = 0;
9488     InitPosition(redraw);
9489     for (i = 0; i < MAX_MOVES; i++) {
9490         if (commentList[i] != NULL) {
9491             free(commentList[i]);
9492             commentList[i] = NULL;
9493         }
9494     }
9495     ResetClocks();
9496     timeRemaining[0][0] = whiteTimeRemaining;
9497     timeRemaining[1][0] = blackTimeRemaining;
9498     if (first.pr == NULL) {
9499         StartChessProgram(&first);
9500     }
9501     if (init) {
9502             InitChessProgram(&first, startedFromSetupPosition);
9503     }
9504     DisplayTitle("");
9505     DisplayMessage("", "");
9506     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9507     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9508 }
9509
9510 void
9511 AutoPlayGameLoop()
9512 {
9513     for (;;) {
9514         if (!AutoPlayOneMove())
9515           return;
9516         if (matchMode || appData.timeDelay == 0)
9517           continue;
9518         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9519           return;
9520         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9521         break;
9522     }
9523 }
9524
9525
9526 int
9527 AutoPlayOneMove()
9528 {
9529     int fromX, fromY, toX, toY;
9530
9531     if (appData.debugMode) {
9532       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9533     }
9534
9535     if (gameMode != PlayFromGameFile)
9536       return FALSE;
9537
9538     if (currentMove >= forwardMostMove) {
9539       gameMode = EditGame;
9540       ModeHighlight();
9541
9542       /* [AS] Clear current move marker at the end of a game */
9543       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9544
9545       return FALSE;
9546     }
9547
9548     toX = moveList[currentMove][2] - AAA;
9549     toY = moveList[currentMove][3] - ONE;
9550
9551     if (moveList[currentMove][1] == '@') {
9552         if (appData.highlightLastMove) {
9553             SetHighlights(-1, -1, toX, toY);
9554         }
9555     } else {
9556         fromX = moveList[currentMove][0] - AAA;
9557         fromY = moveList[currentMove][1] - ONE;
9558
9559         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9560
9561         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9562
9563         if (appData.highlightLastMove) {
9564             SetHighlights(fromX, fromY, toX, toY);
9565         }
9566     }
9567     DisplayMove(currentMove);
9568     SendMoveToProgram(currentMove++, &first);
9569     DisplayBothClocks();
9570     DrawPosition(FALSE, boards[currentMove]);
9571     // [HGM] PV info: always display, routine tests if empty
9572     DisplayComment(currentMove - 1, commentList[currentMove]);
9573     return TRUE;
9574 }
9575
9576
9577 int
9578 LoadGameOneMove(readAhead)
9579      ChessMove readAhead;
9580 {
9581     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9582     char promoChar = NULLCHAR;
9583     ChessMove moveType;
9584     char move[MSG_SIZ];
9585     char *p, *q;
9586
9587     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9588         gameMode != AnalyzeMode && gameMode != Training) {
9589         gameFileFP = NULL;
9590         return FALSE;
9591     }
9592
9593     yyboardindex = forwardMostMove;
9594     if (readAhead != (ChessMove)0) {
9595       moveType = readAhead;
9596     } else {
9597       if (gameFileFP == NULL)
9598           return FALSE;
9599       moveType = (ChessMove) yylex();
9600     }
9601
9602     done = FALSE;
9603     switch (moveType) {
9604       case Comment:
9605         if (appData.debugMode)
9606           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9607         p = yy_text;
9608
9609         /* append the comment but don't display it */
9610         AppendComment(currentMove, p, FALSE);
9611         return TRUE;
9612
9613       case WhiteCapturesEnPassant:
9614       case BlackCapturesEnPassant:
9615       case WhitePromotion:
9616       case BlackPromotion:
9617       case WhiteNonPromotion:
9618       case BlackNonPromotion:
9619       case NormalMove:
9620       case WhiteKingSideCastle:
9621       case WhiteQueenSideCastle:
9622       case BlackKingSideCastle:
9623       case BlackQueenSideCastle:
9624       case WhiteKingSideCastleWild:
9625       case WhiteQueenSideCastleWild:
9626       case BlackKingSideCastleWild:
9627       case BlackQueenSideCastleWild:
9628       /* PUSH Fabien */
9629       case WhiteHSideCastleFR:
9630       case WhiteASideCastleFR:
9631       case BlackHSideCastleFR:
9632       case BlackASideCastleFR:
9633       /* POP Fabien */
9634         if (appData.debugMode)
9635           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9636         fromX = currentMoveString[0] - AAA;
9637         fromY = currentMoveString[1] - ONE;
9638         toX = currentMoveString[2] - AAA;
9639         toY = currentMoveString[3] - ONE;
9640         promoChar = currentMoveString[4];
9641         break;
9642
9643       case WhiteDrop:
9644       case BlackDrop:
9645         if (appData.debugMode)
9646           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9647         fromX = moveType == WhiteDrop ?
9648           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9649         (int) CharToPiece(ToLower(currentMoveString[0]));
9650         fromY = DROP_RANK;
9651         toX = currentMoveString[2] - AAA;
9652         toY = currentMoveString[3] - ONE;
9653         break;
9654
9655       case WhiteWins:
9656       case BlackWins:
9657       case GameIsDrawn:
9658       case GameUnfinished:
9659         if (appData.debugMode)
9660           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9661         p = strchr(yy_text, '{');
9662         if (p == NULL) p = strchr(yy_text, '(');
9663         if (p == NULL) {
9664             p = yy_text;
9665             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9666         } else {
9667             q = strchr(p, *p == '{' ? '}' : ')');
9668             if (q != NULL) *q = NULLCHAR;
9669             p++;
9670         }
9671         GameEnds(moveType, p, GE_FILE);
9672         done = TRUE;
9673         if (cmailMsgLoaded) {
9674             ClearHighlights();
9675             flipView = WhiteOnMove(currentMove);
9676             if (moveType == GameUnfinished) flipView = !flipView;
9677             if (appData.debugMode)
9678               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9679         }
9680         break;
9681
9682       case (ChessMove) 0:       /* end of file */
9683         if (appData.debugMode)
9684           fprintf(debugFP, "Parser hit end of file\n");
9685         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9686           case MT_NONE:
9687           case MT_CHECK:
9688             break;
9689           case MT_CHECKMATE:
9690           case MT_STAINMATE:
9691             if (WhiteOnMove(currentMove)) {
9692                 GameEnds(BlackWins, "Black mates", GE_FILE);
9693             } else {
9694                 GameEnds(WhiteWins, "White mates", GE_FILE);
9695             }
9696             break;
9697           case MT_STALEMATE:
9698             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9699             break;
9700         }
9701         done = TRUE;
9702         break;
9703
9704       case MoveNumberOne:
9705         if (lastLoadGameStart == GNUChessGame) {
9706             /* GNUChessGames have numbers, but they aren't move numbers */
9707             if (appData.debugMode)
9708               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9709                       yy_text, (int) moveType);
9710             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9711         }
9712         /* else fall thru */
9713
9714       case XBoardGame:
9715       case GNUChessGame:
9716       case PGNTag:
9717         /* Reached start of next game in file */
9718         if (appData.debugMode)
9719           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9720         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9721           case MT_NONE:
9722           case MT_CHECK:
9723             break;
9724           case MT_CHECKMATE:
9725           case MT_STAINMATE:
9726             if (WhiteOnMove(currentMove)) {
9727                 GameEnds(BlackWins, "Black mates", GE_FILE);
9728             } else {
9729                 GameEnds(WhiteWins, "White mates", GE_FILE);
9730             }
9731             break;
9732           case MT_STALEMATE:
9733             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9734             break;
9735         }
9736         done = TRUE;
9737         break;
9738
9739       case PositionDiagram:     /* should not happen; ignore */
9740       case ElapsedTime:         /* ignore */
9741       case NAG:                 /* ignore */
9742         if (appData.debugMode)
9743           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9744                   yy_text, (int) moveType);
9745         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9746
9747       case IllegalMove:
9748         if (appData.testLegality) {
9749             if (appData.debugMode)
9750               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9751             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9752                     (forwardMostMove / 2) + 1,
9753                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9754             DisplayError(move, 0);
9755             done = TRUE;
9756         } else {
9757             if (appData.debugMode)
9758               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9759                       yy_text, currentMoveString);
9760             fromX = currentMoveString[0] - AAA;
9761             fromY = currentMoveString[1] - ONE;
9762             toX = currentMoveString[2] - AAA;
9763             toY = currentMoveString[3] - ONE;
9764             promoChar = currentMoveString[4];
9765         }
9766         break;
9767
9768       case AmbiguousMove:
9769         if (appData.debugMode)
9770           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9771         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9772                 (forwardMostMove / 2) + 1,
9773                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9774         DisplayError(move, 0);
9775         done = TRUE;
9776         break;
9777
9778       default:
9779       case ImpossibleMove:
9780         if (appData.debugMode)
9781           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9782         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9783                 (forwardMostMove / 2) + 1,
9784                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9785         DisplayError(move, 0);
9786         done = TRUE;
9787         break;
9788     }
9789
9790     if (done) {
9791         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9792             DrawPosition(FALSE, boards[currentMove]);
9793             DisplayBothClocks();
9794             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9795               DisplayComment(currentMove - 1, commentList[currentMove]);
9796         }
9797         (void) StopLoadGameTimer();
9798         gameFileFP = NULL;
9799         cmailOldMove = forwardMostMove;
9800         return FALSE;
9801     } else {
9802         /* currentMoveString is set as a side-effect of yylex */
9803         strcat(currentMoveString, "\n");
9804         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9805
9806         thinkOutput[0] = NULLCHAR;
9807         MakeMove(fromX, fromY, toX, toY, promoChar);
9808         currentMove = forwardMostMove;
9809         return TRUE;
9810     }
9811 }
9812
9813 /* Load the nth game from the given file */
9814 int
9815 LoadGameFromFile(filename, n, title, useList)
9816      char *filename;
9817      int n;
9818      char *title;
9819      /*Boolean*/ int useList;
9820 {
9821     FILE *f;
9822     char buf[MSG_SIZ];
9823
9824     if (strcmp(filename, "-") == 0) {
9825         f = stdin;
9826         title = "stdin";
9827     } else {
9828         f = fopen(filename, "rb");
9829         if (f == NULL) {
9830           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9831             DisplayError(buf, errno);
9832             return FALSE;
9833         }
9834     }
9835     if (fseek(f, 0, 0) == -1) {
9836         /* f is not seekable; probably a pipe */
9837         useList = FALSE;
9838     }
9839     if (useList && n == 0) {
9840         int error = GameListBuild(f);
9841         if (error) {
9842             DisplayError(_("Cannot build game list"), error);
9843         } else if (!ListEmpty(&gameList) &&
9844                    ((ListGame *) gameList.tailPred)->number > 1) {
9845             GameListPopUp(f, title);
9846             return TRUE;
9847         }
9848         GameListDestroy();
9849         n = 1;
9850     }
9851     if (n == 0) n = 1;
9852     return LoadGame(f, n, title, FALSE);
9853 }
9854
9855
9856 void
9857 MakeRegisteredMove()
9858 {
9859     int fromX, fromY, toX, toY;
9860     char promoChar;
9861     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9862         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9863           case CMAIL_MOVE:
9864           case CMAIL_DRAW:
9865             if (appData.debugMode)
9866               fprintf(debugFP, "Restoring %s for game %d\n",
9867                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9868
9869             thinkOutput[0] = NULLCHAR;
9870             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9871             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9872             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9873             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9874             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9875             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9876             MakeMove(fromX, fromY, toX, toY, promoChar);
9877             ShowMove(fromX, fromY, toX, toY);
9878
9879             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9880               case MT_NONE:
9881               case MT_CHECK:
9882                 break;
9883
9884               case MT_CHECKMATE:
9885               case MT_STAINMATE:
9886                 if (WhiteOnMove(currentMove)) {
9887                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9888                 } else {
9889                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9890                 }
9891                 break;
9892
9893               case MT_STALEMATE:
9894                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9895                 break;
9896             }
9897
9898             break;
9899
9900           case CMAIL_RESIGN:
9901             if (WhiteOnMove(currentMove)) {
9902                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9903             } else {
9904                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9905             }
9906             break;
9907
9908           case CMAIL_ACCEPT:
9909             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9910             break;
9911
9912           default:
9913             break;
9914         }
9915     }
9916
9917     return;
9918 }
9919
9920 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9921 int
9922 CmailLoadGame(f, gameNumber, title, useList)
9923      FILE *f;
9924      int gameNumber;
9925      char *title;
9926      int useList;
9927 {
9928     int retVal;
9929
9930     if (gameNumber > nCmailGames) {
9931         DisplayError(_("No more games in this message"), 0);
9932         return FALSE;
9933     }
9934     if (f == lastLoadGameFP) {
9935         int offset = gameNumber - lastLoadGameNumber;
9936         if (offset == 0) {
9937             cmailMsg[0] = NULLCHAR;
9938             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9939                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9940                 nCmailMovesRegistered--;
9941             }
9942             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9943             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9944                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9945             }
9946         } else {
9947             if (! RegisterMove()) return FALSE;
9948         }
9949     }
9950
9951     retVal = LoadGame(f, gameNumber, title, useList);
9952
9953     /* Make move registered during previous look at this game, if any */
9954     MakeRegisteredMove();
9955
9956     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9957         commentList[currentMove]
9958           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9959         DisplayComment(currentMove - 1, commentList[currentMove]);
9960     }
9961
9962     return retVal;
9963 }
9964
9965 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9966 int
9967 ReloadGame(offset)
9968      int offset;
9969 {
9970     int gameNumber = lastLoadGameNumber + offset;
9971     if (lastLoadGameFP == NULL) {
9972         DisplayError(_("No game has been loaded yet"), 0);
9973         return FALSE;
9974     }
9975     if (gameNumber <= 0) {
9976         DisplayError(_("Can't back up any further"), 0);
9977         return FALSE;
9978     }
9979     if (cmailMsgLoaded) {
9980         return CmailLoadGame(lastLoadGameFP, gameNumber,
9981                              lastLoadGameTitle, lastLoadGameUseList);
9982     } else {
9983         return LoadGame(lastLoadGameFP, gameNumber,
9984                         lastLoadGameTitle, lastLoadGameUseList);
9985     }
9986 }
9987
9988
9989
9990 /* Load the nth game from open file f */
9991 int
9992 LoadGame(f, gameNumber, title, useList)
9993      FILE *f;
9994      int gameNumber;
9995      char *title;
9996      int useList;
9997 {
9998     ChessMove cm;
9999     char buf[MSG_SIZ];
10000     int gn = gameNumber;
10001     ListGame *lg = NULL;
10002     int numPGNTags = 0;
10003     int err;
10004     GameMode oldGameMode;
10005     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10006
10007     if (appData.debugMode)
10008         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10009
10010     if (gameMode == Training )
10011         SetTrainingModeOff();
10012
10013     oldGameMode = gameMode;
10014     if (gameMode != BeginningOfGame) {
10015       Reset(FALSE, TRUE);
10016     }
10017
10018     gameFileFP = f;
10019     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10020         fclose(lastLoadGameFP);
10021     }
10022
10023     if (useList) {
10024         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10025
10026         if (lg) {
10027             fseek(f, lg->offset, 0);
10028             GameListHighlight(gameNumber);
10029             gn = 1;
10030         }
10031         else {
10032             DisplayError(_("Game number out of range"), 0);
10033             return FALSE;
10034         }
10035     } else {
10036         GameListDestroy();
10037         if (fseek(f, 0, 0) == -1) {
10038             if (f == lastLoadGameFP ?
10039                 gameNumber == lastLoadGameNumber + 1 :
10040                 gameNumber == 1) {
10041                 gn = 1;
10042             } else {
10043                 DisplayError(_("Can't seek on game file"), 0);
10044                 return FALSE;
10045             }
10046         }
10047     }
10048     lastLoadGameFP = f;
10049     lastLoadGameNumber = gameNumber;
10050     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10051     lastLoadGameUseList = useList;
10052
10053     yynewfile(f);
10054
10055     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10056       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10057                 lg->gameInfo.black);
10058             DisplayTitle(buf);
10059     } else if (*title != NULLCHAR) {
10060         if (gameNumber > 1) {
10061           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10062             DisplayTitle(buf);
10063         } else {
10064             DisplayTitle(title);
10065         }
10066     }
10067
10068     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10069         gameMode = PlayFromGameFile;
10070         ModeHighlight();
10071     }
10072
10073     currentMove = forwardMostMove = backwardMostMove = 0;
10074     CopyBoard(boards[0], initialPosition);
10075     StopClocks();
10076
10077     /*
10078      * Skip the first gn-1 games in the file.
10079      * Also skip over anything that precedes an identifiable
10080      * start of game marker, to avoid being confused by
10081      * garbage at the start of the file.  Currently
10082      * recognized start of game markers are the move number "1",
10083      * the pattern "gnuchess .* game", the pattern
10084      * "^[#;%] [^ ]* game file", and a PGN tag block.
10085      * A game that starts with one of the latter two patterns
10086      * will also have a move number 1, possibly
10087      * following a position diagram.
10088      * 5-4-02: Let's try being more lenient and allowing a game to
10089      * start with an unnumbered move.  Does that break anything?
10090      */
10091     cm = lastLoadGameStart = (ChessMove) 0;
10092     while (gn > 0) {
10093         yyboardindex = forwardMostMove;
10094         cm = (ChessMove) yylex();
10095         switch (cm) {
10096           case (ChessMove) 0:
10097             if (cmailMsgLoaded) {
10098                 nCmailGames = CMAIL_MAX_GAMES - gn;
10099             } else {
10100                 Reset(TRUE, TRUE);
10101                 DisplayError(_("Game not found in file"), 0);
10102             }
10103             return FALSE;
10104
10105           case GNUChessGame:
10106           case XBoardGame:
10107             gn--;
10108             lastLoadGameStart = cm;
10109             break;
10110
10111           case MoveNumberOne:
10112             switch (lastLoadGameStart) {
10113               case GNUChessGame:
10114               case XBoardGame:
10115               case PGNTag:
10116                 break;
10117               case MoveNumberOne:
10118               case (ChessMove) 0:
10119                 gn--;           /* count this game */
10120                 lastLoadGameStart = cm;
10121                 break;
10122               default:
10123                 /* impossible */
10124                 break;
10125             }
10126             break;
10127
10128           case PGNTag:
10129             switch (lastLoadGameStart) {
10130               case GNUChessGame:
10131               case PGNTag:
10132               case MoveNumberOne:
10133               case (ChessMove) 0:
10134                 gn--;           /* count this game */
10135                 lastLoadGameStart = cm;
10136                 break;
10137               case XBoardGame:
10138                 lastLoadGameStart = cm; /* game counted already */
10139                 break;
10140               default:
10141                 /* impossible */
10142                 break;
10143             }
10144             if (gn > 0) {
10145                 do {
10146                     yyboardindex = forwardMostMove;
10147                     cm = (ChessMove) yylex();
10148                 } while (cm == PGNTag || cm == Comment);
10149             }
10150             break;
10151
10152           case WhiteWins:
10153           case BlackWins:
10154           case GameIsDrawn:
10155             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10156                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10157                     != CMAIL_OLD_RESULT) {
10158                     nCmailResults ++ ;
10159                     cmailResult[  CMAIL_MAX_GAMES
10160                                 - gn - 1] = CMAIL_OLD_RESULT;
10161                 }
10162             }
10163             break;
10164
10165           case NormalMove:
10166             /* Only a NormalMove can be at the start of a game
10167              * without a position diagram. */
10168             if (lastLoadGameStart == (ChessMove) 0) {
10169               gn--;
10170               lastLoadGameStart = MoveNumberOne;
10171             }
10172             break;
10173
10174           default:
10175             break;
10176         }
10177     }
10178
10179     if (appData.debugMode)
10180       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10181
10182     if (cm == XBoardGame) {
10183         /* Skip any header junk before position diagram and/or move 1 */
10184         for (;;) {
10185             yyboardindex = forwardMostMove;
10186             cm = (ChessMove) yylex();
10187
10188             if (cm == (ChessMove) 0 ||
10189                 cm == GNUChessGame || cm == XBoardGame) {
10190                 /* Empty game; pretend end-of-file and handle later */
10191                 cm = (ChessMove) 0;
10192                 break;
10193             }
10194
10195             if (cm == MoveNumberOne || cm == PositionDiagram ||
10196                 cm == PGNTag || cm == Comment)
10197               break;
10198         }
10199     } else if (cm == GNUChessGame) {
10200         if (gameInfo.event != NULL) {
10201             free(gameInfo.event);
10202         }
10203         gameInfo.event = StrSave(yy_text);
10204     }
10205
10206     startedFromSetupPosition = FALSE;
10207     while (cm == PGNTag) {
10208         if (appData.debugMode)
10209           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10210         err = ParsePGNTag(yy_text, &gameInfo);
10211         if (!err) numPGNTags++;
10212
10213         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10214         if(gameInfo.variant != oldVariant) {
10215             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10216             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10217             InitPosition(TRUE);
10218             oldVariant = gameInfo.variant;
10219             if (appData.debugMode)
10220               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10221         }
10222
10223
10224         if (gameInfo.fen != NULL) {
10225           Board initial_position;
10226           startedFromSetupPosition = TRUE;
10227           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10228             Reset(TRUE, TRUE);
10229             DisplayError(_("Bad FEN position in file"), 0);
10230             return FALSE;
10231           }
10232           CopyBoard(boards[0], initial_position);
10233           if (blackPlaysFirst) {
10234             currentMove = forwardMostMove = backwardMostMove = 1;
10235             CopyBoard(boards[1], initial_position);
10236             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10237             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10238             timeRemaining[0][1] = whiteTimeRemaining;
10239             timeRemaining[1][1] = blackTimeRemaining;
10240             if (commentList[0] != NULL) {
10241               commentList[1] = commentList[0];
10242               commentList[0] = NULL;
10243             }
10244           } else {
10245             currentMove = forwardMostMove = backwardMostMove = 0;
10246           }
10247           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10248           {   int i;
10249               initialRulePlies = FENrulePlies;
10250               for( i=0; i< nrCastlingRights; i++ )
10251                   initialRights[i] = initial_position[CASTLING][i];
10252           }
10253           yyboardindex = forwardMostMove;
10254           free(gameInfo.fen);
10255           gameInfo.fen = NULL;
10256         }
10257
10258         yyboardindex = forwardMostMove;
10259         cm = (ChessMove) yylex();
10260
10261         /* Handle comments interspersed among the tags */
10262         while (cm == Comment) {
10263             char *p;
10264             if (appData.debugMode)
10265               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10266             p = yy_text;
10267             AppendComment(currentMove, p, FALSE);
10268             yyboardindex = forwardMostMove;
10269             cm = (ChessMove) yylex();
10270         }
10271     }
10272
10273     /* don't rely on existence of Event tag since if game was
10274      * pasted from clipboard the Event tag may not exist
10275      */
10276     if (numPGNTags > 0){
10277         char *tags;
10278         if (gameInfo.variant == VariantNormal) {
10279           VariantClass v = StringToVariant(gameInfo.event);
10280           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10281           if(v < VariantShogi) gameInfo.variant = v;
10282         }
10283         if (!matchMode) {
10284           if( appData.autoDisplayTags ) {
10285             tags = PGNTags(&gameInfo);
10286             TagsPopUp(tags, CmailMsg());
10287             free(tags);
10288           }
10289         }
10290     } else {
10291         /* Make something up, but don't display it now */
10292         SetGameInfo();
10293         TagsPopDown();
10294     }
10295
10296     if (cm == PositionDiagram) {
10297         int i, j;
10298         char *p;
10299         Board initial_position;
10300
10301         if (appData.debugMode)
10302           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10303
10304         if (!startedFromSetupPosition) {
10305             p = yy_text;
10306             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10307               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10308                 switch (*p) {
10309                   case '[':
10310                   case '-':
10311                   case ' ':
10312                   case '\t':
10313                   case '\n':
10314                   case '\r':
10315                     break;
10316                   default:
10317                     initial_position[i][j++] = CharToPiece(*p);
10318                     break;
10319                 }
10320             while (*p == ' ' || *p == '\t' ||
10321                    *p == '\n' || *p == '\r') p++;
10322
10323             if (strncmp(p, "black", strlen("black"))==0)
10324               blackPlaysFirst = TRUE;
10325             else
10326               blackPlaysFirst = FALSE;
10327             startedFromSetupPosition = TRUE;
10328
10329             CopyBoard(boards[0], initial_position);
10330             if (blackPlaysFirst) {
10331                 currentMove = forwardMostMove = backwardMostMove = 1;
10332                 CopyBoard(boards[1], initial_position);
10333                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10334                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10335                 timeRemaining[0][1] = whiteTimeRemaining;
10336                 timeRemaining[1][1] = blackTimeRemaining;
10337                 if (commentList[0] != NULL) {
10338                     commentList[1] = commentList[0];
10339                     commentList[0] = NULL;
10340                 }
10341             } else {
10342                 currentMove = forwardMostMove = backwardMostMove = 0;
10343             }
10344         }
10345         yyboardindex = forwardMostMove;
10346         cm = (ChessMove) yylex();
10347     }
10348
10349     if (first.pr == NoProc) {
10350         StartChessProgram(&first);
10351     }
10352     InitChessProgram(&first, FALSE);
10353     SendToProgram("force\n", &first);
10354     if (startedFromSetupPosition) {
10355         SendBoard(&first, forwardMostMove);
10356     if (appData.debugMode) {
10357         fprintf(debugFP, "Load Game\n");
10358     }
10359         DisplayBothClocks();
10360     }
10361
10362     /* [HGM] server: flag to write setup moves in broadcast file as one */
10363     loadFlag = appData.suppressLoadMoves;
10364
10365     while (cm == Comment) {
10366         char *p;
10367         if (appData.debugMode)
10368           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10369         p = yy_text;
10370         AppendComment(currentMove, p, FALSE);
10371         yyboardindex = forwardMostMove;
10372         cm = (ChessMove) yylex();
10373     }
10374
10375     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10376         cm == WhiteWins || cm == BlackWins ||
10377         cm == GameIsDrawn || cm == GameUnfinished) {
10378         DisplayMessage("", _("No moves in game"));
10379         if (cmailMsgLoaded) {
10380             if (appData.debugMode)
10381               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10382             ClearHighlights();
10383             flipView = FALSE;
10384         }
10385         DrawPosition(FALSE, boards[currentMove]);
10386         DisplayBothClocks();
10387         gameMode = EditGame;
10388         ModeHighlight();
10389         gameFileFP = NULL;
10390         cmailOldMove = 0;
10391         return TRUE;
10392     }
10393
10394     // [HGM] PV info: routine tests if comment empty
10395     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10396         DisplayComment(currentMove - 1, commentList[currentMove]);
10397     }
10398     if (!matchMode && appData.timeDelay != 0)
10399       DrawPosition(FALSE, boards[currentMove]);
10400
10401     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10402       programStats.ok_to_send = 1;
10403     }
10404
10405     /* if the first token after the PGN tags is a move
10406      * and not move number 1, retrieve it from the parser
10407      */
10408     if (cm != MoveNumberOne)
10409         LoadGameOneMove(cm);
10410
10411     /* load the remaining moves from the file */
10412     while (LoadGameOneMove((ChessMove)0)) {
10413       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10414       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10415     }
10416
10417     /* rewind to the start of the game */
10418     currentMove = backwardMostMove;
10419
10420     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10421
10422     if (oldGameMode == AnalyzeFile ||
10423         oldGameMode == AnalyzeMode) {
10424       AnalyzeFileEvent();
10425     }
10426
10427     if (matchMode || appData.timeDelay == 0) {
10428       ToEndEvent();
10429       gameMode = EditGame;
10430       ModeHighlight();
10431     } else if (appData.timeDelay > 0) {
10432       AutoPlayGameLoop();
10433     }
10434
10435     if (appData.debugMode)
10436         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10437
10438     loadFlag = 0; /* [HGM] true game starts */
10439     return TRUE;
10440 }
10441
10442 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10443 int
10444 ReloadPosition(offset)
10445      int offset;
10446 {
10447     int positionNumber = lastLoadPositionNumber + offset;
10448     if (lastLoadPositionFP == NULL) {
10449         DisplayError(_("No position has been loaded yet"), 0);
10450         return FALSE;
10451     }
10452     if (positionNumber <= 0) {
10453         DisplayError(_("Can't back up any further"), 0);
10454         return FALSE;
10455     }
10456     return LoadPosition(lastLoadPositionFP, positionNumber,
10457                         lastLoadPositionTitle);
10458 }
10459
10460 /* Load the nth position from the given file */
10461 int
10462 LoadPositionFromFile(filename, n, title)
10463      char *filename;
10464      int n;
10465      char *title;
10466 {
10467     FILE *f;
10468     char buf[MSG_SIZ];
10469
10470     if (strcmp(filename, "-") == 0) {
10471         return LoadPosition(stdin, n, "stdin");
10472     } else {
10473         f = fopen(filename, "rb");
10474         if (f == NULL) {
10475             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10476             DisplayError(buf, errno);
10477             return FALSE;
10478         } else {
10479             return LoadPosition(f, n, title);
10480         }
10481     }
10482 }
10483
10484 /* Load the nth position from the given open file, and close it */
10485 int
10486 LoadPosition(f, positionNumber, title)
10487      FILE *f;
10488      int positionNumber;
10489      char *title;
10490 {
10491     char *p, line[MSG_SIZ];
10492     Board initial_position;
10493     int i, j, fenMode, pn;
10494
10495     if (gameMode == Training )
10496         SetTrainingModeOff();
10497
10498     if (gameMode != BeginningOfGame) {
10499         Reset(FALSE, TRUE);
10500     }
10501     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10502         fclose(lastLoadPositionFP);
10503     }
10504     if (positionNumber == 0) positionNumber = 1;
10505     lastLoadPositionFP = f;
10506     lastLoadPositionNumber = positionNumber;
10507     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10508     if (first.pr == NoProc) {
10509       StartChessProgram(&first);
10510       InitChessProgram(&first, FALSE);
10511     }
10512     pn = positionNumber;
10513     if (positionNumber < 0) {
10514         /* Negative position number means to seek to that byte offset */
10515         if (fseek(f, -positionNumber, 0) == -1) {
10516             DisplayError(_("Can't seek on position file"), 0);
10517             return FALSE;
10518         };
10519         pn = 1;
10520     } else {
10521         if (fseek(f, 0, 0) == -1) {
10522             if (f == lastLoadPositionFP ?
10523                 positionNumber == lastLoadPositionNumber + 1 :
10524                 positionNumber == 1) {
10525                 pn = 1;
10526             } else {
10527                 DisplayError(_("Can't seek on position file"), 0);
10528                 return FALSE;
10529             }
10530         }
10531     }
10532     /* See if this file is FEN or old-style xboard */
10533     if (fgets(line, MSG_SIZ, f) == NULL) {
10534         DisplayError(_("Position not found in file"), 0);
10535         return FALSE;
10536     }
10537     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10538     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10539
10540     if (pn >= 2) {
10541         if (fenMode || line[0] == '#') pn--;
10542         while (pn > 0) {
10543             /* skip positions before number pn */
10544             if (fgets(line, MSG_SIZ, f) == NULL) {
10545                 Reset(TRUE, TRUE);
10546                 DisplayError(_("Position not found in file"), 0);
10547                 return FALSE;
10548             }
10549             if (fenMode || line[0] == '#') pn--;
10550         }
10551     }
10552
10553     if (fenMode) {
10554         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10555             DisplayError(_("Bad FEN position in file"), 0);
10556             return FALSE;
10557         }
10558     } else {
10559         (void) fgets(line, MSG_SIZ, f);
10560         (void) fgets(line, MSG_SIZ, f);
10561
10562         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10563             (void) fgets(line, MSG_SIZ, f);
10564             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10565                 if (*p == ' ')
10566                   continue;
10567                 initial_position[i][j++] = CharToPiece(*p);
10568             }
10569         }
10570
10571         blackPlaysFirst = FALSE;
10572         if (!feof(f)) {
10573             (void) fgets(line, MSG_SIZ, f);
10574             if (strncmp(line, "black", strlen("black"))==0)
10575               blackPlaysFirst = TRUE;
10576         }
10577     }
10578     startedFromSetupPosition = TRUE;
10579
10580     SendToProgram("force\n", &first);
10581     CopyBoard(boards[0], initial_position);
10582     if (blackPlaysFirst) {
10583         currentMove = forwardMostMove = backwardMostMove = 1;
10584         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10585         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10586         CopyBoard(boards[1], initial_position);
10587         DisplayMessage("", _("Black to play"));
10588     } else {
10589         currentMove = forwardMostMove = backwardMostMove = 0;
10590         DisplayMessage("", _("White to play"));
10591     }
10592     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10593     SendBoard(&first, forwardMostMove);
10594     if (appData.debugMode) {
10595 int i, j;
10596   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10597   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10598         fprintf(debugFP, "Load Position\n");
10599     }
10600
10601     if (positionNumber > 1) {
10602       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10603         DisplayTitle(line);
10604     } else {
10605         DisplayTitle(title);
10606     }
10607     gameMode = EditGame;
10608     ModeHighlight();
10609     ResetClocks();
10610     timeRemaining[0][1] = whiteTimeRemaining;
10611     timeRemaining[1][1] = blackTimeRemaining;
10612     DrawPosition(FALSE, boards[currentMove]);
10613
10614     return TRUE;
10615 }
10616
10617
10618 void
10619 CopyPlayerNameIntoFileName(dest, src)
10620      char **dest, *src;
10621 {
10622     while (*src != NULLCHAR && *src != ',') {
10623         if (*src == ' ') {
10624             *(*dest)++ = '_';
10625             src++;
10626         } else {
10627             *(*dest)++ = *src++;
10628         }
10629     }
10630 }
10631
10632 char *DefaultFileName(ext)
10633      char *ext;
10634 {
10635     static char def[MSG_SIZ];
10636     char *p;
10637
10638     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10639         p = def;
10640         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10641         *p++ = '-';
10642         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10643         *p++ = '.';
10644         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10645     } else {
10646         def[0] = NULLCHAR;
10647     }
10648     return def;
10649 }
10650
10651 /* Save the current game to the given file */
10652 int
10653 SaveGameToFile(filename, append)
10654      char *filename;
10655      int append;
10656 {
10657     FILE *f;
10658     char buf[MSG_SIZ];
10659
10660     if (strcmp(filename, "-") == 0) {
10661         return SaveGame(stdout, 0, NULL);
10662     } else {
10663         f = fopen(filename, append ? "a" : "w");
10664         if (f == NULL) {
10665             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10666             DisplayError(buf, errno);
10667             return FALSE;
10668         } else {
10669             return SaveGame(f, 0, NULL);
10670         }
10671     }
10672 }
10673
10674 char *
10675 SavePart(str)
10676      char *str;
10677 {
10678     static char buf[MSG_SIZ];
10679     char *p;
10680
10681     p = strchr(str, ' ');
10682     if (p == NULL) return str;
10683     strncpy(buf, str, p - str);
10684     buf[p - str] = NULLCHAR;
10685     return buf;
10686 }
10687
10688 #define PGN_MAX_LINE 75
10689
10690 #define PGN_SIDE_WHITE  0
10691 #define PGN_SIDE_BLACK  1
10692
10693 /* [AS] */
10694 static int FindFirstMoveOutOfBook( int side )
10695 {
10696     int result = -1;
10697
10698     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10699         int index = backwardMostMove;
10700         int has_book_hit = 0;
10701
10702         if( (index % 2) != side ) {
10703             index++;
10704         }
10705
10706         while( index < forwardMostMove ) {
10707             /* Check to see if engine is in book */
10708             int depth = pvInfoList[index].depth;
10709             int score = pvInfoList[index].score;
10710             int in_book = 0;
10711
10712             if( depth <= 2 ) {
10713                 in_book = 1;
10714             }
10715             else if( score == 0 && depth == 63 ) {
10716                 in_book = 1; /* Zappa */
10717             }
10718             else if( score == 2 && depth == 99 ) {
10719                 in_book = 1; /* Abrok */
10720             }
10721
10722             has_book_hit += in_book;
10723
10724             if( ! in_book ) {
10725                 result = index;
10726
10727                 break;
10728             }
10729
10730             index += 2;
10731         }
10732     }
10733
10734     return result;
10735 }
10736
10737 /* [AS] */
10738 void GetOutOfBookInfo( char * buf )
10739 {
10740     int oob[2];
10741     int i;
10742     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10743
10744     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10745     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10746
10747     *buf = '\0';
10748
10749     if( oob[0] >= 0 || oob[1] >= 0 ) {
10750         for( i=0; i<2; i++ ) {
10751             int idx = oob[i];
10752
10753             if( idx >= 0 ) {
10754                 if( i > 0 && oob[0] >= 0 ) {
10755                     strcat( buf, "   " );
10756                 }
10757
10758                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10759                 sprintf( buf+strlen(buf), "%s%.2f",
10760                     pvInfoList[idx].score >= 0 ? "+" : "",
10761                     pvInfoList[idx].score / 100.0 );
10762             }
10763         }
10764     }
10765 }
10766
10767 /* Save game in PGN style and close the file */
10768 int
10769 SaveGamePGN(f)
10770      FILE *f;
10771 {
10772     int i, offset, linelen, newblock;
10773     time_t tm;
10774 //    char *movetext;
10775     char numtext[32];
10776     int movelen, numlen, blank;
10777     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10778
10779     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10780
10781     tm = time((time_t *) NULL);
10782
10783     PrintPGNTags(f, &gameInfo);
10784
10785     if (backwardMostMove > 0 || startedFromSetupPosition) {
10786         char *fen = PositionToFEN(backwardMostMove, NULL);
10787         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10788         fprintf(f, "\n{--------------\n");
10789         PrintPosition(f, backwardMostMove);
10790         fprintf(f, "--------------}\n");
10791         free(fen);
10792     }
10793     else {
10794         /* [AS] Out of book annotation */
10795         if( appData.saveOutOfBookInfo ) {
10796             char buf[64];
10797
10798             GetOutOfBookInfo( buf );
10799
10800             if( buf[0] != '\0' ) {
10801                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10802             }
10803         }
10804
10805         fprintf(f, "\n");
10806     }
10807
10808     i = backwardMostMove;
10809     linelen = 0;
10810     newblock = TRUE;
10811
10812     while (i < forwardMostMove) {
10813         /* Print comments preceding this move */
10814         if (commentList[i] != NULL) {
10815             if (linelen > 0) fprintf(f, "\n");
10816             fprintf(f, "%s", commentList[i]);
10817             linelen = 0;
10818             newblock = TRUE;
10819         }
10820
10821         /* Format move number */
10822         if ((i % 2) == 0)
10823           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10824         else
10825           if (newblock)
10826             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10827           else
10828             numtext[0] = NULLCHAR;
10829
10830         numlen = strlen(numtext);
10831         newblock = FALSE;
10832
10833         /* Print move number */
10834         blank = linelen > 0 && numlen > 0;
10835         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10836             fprintf(f, "\n");
10837             linelen = 0;
10838             blank = 0;
10839         }
10840         if (blank) {
10841             fprintf(f, " ");
10842             linelen++;
10843         }
10844         fprintf(f, "%s", numtext);
10845         linelen += numlen;
10846
10847         /* Get move */
10848         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10849         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10850
10851         /* Print move */
10852         blank = linelen > 0 && movelen > 0;
10853         if (linelen + (blank ? 1 : 0) + movelen > 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", move_buffer);
10863         linelen += movelen;
10864
10865         /* [AS] Add PV info if present */
10866         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10867             /* [HGM] add time */
10868             char buf[MSG_SIZ]; int seconds;
10869
10870             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10871
10872             if( seconds <= 0)
10873               buf[0] = 0;
10874             else
10875               if( seconds < 30 )
10876                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10877               else
10878                 {
10879                   seconds = (seconds + 4)/10; // round to full seconds
10880                   if( seconds < 60 )
10881                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10882                   else
10883                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10884                 }
10885
10886             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10887                       pvInfoList[i].score >= 0 ? "+" : "",
10888                       pvInfoList[i].score / 100.0,
10889                       pvInfoList[i].depth,
10890                       buf );
10891
10892             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10893
10894             /* Print score/depth */
10895             blank = linelen > 0 && movelen > 0;
10896             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10897                 fprintf(f, "\n");
10898                 linelen = 0;
10899                 blank = 0;
10900             }
10901             if (blank) {
10902                 fprintf(f, " ");
10903                 linelen++;
10904             }
10905             fprintf(f, "%s", move_buffer);
10906             linelen += movelen;
10907         }
10908
10909         i++;
10910     }
10911
10912     /* Start a new line */
10913     if (linelen > 0) fprintf(f, "\n");
10914
10915     /* Print comments after last move */
10916     if (commentList[i] != NULL) {
10917         fprintf(f, "%s\n", commentList[i]);
10918     }
10919
10920     /* Print result */
10921     if (gameInfo.resultDetails != NULL &&
10922         gameInfo.resultDetails[0] != NULLCHAR) {
10923         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10924                 PGNResult(gameInfo.result));
10925     } else {
10926         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10927     }
10928
10929     fclose(f);
10930     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10931     return TRUE;
10932 }
10933
10934 /* Save game in old style and close the file */
10935 int
10936 SaveGameOldStyle(f)
10937      FILE *f;
10938 {
10939     int i, offset;
10940     time_t tm;
10941
10942     tm = time((time_t *) NULL);
10943
10944     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10945     PrintOpponents(f);
10946
10947     if (backwardMostMove > 0 || startedFromSetupPosition) {
10948         fprintf(f, "\n[--------------\n");
10949         PrintPosition(f, backwardMostMove);
10950         fprintf(f, "--------------]\n");
10951     } else {
10952         fprintf(f, "\n");
10953     }
10954
10955     i = backwardMostMove;
10956     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10957
10958     while (i < forwardMostMove) {
10959         if (commentList[i] != NULL) {
10960             fprintf(f, "[%s]\n", commentList[i]);
10961         }
10962
10963         if ((i % 2) == 1) {
10964             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10965             i++;
10966         } else {
10967             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10968             i++;
10969             if (commentList[i] != NULL) {
10970                 fprintf(f, "\n");
10971                 continue;
10972             }
10973             if (i >= forwardMostMove) {
10974                 fprintf(f, "\n");
10975                 break;
10976             }
10977             fprintf(f, "%s\n", parseList[i]);
10978             i++;
10979         }
10980     }
10981
10982     if (commentList[i] != NULL) {
10983         fprintf(f, "[%s]\n", commentList[i]);
10984     }
10985
10986     /* This isn't really the old style, but it's close enough */
10987     if (gameInfo.resultDetails != NULL &&
10988         gameInfo.resultDetails[0] != NULLCHAR) {
10989         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10990                 gameInfo.resultDetails);
10991     } else {
10992         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10993     }
10994
10995     fclose(f);
10996     return TRUE;
10997 }
10998
10999 /* Save the current game to open file f and close the file */
11000 int
11001 SaveGame(f, dummy, dummy2)
11002      FILE *f;
11003      int dummy;
11004      char *dummy2;
11005 {
11006     if (gameMode == EditPosition) EditPositionDone(TRUE);
11007     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11008     if (appData.oldSaveStyle)
11009       return SaveGameOldStyle(f);
11010     else
11011       return SaveGamePGN(f);
11012 }
11013
11014 /* Save the current position to the given file */
11015 int
11016 SavePositionToFile(filename)
11017      char *filename;
11018 {
11019     FILE *f;
11020     char buf[MSG_SIZ];
11021
11022     if (strcmp(filename, "-") == 0) {
11023         return SavePosition(stdout, 0, NULL);
11024     } else {
11025         f = fopen(filename, "a");
11026         if (f == NULL) {
11027             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11028             DisplayError(buf, errno);
11029             return FALSE;
11030         } else {
11031             SavePosition(f, 0, NULL);
11032             return TRUE;
11033         }
11034     }
11035 }
11036
11037 /* Save the current position to the given open file and close the file */
11038 int
11039 SavePosition(f, dummy, dummy2)
11040      FILE *f;
11041      int dummy;
11042      char *dummy2;
11043 {
11044     time_t tm;
11045     char *fen;
11046
11047     if (gameMode == EditPosition) EditPositionDone(TRUE);
11048     if (appData.oldSaveStyle) {
11049         tm = time((time_t *) NULL);
11050
11051         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11052         PrintOpponents(f);
11053         fprintf(f, "[--------------\n");
11054         PrintPosition(f, currentMove);
11055         fprintf(f, "--------------]\n");
11056     } else {
11057         fen = PositionToFEN(currentMove, NULL);
11058         fprintf(f, "%s\n", fen);
11059         free(fen);
11060     }
11061     fclose(f);
11062     return TRUE;
11063 }
11064
11065 void
11066 ReloadCmailMsgEvent(unregister)
11067      int unregister;
11068 {
11069 #if !WIN32
11070     static char *inFilename = NULL;
11071     static char *outFilename;
11072     int i;
11073     struct stat inbuf, outbuf;
11074     int status;
11075
11076     /* Any registered moves are unregistered if unregister is set, */
11077     /* i.e. invoked by the signal handler */
11078     if (unregister) {
11079         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11080             cmailMoveRegistered[i] = FALSE;
11081             if (cmailCommentList[i] != NULL) {
11082                 free(cmailCommentList[i]);
11083                 cmailCommentList[i] = NULL;
11084             }
11085         }
11086         nCmailMovesRegistered = 0;
11087     }
11088
11089     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11090         cmailResult[i] = CMAIL_NOT_RESULT;
11091     }
11092     nCmailResults = 0;
11093
11094     if (inFilename == NULL) {
11095         /* Because the filenames are static they only get malloced once  */
11096         /* and they never get freed                                      */
11097         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11098         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11099
11100         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11101         sprintf(outFilename, "%s.out", appData.cmailGameName);
11102     }
11103
11104     status = stat(outFilename, &outbuf);
11105     if (status < 0) {
11106         cmailMailedMove = FALSE;
11107     } else {
11108         status = stat(inFilename, &inbuf);
11109         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11110     }
11111
11112     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11113        counts the games, notes how each one terminated, etc.
11114
11115        It would be nice to remove this kludge and instead gather all
11116        the information while building the game list.  (And to keep it
11117        in the game list nodes instead of having a bunch of fixed-size
11118        parallel arrays.)  Note this will require getting each game's
11119        termination from the PGN tags, as the game list builder does
11120        not process the game moves.  --mann
11121        */
11122     cmailMsgLoaded = TRUE;
11123     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11124
11125     /* Load first game in the file or popup game menu */
11126     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11127
11128 #endif /* !WIN32 */
11129     return;
11130 }
11131
11132 int
11133 RegisterMove()
11134 {
11135     FILE *f;
11136     char string[MSG_SIZ];
11137
11138     if (   cmailMailedMove
11139         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11140         return TRUE;            /* Allow free viewing  */
11141     }
11142
11143     /* Unregister move to ensure that we don't leave RegisterMove        */
11144     /* with the move registered when the conditions for registering no   */
11145     /* longer hold                                                       */
11146     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11147         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11148         nCmailMovesRegistered --;
11149
11150         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11151           {
11152               free(cmailCommentList[lastLoadGameNumber - 1]);
11153               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11154           }
11155     }
11156
11157     if (cmailOldMove == -1) {
11158         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11159         return FALSE;
11160     }
11161
11162     if (currentMove > cmailOldMove + 1) {
11163         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11164         return FALSE;
11165     }
11166
11167     if (currentMove < cmailOldMove) {
11168         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11169         return FALSE;
11170     }
11171
11172     if (forwardMostMove > currentMove) {
11173         /* Silently truncate extra moves */
11174         TruncateGame();
11175     }
11176
11177     if (   (currentMove == cmailOldMove + 1)
11178         || (   (currentMove == cmailOldMove)
11179             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11180                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11181         if (gameInfo.result != GameUnfinished) {
11182             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11183         }
11184
11185         if (commentList[currentMove] != NULL) {
11186             cmailCommentList[lastLoadGameNumber - 1]
11187               = StrSave(commentList[currentMove]);
11188         }
11189         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11190
11191         if (appData.debugMode)
11192           fprintf(debugFP, "Saving %s for game %d\n",
11193                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11194
11195         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11196
11197         f = fopen(string, "w");
11198         if (appData.oldSaveStyle) {
11199             SaveGameOldStyle(f); /* also closes the file */
11200
11201             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11202             f = fopen(string, "w");
11203             SavePosition(f, 0, NULL); /* also closes the file */
11204         } else {
11205             fprintf(f, "{--------------\n");
11206             PrintPosition(f, currentMove);
11207             fprintf(f, "--------------}\n\n");
11208
11209             SaveGame(f, 0, NULL); /* also closes the file*/
11210         }
11211
11212         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11213         nCmailMovesRegistered ++;
11214     } else if (nCmailGames == 1) {
11215         DisplayError(_("You have not made a move yet"), 0);
11216         return FALSE;
11217     }
11218
11219     return TRUE;
11220 }
11221
11222 void
11223 MailMoveEvent()
11224 {
11225 #if !WIN32
11226     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11227     FILE *commandOutput;
11228     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11229     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11230     int nBuffers;
11231     int i;
11232     int archived;
11233     char *arcDir;
11234
11235     if (! cmailMsgLoaded) {
11236         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11237         return;
11238     }
11239
11240     if (nCmailGames == nCmailResults) {
11241         DisplayError(_("No unfinished games"), 0);
11242         return;
11243     }
11244
11245 #if CMAIL_PROHIBIT_REMAIL
11246     if (cmailMailedMove) {
11247       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);
11248         DisplayError(msg, 0);
11249         return;
11250     }
11251 #endif
11252
11253     if (! (cmailMailedMove || RegisterMove())) return;
11254
11255     if (   cmailMailedMove
11256         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11257       snprintf(string, MSG_SIZ, partCommandString,
11258                appData.debugMode ? " -v" : "", appData.cmailGameName);
11259         commandOutput = popen(string, "r");
11260
11261         if (commandOutput == NULL) {
11262             DisplayError(_("Failed to invoke cmail"), 0);
11263         } else {
11264             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11265                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11266             }
11267             if (nBuffers > 1) {
11268                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11269                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11270                 nBytes = MSG_SIZ - 1;
11271             } else {
11272                 (void) memcpy(msg, buffer, nBytes);
11273             }
11274             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11275
11276             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11277                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11278
11279                 archived = TRUE;
11280                 for (i = 0; i < nCmailGames; i ++) {
11281                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11282                         archived = FALSE;
11283                     }
11284                 }
11285                 if (   archived
11286                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11287                         != NULL)) {
11288                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11289                            arcDir,
11290                            appData.cmailGameName,
11291                            gameInfo.date);
11292                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11293                     cmailMsgLoaded = FALSE;
11294                 }
11295             }
11296
11297             DisplayInformation(msg);
11298             pclose(commandOutput);
11299         }
11300     } else {
11301         if ((*cmailMsg) != '\0') {
11302             DisplayInformation(cmailMsg);
11303         }
11304     }
11305
11306     return;
11307 #endif /* !WIN32 */
11308 }
11309
11310 char *
11311 CmailMsg()
11312 {
11313 #if WIN32
11314     return NULL;
11315 #else
11316     int  prependComma = 0;
11317     char number[5];
11318     char string[MSG_SIZ];       /* Space for game-list */
11319     int  i;
11320
11321     if (!cmailMsgLoaded) return "";
11322
11323     if (cmailMailedMove) {
11324       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11325     } else {
11326         /* Create a list of games left */
11327       snprintf(string, MSG_SIZ, "[");
11328         for (i = 0; i < nCmailGames; i ++) {
11329             if (! (   cmailMoveRegistered[i]
11330                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11331                 if (prependComma) {
11332                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11333                 } else {
11334                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11335                     prependComma = 1;
11336                 }
11337
11338                 strcat(string, number);
11339             }
11340         }
11341         strcat(string, "]");
11342
11343         if (nCmailMovesRegistered + nCmailResults == 0) {
11344             switch (nCmailGames) {
11345               case 1:
11346                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11347                 break;
11348
11349               case 2:
11350                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11351                 break;
11352
11353               default:
11354                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11355                          nCmailGames);
11356                 break;
11357             }
11358         } else {
11359             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11360               case 1:
11361                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11362                          string);
11363                 break;
11364
11365               case 0:
11366                 if (nCmailResults == nCmailGames) {
11367                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11368                 } else {
11369                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11370                 }
11371                 break;
11372
11373               default:
11374                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11375                          string);
11376             }
11377         }
11378     }
11379     return cmailMsg;
11380 #endif /* WIN32 */
11381 }
11382
11383 void
11384 ResetGameEvent()
11385 {
11386     if (gameMode == Training)
11387       SetTrainingModeOff();
11388
11389     Reset(TRUE, TRUE);
11390     cmailMsgLoaded = FALSE;
11391     if (appData.icsActive) {
11392       SendToICS(ics_prefix);
11393       SendToICS("refresh\n");
11394     }
11395 }
11396
11397 void
11398 ExitEvent(status)
11399      int status;
11400 {
11401     exiting++;
11402     if (exiting > 2) {
11403       /* Give up on clean exit */
11404       exit(status);
11405     }
11406     if (exiting > 1) {
11407       /* Keep trying for clean exit */
11408       return;
11409     }
11410
11411     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11412
11413     if (telnetISR != NULL) {
11414       RemoveInputSource(telnetISR);
11415     }
11416     if (icsPR != NoProc) {
11417       DestroyChildProcess(icsPR, TRUE);
11418     }
11419
11420     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11421     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11422
11423     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11424     /* make sure this other one finishes before killing it!                  */
11425     if(endingGame) { int count = 0;
11426         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11427         while(endingGame && count++ < 10) DoSleep(1);
11428         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11429     }
11430
11431     /* Kill off chess programs */
11432     if (first.pr != NoProc) {
11433         ExitAnalyzeMode();
11434
11435         DoSleep( appData.delayBeforeQuit );
11436         SendToProgram("quit\n", &first);
11437         DoSleep( appData.delayAfterQuit );
11438         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11439     }
11440     if (second.pr != NoProc) {
11441         DoSleep( appData.delayBeforeQuit );
11442         SendToProgram("quit\n", &second);
11443         DoSleep( appData.delayAfterQuit );
11444         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11445     }
11446     if (first.isr != NULL) {
11447         RemoveInputSource(first.isr);
11448     }
11449     if (second.isr != NULL) {
11450         RemoveInputSource(second.isr);
11451     }
11452
11453     ShutDownFrontEnd();
11454     exit(status);
11455 }
11456
11457 void
11458 PauseEvent()
11459 {
11460     if (appData.debugMode)
11461         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11462     if (pausing) {
11463         pausing = FALSE;
11464         ModeHighlight();
11465         if (gameMode == MachinePlaysWhite ||
11466             gameMode == MachinePlaysBlack) {
11467             StartClocks();
11468         } else {
11469             DisplayBothClocks();
11470         }
11471         if (gameMode == PlayFromGameFile) {
11472             if (appData.timeDelay >= 0)
11473                 AutoPlayGameLoop();
11474         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11475             Reset(FALSE, TRUE);
11476             SendToICS(ics_prefix);
11477             SendToICS("refresh\n");
11478         } else if (currentMove < forwardMostMove) {
11479             ForwardInner(forwardMostMove);
11480         }
11481         pauseExamInvalid = FALSE;
11482     } else {
11483         switch (gameMode) {
11484           default:
11485             return;
11486           case IcsExamining:
11487             pauseExamForwardMostMove = forwardMostMove;
11488             pauseExamInvalid = FALSE;
11489             /* fall through */
11490           case IcsObserving:
11491           case IcsPlayingWhite:
11492           case IcsPlayingBlack:
11493             pausing = TRUE;
11494             ModeHighlight();
11495             return;
11496           case PlayFromGameFile:
11497             (void) StopLoadGameTimer();
11498             pausing = TRUE;
11499             ModeHighlight();
11500             break;
11501           case BeginningOfGame:
11502             if (appData.icsActive) return;
11503             /* else fall through */
11504           case MachinePlaysWhite:
11505           case MachinePlaysBlack:
11506           case TwoMachinesPlay:
11507             if (forwardMostMove == 0)
11508               return;           /* don't pause if no one has moved */
11509             if ((gameMode == MachinePlaysWhite &&
11510                  !WhiteOnMove(forwardMostMove)) ||
11511                 (gameMode == MachinePlaysBlack &&
11512                  WhiteOnMove(forwardMostMove))) {
11513                 StopClocks();
11514             }
11515             pausing = TRUE;
11516             ModeHighlight();
11517             break;
11518         }
11519     }
11520 }
11521
11522 void
11523 EditCommentEvent()
11524 {
11525     char title[MSG_SIZ];
11526
11527     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11528       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11529     } else {
11530       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11531                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11532                parseList[currentMove - 1]);
11533     }
11534
11535     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11536 }
11537
11538
11539 void
11540 EditTagsEvent()
11541 {
11542     char *tags = PGNTags(&gameInfo);
11543     EditTagsPopUp(tags);
11544     free(tags);
11545 }
11546
11547 void
11548 AnalyzeModeEvent()
11549 {
11550     if (appData.noChessProgram || gameMode == AnalyzeMode)
11551       return;
11552
11553     if (gameMode != AnalyzeFile) {
11554         if (!appData.icsEngineAnalyze) {
11555                EditGameEvent();
11556                if (gameMode != EditGame) return;
11557         }
11558         ResurrectChessProgram();
11559         SendToProgram("analyze\n", &first);
11560         first.analyzing = TRUE;
11561         /*first.maybeThinking = TRUE;*/
11562         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11563         EngineOutputPopUp();
11564     }
11565     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11566     pausing = FALSE;
11567     ModeHighlight();
11568     SetGameInfo();
11569
11570     StartAnalysisClock();
11571     GetTimeMark(&lastNodeCountTime);
11572     lastNodeCount = 0;
11573 }
11574
11575 void
11576 AnalyzeFileEvent()
11577 {
11578     if (appData.noChessProgram || gameMode == AnalyzeFile)
11579       return;
11580
11581     if (gameMode != AnalyzeMode) {
11582         EditGameEvent();
11583         if (gameMode != EditGame) return;
11584         ResurrectChessProgram();
11585         SendToProgram("analyze\n", &first);
11586         first.analyzing = TRUE;
11587         /*first.maybeThinking = TRUE;*/
11588         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11589         EngineOutputPopUp();
11590     }
11591     gameMode = AnalyzeFile;
11592     pausing = FALSE;
11593     ModeHighlight();
11594     SetGameInfo();
11595
11596     StartAnalysisClock();
11597     GetTimeMark(&lastNodeCountTime);
11598     lastNodeCount = 0;
11599 }
11600
11601 void
11602 MachineWhiteEvent()
11603 {
11604     char buf[MSG_SIZ];
11605     char *bookHit = NULL;
11606
11607     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11608       return;
11609
11610
11611     if (gameMode == PlayFromGameFile ||
11612         gameMode == TwoMachinesPlay  ||
11613         gameMode == Training         ||
11614         gameMode == AnalyzeMode      ||
11615         gameMode == EndOfGame)
11616         EditGameEvent();
11617
11618     if (gameMode == EditPosition)
11619         EditPositionDone(TRUE);
11620
11621     if (!WhiteOnMove(currentMove)) {
11622         DisplayError(_("It is not White's turn"), 0);
11623         return;
11624     }
11625
11626     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11627       ExitAnalyzeMode();
11628
11629     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11630         gameMode == AnalyzeFile)
11631         TruncateGame();
11632
11633     ResurrectChessProgram();    /* in case it isn't running */
11634     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11635         gameMode = MachinePlaysWhite;
11636         ResetClocks();
11637     } else
11638     gameMode = MachinePlaysWhite;
11639     pausing = FALSE;
11640     ModeHighlight();
11641     SetGameInfo();
11642     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11643     DisplayTitle(buf);
11644     if (first.sendName) {
11645       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11646       SendToProgram(buf, &first);
11647     }
11648     if (first.sendTime) {
11649       if (first.useColors) {
11650         SendToProgram("black\n", &first); /*gnu kludge*/
11651       }
11652       SendTimeRemaining(&first, TRUE);
11653     }
11654     if (first.useColors) {
11655       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11656     }
11657     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11658     SetMachineThinkingEnables();
11659     first.maybeThinking = TRUE;
11660     StartClocks();
11661     firstMove = FALSE;
11662
11663     if (appData.autoFlipView && !flipView) {
11664       flipView = !flipView;
11665       DrawPosition(FALSE, NULL);
11666       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11667     }
11668
11669     if(bookHit) { // [HGM] book: simulate book reply
11670         static char bookMove[MSG_SIZ]; // a bit generous?
11671
11672         programStats.nodes = programStats.depth = programStats.time =
11673         programStats.score = programStats.got_only_move = 0;
11674         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11675
11676         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11677         strcat(bookMove, bookHit);
11678         HandleMachineMove(bookMove, &first);
11679     }
11680 }
11681
11682 void
11683 MachineBlackEvent()
11684 {
11685   char buf[MSG_SIZ];
11686   char *bookHit = NULL;
11687
11688     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11689         return;
11690
11691
11692     if (gameMode == PlayFromGameFile ||
11693         gameMode == TwoMachinesPlay  ||
11694         gameMode == Training         ||
11695         gameMode == AnalyzeMode      ||
11696         gameMode == EndOfGame)
11697         EditGameEvent();
11698
11699     if (gameMode == EditPosition)
11700         EditPositionDone(TRUE);
11701
11702     if (WhiteOnMove(currentMove)) {
11703         DisplayError(_("It is not Black's turn"), 0);
11704         return;
11705     }
11706
11707     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11708       ExitAnalyzeMode();
11709
11710     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11711         gameMode == AnalyzeFile)
11712         TruncateGame();
11713
11714     ResurrectChessProgram();    /* in case it isn't running */
11715     gameMode = MachinePlaysBlack;
11716     pausing = FALSE;
11717     ModeHighlight();
11718     SetGameInfo();
11719     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11720     DisplayTitle(buf);
11721     if (first.sendName) {
11722       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11723       SendToProgram(buf, &first);
11724     }
11725     if (first.sendTime) {
11726       if (first.useColors) {
11727         SendToProgram("white\n", &first); /*gnu kludge*/
11728       }
11729       SendTimeRemaining(&first, FALSE);
11730     }
11731     if (first.useColors) {
11732       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11733     }
11734     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11735     SetMachineThinkingEnables();
11736     first.maybeThinking = TRUE;
11737     StartClocks();
11738
11739     if (appData.autoFlipView && flipView) {
11740       flipView = !flipView;
11741       DrawPosition(FALSE, NULL);
11742       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11743     }
11744     if(bookHit) { // [HGM] book: simulate book reply
11745         static char bookMove[MSG_SIZ]; // a bit generous?
11746
11747         programStats.nodes = programStats.depth = programStats.time =
11748         programStats.score = programStats.got_only_move = 0;
11749         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11750
11751         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11752         strcat(bookMove, bookHit);
11753         HandleMachineMove(bookMove, &first);
11754     }
11755 }
11756
11757
11758 void
11759 DisplayTwoMachinesTitle()
11760 {
11761     char buf[MSG_SIZ];
11762     if (appData.matchGames > 0) {
11763         if (first.twoMachinesColor[0] == 'w') {
11764           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11765                    gameInfo.white, gameInfo.black,
11766                    first.matchWins, second.matchWins,
11767                    matchGame - 1 - (first.matchWins + second.matchWins));
11768         } else {
11769           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11770                    gameInfo.white, gameInfo.black,
11771                    second.matchWins, first.matchWins,
11772                    matchGame - 1 - (first.matchWins + second.matchWins));
11773         }
11774     } else {
11775       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11776     }
11777     DisplayTitle(buf);
11778 }
11779
11780 void
11781 TwoMachinesEvent P((void))
11782 {
11783     int i;
11784     char buf[MSG_SIZ];
11785     ChessProgramState *onmove;
11786     char *bookHit = NULL;
11787
11788     if (appData.noChessProgram) return;
11789
11790     switch (gameMode) {
11791       case TwoMachinesPlay:
11792         return;
11793       case MachinePlaysWhite:
11794       case MachinePlaysBlack:
11795         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11796             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11797             return;
11798         }
11799         /* fall through */
11800       case BeginningOfGame:
11801       case PlayFromGameFile:
11802       case EndOfGame:
11803         EditGameEvent();
11804         if (gameMode != EditGame) return;
11805         break;
11806       case EditPosition:
11807         EditPositionDone(TRUE);
11808         break;
11809       case AnalyzeMode:
11810       case AnalyzeFile:
11811         ExitAnalyzeMode();
11812         break;
11813       case EditGame:
11814       default:
11815         break;
11816     }
11817
11818 //    forwardMostMove = currentMove;
11819     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11820     ResurrectChessProgram();    /* in case first program isn't running */
11821
11822     if (second.pr == NULL) {
11823         StartChessProgram(&second);
11824         if (second.protocolVersion == 1) {
11825           TwoMachinesEventIfReady();
11826         } else {
11827           /* kludge: allow timeout for initial "feature" command */
11828           FreezeUI();
11829           DisplayMessage("", _("Starting second chess program"));
11830           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11831         }
11832         return;
11833     }
11834     DisplayMessage("", "");
11835     InitChessProgram(&second, FALSE);
11836     SendToProgram("force\n", &second);
11837     if (startedFromSetupPosition) {
11838         SendBoard(&second, backwardMostMove);
11839     if (appData.debugMode) {
11840         fprintf(debugFP, "Two Machines\n");
11841     }
11842     }
11843     for (i = backwardMostMove; i < forwardMostMove; i++) {
11844         SendMoveToProgram(i, &second);
11845     }
11846
11847     gameMode = TwoMachinesPlay;
11848     pausing = FALSE;
11849     ModeHighlight();
11850     SetGameInfo();
11851     DisplayTwoMachinesTitle();
11852     firstMove = TRUE;
11853     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11854         onmove = &first;
11855     } else {
11856         onmove = &second;
11857     }
11858
11859     SendToProgram(first.computerString, &first);
11860     if (first.sendName) {
11861       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11862       SendToProgram(buf, &first);
11863     }
11864     SendToProgram(second.computerString, &second);
11865     if (second.sendName) {
11866       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11867       SendToProgram(buf, &second);
11868     }
11869
11870     ResetClocks();
11871     if (!first.sendTime || !second.sendTime) {
11872         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11873         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11874     }
11875     if (onmove->sendTime) {
11876       if (onmove->useColors) {
11877         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11878       }
11879       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11880     }
11881     if (onmove->useColors) {
11882       SendToProgram(onmove->twoMachinesColor, onmove);
11883     }
11884     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11885 //    SendToProgram("go\n", onmove);
11886     onmove->maybeThinking = TRUE;
11887     SetMachineThinkingEnables();
11888
11889     StartClocks();
11890
11891     if(bookHit) { // [HGM] book: simulate book reply
11892         static char bookMove[MSG_SIZ]; // a bit generous?
11893
11894         programStats.nodes = programStats.depth = programStats.time =
11895         programStats.score = programStats.got_only_move = 0;
11896         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11897
11898         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11899         strcat(bookMove, bookHit);
11900         savedMessage = bookMove; // args for deferred call
11901         savedState = onmove;
11902         ScheduleDelayedEvent(DeferredBookMove, 1);
11903     }
11904 }
11905
11906 void
11907 TrainingEvent()
11908 {
11909     if (gameMode == Training) {
11910       SetTrainingModeOff();
11911       gameMode = PlayFromGameFile;
11912       DisplayMessage("", _("Training mode off"));
11913     } else {
11914       gameMode = Training;
11915       animateTraining = appData.animate;
11916
11917       /* make sure we are not already at the end of the game */
11918       if (currentMove < forwardMostMove) {
11919         SetTrainingModeOn();
11920         DisplayMessage("", _("Training mode on"));
11921       } else {
11922         gameMode = PlayFromGameFile;
11923         DisplayError(_("Already at end of game"), 0);
11924       }
11925     }
11926     ModeHighlight();
11927 }
11928
11929 void
11930 IcsClientEvent()
11931 {
11932     if (!appData.icsActive) return;
11933     switch (gameMode) {
11934       case IcsPlayingWhite:
11935       case IcsPlayingBlack:
11936       case IcsObserving:
11937       case IcsIdle:
11938       case BeginningOfGame:
11939       case IcsExamining:
11940         return;
11941
11942       case EditGame:
11943         break;
11944
11945       case EditPosition:
11946         EditPositionDone(TRUE);
11947         break;
11948
11949       case AnalyzeMode:
11950       case AnalyzeFile:
11951         ExitAnalyzeMode();
11952         break;
11953
11954       default:
11955         EditGameEvent();
11956         break;
11957     }
11958
11959     gameMode = IcsIdle;
11960     ModeHighlight();
11961     return;
11962 }
11963
11964
11965 void
11966 EditGameEvent()
11967 {
11968     int i;
11969
11970     switch (gameMode) {
11971       case Training:
11972         SetTrainingModeOff();
11973         break;
11974       case MachinePlaysWhite:
11975       case MachinePlaysBlack:
11976       case BeginningOfGame:
11977         SendToProgram("force\n", &first);
11978         SetUserThinkingEnables();
11979         break;
11980       case PlayFromGameFile:
11981         (void) StopLoadGameTimer();
11982         if (gameFileFP != NULL) {
11983             gameFileFP = NULL;
11984         }
11985         break;
11986       case EditPosition:
11987         EditPositionDone(TRUE);
11988         break;
11989       case AnalyzeMode:
11990       case AnalyzeFile:
11991         ExitAnalyzeMode();
11992         SendToProgram("force\n", &first);
11993         break;
11994       case TwoMachinesPlay:
11995         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11996         ResurrectChessProgram();
11997         SetUserThinkingEnables();
11998         break;
11999       case EndOfGame:
12000         ResurrectChessProgram();
12001         break;
12002       case IcsPlayingBlack:
12003       case IcsPlayingWhite:
12004         DisplayError(_("Warning: You are still playing a game"), 0);
12005         break;
12006       case IcsObserving:
12007         DisplayError(_("Warning: You are still observing a game"), 0);
12008         break;
12009       case IcsExamining:
12010         DisplayError(_("Warning: You are still examining a game"), 0);
12011         break;
12012       case IcsIdle:
12013         break;
12014       case EditGame:
12015       default:
12016         return;
12017     }
12018
12019     pausing = FALSE;
12020     StopClocks();
12021     first.offeredDraw = second.offeredDraw = 0;
12022
12023     if (gameMode == PlayFromGameFile) {
12024         whiteTimeRemaining = timeRemaining[0][currentMove];
12025         blackTimeRemaining = timeRemaining[1][currentMove];
12026         DisplayTitle("");
12027     }
12028
12029     if (gameMode == MachinePlaysWhite ||
12030         gameMode == MachinePlaysBlack ||
12031         gameMode == TwoMachinesPlay ||
12032         gameMode == EndOfGame) {
12033         i = forwardMostMove;
12034         while (i > currentMove) {
12035             SendToProgram("undo\n", &first);
12036             i--;
12037         }
12038         whiteTimeRemaining = timeRemaining[0][currentMove];
12039         blackTimeRemaining = timeRemaining[1][currentMove];
12040         DisplayBothClocks();
12041         if (whiteFlag || blackFlag) {
12042             whiteFlag = blackFlag = 0;
12043         }
12044         DisplayTitle("");
12045     }
12046
12047     gameMode = EditGame;
12048     ModeHighlight();
12049     SetGameInfo();
12050 }
12051
12052
12053 void
12054 EditPositionEvent()
12055 {
12056     if (gameMode == EditPosition) {
12057         EditGameEvent();
12058         return;
12059     }
12060
12061     EditGameEvent();
12062     if (gameMode != EditGame) return;
12063
12064     gameMode = EditPosition;
12065     ModeHighlight();
12066     SetGameInfo();
12067     if (currentMove > 0)
12068       CopyBoard(boards[0], boards[currentMove]);
12069
12070     blackPlaysFirst = !WhiteOnMove(currentMove);
12071     ResetClocks();
12072     currentMove = forwardMostMove = backwardMostMove = 0;
12073     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12074     DisplayMove(-1);
12075 }
12076
12077 void
12078 ExitAnalyzeMode()
12079 {
12080     /* [DM] icsEngineAnalyze - possible call from other functions */
12081     if (appData.icsEngineAnalyze) {
12082         appData.icsEngineAnalyze = FALSE;
12083
12084         DisplayMessage("",_("Close ICS engine analyze..."));
12085     }
12086     if (first.analysisSupport && first.analyzing) {
12087       SendToProgram("exit\n", &first);
12088       first.analyzing = FALSE;
12089     }
12090     thinkOutput[0] = NULLCHAR;
12091 }
12092
12093 void
12094 EditPositionDone(Boolean fakeRights)
12095 {
12096     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12097
12098     startedFromSetupPosition = TRUE;
12099     InitChessProgram(&first, FALSE);
12100     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12101       boards[0][EP_STATUS] = EP_NONE;
12102       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12103     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12104         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12105         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12106       } else boards[0][CASTLING][2] = NoRights;
12107     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12108         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12109         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12110       } else boards[0][CASTLING][5] = NoRights;
12111     }
12112     SendToProgram("force\n", &first);
12113     if (blackPlaysFirst) {
12114         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12115         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12116         currentMove = forwardMostMove = backwardMostMove = 1;
12117         CopyBoard(boards[1], boards[0]);
12118     } else {
12119         currentMove = forwardMostMove = backwardMostMove = 0;
12120     }
12121     SendBoard(&first, forwardMostMove);
12122     if (appData.debugMode) {
12123         fprintf(debugFP, "EditPosDone\n");
12124     }
12125     DisplayTitle("");
12126     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12127     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12128     gameMode = EditGame;
12129     ModeHighlight();
12130     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12131     ClearHighlights(); /* [AS] */
12132 }
12133
12134 /* Pause for `ms' milliseconds */
12135 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12136 void
12137 TimeDelay(ms)
12138      long ms;
12139 {
12140     TimeMark m1, m2;
12141
12142     GetTimeMark(&m1);
12143     do {
12144         GetTimeMark(&m2);
12145     } while (SubtractTimeMarks(&m2, &m1) < ms);
12146 }
12147
12148 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12149 void
12150 SendMultiLineToICS(buf)
12151      char *buf;
12152 {
12153     char temp[MSG_SIZ+1], *p;
12154     int len;
12155
12156     len = strlen(buf);
12157     if (len > MSG_SIZ)
12158       len = MSG_SIZ;
12159
12160     strncpy(temp, buf, len);
12161     temp[len] = 0;
12162
12163     p = temp;
12164     while (*p) {
12165         if (*p == '\n' || *p == '\r')
12166           *p = ' ';
12167         ++p;
12168     }
12169
12170     strcat(temp, "\n");
12171     SendToICS(temp);
12172     SendToPlayer(temp, strlen(temp));
12173 }
12174
12175 void
12176 SetWhiteToPlayEvent()
12177 {
12178     if (gameMode == EditPosition) {
12179         blackPlaysFirst = FALSE;
12180         DisplayBothClocks();    /* works because currentMove is 0 */
12181     } else if (gameMode == IcsExamining) {
12182         SendToICS(ics_prefix);
12183         SendToICS("tomove white\n");
12184     }
12185 }
12186
12187 void
12188 SetBlackToPlayEvent()
12189 {
12190     if (gameMode == EditPosition) {
12191         blackPlaysFirst = TRUE;
12192         currentMove = 1;        /* kludge */
12193         DisplayBothClocks();
12194         currentMove = 0;
12195     } else if (gameMode == IcsExamining) {
12196         SendToICS(ics_prefix);
12197         SendToICS("tomove black\n");
12198     }
12199 }
12200
12201 void
12202 EditPositionMenuEvent(selection, x, y)
12203      ChessSquare selection;
12204      int x, y;
12205 {
12206     char buf[MSG_SIZ];
12207     ChessSquare piece = boards[0][y][x];
12208
12209     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12210
12211     switch (selection) {
12212       case ClearBoard:
12213         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12214             SendToICS(ics_prefix);
12215             SendToICS("bsetup clear\n");
12216         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12217             SendToICS(ics_prefix);
12218             SendToICS("clearboard\n");
12219         } else {
12220             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12221                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12222                 for (y = 0; y < BOARD_HEIGHT; y++) {
12223                     if (gameMode == IcsExamining) {
12224                         if (boards[currentMove][y][x] != EmptySquare) {
12225                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12226                                     AAA + x, ONE + y);
12227                             SendToICS(buf);
12228                         }
12229                     } else {
12230                         boards[0][y][x] = p;
12231                     }
12232                 }
12233             }
12234         }
12235         if (gameMode == EditPosition) {
12236             DrawPosition(FALSE, boards[0]);
12237         }
12238         break;
12239
12240       case WhitePlay:
12241         SetWhiteToPlayEvent();
12242         break;
12243
12244       case BlackPlay:
12245         SetBlackToPlayEvent();
12246         break;
12247
12248       case EmptySquare:
12249         if (gameMode == IcsExamining) {
12250             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12251             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12252             SendToICS(buf);
12253         } else {
12254             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12255                 if(x == BOARD_LEFT-2) {
12256                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12257                     boards[0][y][1] = 0;
12258                 } else
12259                 if(x == BOARD_RGHT+1) {
12260                     if(y >= gameInfo.holdingsSize) break;
12261                     boards[0][y][BOARD_WIDTH-2] = 0;
12262                 } else break;
12263             }
12264             boards[0][y][x] = EmptySquare;
12265             DrawPosition(FALSE, boards[0]);
12266         }
12267         break;
12268
12269       case PromotePiece:
12270         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12271            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12272             selection = (ChessSquare) (PROMOTED piece);
12273         } else if(piece == EmptySquare) selection = WhiteSilver;
12274         else selection = (ChessSquare)((int)piece - 1);
12275         goto defaultlabel;
12276
12277       case DemotePiece:
12278         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12279            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12280             selection = (ChessSquare) (DEMOTED piece);
12281         } else if(piece == EmptySquare) selection = BlackSilver;
12282         else selection = (ChessSquare)((int)piece + 1);
12283         goto defaultlabel;
12284
12285       case WhiteQueen:
12286       case BlackQueen:
12287         if(gameInfo.variant == VariantShatranj ||
12288            gameInfo.variant == VariantXiangqi  ||
12289            gameInfo.variant == VariantCourier  ||
12290            gameInfo.variant == VariantMakruk     )
12291             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12292         goto defaultlabel;
12293
12294       case WhiteKing:
12295       case BlackKing:
12296         if(gameInfo.variant == VariantXiangqi)
12297             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12298         if(gameInfo.variant == VariantKnightmate)
12299             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12300       default:
12301         defaultlabel:
12302         if (gameMode == IcsExamining) {
12303             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12304             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12305                      PieceToChar(selection), AAA + x, ONE + y);
12306             SendToICS(buf);
12307         } else {
12308             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12309                 int n;
12310                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12311                     n = PieceToNumber(selection - BlackPawn);
12312                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12313                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12314                     boards[0][BOARD_HEIGHT-1-n][1]++;
12315                 } else
12316                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12317                     n = PieceToNumber(selection);
12318                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12319                     boards[0][n][BOARD_WIDTH-1] = selection;
12320                     boards[0][n][BOARD_WIDTH-2]++;
12321                 }
12322             } else
12323             boards[0][y][x] = selection;
12324             DrawPosition(TRUE, boards[0]);
12325         }
12326         break;
12327     }
12328 }
12329
12330
12331 void
12332 DropMenuEvent(selection, x, y)
12333      ChessSquare selection;
12334      int x, y;
12335 {
12336     ChessMove moveType;
12337
12338     switch (gameMode) {
12339       case IcsPlayingWhite:
12340       case MachinePlaysBlack:
12341         if (!WhiteOnMove(currentMove)) {
12342             DisplayMoveError(_("It is Black's turn"));
12343             return;
12344         }
12345         moveType = WhiteDrop;
12346         break;
12347       case IcsPlayingBlack:
12348       case MachinePlaysWhite:
12349         if (WhiteOnMove(currentMove)) {
12350             DisplayMoveError(_("It is White's turn"));
12351             return;
12352         }
12353         moveType = BlackDrop;
12354         break;
12355       case EditGame:
12356         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12357         break;
12358       default:
12359         return;
12360     }
12361
12362     if (moveType == BlackDrop && selection < BlackPawn) {
12363       selection = (ChessSquare) ((int) selection
12364                                  + (int) BlackPawn - (int) WhitePawn);
12365     }
12366     if (boards[currentMove][y][x] != EmptySquare) {
12367         DisplayMoveError(_("That square is occupied"));
12368         return;
12369     }
12370
12371     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12372 }
12373
12374 void
12375 AcceptEvent()
12376 {
12377     /* Accept a pending offer of any kind from opponent */
12378
12379     if (appData.icsActive) {
12380         SendToICS(ics_prefix);
12381         SendToICS("accept\n");
12382     } else if (cmailMsgLoaded) {
12383         if (currentMove == cmailOldMove &&
12384             commentList[cmailOldMove] != NULL &&
12385             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12386                    "Black offers a draw" : "White offers a draw")) {
12387             TruncateGame();
12388             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12389             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12390         } else {
12391             DisplayError(_("There is no pending offer on this move"), 0);
12392             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12393         }
12394     } else {
12395         /* Not used for offers from chess program */
12396     }
12397 }
12398
12399 void
12400 DeclineEvent()
12401 {
12402     /* Decline a pending offer of any kind from opponent */
12403
12404     if (appData.icsActive) {
12405         SendToICS(ics_prefix);
12406         SendToICS("decline\n");
12407     } else if (cmailMsgLoaded) {
12408         if (currentMove == cmailOldMove &&
12409             commentList[cmailOldMove] != NULL &&
12410             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12411                    "Black offers a draw" : "White offers a draw")) {
12412 #ifdef NOTDEF
12413             AppendComment(cmailOldMove, "Draw declined", TRUE);
12414             DisplayComment(cmailOldMove - 1, "Draw declined");
12415 #endif /*NOTDEF*/
12416         } else {
12417             DisplayError(_("There is no pending offer on this move"), 0);
12418         }
12419     } else {
12420         /* Not used for offers from chess program */
12421     }
12422 }
12423
12424 void
12425 RematchEvent()
12426 {
12427     /* Issue ICS rematch command */
12428     if (appData.icsActive) {
12429         SendToICS(ics_prefix);
12430         SendToICS("rematch\n");
12431     }
12432 }
12433
12434 void
12435 CallFlagEvent()
12436 {
12437     /* Call your opponent's flag (claim a win on time) */
12438     if (appData.icsActive) {
12439         SendToICS(ics_prefix);
12440         SendToICS("flag\n");
12441     } else {
12442         switch (gameMode) {
12443           default:
12444             return;
12445           case MachinePlaysWhite:
12446             if (whiteFlag) {
12447                 if (blackFlag)
12448                   GameEnds(GameIsDrawn, "Both players ran out of time",
12449                            GE_PLAYER);
12450                 else
12451                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12452             } else {
12453                 DisplayError(_("Your opponent is not out of time"), 0);
12454             }
12455             break;
12456           case MachinePlaysBlack:
12457             if (blackFlag) {
12458                 if (whiteFlag)
12459                   GameEnds(GameIsDrawn, "Both players ran out of time",
12460                            GE_PLAYER);
12461                 else
12462                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12463             } else {
12464                 DisplayError(_("Your opponent is not out of time"), 0);
12465             }
12466             break;
12467         }
12468     }
12469 }
12470
12471 void
12472 DrawEvent()
12473 {
12474     /* Offer draw or accept pending draw offer from opponent */
12475
12476     if (appData.icsActive) {
12477         /* Note: tournament rules require draw offers to be
12478            made after you make your move but before you punch
12479            your clock.  Currently ICS doesn't let you do that;
12480            instead, you immediately punch your clock after making
12481            a move, but you can offer a draw at any time. */
12482
12483         SendToICS(ics_prefix);
12484         SendToICS("draw\n");
12485         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12486     } else if (cmailMsgLoaded) {
12487         if (currentMove == cmailOldMove &&
12488             commentList[cmailOldMove] != NULL &&
12489             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12490                    "Black offers a draw" : "White offers a draw")) {
12491             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12492             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12493         } else if (currentMove == cmailOldMove + 1) {
12494             char *offer = WhiteOnMove(cmailOldMove) ?
12495               "White offers a draw" : "Black offers a draw";
12496             AppendComment(currentMove, offer, TRUE);
12497             DisplayComment(currentMove - 1, offer);
12498             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12499         } else {
12500             DisplayError(_("You must make your move before offering a draw"), 0);
12501             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12502         }
12503     } else if (first.offeredDraw) {
12504         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12505     } else {
12506         if (first.sendDrawOffers) {
12507             SendToProgram("draw\n", &first);
12508             userOfferedDraw = TRUE;
12509         }
12510     }
12511 }
12512
12513 void
12514 AdjournEvent()
12515 {
12516     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12517
12518     if (appData.icsActive) {
12519         SendToICS(ics_prefix);
12520         SendToICS("adjourn\n");
12521     } else {
12522         /* Currently GNU Chess doesn't offer or accept Adjourns */
12523     }
12524 }
12525
12526
12527 void
12528 AbortEvent()
12529 {
12530     /* Offer Abort or accept pending Abort offer from opponent */
12531
12532     if (appData.icsActive) {
12533         SendToICS(ics_prefix);
12534         SendToICS("abort\n");
12535     } else {
12536         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12537     }
12538 }
12539
12540 void
12541 ResignEvent()
12542 {
12543     /* Resign.  You can do this even if it's not your turn. */
12544
12545     if (appData.icsActive) {
12546         SendToICS(ics_prefix);
12547         SendToICS("resign\n");
12548     } else {
12549         switch (gameMode) {
12550           case MachinePlaysWhite:
12551             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12552             break;
12553           case MachinePlaysBlack:
12554             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12555             break;
12556           case EditGame:
12557             if (cmailMsgLoaded) {
12558                 TruncateGame();
12559                 if (WhiteOnMove(cmailOldMove)) {
12560                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12561                 } else {
12562                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12563                 }
12564                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12565             }
12566             break;
12567           default:
12568             break;
12569         }
12570     }
12571 }
12572
12573
12574 void
12575 StopObservingEvent()
12576 {
12577     /* Stop observing current games */
12578     SendToICS(ics_prefix);
12579     SendToICS("unobserve\n");
12580 }
12581
12582 void
12583 StopExaminingEvent()
12584 {
12585     /* Stop observing current game */
12586     SendToICS(ics_prefix);
12587     SendToICS("unexamine\n");
12588 }
12589
12590 void
12591 ForwardInner(target)
12592      int target;
12593 {
12594     int limit;
12595
12596     if (appData.debugMode)
12597         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12598                 target, currentMove, forwardMostMove);
12599
12600     if (gameMode == EditPosition)
12601       return;
12602
12603     if (gameMode == PlayFromGameFile && !pausing)
12604       PauseEvent();
12605
12606     if (gameMode == IcsExamining && pausing)
12607       limit = pauseExamForwardMostMove;
12608     else
12609       limit = forwardMostMove;
12610
12611     if (target > limit) target = limit;
12612
12613     if (target > 0 && moveList[target - 1][0]) {
12614         int fromX, fromY, toX, toY;
12615         toX = moveList[target - 1][2] - AAA;
12616         toY = moveList[target - 1][3] - ONE;
12617         if (moveList[target - 1][1] == '@') {
12618             if (appData.highlightLastMove) {
12619                 SetHighlights(-1, -1, toX, toY);
12620             }
12621         } else {
12622             fromX = moveList[target - 1][0] - AAA;
12623             fromY = moveList[target - 1][1] - ONE;
12624             if (target == currentMove + 1) {
12625                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12626             }
12627             if (appData.highlightLastMove) {
12628                 SetHighlights(fromX, fromY, toX, toY);
12629             }
12630         }
12631     }
12632     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12633         gameMode == Training || gameMode == PlayFromGameFile ||
12634         gameMode == AnalyzeFile) {
12635         while (currentMove < target) {
12636             SendMoveToProgram(currentMove++, &first);
12637         }
12638     } else {
12639         currentMove = target;
12640     }
12641
12642     if (gameMode == EditGame || gameMode == EndOfGame) {
12643         whiteTimeRemaining = timeRemaining[0][currentMove];
12644         blackTimeRemaining = timeRemaining[1][currentMove];
12645     }
12646     DisplayBothClocks();
12647     DisplayMove(currentMove - 1);
12648     DrawPosition(FALSE, boards[currentMove]);
12649     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12650     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12651         DisplayComment(currentMove - 1, commentList[currentMove]);
12652     }
12653 }
12654
12655
12656 void
12657 ForwardEvent()
12658 {
12659     if (gameMode == IcsExamining && !pausing) {
12660         SendToICS(ics_prefix);
12661         SendToICS("forward\n");
12662     } else {
12663         ForwardInner(currentMove + 1);
12664     }
12665 }
12666
12667 void
12668 ToEndEvent()
12669 {
12670     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12671         /* to optimze, we temporarily turn off analysis mode while we feed
12672          * the remaining moves to the engine. Otherwise we get analysis output
12673          * after each move.
12674          */
12675         if (first.analysisSupport) {
12676           SendToProgram("exit\nforce\n", &first);
12677           first.analyzing = FALSE;
12678         }
12679     }
12680
12681     if (gameMode == IcsExamining && !pausing) {
12682         SendToICS(ics_prefix);
12683         SendToICS("forward 999999\n");
12684     } else {
12685         ForwardInner(forwardMostMove);
12686     }
12687
12688     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12689         /* we have fed all the moves, so reactivate analysis mode */
12690         SendToProgram("analyze\n", &first);
12691         first.analyzing = TRUE;
12692         /*first.maybeThinking = TRUE;*/
12693         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12694     }
12695 }
12696
12697 void
12698 BackwardInner(target)
12699      int target;
12700 {
12701     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12702
12703     if (appData.debugMode)
12704         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12705                 target, currentMove, forwardMostMove);
12706
12707     if (gameMode == EditPosition) return;
12708     if (currentMove <= backwardMostMove) {
12709         ClearHighlights();
12710         DrawPosition(full_redraw, boards[currentMove]);
12711         return;
12712     }
12713     if (gameMode == PlayFromGameFile && !pausing)
12714       PauseEvent();
12715
12716     if (moveList[target][0]) {
12717         int fromX, fromY, toX, toY;
12718         toX = moveList[target][2] - AAA;
12719         toY = moveList[target][3] - ONE;
12720         if (moveList[target][1] == '@') {
12721             if (appData.highlightLastMove) {
12722                 SetHighlights(-1, -1, toX, toY);
12723             }
12724         } else {
12725             fromX = moveList[target][0] - AAA;
12726             fromY = moveList[target][1] - ONE;
12727             if (target == currentMove - 1) {
12728                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12729             }
12730             if (appData.highlightLastMove) {
12731                 SetHighlights(fromX, fromY, toX, toY);
12732             }
12733         }
12734     }
12735     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12736         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12737         while (currentMove > target) {
12738             SendToProgram("undo\n", &first);
12739             currentMove--;
12740         }
12741     } else {
12742         currentMove = target;
12743     }
12744
12745     if (gameMode == EditGame || gameMode == EndOfGame) {
12746         whiteTimeRemaining = timeRemaining[0][currentMove];
12747         blackTimeRemaining = timeRemaining[1][currentMove];
12748     }
12749     DisplayBothClocks();
12750     DisplayMove(currentMove - 1);
12751     DrawPosition(full_redraw, boards[currentMove]);
12752     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12753     // [HGM] PV info: routine tests if comment empty
12754     DisplayComment(currentMove - 1, commentList[currentMove]);
12755 }
12756
12757 void
12758 BackwardEvent()
12759 {
12760     if (gameMode == IcsExamining && !pausing) {
12761         SendToICS(ics_prefix);
12762         SendToICS("backward\n");
12763     } else {
12764         BackwardInner(currentMove - 1);
12765     }
12766 }
12767
12768 void
12769 ToStartEvent()
12770 {
12771     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12772         /* to optimize, we temporarily turn off analysis mode while we undo
12773          * all the moves. Otherwise we get analysis output after each undo.
12774          */
12775         if (first.analysisSupport) {
12776           SendToProgram("exit\nforce\n", &first);
12777           first.analyzing = FALSE;
12778         }
12779     }
12780
12781     if (gameMode == IcsExamining && !pausing) {
12782         SendToICS(ics_prefix);
12783         SendToICS("backward 999999\n");
12784     } else {
12785         BackwardInner(backwardMostMove);
12786     }
12787
12788     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12789         /* we have fed all the moves, so reactivate analysis mode */
12790         SendToProgram("analyze\n", &first);
12791         first.analyzing = TRUE;
12792         /*first.maybeThinking = TRUE;*/
12793         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12794     }
12795 }
12796
12797 void
12798 ToNrEvent(int to)
12799 {
12800   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12801   if (to >= forwardMostMove) to = forwardMostMove;
12802   if (to <= backwardMostMove) to = backwardMostMove;
12803   if (to < currentMove) {
12804     BackwardInner(to);
12805   } else {
12806     ForwardInner(to);
12807   }
12808 }
12809
12810 void
12811 RevertEvent(Boolean annotate)
12812 {
12813     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12814         return;
12815     }
12816     if (gameMode != IcsExamining) {
12817         DisplayError(_("You are not examining a game"), 0);
12818         return;
12819     }
12820     if (pausing) {
12821         DisplayError(_("You can't revert while pausing"), 0);
12822         return;
12823     }
12824     SendToICS(ics_prefix);
12825     SendToICS("revert\n");
12826 }
12827
12828 void
12829 RetractMoveEvent()
12830 {
12831     switch (gameMode) {
12832       case MachinePlaysWhite:
12833       case MachinePlaysBlack:
12834         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12835             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12836             return;
12837         }
12838         if (forwardMostMove < 2) return;
12839         currentMove = forwardMostMove = forwardMostMove - 2;
12840         whiteTimeRemaining = timeRemaining[0][currentMove];
12841         blackTimeRemaining = timeRemaining[1][currentMove];
12842         DisplayBothClocks();
12843         DisplayMove(currentMove - 1);
12844         ClearHighlights();/*!! could figure this out*/
12845         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12846         SendToProgram("remove\n", &first);
12847         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12848         break;
12849
12850       case BeginningOfGame:
12851       default:
12852         break;
12853
12854       case IcsPlayingWhite:
12855       case IcsPlayingBlack:
12856         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12857             SendToICS(ics_prefix);
12858             SendToICS("takeback 2\n");
12859         } else {
12860             SendToICS(ics_prefix);
12861             SendToICS("takeback 1\n");
12862         }
12863         break;
12864     }
12865 }
12866
12867 void
12868 MoveNowEvent()
12869 {
12870     ChessProgramState *cps;
12871
12872     switch (gameMode) {
12873       case MachinePlaysWhite:
12874         if (!WhiteOnMove(forwardMostMove)) {
12875             DisplayError(_("It is your turn"), 0);
12876             return;
12877         }
12878         cps = &first;
12879         break;
12880       case MachinePlaysBlack:
12881         if (WhiteOnMove(forwardMostMove)) {
12882             DisplayError(_("It is your turn"), 0);
12883             return;
12884         }
12885         cps = &first;
12886         break;
12887       case TwoMachinesPlay:
12888         if (WhiteOnMove(forwardMostMove) ==
12889             (first.twoMachinesColor[0] == 'w')) {
12890             cps = &first;
12891         } else {
12892             cps = &second;
12893         }
12894         break;
12895       case BeginningOfGame:
12896       default:
12897         return;
12898     }
12899     SendToProgram("?\n", cps);
12900 }
12901
12902 void
12903 TruncateGameEvent()
12904 {
12905     EditGameEvent();
12906     if (gameMode != EditGame) return;
12907     TruncateGame();
12908 }
12909
12910 void
12911 TruncateGame()
12912 {
12913     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12914     if (forwardMostMove > currentMove) {
12915         if (gameInfo.resultDetails != NULL) {
12916             free(gameInfo.resultDetails);
12917             gameInfo.resultDetails = NULL;
12918             gameInfo.result = GameUnfinished;
12919         }
12920         forwardMostMove = currentMove;
12921         HistorySet(parseList, backwardMostMove, forwardMostMove,
12922                    currentMove-1);
12923     }
12924 }
12925
12926 void
12927 HintEvent()
12928 {
12929     if (appData.noChessProgram) return;
12930     switch (gameMode) {
12931       case MachinePlaysWhite:
12932         if (WhiteOnMove(forwardMostMove)) {
12933             DisplayError(_("Wait until your turn"), 0);
12934             return;
12935         }
12936         break;
12937       case BeginningOfGame:
12938       case MachinePlaysBlack:
12939         if (!WhiteOnMove(forwardMostMove)) {
12940             DisplayError(_("Wait until your turn"), 0);
12941             return;
12942         }
12943         break;
12944       default:
12945         DisplayError(_("No hint available"), 0);
12946         return;
12947     }
12948     SendToProgram("hint\n", &first);
12949     hintRequested = TRUE;
12950 }
12951
12952 void
12953 BookEvent()
12954 {
12955     if (appData.noChessProgram) return;
12956     switch (gameMode) {
12957       case MachinePlaysWhite:
12958         if (WhiteOnMove(forwardMostMove)) {
12959             DisplayError(_("Wait until your turn"), 0);
12960             return;
12961         }
12962         break;
12963       case BeginningOfGame:
12964       case MachinePlaysBlack:
12965         if (!WhiteOnMove(forwardMostMove)) {
12966             DisplayError(_("Wait until your turn"), 0);
12967             return;
12968         }
12969         break;
12970       case EditPosition:
12971         EditPositionDone(TRUE);
12972         break;
12973       case TwoMachinesPlay:
12974         return;
12975       default:
12976         break;
12977     }
12978     SendToProgram("bk\n", &first);
12979     bookOutput[0] = NULLCHAR;
12980     bookRequested = TRUE;
12981 }
12982
12983 void
12984 AboutGameEvent()
12985 {
12986     char *tags = PGNTags(&gameInfo);
12987     TagsPopUp(tags, CmailMsg());
12988     free(tags);
12989 }
12990
12991 /* end button procedures */
12992
12993 void
12994 PrintPosition(fp, move)
12995      FILE *fp;
12996      int move;
12997 {
12998     int i, j;
12999
13000     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13001         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13002             char c = PieceToChar(boards[move][i][j]);
13003             fputc(c == 'x' ? '.' : c, fp);
13004             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13005         }
13006     }
13007     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13008       fprintf(fp, "white to play\n");
13009     else
13010       fprintf(fp, "black to play\n");
13011 }
13012
13013 void
13014 PrintOpponents(fp)
13015      FILE *fp;
13016 {
13017     if (gameInfo.white != NULL) {
13018         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13019     } else {
13020         fprintf(fp, "\n");
13021     }
13022 }
13023
13024 /* Find last component of program's own name, using some heuristics */
13025 void
13026 TidyProgramName(prog, host, buf)
13027      char *prog, *host, buf[MSG_SIZ];
13028 {
13029     char *p, *q;
13030     int local = (strcmp(host, "localhost") == 0);
13031     while (!local && (p = strchr(prog, ';')) != NULL) {
13032         p++;
13033         while (*p == ' ') p++;
13034         prog = p;
13035     }
13036     if (*prog == '"' || *prog == '\'') {
13037         q = strchr(prog + 1, *prog);
13038     } else {
13039         q = strchr(prog, ' ');
13040     }
13041     if (q == NULL) q = prog + strlen(prog);
13042     p = q;
13043     while (p >= prog && *p != '/' && *p != '\\') p--;
13044     p++;
13045     if(p == prog && *p == '"') p++;
13046     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13047     memcpy(buf, p, q - p);
13048     buf[q - p] = NULLCHAR;
13049     if (!local) {
13050         strcat(buf, "@");
13051         strcat(buf, host);
13052     }
13053 }
13054
13055 char *
13056 TimeControlTagValue()
13057 {
13058     char buf[MSG_SIZ];
13059     if (!appData.clockMode) {
13060       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13061     } else if (movesPerSession > 0) {
13062       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13063     } else if (timeIncrement == 0) {
13064       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13065     } else {
13066       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13067     }
13068     return StrSave(buf);
13069 }
13070
13071 void
13072 SetGameInfo()
13073 {
13074     /* This routine is used only for certain modes */
13075     VariantClass v = gameInfo.variant;
13076     ChessMove r = GameUnfinished;
13077     char *p = NULL;
13078
13079     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13080         r = gameInfo.result;
13081         p = gameInfo.resultDetails;
13082         gameInfo.resultDetails = NULL;
13083     }
13084     ClearGameInfo(&gameInfo);
13085     gameInfo.variant = v;
13086
13087     switch (gameMode) {
13088       case MachinePlaysWhite:
13089         gameInfo.event = StrSave( appData.pgnEventHeader );
13090         gameInfo.site = StrSave(HostName());
13091         gameInfo.date = PGNDate();
13092         gameInfo.round = StrSave("-");
13093         gameInfo.white = StrSave(first.tidy);
13094         gameInfo.black = StrSave(UserName());
13095         gameInfo.timeControl = TimeControlTagValue();
13096         break;
13097
13098       case MachinePlaysBlack:
13099         gameInfo.event = StrSave( appData.pgnEventHeader );
13100         gameInfo.site = StrSave(HostName());
13101         gameInfo.date = PGNDate();
13102         gameInfo.round = StrSave("-");
13103         gameInfo.white = StrSave(UserName());
13104         gameInfo.black = StrSave(first.tidy);
13105         gameInfo.timeControl = TimeControlTagValue();
13106         break;
13107
13108       case TwoMachinesPlay:
13109         gameInfo.event = StrSave( appData.pgnEventHeader );
13110         gameInfo.site = StrSave(HostName());
13111         gameInfo.date = PGNDate();
13112         if (matchGame > 0) {
13113             char buf[MSG_SIZ];
13114             snprintf(buf, MSG_SIZ, "%d", matchGame);
13115             gameInfo.round = StrSave(buf);
13116         } else {
13117             gameInfo.round = StrSave("-");
13118         }
13119         if (first.twoMachinesColor[0] == 'w') {
13120             gameInfo.white = StrSave(first.tidy);
13121             gameInfo.black = StrSave(second.tidy);
13122         } else {
13123             gameInfo.white = StrSave(second.tidy);
13124             gameInfo.black = StrSave(first.tidy);
13125         }
13126         gameInfo.timeControl = TimeControlTagValue();
13127         break;
13128
13129       case EditGame:
13130         gameInfo.event = StrSave("Edited game");
13131         gameInfo.site = StrSave(HostName());
13132         gameInfo.date = PGNDate();
13133         gameInfo.round = StrSave("-");
13134         gameInfo.white = StrSave("-");
13135         gameInfo.black = StrSave("-");
13136         gameInfo.result = r;
13137         gameInfo.resultDetails = p;
13138         break;
13139
13140       case EditPosition:
13141         gameInfo.event = StrSave("Edited position");
13142         gameInfo.site = StrSave(HostName());
13143         gameInfo.date = PGNDate();
13144         gameInfo.round = StrSave("-");
13145         gameInfo.white = StrSave("-");
13146         gameInfo.black = StrSave("-");
13147         break;
13148
13149       case IcsPlayingWhite:
13150       case IcsPlayingBlack:
13151       case IcsObserving:
13152       case IcsExamining:
13153         break;
13154
13155       case PlayFromGameFile:
13156         gameInfo.event = StrSave("Game from non-PGN file");
13157         gameInfo.site = StrSave(HostName());
13158         gameInfo.date = PGNDate();
13159         gameInfo.round = StrSave("-");
13160         gameInfo.white = StrSave("?");
13161         gameInfo.black = StrSave("?");
13162         break;
13163
13164       default:
13165         break;
13166     }
13167 }
13168
13169 void
13170 ReplaceComment(index, text)
13171      int index;
13172      char *text;
13173 {
13174     int len;
13175
13176     while (*text == '\n') text++;
13177     len = strlen(text);
13178     while (len > 0 && text[len - 1] == '\n') len--;
13179
13180     if (commentList[index] != NULL)
13181       free(commentList[index]);
13182
13183     if (len == 0) {
13184         commentList[index] = NULL;
13185         return;
13186     }
13187   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13188       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13189       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13190     commentList[index] = (char *) malloc(len + 2);
13191     strncpy(commentList[index], text, len);
13192     commentList[index][len] = '\n';
13193     commentList[index][len + 1] = NULLCHAR;
13194   } else {
13195     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13196     char *p;
13197     commentList[index] = (char *) malloc(len + 7);
13198     safeStrCpy(commentList[index], "{\n", 3);
13199     safeStrCpy(commentList[index]+2, text, len+1);
13200     commentList[index][len+2] = NULLCHAR;
13201     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13202     strcat(commentList[index], "\n}\n");
13203   }
13204 }
13205
13206 void
13207 CrushCRs(text)
13208      char *text;
13209 {
13210   char *p = text;
13211   char *q = text;
13212   char ch;
13213
13214   do {
13215     ch = *p++;
13216     if (ch == '\r') continue;
13217     *q++ = ch;
13218   } while (ch != '\0');
13219 }
13220
13221 void
13222 AppendComment(index, text, addBraces)
13223      int index;
13224      char *text;
13225      Boolean addBraces; // [HGM] braces: tells if we should add {}
13226 {
13227     int oldlen, len;
13228     char *old;
13229
13230 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13231     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13232
13233     CrushCRs(text);
13234     while (*text == '\n') text++;
13235     len = strlen(text);
13236     while (len > 0 && text[len - 1] == '\n') len--;
13237
13238     if (len == 0) return;
13239
13240     if (commentList[index] != NULL) {
13241         old = commentList[index];
13242         oldlen = strlen(old);
13243         while(commentList[index][oldlen-1] ==  '\n')
13244           commentList[index][--oldlen] = NULLCHAR;
13245         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13246         safeStrCpy(commentList[index], old, oldlen);
13247         free(old);
13248         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13249         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13250           if(addBraces) addBraces = FALSE; else { text++; len--; }
13251           while (*text == '\n') { text++; len--; }
13252           commentList[index][--oldlen] = NULLCHAR;
13253       }
13254         if(addBraces) strcat(commentList[index], "\n{\n");
13255         else          strcat(commentList[index], "\n");
13256         strcat(commentList[index], text);
13257         if(addBraces) strcat(commentList[index], "\n}\n");
13258         else          strcat(commentList[index], "\n");
13259     } else {
13260         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13261         if(addBraces)
13262           safeStrCpy(commentList[index], "{\n", sizeof(commentList[index])/sizeof(commentList[index][0]));
13263         else commentList[index][0] = NULLCHAR;
13264         strcat(commentList[index], text);
13265         strcat(commentList[index], "\n");
13266         if(addBraces) strcat(commentList[index], "}\n");
13267     }
13268 }
13269
13270 static char * FindStr( char * text, char * sub_text )
13271 {
13272     char * result = strstr( text, sub_text );
13273
13274     if( result != NULL ) {
13275         result += strlen( sub_text );
13276     }
13277
13278     return result;
13279 }
13280
13281 /* [AS] Try to extract PV info from PGN comment */
13282 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13283 char *GetInfoFromComment( int index, char * text )
13284 {
13285     char * sep = text;
13286
13287     if( text != NULL && index > 0 ) {
13288         int score = 0;
13289         int depth = 0;
13290         int time = -1, sec = 0, deci;
13291         char * s_eval = FindStr( text, "[%eval " );
13292         char * s_emt = FindStr( text, "[%emt " );
13293
13294         if( s_eval != NULL || s_emt != NULL ) {
13295             /* New style */
13296             char delim;
13297
13298             if( s_eval != NULL ) {
13299                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13300                     return text;
13301                 }
13302
13303                 if( delim != ']' ) {
13304                     return text;
13305                 }
13306             }
13307
13308             if( s_emt != NULL ) {
13309             }
13310                 return text;
13311         }
13312         else {
13313             /* We expect something like: [+|-]nnn.nn/dd */
13314             int score_lo = 0;
13315
13316             if(*text != '{') return text; // [HGM] braces: must be normal comment
13317
13318             sep = strchr( text, '/' );
13319             if( sep == NULL || sep < (text+4) ) {
13320                 return text;
13321             }
13322
13323             time = -1; sec = -1; deci = -1;
13324             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13325                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13326                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13327                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13328                 return text;
13329             }
13330
13331             if( score_lo < 0 || score_lo >= 100 ) {
13332                 return text;
13333             }
13334
13335             if(sec >= 0) time = 600*time + 10*sec; else
13336             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13337
13338             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13339
13340             /* [HGM] PV time: now locate end of PV info */
13341             while( *++sep >= '0' && *sep <= '9'); // strip depth
13342             if(time >= 0)
13343             while( *++sep >= '0' && *sep <= '9'); // strip time
13344             if(sec >= 0)
13345             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13346             if(deci >= 0)
13347             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13348             while(*sep == ' ') sep++;
13349         }
13350
13351         if( depth <= 0 ) {
13352             return text;
13353         }
13354
13355         if( time < 0 ) {
13356             time = -1;
13357         }
13358
13359         pvInfoList[index-1].depth = depth;
13360         pvInfoList[index-1].score = score;
13361         pvInfoList[index-1].time  = 10*time; // centi-sec
13362         if(*sep == '}') *sep = 0; else *--sep = '{';
13363     }
13364     return sep;
13365 }
13366
13367 void
13368 SendToProgram(message, cps)
13369      char *message;
13370      ChessProgramState *cps;
13371 {
13372     int count, outCount, error;
13373     char buf[MSG_SIZ];
13374
13375     if (cps->pr == NULL) return;
13376     Attention(cps);
13377
13378     if (appData.debugMode) {
13379         TimeMark now;
13380         GetTimeMark(&now);
13381         fprintf(debugFP, "%ld >%-6s: %s",
13382                 SubtractTimeMarks(&now, &programStartTime),
13383                 cps->which, message);
13384     }
13385
13386     count = strlen(message);
13387     outCount = OutputToProcess(cps->pr, message, count, &error);
13388     if (outCount < count && !exiting
13389                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13390       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13391         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13392             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13393                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13394                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13395             } else {
13396                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13397             }
13398             gameInfo.resultDetails = StrSave(buf);
13399         }
13400         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13401     }
13402 }
13403
13404 void
13405 ReceiveFromProgram(isr, closure, message, count, error)
13406      InputSourceRef isr;
13407      VOIDSTAR closure;
13408      char *message;
13409      int count;
13410      int error;
13411 {
13412     char *end_str;
13413     char buf[MSG_SIZ];
13414     ChessProgramState *cps = (ChessProgramState *)closure;
13415
13416     if (isr != cps->isr) return; /* Killed intentionally */
13417     if (count <= 0) {
13418         if (count == 0) {
13419             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13420                     cps->which, cps->program);
13421         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13422                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13423                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13424                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13425                 } else {
13426                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13427                 }
13428                 gameInfo.resultDetails = StrSave(buf);
13429             }
13430             RemoveInputSource(cps->isr);
13431             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13432         } else {
13433             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13434                     cps->which, cps->program);
13435             RemoveInputSource(cps->isr);
13436
13437             /* [AS] Program is misbehaving badly... kill it */
13438             if( count == -2 ) {
13439                 DestroyChildProcess( cps->pr, 9 );
13440                 cps->pr = NoProc;
13441             }
13442
13443             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13444         }
13445         return;
13446     }
13447
13448     if ((end_str = strchr(message, '\r')) != NULL)
13449       *end_str = NULLCHAR;
13450     if ((end_str = strchr(message, '\n')) != NULL)
13451       *end_str = NULLCHAR;
13452
13453     if (appData.debugMode) {
13454         TimeMark now; int print = 1;
13455         char *quote = ""; char c; int i;
13456
13457         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13458                 char start = message[0];
13459                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13460                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13461                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13462                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13463                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13464                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13465                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13466                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13467                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13468                     print = (appData.engineComments >= 2);
13469                 }
13470                 message[0] = start; // restore original message
13471         }
13472         if(print) {
13473                 GetTimeMark(&now);
13474                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13475                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13476                         quote,
13477                         message);
13478         }
13479     }
13480
13481     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13482     if (appData.icsEngineAnalyze) {
13483         if (strstr(message, "whisper") != NULL ||
13484              strstr(message, "kibitz") != NULL ||
13485             strstr(message, "tellics") != NULL) return;
13486     }
13487
13488     HandleMachineMove(message, cps);
13489 }
13490
13491
13492 void
13493 SendTimeControl(cps, mps, tc, inc, sd, st)
13494      ChessProgramState *cps;
13495      int mps, inc, sd, st;
13496      long tc;
13497 {
13498     char buf[MSG_SIZ];
13499     int seconds;
13500
13501     if( timeControl_2 > 0 ) {
13502         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13503             tc = timeControl_2;
13504         }
13505     }
13506     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13507     inc /= cps->timeOdds;
13508     st  /= cps->timeOdds;
13509
13510     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13511
13512     if (st > 0) {
13513       /* Set exact time per move, normally using st command */
13514       if (cps->stKludge) {
13515         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13516         seconds = st % 60;
13517         if (seconds == 0) {
13518           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13519         } else {
13520           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13521         }
13522       } else {
13523         snprintf(buf, MSG_SIZ, "st %d\n", st);
13524       }
13525     } else {
13526       /* Set conventional or incremental time control, using level command */
13527       if (seconds == 0) {
13528         /* Note old gnuchess bug -- minutes:seconds used to not work.
13529            Fixed in later versions, but still avoid :seconds
13530            when seconds is 0. */
13531         snprintf(buf, MSG_SIZ, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13532       } else {
13533         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %d\n", mps, tc/60000,
13534                  seconds, inc/1000);
13535       }
13536     }
13537     SendToProgram(buf, cps);
13538
13539     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13540     /* Orthogonally, limit search to given depth */
13541     if (sd > 0) {
13542       if (cps->sdKludge) {
13543         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13544       } else {
13545         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13546       }
13547       SendToProgram(buf, cps);
13548     }
13549
13550     if(cps->nps > 0) { /* [HGM] nps */
13551         if(cps->supportsNPS == FALSE)
13552           cps->nps = -1; // don't use if engine explicitly says not supported!
13553         else {
13554           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13555           SendToProgram(buf, cps);
13556         }
13557     }
13558 }
13559
13560 ChessProgramState *WhitePlayer()
13561 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13562 {
13563     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13564        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13565         return &second;
13566     return &first;
13567 }
13568
13569 void
13570 SendTimeRemaining(cps, machineWhite)
13571      ChessProgramState *cps;
13572      int /*boolean*/ machineWhite;
13573 {
13574     char message[MSG_SIZ];
13575     long time, otime;
13576
13577     /* Note: this routine must be called when the clocks are stopped
13578        or when they have *just* been set or switched; otherwise
13579        it will be off by the time since the current tick started.
13580     */
13581     if (machineWhite) {
13582         time = whiteTimeRemaining / 10;
13583         otime = blackTimeRemaining / 10;
13584     } else {
13585         time = blackTimeRemaining / 10;
13586         otime = whiteTimeRemaining / 10;
13587     }
13588     /* [HGM] translate opponent's time by time-odds factor */
13589     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13590     if (appData.debugMode) {
13591         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13592     }
13593
13594     if (time <= 0) time = 1;
13595     if (otime <= 0) otime = 1;
13596
13597     snprintf(message, MSG_SIZ, "time %ld\n", time);
13598     SendToProgram(message, cps);
13599
13600     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13601     SendToProgram(message, cps);
13602 }
13603
13604 int
13605 BoolFeature(p, name, loc, cps)
13606      char **p;
13607      char *name;
13608      int *loc;
13609      ChessProgramState *cps;
13610 {
13611   char buf[MSG_SIZ];
13612   int len = strlen(name);
13613   int val;
13614
13615   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13616     (*p) += len + 1;
13617     sscanf(*p, "%d", &val);
13618     *loc = (val != 0);
13619     while (**p && **p != ' ')
13620       (*p)++;
13621     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13622     SendToProgram(buf, cps);
13623     return TRUE;
13624   }
13625   return FALSE;
13626 }
13627
13628 int
13629 IntFeature(p, name, loc, cps)
13630      char **p;
13631      char *name;
13632      int *loc;
13633      ChessProgramState *cps;
13634 {
13635   char buf[MSG_SIZ];
13636   int len = strlen(name);
13637   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13638     (*p) += len + 1;
13639     sscanf(*p, "%d", loc);
13640     while (**p && **p != ' ') (*p)++;
13641     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13642     SendToProgram(buf, cps);
13643     return TRUE;
13644   }
13645   return FALSE;
13646 }
13647
13648 int
13649 StringFeature(p, name, loc, cps)
13650      char **p;
13651      char *name;
13652      char loc[];
13653      ChessProgramState *cps;
13654 {
13655   char buf[MSG_SIZ];
13656   int len = strlen(name);
13657   if (strncmp((*p), name, len) == 0
13658       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13659     (*p) += len + 2;
13660     sscanf(*p, "%[^\"]", loc);
13661     while (**p && **p != '\"') (*p)++;
13662     if (**p == '\"') (*p)++;
13663     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13664     SendToProgram(buf, cps);
13665     return TRUE;
13666   }
13667   return FALSE;
13668 }
13669
13670 int
13671 ParseOption(Option *opt, ChessProgramState *cps)
13672 // [HGM] options: process the string that defines an engine option, and determine
13673 // name, type, default value, and allowed value range
13674 {
13675         char *p, *q, buf[MSG_SIZ];
13676         int n, min = (-1)<<31, max = 1<<31, def;
13677
13678         if(p = strstr(opt->name, " -spin ")) {
13679             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13680             if(max < min) max = min; // enforce consistency
13681             if(def < min) def = min;
13682             if(def > max) def = max;
13683             opt->value = def;
13684             opt->min = min;
13685             opt->max = max;
13686             opt->type = Spin;
13687         } else if((p = strstr(opt->name, " -slider "))) {
13688             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13689             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13690             if(max < min) max = min; // enforce consistency
13691             if(def < min) def = min;
13692             if(def > max) def = max;
13693             opt->value = def;
13694             opt->min = min;
13695             opt->max = max;
13696             opt->type = Spin; // Slider;
13697         } else if((p = strstr(opt->name, " -string "))) {
13698             opt->textValue = p+9;
13699             opt->type = TextBox;
13700         } else if((p = strstr(opt->name, " -file "))) {
13701             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13702             opt->textValue = p+7;
13703             opt->type = TextBox; // FileName;
13704         } else if((p = strstr(opt->name, " -path "))) {
13705             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13706             opt->textValue = p+7;
13707             opt->type = TextBox; // PathName;
13708         } else if(p = strstr(opt->name, " -check ")) {
13709             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13710             opt->value = (def != 0);
13711             opt->type = CheckBox;
13712         } else if(p = strstr(opt->name, " -combo ")) {
13713             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13714             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13715             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13716             opt->value = n = 0;
13717             while(q = StrStr(q, " /// ")) {
13718                 n++; *q = 0;    // count choices, and null-terminate each of them
13719                 q += 5;
13720                 if(*q == '*') { // remember default, which is marked with * prefix
13721                     q++;
13722                     opt->value = n;
13723                 }
13724                 cps->comboList[cps->comboCnt++] = q;
13725             }
13726             cps->comboList[cps->comboCnt++] = NULL;
13727             opt->max = n + 1;
13728             opt->type = ComboBox;
13729         } else if(p = strstr(opt->name, " -button")) {
13730             opt->type = Button;
13731         } else if(p = strstr(opt->name, " -save")) {
13732             opt->type = SaveButton;
13733         } else return FALSE;
13734         *p = 0; // terminate option name
13735         // now look if the command-line options define a setting for this engine option.
13736         if(cps->optionSettings && cps->optionSettings[0])
13737             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13738         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13739           snprintf(buf, MSG_SIZ, "option %s", p);
13740                 if(p = strstr(buf, ",")) *p = 0;
13741                 strcat(buf, "\n");
13742                 SendToProgram(buf, cps);
13743         }
13744         return TRUE;
13745 }
13746
13747 void
13748 FeatureDone(cps, val)
13749      ChessProgramState* cps;
13750      int val;
13751 {
13752   DelayedEventCallback cb = GetDelayedEvent();
13753   if ((cb == InitBackEnd3 && cps == &first) ||
13754       (cb == TwoMachinesEventIfReady && cps == &second)) {
13755     CancelDelayedEvent();
13756     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13757   }
13758   cps->initDone = val;
13759 }
13760
13761 /* Parse feature command from engine */
13762 void
13763 ParseFeatures(args, cps)
13764      char* args;
13765      ChessProgramState *cps;
13766 {
13767   char *p = args;
13768   char *q;
13769   int val;
13770   char buf[MSG_SIZ];
13771
13772   for (;;) {
13773     while (*p == ' ') p++;
13774     if (*p == NULLCHAR) return;
13775
13776     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13777     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13778     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13779     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13780     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13781     if (BoolFeature(&p, "reuse", &val, cps)) {
13782       /* Engine can disable reuse, but can't enable it if user said no */
13783       if (!val) cps->reuse = FALSE;
13784       continue;
13785     }
13786     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13787     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13788       if (gameMode == TwoMachinesPlay) {
13789         DisplayTwoMachinesTitle();
13790       } else {
13791         DisplayTitle("");
13792       }
13793       continue;
13794     }
13795     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13796     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13797     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13798     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13799     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13800     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13801     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13802     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13803     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13804     if (IntFeature(&p, "done", &val, cps)) {
13805       FeatureDone(cps, val);
13806       continue;
13807     }
13808     /* Added by Tord: */
13809     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13810     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13811     /* End of additions by Tord */
13812
13813     /* [HGM] added features: */
13814     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13815     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13816     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13817     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13818     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13819     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13820     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13821         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13822           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13823             SendToProgram(buf, cps);
13824             continue;
13825         }
13826         if(cps->nrOptions >= MAX_OPTIONS) {
13827             cps->nrOptions--;
13828             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13829             DisplayError(buf, 0);
13830         }
13831         continue;
13832     }
13833     /* End of additions by HGM */
13834
13835     /* unknown feature: complain and skip */
13836     q = p;
13837     while (*q && *q != '=') q++;
13838     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13839     SendToProgram(buf, cps);
13840     p = q;
13841     if (*p == '=') {
13842       p++;
13843       if (*p == '\"') {
13844         p++;
13845         while (*p && *p != '\"') p++;
13846         if (*p == '\"') p++;
13847       } else {
13848         while (*p && *p != ' ') p++;
13849       }
13850     }
13851   }
13852
13853 }
13854
13855 void
13856 PeriodicUpdatesEvent(newState)
13857      int newState;
13858 {
13859     if (newState == appData.periodicUpdates)
13860       return;
13861
13862     appData.periodicUpdates=newState;
13863
13864     /* Display type changes, so update it now */
13865 //    DisplayAnalysis();
13866
13867     /* Get the ball rolling again... */
13868     if (newState) {
13869         AnalysisPeriodicEvent(1);
13870         StartAnalysisClock();
13871     }
13872 }
13873
13874 void
13875 PonderNextMoveEvent(newState)
13876      int newState;
13877 {
13878     if (newState == appData.ponderNextMove) return;
13879     if (gameMode == EditPosition) EditPositionDone(TRUE);
13880     if (newState) {
13881         SendToProgram("hard\n", &first);
13882         if (gameMode == TwoMachinesPlay) {
13883             SendToProgram("hard\n", &second);
13884         }
13885     } else {
13886         SendToProgram("easy\n", &first);
13887         thinkOutput[0] = NULLCHAR;
13888         if (gameMode == TwoMachinesPlay) {
13889             SendToProgram("easy\n", &second);
13890         }
13891     }
13892     appData.ponderNextMove = newState;
13893 }
13894
13895 void
13896 NewSettingEvent(option, feature, command, value)
13897      char *command;
13898      int option, value, *feature;
13899 {
13900     char buf[MSG_SIZ];
13901
13902     if (gameMode == EditPosition) EditPositionDone(TRUE);
13903     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
13904     if(feature == NULL || *feature) SendToProgram(buf, &first);
13905     if (gameMode == TwoMachinesPlay) {
13906         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13907     }
13908 }
13909
13910 void
13911 ShowThinkingEvent()
13912 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13913 {
13914     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13915     int newState = appData.showThinking
13916         // [HGM] thinking: other features now need thinking output as well
13917         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13918
13919     if (oldState == newState) return;
13920     oldState = newState;
13921     if (gameMode == EditPosition) EditPositionDone(TRUE);
13922     if (oldState) {
13923         SendToProgram("post\n", &first);
13924         if (gameMode == TwoMachinesPlay) {
13925             SendToProgram("post\n", &second);
13926         }
13927     } else {
13928         SendToProgram("nopost\n", &first);
13929         thinkOutput[0] = NULLCHAR;
13930         if (gameMode == TwoMachinesPlay) {
13931             SendToProgram("nopost\n", &second);
13932         }
13933     }
13934 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13935 }
13936
13937 void
13938 AskQuestionEvent(title, question, replyPrefix, which)
13939      char *title; char *question; char *replyPrefix; char *which;
13940 {
13941   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13942   if (pr == NoProc) return;
13943   AskQuestion(title, question, replyPrefix, pr);
13944 }
13945
13946 void
13947 DisplayMove(moveNumber)
13948      int moveNumber;
13949 {
13950     char message[MSG_SIZ];
13951     char res[MSG_SIZ];
13952     char cpThinkOutput[MSG_SIZ];
13953
13954     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13955
13956     if (moveNumber == forwardMostMove - 1 ||
13957         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13958
13959         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
13960
13961         if (strchr(cpThinkOutput, '\n')) {
13962             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13963         }
13964     } else {
13965         *cpThinkOutput = NULLCHAR;
13966     }
13967
13968     /* [AS] Hide thinking from human user */
13969     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13970         *cpThinkOutput = NULLCHAR;
13971         if( thinkOutput[0] != NULLCHAR ) {
13972             int i;
13973
13974             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13975                 cpThinkOutput[i] = '.';
13976             }
13977             cpThinkOutput[i] = NULLCHAR;
13978             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13979         }
13980     }
13981
13982     if (moveNumber == forwardMostMove - 1 &&
13983         gameInfo.resultDetails != NULL) {
13984         if (gameInfo.resultDetails[0] == NULLCHAR) {
13985           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
13986         } else {
13987           snprintf(res, MSG_SIZ, " {%s} %s",
13988                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
13989         }
13990     } else {
13991         res[0] = NULLCHAR;
13992     }
13993
13994     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13995         DisplayMessage(res, cpThinkOutput);
13996     } else {
13997       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
13998                 WhiteOnMove(moveNumber) ? " " : ".. ",
13999                 parseList[moveNumber], res);
14000         DisplayMessage(message, cpThinkOutput);
14001     }
14002 }
14003
14004 void
14005 DisplayComment(moveNumber, text)
14006      int moveNumber;
14007      char *text;
14008 {
14009     char title[MSG_SIZ];
14010     char buf[8000]; // comment can be long!
14011     int score, depth;
14012
14013     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14014       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14015     } else {
14016       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14017               WhiteOnMove(moveNumber) ? " " : ".. ",
14018               parseList[moveNumber]);
14019     }
14020     // [HGM] PV info: display PV info together with (or as) comment
14021     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14022       if(text == NULL) text = "";
14023       score = pvInfoList[moveNumber].score;
14024       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14025               depth, (pvInfoList[moveNumber].time+50)/100, text);
14026       text = buf;
14027     }
14028     if (text != NULL && (appData.autoDisplayComment || commentUp))
14029         CommentPopUp(title, text);
14030 }
14031
14032 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14033  * might be busy thinking or pondering.  It can be omitted if your
14034  * gnuchess is configured to stop thinking immediately on any user
14035  * input.  However, that gnuchess feature depends on the FIONREAD
14036  * ioctl, which does not work properly on some flavors of Unix.
14037  */
14038 void
14039 Attention(cps)
14040      ChessProgramState *cps;
14041 {
14042 #if ATTENTION
14043     if (!cps->useSigint) return;
14044     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14045     switch (gameMode) {
14046       case MachinePlaysWhite:
14047       case MachinePlaysBlack:
14048       case TwoMachinesPlay:
14049       case IcsPlayingWhite:
14050       case IcsPlayingBlack:
14051       case AnalyzeMode:
14052       case AnalyzeFile:
14053         /* Skip if we know it isn't thinking */
14054         if (!cps->maybeThinking) return;
14055         if (appData.debugMode)
14056           fprintf(debugFP, "Interrupting %s\n", cps->which);
14057         InterruptChildProcess(cps->pr);
14058         cps->maybeThinking = FALSE;
14059         break;
14060       default:
14061         break;
14062     }
14063 #endif /*ATTENTION*/
14064 }
14065
14066 int
14067 CheckFlags()
14068 {
14069     if (whiteTimeRemaining <= 0) {
14070         if (!whiteFlag) {
14071             whiteFlag = TRUE;
14072             if (appData.icsActive) {
14073                 if (appData.autoCallFlag &&
14074                     gameMode == IcsPlayingBlack && !blackFlag) {
14075                   SendToICS(ics_prefix);
14076                   SendToICS("flag\n");
14077                 }
14078             } else {
14079                 if (blackFlag) {
14080                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14081                 } else {
14082                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14083                     if (appData.autoCallFlag) {
14084                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14085                         return TRUE;
14086                     }
14087                 }
14088             }
14089         }
14090     }
14091     if (blackTimeRemaining <= 0) {
14092         if (!blackFlag) {
14093             blackFlag = TRUE;
14094             if (appData.icsActive) {
14095                 if (appData.autoCallFlag &&
14096                     gameMode == IcsPlayingWhite && !whiteFlag) {
14097                   SendToICS(ics_prefix);
14098                   SendToICS("flag\n");
14099                 }
14100             } else {
14101                 if (whiteFlag) {
14102                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14103                 } else {
14104                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14105                     if (appData.autoCallFlag) {
14106                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14107                         return TRUE;
14108                     }
14109                 }
14110             }
14111         }
14112     }
14113     return FALSE;
14114 }
14115
14116 void
14117 CheckTimeControl()
14118 {
14119     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14120         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14121
14122     /*
14123      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14124      */
14125     if ( !WhiteOnMove(forwardMostMove) ) {
14126         /* White made time control */
14127         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14128         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14129         /* [HGM] time odds: correct new time quota for time odds! */
14130                                             / WhitePlayer()->timeOdds;
14131         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14132     } else {
14133         lastBlack -= blackTimeRemaining;
14134         /* Black made time control */
14135         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14136                                             / WhitePlayer()->other->timeOdds;
14137         lastWhite = whiteTimeRemaining;
14138     }
14139 }
14140
14141 void
14142 DisplayBothClocks()
14143 {
14144     int wom = gameMode == EditPosition ?
14145       !blackPlaysFirst : WhiteOnMove(currentMove);
14146     DisplayWhiteClock(whiteTimeRemaining, wom);
14147     DisplayBlackClock(blackTimeRemaining, !wom);
14148 }
14149
14150
14151 /* Timekeeping seems to be a portability nightmare.  I think everyone
14152    has ftime(), but I'm really not sure, so I'm including some ifdefs
14153    to use other calls if you don't.  Clocks will be less accurate if
14154    you have neither ftime nor gettimeofday.
14155 */
14156
14157 /* VS 2008 requires the #include outside of the function */
14158 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14159 #include <sys/timeb.h>
14160 #endif
14161
14162 /* Get the current time as a TimeMark */
14163 void
14164 GetTimeMark(tm)
14165      TimeMark *tm;
14166 {
14167 #if HAVE_GETTIMEOFDAY
14168
14169     struct timeval timeVal;
14170     struct timezone timeZone;
14171
14172     gettimeofday(&timeVal, &timeZone);
14173     tm->sec = (long) timeVal.tv_sec;
14174     tm->ms = (int) (timeVal.tv_usec / 1000L);
14175
14176 #else /*!HAVE_GETTIMEOFDAY*/
14177 #if HAVE_FTIME
14178
14179 // include <sys/timeb.h> / moved to just above start of function
14180     struct timeb timeB;
14181
14182     ftime(&timeB);
14183     tm->sec = (long) timeB.time;
14184     tm->ms = (int) timeB.millitm;
14185
14186 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14187     tm->sec = (long) time(NULL);
14188     tm->ms = 0;
14189 #endif
14190 #endif
14191 }
14192
14193 /* Return the difference in milliseconds between two
14194    time marks.  We assume the difference will fit in a long!
14195 */
14196 long
14197 SubtractTimeMarks(tm2, tm1)
14198      TimeMark *tm2, *tm1;
14199 {
14200     return 1000L*(tm2->sec - tm1->sec) +
14201            (long) (tm2->ms - tm1->ms);
14202 }
14203
14204
14205 /*
14206  * Code to manage the game clocks.
14207  *
14208  * In tournament play, black starts the clock and then white makes a move.
14209  * We give the human user a slight advantage if he is playing white---the
14210  * clocks don't run until he makes his first move, so it takes zero time.
14211  * Also, we don't account for network lag, so we could get out of sync
14212  * with GNU Chess's clock -- but then, referees are always right.
14213  */
14214
14215 static TimeMark tickStartTM;
14216 static long intendedTickLength;
14217
14218 long
14219 NextTickLength(timeRemaining)
14220      long timeRemaining;
14221 {
14222     long nominalTickLength, nextTickLength;
14223
14224     if (timeRemaining > 0L && timeRemaining <= 10000L)
14225       nominalTickLength = 100L;
14226     else
14227       nominalTickLength = 1000L;
14228     nextTickLength = timeRemaining % nominalTickLength;
14229     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14230
14231     return nextTickLength;
14232 }
14233
14234 /* Adjust clock one minute up or down */
14235 void
14236 AdjustClock(Boolean which, int dir)
14237 {
14238     if(which) blackTimeRemaining += 60000*dir;
14239     else      whiteTimeRemaining += 60000*dir;
14240     DisplayBothClocks();
14241 }
14242
14243 /* Stop clocks and reset to a fresh time control */
14244 void
14245 ResetClocks()
14246 {
14247     (void) StopClockTimer();
14248     if (appData.icsActive) {
14249         whiteTimeRemaining = blackTimeRemaining = 0;
14250     } else if (searchTime) {
14251         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14252         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14253     } else { /* [HGM] correct new time quote for time odds */
14254         whiteTC = blackTC = fullTimeControlString;
14255         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14256         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14257     }
14258     if (whiteFlag || blackFlag) {
14259         DisplayTitle("");
14260         whiteFlag = blackFlag = FALSE;
14261     }
14262     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14263     DisplayBothClocks();
14264 }
14265
14266 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14267
14268 /* Decrement running clock by amount of time that has passed */
14269 void
14270 DecrementClocks()
14271 {
14272     long timeRemaining;
14273     long lastTickLength, fudge;
14274     TimeMark now;
14275
14276     if (!appData.clockMode) return;
14277     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14278
14279     GetTimeMark(&now);
14280
14281     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14282
14283     /* Fudge if we woke up a little too soon */
14284     fudge = intendedTickLength - lastTickLength;
14285     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14286
14287     if (WhiteOnMove(forwardMostMove)) {
14288         if(whiteNPS >= 0) lastTickLength = 0;
14289         timeRemaining = whiteTimeRemaining -= lastTickLength;
14290         if(timeRemaining < 0) {
14291             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14292             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14293                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14294                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14295             }
14296         }
14297         DisplayWhiteClock(whiteTimeRemaining - fudge,
14298                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14299     } else {
14300         if(blackNPS >= 0) lastTickLength = 0;
14301         timeRemaining = blackTimeRemaining -= lastTickLength;
14302         if(timeRemaining < 0) { // [HGM] if we run out of a non-last incremental session, go to the next
14303             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14304             if(suddenDeath) {
14305                 blackStartMove = forwardMostMove;
14306                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14307             }
14308         }
14309         DisplayBlackClock(blackTimeRemaining - fudge,
14310                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14311     }
14312     if (CheckFlags()) return;
14313
14314     tickStartTM = now;
14315     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14316     StartClockTimer(intendedTickLength);
14317
14318     /* if the time remaining has fallen below the alarm threshold, sound the
14319      * alarm. if the alarm has sounded and (due to a takeback or time control
14320      * with increment) the time remaining has increased to a level above the
14321      * threshold, reset the alarm so it can sound again.
14322      */
14323
14324     if (appData.icsActive && appData.icsAlarm) {
14325
14326         /* make sure we are dealing with the user's clock */
14327         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14328                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14329            )) return;
14330
14331         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14332             alarmSounded = FALSE;
14333         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14334             PlayAlarmSound();
14335             alarmSounded = TRUE;
14336         }
14337     }
14338 }
14339
14340
14341 /* A player has just moved, so stop the previously running
14342    clock and (if in clock mode) start the other one.
14343    We redisplay both clocks in case we're in ICS mode, because
14344    ICS gives us an update to both clocks after every move.
14345    Note that this routine is called *after* forwardMostMove
14346    is updated, so the last fractional tick must be subtracted
14347    from the color that is *not* on move now.
14348 */
14349 void
14350 SwitchClocks(int newMoveNr)
14351 {
14352     long lastTickLength;
14353     TimeMark now;
14354     int flagged = FALSE;
14355
14356     GetTimeMark(&now);
14357
14358     if (StopClockTimer() && appData.clockMode) {
14359         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14360         if (!WhiteOnMove(forwardMostMove)) {
14361             if(blackNPS >= 0) lastTickLength = 0;
14362             blackTimeRemaining -= lastTickLength;
14363            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14364 //         if(pvInfoList[forwardMostMove-1].time == -1)
14365                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14366                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14367         } else {
14368            if(whiteNPS >= 0) lastTickLength = 0;
14369            whiteTimeRemaining -= lastTickLength;
14370            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14371 //         if(pvInfoList[forwardMostMove-1].time == -1)
14372                  pvInfoList[forwardMostMove-1].time =
14373                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14374         }
14375         flagged = CheckFlags();
14376     }
14377     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14378     CheckTimeControl();
14379
14380     if (flagged || !appData.clockMode) return;
14381
14382     switch (gameMode) {
14383       case MachinePlaysBlack:
14384       case MachinePlaysWhite:
14385       case BeginningOfGame:
14386         if (pausing) return;
14387         break;
14388
14389       case EditGame:
14390       case PlayFromGameFile:
14391       case IcsExamining:
14392         return;
14393
14394       default:
14395         break;
14396     }
14397
14398     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14399         if(WhiteOnMove(forwardMostMove))
14400              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14401         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14402     }
14403
14404     tickStartTM = now;
14405     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14406       whiteTimeRemaining : blackTimeRemaining);
14407     StartClockTimer(intendedTickLength);
14408 }
14409
14410
14411 /* Stop both clocks */
14412 void
14413 StopClocks()
14414 {
14415     long lastTickLength;
14416     TimeMark now;
14417
14418     if (!StopClockTimer()) return;
14419     if (!appData.clockMode) return;
14420
14421     GetTimeMark(&now);
14422
14423     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14424     if (WhiteOnMove(forwardMostMove)) {
14425         if(whiteNPS >= 0) lastTickLength = 0;
14426         whiteTimeRemaining -= lastTickLength;
14427         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14428     } else {
14429         if(blackNPS >= 0) lastTickLength = 0;
14430         blackTimeRemaining -= lastTickLength;
14431         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14432     }
14433     CheckFlags();
14434 }
14435
14436 /* Start clock of player on move.  Time may have been reset, so
14437    if clock is already running, stop and restart it. */
14438 void
14439 StartClocks()
14440 {
14441     (void) StopClockTimer(); /* in case it was running already */
14442     DisplayBothClocks();
14443     if (CheckFlags()) return;
14444
14445     if (!appData.clockMode) return;
14446     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14447
14448     GetTimeMark(&tickStartTM);
14449     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14450       whiteTimeRemaining : blackTimeRemaining);
14451
14452    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14453     whiteNPS = blackNPS = -1;
14454     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14455        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14456         whiteNPS = first.nps;
14457     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14458        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14459         blackNPS = first.nps;
14460     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14461         whiteNPS = second.nps;
14462     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14463         blackNPS = second.nps;
14464     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14465
14466     StartClockTimer(intendedTickLength);
14467 }
14468
14469 char *
14470 TimeString(ms)
14471      long ms;
14472 {
14473     long second, minute, hour, day;
14474     char *sign = "";
14475     static char buf[32];
14476
14477     if (ms > 0 && ms <= 9900) {
14478       /* convert milliseconds to tenths, rounding up */
14479       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14480
14481       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14482       return buf;
14483     }
14484
14485     /* convert milliseconds to seconds, rounding up */
14486     /* use floating point to avoid strangeness of integer division
14487        with negative dividends on many machines */
14488     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14489
14490     if (second < 0) {
14491         sign = "-";
14492         second = -second;
14493     }
14494
14495     day = second / (60 * 60 * 24);
14496     second = second % (60 * 60 * 24);
14497     hour = second / (60 * 60);
14498     second = second % (60 * 60);
14499     minute = second / 60;
14500     second = second % 60;
14501
14502     if (day > 0)
14503       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14504               sign, day, hour, minute, second);
14505     else if (hour > 0)
14506       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14507     else
14508       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14509
14510     return buf;
14511 }
14512
14513
14514 /*
14515  * This is necessary because some C libraries aren't ANSI C compliant yet.
14516  */
14517 char *
14518 StrStr(string, match)
14519      char *string, *match;
14520 {
14521     int i, length;
14522
14523     length = strlen(match);
14524
14525     for (i = strlen(string) - length; i >= 0; i--, string++)
14526       if (!strncmp(match, string, length))
14527         return string;
14528
14529     return NULL;
14530 }
14531
14532 char *
14533 StrCaseStr(string, match)
14534      char *string, *match;
14535 {
14536     int i, j, length;
14537
14538     length = strlen(match);
14539
14540     for (i = strlen(string) - length; i >= 0; i--, string++) {
14541         for (j = 0; j < length; j++) {
14542             if (ToLower(match[j]) != ToLower(string[j]))
14543               break;
14544         }
14545         if (j == length) return string;
14546     }
14547
14548     return NULL;
14549 }
14550
14551 #ifndef _amigados
14552 int
14553 StrCaseCmp(s1, s2)
14554      char *s1, *s2;
14555 {
14556     char c1, c2;
14557
14558     for (;;) {
14559         c1 = ToLower(*s1++);
14560         c2 = ToLower(*s2++);
14561         if (c1 > c2) return 1;
14562         if (c1 < c2) return -1;
14563         if (c1 == NULLCHAR) return 0;
14564     }
14565 }
14566
14567
14568 int
14569 ToLower(c)
14570      int c;
14571 {
14572     return isupper(c) ? tolower(c) : c;
14573 }
14574
14575
14576 int
14577 ToUpper(c)
14578      int c;
14579 {
14580     return islower(c) ? toupper(c) : c;
14581 }
14582 #endif /* !_amigados    */
14583
14584 char *
14585 StrSave(s)
14586      char *s;
14587 {
14588   char *ret;
14589
14590   if ((ret = (char *) malloc(strlen(s) + 1)))
14591     {
14592       safeStrCpy(ret, s, strlen(s)+1);
14593     }
14594   return ret;
14595 }
14596
14597 char *
14598 StrSavePtr(s, savePtr)
14599      char *s, **savePtr;
14600 {
14601     if (*savePtr) {
14602         free(*savePtr);
14603     }
14604     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14605       safeStrCpy(*savePtr, s, strlen(s)+1);
14606     }
14607     return(*savePtr);
14608 }
14609
14610 char *
14611 PGNDate()
14612 {
14613     time_t clock;
14614     struct tm *tm;
14615     char buf[MSG_SIZ];
14616
14617     clock = time((time_t *)NULL);
14618     tm = localtime(&clock);
14619     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14620             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14621     return StrSave(buf);
14622 }
14623
14624
14625 char *
14626 PositionToFEN(move, overrideCastling)
14627      int move;
14628      char *overrideCastling;
14629 {
14630     int i, j, fromX, fromY, toX, toY;
14631     int whiteToPlay;
14632     char buf[128];
14633     char *p, *q;
14634     int emptycount;
14635     ChessSquare piece;
14636
14637     whiteToPlay = (gameMode == EditPosition) ?
14638       !blackPlaysFirst : (move % 2 == 0);
14639     p = buf;
14640
14641     /* Piece placement data */
14642     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14643         emptycount = 0;
14644         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14645             if (boards[move][i][j] == EmptySquare) {
14646                 emptycount++;
14647             } else { ChessSquare piece = boards[move][i][j];
14648                 if (emptycount > 0) {
14649                     if(emptycount<10) /* [HGM] can be >= 10 */
14650                         *p++ = '0' + emptycount;
14651                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14652                     emptycount = 0;
14653                 }
14654                 if(PieceToChar(piece) == '+') {
14655                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14656                     *p++ = '+';
14657                     piece = (ChessSquare)(DEMOTED piece);
14658                 }
14659                 *p++ = PieceToChar(piece);
14660                 if(p[-1] == '~') {
14661                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14662                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14663                     *p++ = '~';
14664                 }
14665             }
14666         }
14667         if (emptycount > 0) {
14668             if(emptycount<10) /* [HGM] can be >= 10 */
14669                 *p++ = '0' + emptycount;
14670             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14671             emptycount = 0;
14672         }
14673         *p++ = '/';
14674     }
14675     *(p - 1) = ' ';
14676
14677     /* [HGM] print Crazyhouse or Shogi holdings */
14678     if( gameInfo.holdingsWidth ) {
14679         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14680         q = p;
14681         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14682             piece = boards[move][i][BOARD_WIDTH-1];
14683             if( piece != EmptySquare )
14684               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14685                   *p++ = PieceToChar(piece);
14686         }
14687         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14688             piece = boards[move][BOARD_HEIGHT-i-1][0];
14689             if( piece != EmptySquare )
14690               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14691                   *p++ = PieceToChar(piece);
14692         }
14693
14694         if( q == p ) *p++ = '-';
14695         *p++ = ']';
14696         *p++ = ' ';
14697     }
14698
14699     /* Active color */
14700     *p++ = whiteToPlay ? 'w' : 'b';
14701     *p++ = ' ';
14702
14703   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14704     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14705   } else {
14706   if(nrCastlingRights) {
14707      q = p;
14708      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14709        /* [HGM] write directly from rights */
14710            if(boards[move][CASTLING][2] != NoRights &&
14711               boards[move][CASTLING][0] != NoRights   )
14712                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14713            if(boards[move][CASTLING][2] != NoRights &&
14714               boards[move][CASTLING][1] != NoRights   )
14715                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14716            if(boards[move][CASTLING][5] != NoRights &&
14717               boards[move][CASTLING][3] != NoRights   )
14718                 *p++ = boards[move][CASTLING][3] + AAA;
14719            if(boards[move][CASTLING][5] != NoRights &&
14720               boards[move][CASTLING][4] != NoRights   )
14721                 *p++ = boards[move][CASTLING][4] + AAA;
14722      } else {
14723
14724         /* [HGM] write true castling rights */
14725         if( nrCastlingRights == 6 ) {
14726             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14727                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14728             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14729                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14730             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14731                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14732             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14733                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14734         }
14735      }
14736      if (q == p) *p++ = '-'; /* No castling rights */
14737      *p++ = ' ';
14738   }
14739
14740   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14741      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14742     /* En passant target square */
14743     if (move > backwardMostMove) {
14744         fromX = moveList[move - 1][0] - AAA;
14745         fromY = moveList[move - 1][1] - ONE;
14746         toX = moveList[move - 1][2] - AAA;
14747         toY = moveList[move - 1][3] - ONE;
14748         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14749             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14750             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14751             fromX == toX) {
14752             /* 2-square pawn move just happened */
14753             *p++ = toX + AAA;
14754             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14755         } else {
14756             *p++ = '-';
14757         }
14758     } else if(move == backwardMostMove) {
14759         // [HGM] perhaps we should always do it like this, and forget the above?
14760         if((signed char)boards[move][EP_STATUS] >= 0) {
14761             *p++ = boards[move][EP_STATUS] + AAA;
14762             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14763         } else {
14764             *p++ = '-';
14765         }
14766     } else {
14767         *p++ = '-';
14768     }
14769     *p++ = ' ';
14770   }
14771   }
14772
14773     /* [HGM] find reversible plies */
14774     {   int i = 0, j=move;
14775
14776         if (appData.debugMode) { int k;
14777             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14778             for(k=backwardMostMove; k<=forwardMostMove; k++)
14779                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14780
14781         }
14782
14783         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14784         if( j == backwardMostMove ) i += initialRulePlies;
14785         sprintf(p, "%d ", i);
14786         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14787     }
14788     /* Fullmove number */
14789     sprintf(p, "%d", (move / 2) + 1);
14790
14791     return StrSave(buf);
14792 }
14793
14794 Boolean
14795 ParseFEN(board, blackPlaysFirst, fen)
14796     Board board;
14797      int *blackPlaysFirst;
14798      char *fen;
14799 {
14800     int i, j;
14801     char *p, c;
14802     int emptycount;
14803     ChessSquare piece;
14804
14805     p = fen;
14806
14807     /* [HGM] by default clear Crazyhouse holdings, if present */
14808     if(gameInfo.holdingsWidth) {
14809        for(i=0; i<BOARD_HEIGHT; i++) {
14810            board[i][0]             = EmptySquare; /* black holdings */
14811            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14812            board[i][1]             = (ChessSquare) 0; /* black counts */
14813            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14814        }
14815     }
14816
14817     /* Piece placement data */
14818     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14819         j = 0;
14820         for (;;) {
14821             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14822                 if (*p == '/') p++;
14823                 emptycount = gameInfo.boardWidth - j;
14824                 while (emptycount--)
14825                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14826                 break;
14827 #if(BOARD_FILES >= 10)
14828             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14829                 p++; emptycount=10;
14830                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14831                 while (emptycount--)
14832                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14833 #endif
14834             } else if (isdigit(*p)) {
14835                 emptycount = *p++ - '0';
14836                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14837                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14838                 while (emptycount--)
14839                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14840             } else if (*p == '+' || isalpha(*p)) {
14841                 if (j >= gameInfo.boardWidth) return FALSE;
14842                 if(*p=='+') {
14843                     piece = CharToPiece(*++p);
14844                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14845                     piece = (ChessSquare) (PROMOTED piece ); p++;
14846                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14847                 } else piece = CharToPiece(*p++);
14848
14849                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14850                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14851                     piece = (ChessSquare) (PROMOTED piece);
14852                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14853                     p++;
14854                 }
14855                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14856             } else {
14857                 return FALSE;
14858             }
14859         }
14860     }
14861     while (*p == '/' || *p == ' ') p++;
14862
14863     /* [HGM] look for Crazyhouse holdings here */
14864     while(*p==' ') p++;
14865     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14866         if(*p == '[') p++;
14867         if(*p == '-' ) *p++; /* empty holdings */ else {
14868             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14869             /* if we would allow FEN reading to set board size, we would   */
14870             /* have to add holdings and shift the board read so far here   */
14871             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14872                 *p++;
14873                 if((int) piece >= (int) BlackPawn ) {
14874                     i = (int)piece - (int)BlackPawn;
14875                     i = PieceToNumber((ChessSquare)i);
14876                     if( i >= gameInfo.holdingsSize ) return FALSE;
14877                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14878                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14879                 } else {
14880                     i = (int)piece - (int)WhitePawn;
14881                     i = PieceToNumber((ChessSquare)i);
14882                     if( i >= gameInfo.holdingsSize ) return FALSE;
14883                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14884                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14885                 }
14886             }
14887         }
14888         if(*p == ']') *p++;
14889     }
14890
14891     while(*p == ' ') p++;
14892
14893     /* Active color */
14894     c = *p++;
14895     if(appData.colorNickNames) {
14896       if( c == appData.colorNickNames[0] ) c = 'w'; else
14897       if( c == appData.colorNickNames[1] ) c = 'b';
14898     }
14899     switch (c) {
14900       case 'w':
14901         *blackPlaysFirst = FALSE;
14902         break;
14903       case 'b':
14904         *blackPlaysFirst = TRUE;
14905         break;
14906       default:
14907         return FALSE;
14908     }
14909
14910     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14911     /* return the extra info in global variiables             */
14912
14913     /* set defaults in case FEN is incomplete */
14914     board[EP_STATUS] = EP_UNKNOWN;
14915     for(i=0; i<nrCastlingRights; i++ ) {
14916         board[CASTLING][i] =
14917             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14918     }   /* assume possible unless obviously impossible */
14919     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14920     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14921     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14922                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14923     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14924     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14925     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14926                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14927     FENrulePlies = 0;
14928
14929     while(*p==' ') p++;
14930     if(nrCastlingRights) {
14931       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14932           /* castling indicator present, so default becomes no castlings */
14933           for(i=0; i<nrCastlingRights; i++ ) {
14934                  board[CASTLING][i] = NoRights;
14935           }
14936       }
14937       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14938              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14939              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14940              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14941         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14942
14943         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14944             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14945             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14946         }
14947         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14948             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14949         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14950                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14951         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14952                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14953         switch(c) {
14954           case'K':
14955               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14956               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14957               board[CASTLING][2] = whiteKingFile;
14958               break;
14959           case'Q':
14960               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14961               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14962               board[CASTLING][2] = whiteKingFile;
14963               break;
14964           case'k':
14965               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14966               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14967               board[CASTLING][5] = blackKingFile;
14968               break;
14969           case'q':
14970               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14971               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14972               board[CASTLING][5] = blackKingFile;
14973           case '-':
14974               break;
14975           default: /* FRC castlings */
14976               if(c >= 'a') { /* black rights */
14977                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14978                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14979                   if(i == BOARD_RGHT) break;
14980                   board[CASTLING][5] = i;
14981                   c -= AAA;
14982                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14983                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14984                   if(c > i)
14985                       board[CASTLING][3] = c;
14986                   else
14987                       board[CASTLING][4] = c;
14988               } else { /* white rights */
14989                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14990                     if(board[0][i] == WhiteKing) break;
14991                   if(i == BOARD_RGHT) break;
14992                   board[CASTLING][2] = i;
14993                   c -= AAA - 'a' + 'A';
14994                   if(board[0][c] >= WhiteKing) break;
14995                   if(c > i)
14996                       board[CASTLING][0] = c;
14997                   else
14998                       board[CASTLING][1] = c;
14999               }
15000         }
15001       }
15002       for(i=0; i<nrCastlingRights; i++)
15003         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15004     if (appData.debugMode) {
15005         fprintf(debugFP, "FEN castling rights:");
15006         for(i=0; i<nrCastlingRights; i++)
15007         fprintf(debugFP, " %d", board[CASTLING][i]);
15008         fprintf(debugFP, "\n");
15009     }
15010
15011       while(*p==' ') p++;
15012     }
15013
15014     /* read e.p. field in games that know e.p. capture */
15015     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15016        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15017       if(*p=='-') {
15018         p++; board[EP_STATUS] = EP_NONE;
15019       } else {
15020          char c = *p++ - AAA;
15021
15022          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15023          if(*p >= '0' && *p <='9') *p++;
15024          board[EP_STATUS] = c;
15025       }
15026     }
15027
15028
15029     if(sscanf(p, "%d", &i) == 1) {
15030         FENrulePlies = i; /* 50-move ply counter */
15031         /* (The move number is still ignored)    */
15032     }
15033
15034     return TRUE;
15035 }
15036
15037 void
15038 EditPositionPasteFEN(char *fen)
15039 {
15040   if (fen != NULL) {
15041     Board initial_position;
15042
15043     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15044       DisplayError(_("Bad FEN position in clipboard"), 0);
15045       return ;
15046     } else {
15047       int savedBlackPlaysFirst = blackPlaysFirst;
15048       EditPositionEvent();
15049       blackPlaysFirst = savedBlackPlaysFirst;
15050       CopyBoard(boards[0], initial_position);
15051       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15052       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15053       DisplayBothClocks();
15054       DrawPosition(FALSE, boards[currentMove]);
15055     }
15056   }
15057 }
15058
15059 static char cseq[12] = "\\   ";
15060
15061 Boolean set_cont_sequence(char *new_seq)
15062 {
15063     int len;
15064     Boolean ret;
15065
15066     // handle bad attempts to set the sequence
15067         if (!new_seq)
15068                 return 0; // acceptable error - no debug
15069
15070     len = strlen(new_seq);
15071     ret = (len > 0) && (len < sizeof(cseq));
15072     if (ret)
15073       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15074     else if (appData.debugMode)
15075       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15076     return ret;
15077 }
15078
15079 /*
15080     reformat a source message so words don't cross the width boundary.  internal
15081     newlines are not removed.  returns the wrapped size (no null character unless
15082     included in source message).  If dest is NULL, only calculate the size required
15083     for the dest buffer.  lp argument indicats line position upon entry, and it's
15084     passed back upon exit.
15085 */
15086 int wrap(char *dest, char *src, int count, int width, int *lp)
15087 {
15088     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15089
15090     cseq_len = strlen(cseq);
15091     old_line = line = *lp;
15092     ansi = len = clen = 0;
15093
15094     for (i=0; i < count; i++)
15095     {
15096         if (src[i] == '\033')
15097             ansi = 1;
15098
15099         // if we hit the width, back up
15100         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15101         {
15102             // store i & len in case the word is too long
15103             old_i = i, old_len = len;
15104
15105             // find the end of the last word
15106             while (i && src[i] != ' ' && src[i] != '\n')
15107             {
15108                 i--;
15109                 len--;
15110             }
15111
15112             // word too long?  restore i & len before splitting it
15113             if ((old_i-i+clen) >= width)
15114             {
15115                 i = old_i;
15116                 len = old_len;
15117             }
15118
15119             // extra space?
15120             if (i && src[i-1] == ' ')
15121                 len--;
15122
15123             if (src[i] != ' ' && src[i] != '\n')
15124             {
15125                 i--;
15126                 if (len)
15127                     len--;
15128             }
15129
15130             // now append the newline and continuation sequence
15131             if (dest)
15132                 dest[len] = '\n';
15133             len++;
15134             if (dest)
15135                 strncpy(dest+len, cseq, cseq_len);
15136             len += cseq_len;
15137             line = cseq_len;
15138             clen = cseq_len;
15139             continue;
15140         }
15141
15142         if (dest)
15143             dest[len] = src[i];
15144         len++;
15145         if (!ansi)
15146             line++;
15147         if (src[i] == '\n')
15148             line = 0;
15149         if (src[i] == 'm')
15150             ansi = 0;
15151     }
15152     if (dest && appData.debugMode)
15153     {
15154         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15155             count, width, line, len, *lp);
15156         show_bytes(debugFP, src, count);
15157         fprintf(debugFP, "\ndest: ");
15158         show_bytes(debugFP, dest, len);
15159         fprintf(debugFP, "\n");
15160     }
15161     *lp = dest ? line : old_line;
15162
15163     return len;
15164 }
15165
15166 // [HGM] vari: routines for shelving variations
15167
15168 void
15169 PushTail(int firstMove, int lastMove)
15170 {
15171         int i, j, nrMoves = lastMove - firstMove;
15172
15173         if(appData.icsActive) { // only in local mode
15174                 forwardMostMove = currentMove; // mimic old ICS behavior
15175                 return;
15176         }
15177         if(storedGames >= MAX_VARIATIONS-1) return;
15178
15179         // push current tail of game on stack
15180         savedResult[storedGames] = gameInfo.result;
15181         savedDetails[storedGames] = gameInfo.resultDetails;
15182         gameInfo.resultDetails = NULL;
15183         savedFirst[storedGames] = firstMove;
15184         savedLast [storedGames] = lastMove;
15185         savedFramePtr[storedGames] = framePtr;
15186         framePtr -= nrMoves; // reserve space for the boards
15187         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15188             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15189             for(j=0; j<MOVE_LEN; j++)
15190                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15191             for(j=0; j<2*MOVE_LEN; j++)
15192                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15193             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15194             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15195             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15196             pvInfoList[firstMove+i-1].depth = 0;
15197             commentList[framePtr+i] = commentList[firstMove+i];
15198             commentList[firstMove+i] = NULL;
15199         }
15200
15201         storedGames++;
15202         forwardMostMove = firstMove; // truncate game so we can start variation
15203         if(storedGames == 1) GreyRevert(FALSE);
15204 }
15205
15206 Boolean
15207 PopTail(Boolean annotate)
15208 {
15209         int i, j, nrMoves;
15210         char buf[8000], moveBuf[20];
15211
15212         if(appData.icsActive) return FALSE; // only in local mode
15213         if(!storedGames) return FALSE; // sanity
15214         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15215
15216         storedGames--;
15217         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15218         nrMoves = savedLast[storedGames] - currentMove;
15219         if(annotate) {
15220                 int cnt = 10;
15221                 if(!WhiteOnMove(currentMove))
15222                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", currentMove+2>>1);
15223                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15224                 for(i=currentMove; i<forwardMostMove; i++) {
15225                         if(WhiteOnMove(i))
15226                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", i+2>>1, SavePart(parseList[i]));
15227                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15228                         strcat(buf, moveBuf);
15229                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15230                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15231                 }
15232                 strcat(buf, ")");
15233         }
15234         for(i=1; i<=nrMoves; i++) { // copy last variation back
15235             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15236             for(j=0; j<MOVE_LEN; j++)
15237                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15238             for(j=0; j<2*MOVE_LEN; j++)
15239                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15240             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15241             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15242             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15243             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15244             commentList[currentMove+i] = commentList[framePtr+i];
15245             commentList[framePtr+i] = NULL;
15246         }
15247         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15248         framePtr = savedFramePtr[storedGames];
15249         gameInfo.result = savedResult[storedGames];
15250         if(gameInfo.resultDetails != NULL) {
15251             free(gameInfo.resultDetails);
15252       }
15253         gameInfo.resultDetails = savedDetails[storedGames];
15254         forwardMostMove = currentMove + nrMoves;
15255         if(storedGames == 0) GreyRevert(TRUE);
15256         return TRUE;
15257 }
15258
15259 void
15260 CleanupTail()
15261 {       // remove all shelved variations
15262         int i;
15263         for(i=0; i<storedGames; i++) {
15264             if(savedDetails[i])
15265                 free(savedDetails[i]);
15266             savedDetails[i] = NULL;
15267         }
15268         for(i=framePtr; i<MAX_MOVES; i++) {
15269                 if(commentList[i]) free(commentList[i]);
15270                 commentList[i] = NULL;
15271         }
15272         framePtr = MAX_MOVES-1;
15273         storedGames = 0;
15274 }
15275
15276 void
15277 LoadVariation(int index, char *text)
15278 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15279         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15280         int level = 0, move;
15281
15282         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15283         // first find outermost bracketing variation
15284         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15285             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15286                 if(*p == '{') wait = '}'; else
15287                 if(*p == '[') wait = ']'; else
15288                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15289                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15290             }
15291             if(*p == wait) wait = NULLCHAR; // closing ]} found
15292             p++;
15293         }
15294         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15295         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15296         end[1] = NULLCHAR; // clip off comment beyond variation
15297         ToNrEvent(currentMove-1);
15298         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15299         // kludge: use ParsePV() to append variation to game
15300         move = currentMove;
15301         ParsePV(start, TRUE);
15302         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15303         ClearPremoveHighlights();
15304         CommentPopDown();
15305         ToNrEvent(currentMove+1);
15306 }
15307