2cbe2e0691d2dd1ab906919a95132157612ab807
[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, 2011 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 int flock(int f, int code);
61 #define LOCK_EX 2
62 #define SLASH '\\'
63
64 #else
65
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
67 #define SLASH '/'
68
69 #endif
70
71 #include "config.h"
72
73 #include <assert.h>
74 #include <stdio.h>
75 #include <ctype.h>
76 #include <errno.h>
77 #include <sys/types.h>
78 #include <sys/stat.h>
79 #include <math.h>
80 #include <ctype.h>
81
82 #if STDC_HEADERS
83 # include <stdlib.h>
84 # include <string.h>
85 # include <stdarg.h>
86 #else /* not STDC_HEADERS */
87 # if HAVE_STRING_H
88 #  include <string.h>
89 # else /* not HAVE_STRING_H */
90 #  include <strings.h>
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
93
94 #if HAVE_SYS_FCNTL_H
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
97 # if HAVE_FCNTL_H
98 #  include <fcntl.h>
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
101
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
104 # include <time.h>
105 #else
106 # if HAVE_SYS_TIME_H
107 #  include <sys/time.h>
108 # else
109 #  include <time.h>
110 # endif
111 #endif
112
113 #if defined(_amigados) && !defined(__GNUC__)
114 struct timezone {
115     int tz_minuteswest;
116     int tz_dsttime;
117 };
118 extern int gettimeofday(struct timeval *, struct timezone *);
119 #endif
120
121 #if HAVE_UNISTD_H
122 # include <unistd.h>
123 #endif
124
125 #include "common.h"
126 #include "frontend.h"
127 #include "backend.h"
128 #include "parser.h"
129 #include "moves.h"
130 #if ZIPPY
131 # include "zippy.h"
132 #endif
133 #include "backendz.h"
134 #include "gettext.h"
135
136 #ifdef ENABLE_NLS
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
140 #else
141 # ifdef WIN32
142 #   define _(s) T_(s)
143 #   define N_(s) s
144 # else
145 #   define _(s) (s)
146 #   define N_(s) s
147 #   define T_(s) s
148 # endif
149 #endif
150
151
152 /* A point in time */
153 typedef struct {
154     long sec;  /* Assuming this is >= 32 bits */
155     int ms;    /* Assuming this is >= 16 bits */
156 } TimeMark;
157
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160                          char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162                       char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
174                                                                                 Board board));
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178                    /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190                            char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192                         int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
199
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
236 FILE *WriteTourneyFile P((char *results));
237 void DisplayTwoMachinesTitle P(());
238
239 #ifdef WIN32
240        extern void ConsoleCreate();
241 #endif
242
243 ChessProgramState *WhitePlayer();
244 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
245 int VerifyDisplayMode P(());
246
247 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
248 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
249 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
250 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
251 void ics_update_width P((int new_width));
252 extern char installDir[MSG_SIZ];
253 VariantClass startVariant; /* [HGM] nicks: initial variant */
254 Boolean abortMatch;
255
256 extern int tinyLayout, smallLayout;
257 ChessProgramStats programStats;
258 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
259 int endPV = -1;
260 static int exiting = 0; /* [HGM] moved to top */
261 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
262 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
263 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
264 int partnerHighlight[2];
265 Boolean partnerBoardValid = 0;
266 char partnerStatus[MSG_SIZ];
267 Boolean partnerUp;
268 Boolean originalFlip;
269 Boolean twoBoards = 0;
270 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
271 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
272 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
273 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
274 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
275 int opponentKibitzes;
276 int lastSavedGame; /* [HGM] save: ID of game */
277 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
278 extern int chatCount;
279 int chattingPartner;
280 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
281 char lastMsg[MSG_SIZ];
282 ChessSquare pieceSweep = EmptySquare;
283 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
284 int promoDefaultAltered;
285
286 /* States for ics_getting_history */
287 #define H_FALSE 0
288 #define H_REQUESTED 1
289 #define H_GOT_REQ_HEADER 2
290 #define H_GOT_UNREQ_HEADER 3
291 #define H_GETTING_MOVES 4
292 #define H_GOT_UNWANTED_HEADER 5
293
294 /* whosays values for GameEnds */
295 #define GE_ICS 0
296 #define GE_ENGINE 1
297 #define GE_PLAYER 2
298 #define GE_FILE 3
299 #define GE_XBOARD 4
300 #define GE_ENGINE1 5
301 #define GE_ENGINE2 6
302
303 /* Maximum number of games in a cmail message */
304 #define CMAIL_MAX_GAMES 20
305
306 /* Different types of move when calling RegisterMove */
307 #define CMAIL_MOVE   0
308 #define CMAIL_RESIGN 1
309 #define CMAIL_DRAW   2
310 #define CMAIL_ACCEPT 3
311
312 /* Different types of result to remember for each game */
313 #define CMAIL_NOT_RESULT 0
314 #define CMAIL_OLD_RESULT 1
315 #define CMAIL_NEW_RESULT 2
316
317 /* Telnet protocol constants */
318 #define TN_WILL 0373
319 #define TN_WONT 0374
320 #define TN_DO   0375
321 #define TN_DONT 0376
322 #define TN_IAC  0377
323 #define TN_ECHO 0001
324 #define TN_SGA  0003
325 #define TN_PORT 23
326
327 char*
328 safeStrCpy( char *dst, const char *src, size_t count )
329 { // [HGM] made safe
330   int i;
331   assert( dst != NULL );
332   assert( src != NULL );
333   assert( count > 0 );
334
335   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
336   if(  i == count && dst[count-1] != NULLCHAR)
337     {
338       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
339       if(appData.debugMode)
340       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
341     }
342
343   return dst;
344 }
345
346 /* Some compiler can't cast u64 to double
347  * This function do the job for us:
348
349  * We use the highest bit for cast, this only
350  * works if the highest bit is not
351  * in use (This should not happen)
352  *
353  * We used this for all compiler
354  */
355 double
356 u64ToDouble(u64 value)
357 {
358   double r;
359   u64 tmp = value & u64Const(0x7fffffffffffffff);
360   r = (double)(s64)tmp;
361   if (value & u64Const(0x8000000000000000))
362        r +=  9.2233720368547758080e18; /* 2^63 */
363  return r;
364 }
365
366 /* Fake up flags for now, as we aren't keeping track of castling
367    availability yet. [HGM] Change of logic: the flag now only
368    indicates the type of castlings allowed by the rule of the game.
369    The actual rights themselves are maintained in the array
370    castlingRights, as part of the game history, and are not probed
371    by this function.
372  */
373 int
374 PosFlags(index)
375 {
376   int flags = F_ALL_CASTLE_OK;
377   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
378   switch (gameInfo.variant) {
379   case VariantSuicide:
380     flags &= ~F_ALL_CASTLE_OK;
381   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
382     flags |= F_IGNORE_CHECK;
383   case VariantLosers:
384     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
385     break;
386   case VariantAtomic:
387     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
388     break;
389   case VariantKriegspiel:
390     flags |= F_KRIEGSPIEL_CAPTURE;
391     break;
392   case VariantCapaRandom:
393   case VariantFischeRandom:
394     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
395   case VariantNoCastle:
396   case VariantShatranj:
397   case VariantCourier:
398   case VariantMakruk:
399     flags &= ~F_ALL_CASTLE_OK;
400     break;
401   default:
402     break;
403   }
404   return flags;
405 }
406
407 FILE *gameFileFP, *debugFP;
408
409 /*
410     [AS] Note: sometimes, the sscanf() function is used to parse the input
411     into a fixed-size buffer. Because of this, we must be prepared to
412     receive strings as long as the size of the input buffer, which is currently
413     set to 4K for Windows and 8K for the rest.
414     So, we must either allocate sufficiently large buffers here, or
415     reduce the size of the input buffer in the input reading part.
416 */
417
418 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
419 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
420 char thinkOutput1[MSG_SIZ*10];
421
422 ChessProgramState first, second, pairing;
423
424 /* premove variables */
425 int premoveToX = 0;
426 int premoveToY = 0;
427 int premoveFromX = 0;
428 int premoveFromY = 0;
429 int premovePromoChar = 0;
430 int gotPremove = 0;
431 Boolean alarmSounded;
432 /* end premove variables */
433
434 char *ics_prefix = "$";
435 int ics_type = ICS_GENERIC;
436
437 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
438 int pauseExamForwardMostMove = 0;
439 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
440 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
441 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
442 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
443 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
444 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
445 int whiteFlag = FALSE, blackFlag = FALSE;
446 int userOfferedDraw = FALSE;
447 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
448 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
449 int cmailMoveType[CMAIL_MAX_GAMES];
450 long ics_clock_paused = 0;
451 ProcRef icsPR = NoProc, cmailPR = NoProc;
452 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
453 GameMode gameMode = BeginningOfGame;
454 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
455 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
456 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
457 int hiddenThinkOutputState = 0; /* [AS] */
458 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
459 int adjudicateLossPlies = 6;
460 char white_holding[64], black_holding[64];
461 TimeMark lastNodeCountTime;
462 long lastNodeCount=0;
463 int shiftKey; // [HGM] set by mouse handler
464
465 int have_sent_ICS_logon = 0;
466 int movesPerSession;
467 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
468 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
469 long timeControl_2; /* [AS] Allow separate time controls */
470 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
471 long timeRemaining[2][MAX_MOVES];
472 int matchGame = 0, nextGame = 0, roundNr = 0;
473 Boolean waitingForGame = FALSE;
474 TimeMark programStartTime, pauseStart;
475 char ics_handle[MSG_SIZ];
476 int have_set_title = 0;
477
478 /* animateTraining preserves the state of appData.animate
479  * when Training mode is activated. This allows the
480  * response to be animated when appData.animate == TRUE and
481  * appData.animateDragging == TRUE.
482  */
483 Boolean animateTraining;
484
485 GameInfo gameInfo;
486
487 AppData appData;
488
489 Board boards[MAX_MOVES];
490 /* [HGM] Following 7 needed for accurate legality tests: */
491 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
492 signed char  initialRights[BOARD_FILES];
493 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
494 int   initialRulePlies, FENrulePlies;
495 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
496 int loadFlag = 0;
497 Boolean shuffleOpenings;
498 int mute; // mute all sounds
499
500 // [HGM] vari: next 12 to save and restore variations
501 #define MAX_VARIATIONS 10
502 int framePtr = MAX_MOVES-1; // points to free stack entry
503 int storedGames = 0;
504 int savedFirst[MAX_VARIATIONS];
505 int savedLast[MAX_VARIATIONS];
506 int savedFramePtr[MAX_VARIATIONS];
507 char *savedDetails[MAX_VARIATIONS];
508 ChessMove savedResult[MAX_VARIATIONS];
509
510 void PushTail P((int firstMove, int lastMove));
511 Boolean PopTail P((Boolean annotate));
512 void PushInner P((int firstMove, int lastMove));
513 void PopInner P((Boolean annotate));
514 void CleanupTail P((void));
515
516 ChessSquare  FIDEArray[2][BOARD_FILES] = {
517     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
518         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
519     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
520         BlackKing, BlackBishop, BlackKnight, BlackRook }
521 };
522
523 ChessSquare twoKingsArray[2][BOARD_FILES] = {
524     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
525         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
526     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
527         BlackKing, BlackKing, BlackKnight, BlackRook }
528 };
529
530 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
531     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
532         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
533     { BlackRook, BlackMan, BlackBishop, BlackQueen,
534         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
535 };
536
537 ChessSquare SpartanArray[2][BOARD_FILES] = {
538     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
539         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
540     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
541         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
542 };
543
544 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
545     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
548         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
549 };
550
551 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
552     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
553         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
555         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
556 };
557
558 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
559     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
560         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackMan, BlackFerz,
562         BlackKing, BlackMan, BlackKnight, BlackRook }
563 };
564
565
566 #if (BOARD_FILES>=10)
567 ChessSquare ShogiArray[2][BOARD_FILES] = {
568     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
569         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
570     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
571         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
572 };
573
574 ChessSquare XiangqiArray[2][BOARD_FILES] = {
575     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
576         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
577     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
578         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
579 };
580
581 ChessSquare CapablancaArray[2][BOARD_FILES] = {
582     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
583         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
584     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
585         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
586 };
587
588 ChessSquare GreatArray[2][BOARD_FILES] = {
589     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
590         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
591     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
592         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
593 };
594
595 ChessSquare JanusArray[2][BOARD_FILES] = {
596     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
597         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
598     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
599         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
600 };
601
602 #ifdef GOTHIC
603 ChessSquare GothicArray[2][BOARD_FILES] = {
604     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
605         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
606     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
607         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
608 };
609 #else // !GOTHIC
610 #define GothicArray CapablancaArray
611 #endif // !GOTHIC
612
613 #ifdef FALCON
614 ChessSquare FalconArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
616         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
618         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
619 };
620 #else // !FALCON
621 #define FalconArray CapablancaArray
622 #endif // !FALCON
623
624 #else // !(BOARD_FILES>=10)
625 #define XiangqiPosition FIDEArray
626 #define CapablancaArray FIDEArray
627 #define GothicArray FIDEArray
628 #define GreatArray FIDEArray
629 #endif // !(BOARD_FILES>=10)
630
631 #if (BOARD_FILES>=12)
632 ChessSquare CourierArray[2][BOARD_FILES] = {
633     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
634         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
635     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
636         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
637 };
638 #else // !(BOARD_FILES>=12)
639 #define CourierArray CapablancaArray
640 #endif // !(BOARD_FILES>=12)
641
642
643 Board initialPosition;
644
645
646 /* Convert str to a rating. Checks for special cases of "----",
647
648    "++++", etc. Also strips ()'s */
649 int
650 string_to_rating(str)
651   char *str;
652 {
653   while(*str && !isdigit(*str)) ++str;
654   if (!*str)
655     return 0;   /* One of the special "no rating" cases */
656   else
657     return atoi(str);
658 }
659
660 void
661 ClearProgramStats()
662 {
663     /* Init programStats */
664     programStats.movelist[0] = 0;
665     programStats.depth = 0;
666     programStats.nr_moves = 0;
667     programStats.moves_left = 0;
668     programStats.nodes = 0;
669     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
670     programStats.score = 0;
671     programStats.got_only_move = 0;
672     programStats.got_fail = 0;
673     programStats.line_is_book = 0;
674 }
675
676 void
677 CommonEngineInit()
678 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
679     if (appData.firstPlaysBlack) {
680         first.twoMachinesColor = "black\n";
681         second.twoMachinesColor = "white\n";
682     } else {
683         first.twoMachinesColor = "white\n";
684         second.twoMachinesColor = "black\n";
685     }
686
687     first.other = &second;
688     second.other = &first;
689
690     { float norm = 1;
691         if(appData.timeOddsMode) {
692             norm = appData.timeOdds[0];
693             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
694         }
695         first.timeOdds  = appData.timeOdds[0]/norm;
696         second.timeOdds = appData.timeOdds[1]/norm;
697     }
698
699     if(programVersion) free(programVersion);
700     if (appData.noChessProgram) {
701         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
702         sprintf(programVersion, "%s", PACKAGE_STRING);
703     } else {
704       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
705       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
706       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
707     }
708 }
709
710 void
711 UnloadEngine(ChessProgramState *cps)
712 {
713         /* Kill off first chess program */
714         if (cps->isr != NULL)
715           RemoveInputSource(cps->isr);
716         cps->isr = NULL;
717
718         if (cps->pr != NoProc) {
719             ExitAnalyzeMode();
720             DoSleep( appData.delayBeforeQuit );
721             SendToProgram("quit\n", cps);
722             DoSleep( appData.delayAfterQuit );
723             DestroyChildProcess(cps->pr, cps->useSigterm);
724         }
725         cps->pr = NoProc;
726         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
727 }
728
729 void
730 ClearOptions(ChessProgramState *cps)
731 {
732     int i;
733     cps->nrOptions = cps->comboCnt = 0;
734     for(i=0; i<MAX_OPTIONS; i++) {
735         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
736         cps->option[i].textValue = 0;
737     }
738 }
739
740 char *engineNames[] = {
741 "first",
742 "second"
743 };
744
745 void
746 InitEngine(ChessProgramState *cps, int n)
747 {   // [HGM] all engine initialiation put in a function that does one engine
748
749     ClearOptions(cps);
750
751     cps->which = engineNames[n];
752     cps->maybeThinking = FALSE;
753     cps->pr = NoProc;
754     cps->isr = NULL;
755     cps->sendTime = 2;
756     cps->sendDrawOffers = 1;
757
758     cps->program = appData.chessProgram[n];
759     cps->host = appData.host[n];
760     cps->dir = appData.directory[n];
761     cps->initString = appData.engInitString[n];
762     cps->computerString = appData.computerString[n];
763     cps->useSigint  = TRUE;
764     cps->useSigterm = TRUE;
765     cps->reuse = appData.reuse[n];
766     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
767     cps->useSetboard = FALSE;
768     cps->useSAN = FALSE;
769     cps->usePing = FALSE;
770     cps->lastPing = 0;
771     cps->lastPong = 0;
772     cps->usePlayother = FALSE;
773     cps->useColors = TRUE;
774     cps->useUsermove = FALSE;
775     cps->sendICS = FALSE;
776     cps->sendName = appData.icsActive;
777     cps->sdKludge = FALSE;
778     cps->stKludge = FALSE;
779     TidyProgramName(cps->program, cps->host, cps->tidy);
780     cps->matchWins = 0;
781     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782     cps->analysisSupport = 2; /* detect */
783     cps->analyzing = FALSE;
784     cps->initDone = FALSE;
785
786     /* New features added by Tord: */
787     cps->useFEN960 = FALSE;
788     cps->useOOCastle = TRUE;
789     /* End of new features added by Tord. */
790     cps->fenOverride  = appData.fenOverride[n];
791
792     /* [HGM] time odds: set factor for each machine */
793     cps->timeOdds  = appData.timeOdds[n];
794
795     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796     cps->accumulateTC = appData.accumulateTC[n];
797     cps->maxNrOfSessions = 1;
798
799     /* [HGM] debug */
800     cps->debug = FALSE;
801
802     cps->supportsNPS = UNKNOWN;
803     cps->memSize = FALSE;
804     cps->maxCores = FALSE;
805     cps->egtFormats[0] = NULLCHAR;
806
807     /* [HGM] options */
808     cps->optionSettings  = appData.engOptions[n];
809
810     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811     cps->isUCI = appData.isUCI[n]; /* [AS] */
812     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
813
814     if (appData.protocolVersion[n] > PROTOVER
815         || appData.protocolVersion[n] < 1)
816       {
817         char buf[MSG_SIZ];
818         int len;
819
820         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821                        appData.protocolVersion[n]);
822         if( (len > MSG_SIZ) && appData.debugMode )
823           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
824
825         DisplayFatalError(buf, 0, 2);
826       }
827     else
828       {
829         cps->protocolVersion = appData.protocolVersion[n];
830       }
831
832     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
833 }
834
835 ChessProgramState *savCps;
836
837 void
838 LoadEngine()
839 {
840     int i;
841     if(WaitForEngine(savCps, LoadEngine)) return;
842     CommonEngineInit(); // recalculate time odds
843     if(gameInfo.variant != StringToVariant(appData.variant)) {
844         // we changed variant when loading the engine; this forces us to reset
845         Reset(TRUE, savCps != &first);
846         EditGameEvent(); // for consistency with other path, as Reset changes mode
847     }
848     InitChessProgram(savCps, FALSE);
849     SendToProgram("force\n", savCps);
850     DisplayMessage("", "");
851     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
852     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
853     ThawUI();
854     SetGNUMode();
855 }
856
857 void
858 ReplaceEngine(ChessProgramState *cps, int n)
859 {
860     EditGameEvent();
861     UnloadEngine(cps);
862     appData.noChessProgram = FALSE;
863     appData.clockMode = TRUE;
864     InitEngine(cps, n);
865     UpdateLogos(TRUE);
866     if(n) return; // only startup first engine immediately; second can wait
867     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
868     LoadEngine();
869 }
870
871 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
872 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
873
874 static char resetOptions[] = 
875         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
876         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
877
878 void
879 Load(ChessProgramState *cps, int i)
880 {
881     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
882     if(engineLine[0]) { // an engine was selected from the combo box
883         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
884         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
885         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
886         ParseArgsFromString(buf);
887         SwapEngines(i);
888         ReplaceEngine(cps, i);
889         return;
890     }
891     p = engineName;
892     while(q = strchr(p, SLASH)) p = q+1;
893     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
894     if(engineDir[0] != NULLCHAR)
895         appData.directory[i] = engineDir;
896     else if(p != engineName) { // derive directory from engine path, when not given
897         p[-1] = 0;
898         appData.directory[i] = strdup(engineName);
899         p[-1] = SLASH;
900     } else appData.directory[i] = ".";
901     if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
902     if(params[0]) {
903         snprintf(command, MSG_SIZ, "%s %s", p, params);
904         p = command;
905     }
906     appData.chessProgram[i] = strdup(p);
907     appData.isUCI[i] = isUCI;
908     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
909     appData.hasOwnBookUCI[i] = hasBook;
910     if(!nickName[0]) useNick = FALSE;
911     if(useNick) ASSIGN(appData.pgnName[i], nickName);
912     if(addToList) {
913         int len;
914         char quote;
915         q = firstChessProgramNames;
916         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
917         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
918         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
919                         quote, p, quote, appData.directory[i], 
920                         useNick ? " -fn \"" : "",
921                         useNick ? nickName : "",
922                         useNick ? "\"" : "",
923                         v1 ? " -firstProtocolVersion 1" : "",
924                         hasBook ? "" : " -fNoOwnBookUCI",
925                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
926                         storeVariant ? " -variant " : "",
927                         storeVariant ? VariantName(gameInfo.variant) : "");
928         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
929         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
930         if(q)   free(q);
931     }
932     ReplaceEngine(cps, i);
933 }
934
935 void
936 InitTimeControls()
937 {
938     int matched, min, sec;
939     /*
940      * Parse timeControl resource
941      */
942     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
943                           appData.movesPerSession)) {
944         char buf[MSG_SIZ];
945         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
946         DisplayFatalError(buf, 0, 2);
947     }
948
949     /*
950      * Parse searchTime resource
951      */
952     if (*appData.searchTime != NULLCHAR) {
953         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
954         if (matched == 1) {
955             searchTime = min * 60;
956         } else if (matched == 2) {
957             searchTime = min * 60 + sec;
958         } else {
959             char buf[MSG_SIZ];
960             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
961             DisplayFatalError(buf, 0, 2);
962         }
963     }
964 }
965
966 void
967 InitBackEnd1()
968 {
969
970     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
971     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
972
973     GetTimeMark(&programStartTime);
974     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
975     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
976
977     ClearProgramStats();
978     programStats.ok_to_send = 1;
979     programStats.seen_stat = 0;
980
981     /*
982      * Initialize game list
983      */
984     ListNew(&gameList);
985
986
987     /*
988      * Internet chess server status
989      */
990     if (appData.icsActive) {
991         appData.matchMode = FALSE;
992         appData.matchGames = 0;
993 #if ZIPPY
994         appData.noChessProgram = !appData.zippyPlay;
995 #else
996         appData.zippyPlay = FALSE;
997         appData.zippyTalk = FALSE;
998         appData.noChessProgram = TRUE;
999 #endif
1000         if (*appData.icsHelper != NULLCHAR) {
1001             appData.useTelnet = TRUE;
1002             appData.telnetProgram = appData.icsHelper;
1003         }
1004     } else {
1005         appData.zippyTalk = appData.zippyPlay = FALSE;
1006     }
1007
1008     /* [AS] Initialize pv info list [HGM] and game state */
1009     {
1010         int i, j;
1011
1012         for( i=0; i<=framePtr; i++ ) {
1013             pvInfoList[i].depth = -1;
1014             boards[i][EP_STATUS] = EP_NONE;
1015             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1016         }
1017     }
1018
1019     InitTimeControls();
1020
1021     /* [AS] Adjudication threshold */
1022     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1023
1024     InitEngine(&first, 0);
1025     InitEngine(&second, 1);
1026     CommonEngineInit();
1027
1028     pairing.which = "pairing"; // pairing engine
1029     pairing.pr = NoProc;
1030     pairing.isr = NULL;
1031     pairing.program = appData.pairingEngine;
1032     pairing.host = "localhost";
1033     pairing.dir = ".";
1034
1035     if (appData.icsActive) {
1036         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1037     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1038         appData.clockMode = FALSE;
1039         first.sendTime = second.sendTime = 0;
1040     }
1041
1042 #if ZIPPY
1043     /* Override some settings from environment variables, for backward
1044        compatibility.  Unfortunately it's not feasible to have the env
1045        vars just set defaults, at least in xboard.  Ugh.
1046     */
1047     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1048       ZippyInit();
1049     }
1050 #endif
1051
1052     if (!appData.icsActive) {
1053       char buf[MSG_SIZ];
1054       int len;
1055
1056       /* Check for variants that are supported only in ICS mode,
1057          or not at all.  Some that are accepted here nevertheless
1058          have bugs; see comments below.
1059       */
1060       VariantClass variant = StringToVariant(appData.variant);
1061       switch (variant) {
1062       case VariantBughouse:     /* need four players and two boards */
1063       case VariantKriegspiel:   /* need to hide pieces and move details */
1064         /* case VariantFischeRandom: (Fabien: moved below) */
1065         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1066         if( (len > MSG_SIZ) && appData.debugMode )
1067           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1068
1069         DisplayFatalError(buf, 0, 2);
1070         return;
1071
1072       case VariantUnknown:
1073       case VariantLoadable:
1074       case Variant29:
1075       case Variant30:
1076       case Variant31:
1077       case Variant32:
1078       case Variant33:
1079       case Variant34:
1080       case Variant35:
1081       case Variant36:
1082       default:
1083         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1084         if( (len > MSG_SIZ) && appData.debugMode )
1085           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1086
1087         DisplayFatalError(buf, 0, 2);
1088         return;
1089
1090       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1091       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1092       case VariantGothic:     /* [HGM] should work */
1093       case VariantCapablanca: /* [HGM] should work */
1094       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1095       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1096       case VariantKnightmate: /* [HGM] should work */
1097       case VariantCylinder:   /* [HGM] untested */
1098       case VariantFalcon:     /* [HGM] untested */
1099       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1100                                  offboard interposition not understood */
1101       case VariantNormal:     /* definitely works! */
1102       case VariantWildCastle: /* pieces not automatically shuffled */
1103       case VariantNoCastle:   /* pieces not automatically shuffled */
1104       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1105       case VariantLosers:     /* should work except for win condition,
1106                                  and doesn't know captures are mandatory */
1107       case VariantSuicide:    /* should work except for win condition,
1108                                  and doesn't know captures are mandatory */
1109       case VariantGiveaway:   /* should work except for win condition,
1110                                  and doesn't know captures are mandatory */
1111       case VariantTwoKings:   /* should work */
1112       case VariantAtomic:     /* should work except for win condition */
1113       case Variant3Check:     /* should work except for win condition */
1114       case VariantShatranj:   /* should work except for all win conditions */
1115       case VariantMakruk:     /* should work except for daw countdown */
1116       case VariantBerolina:   /* might work if TestLegality is off */
1117       case VariantCapaRandom: /* should work */
1118       case VariantJanus:      /* should work */
1119       case VariantSuper:      /* experimental */
1120       case VariantGreat:      /* experimental, requires legality testing to be off */
1121       case VariantSChess:     /* S-Chess, should work */
1122       case VariantSpartan:    /* should work */
1123         break;
1124       }
1125     }
1126
1127 }
1128
1129 int NextIntegerFromString( char ** str, long * value )
1130 {
1131     int result = -1;
1132     char * s = *str;
1133
1134     while( *s == ' ' || *s == '\t' ) {
1135         s++;
1136     }
1137
1138     *value = 0;
1139
1140     if( *s >= '0' && *s <= '9' ) {
1141         while( *s >= '0' && *s <= '9' ) {
1142             *value = *value * 10 + (*s - '0');
1143             s++;
1144         }
1145
1146         result = 0;
1147     }
1148
1149     *str = s;
1150
1151     return result;
1152 }
1153
1154 int NextTimeControlFromString( char ** str, long * value )
1155 {
1156     long temp;
1157     int result = NextIntegerFromString( str, &temp );
1158
1159     if( result == 0 ) {
1160         *value = temp * 60; /* Minutes */
1161         if( **str == ':' ) {
1162             (*str)++;
1163             result = NextIntegerFromString( str, &temp );
1164             *value += temp; /* Seconds */
1165         }
1166     }
1167
1168     return result;
1169 }
1170
1171 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1172 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1173     int result = -1, type = 0; long temp, temp2;
1174
1175     if(**str != ':') return -1; // old params remain in force!
1176     (*str)++;
1177     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1178     if( NextIntegerFromString( str, &temp ) ) return -1;
1179     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1180
1181     if(**str != '/') {
1182         /* time only: incremental or sudden-death time control */
1183         if(**str == '+') { /* increment follows; read it */
1184             (*str)++;
1185             if(**str == '!') type = *(*str)++; // Bronstein TC
1186             if(result = NextIntegerFromString( str, &temp2)) return -1;
1187             *inc = temp2 * 1000;
1188             if(**str == '.') { // read fraction of increment
1189                 char *start = ++(*str);
1190                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1191                 temp2 *= 1000;
1192                 while(start++ < *str) temp2 /= 10;
1193                 *inc += temp2;
1194             }
1195         } else *inc = 0;
1196         *moves = 0; *tc = temp * 1000; *incType = type;
1197         return 0;
1198     }
1199
1200     (*str)++; /* classical time control */
1201     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1202
1203     if(result == 0) {
1204         *moves = temp;
1205         *tc    = temp2 * 1000;
1206         *inc   = 0;
1207         *incType = type;
1208     }
1209     return result;
1210 }
1211
1212 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1213 {   /* [HGM] get time to add from the multi-session time-control string */
1214     int incType, moves=1; /* kludge to force reading of first session */
1215     long time, increment;
1216     char *s = tcString;
1217
1218     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1219     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1220     do {
1221         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1222         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1223         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1224         if(movenr == -1) return time;    /* last move before new session     */
1225         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1226         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1227         if(!moves) return increment;     /* current session is incremental   */
1228         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1229     } while(movenr >= -1);               /* try again for next session       */
1230
1231     return 0; // no new time quota on this move
1232 }
1233
1234 int
1235 ParseTimeControl(tc, ti, mps)
1236      char *tc;
1237      float ti;
1238      int mps;
1239 {
1240   long tc1;
1241   long tc2;
1242   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1243   int min, sec=0;
1244
1245   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1246   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1247       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1248   if(ti > 0) {
1249
1250     if(mps)
1251       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1252     else 
1253       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1254   } else {
1255     if(mps)
1256       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1257     else 
1258       snprintf(buf, MSG_SIZ, ":%s", mytc);
1259   }
1260   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1261   
1262   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1263     return FALSE;
1264   }
1265
1266   if( *tc == '/' ) {
1267     /* Parse second time control */
1268     tc++;
1269
1270     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1271       return FALSE;
1272     }
1273
1274     if( tc2 == 0 ) {
1275       return FALSE;
1276     }
1277
1278     timeControl_2 = tc2 * 1000;
1279   }
1280   else {
1281     timeControl_2 = 0;
1282   }
1283
1284   if( tc1 == 0 ) {
1285     return FALSE;
1286   }
1287
1288   timeControl = tc1 * 1000;
1289
1290   if (ti >= 0) {
1291     timeIncrement = ti * 1000;  /* convert to ms */
1292     movesPerSession = 0;
1293   } else {
1294     timeIncrement = 0;
1295     movesPerSession = mps;
1296   }
1297   return TRUE;
1298 }
1299
1300 void
1301 InitBackEnd2()
1302 {
1303     if (appData.debugMode) {
1304         fprintf(debugFP, "%s\n", programVersion);
1305     }
1306
1307     set_cont_sequence(appData.wrapContSeq);
1308     if (appData.matchGames > 0) {
1309         appData.matchMode = TRUE;
1310     } else if (appData.matchMode) {
1311         appData.matchGames = 1;
1312     }
1313     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1314         appData.matchGames = appData.sameColorGames;
1315     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1316         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1317         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1318     }
1319     Reset(TRUE, FALSE);
1320     if (appData.noChessProgram || first.protocolVersion == 1) {
1321       InitBackEnd3();
1322     } else {
1323       /* kludge: allow timeout for initial "feature" commands */
1324       FreezeUI();
1325       DisplayMessage("", _("Starting chess program"));
1326       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1327     }
1328 }
1329
1330 int
1331 CalculateIndex(int index, int gameNr)
1332 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1333     int res;
1334     if(index > 0) return index; // fixed nmber
1335     if(index == 0) return 1;
1336     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1337     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1338     return res;
1339 }
1340
1341 int
1342 LoadGameOrPosition(int gameNr)
1343 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1344     if (*appData.loadGameFile != NULLCHAR) {
1345         if (!LoadGameFromFile(appData.loadGameFile,
1346                 CalculateIndex(appData.loadGameIndex, gameNr),
1347                               appData.loadGameFile, FALSE)) {
1348             DisplayFatalError(_("Bad game file"), 0, 1);
1349             return 0;
1350         }
1351     } else if (*appData.loadPositionFile != NULLCHAR) {
1352         if (!LoadPositionFromFile(appData.loadPositionFile,
1353                 CalculateIndex(appData.loadPositionIndex, gameNr),
1354                                   appData.loadPositionFile)) {
1355             DisplayFatalError(_("Bad position file"), 0, 1);
1356             return 0;
1357         }
1358     }
1359     return 1;
1360 }
1361
1362 void
1363 ReserveGame(int gameNr, char resChar)
1364 {
1365     FILE *tf = fopen(appData.tourneyFile, "r+");
1366     char *p, *q, c, buf[MSG_SIZ];
1367     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1368     safeStrCpy(buf, lastMsg, MSG_SIZ);
1369     DisplayMessage(_("Pick new game"), "");
1370     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1371     ParseArgsFromFile(tf);
1372     p = q = appData.results;
1373     if(appData.debugMode) {
1374       char *r = appData.participants;
1375       fprintf(debugFP, "results = '%s'\n", p);
1376       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1377       fprintf(debugFP, "\n");
1378     }
1379     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1380     nextGame = q - p;
1381     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1382     safeStrCpy(q, p, strlen(p) + 2);
1383     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1384     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1385     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1386         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1387         q[nextGame] = '*';
1388     }
1389     fseek(tf, -(strlen(p)+4), SEEK_END);
1390     c = fgetc(tf);
1391     if(c != '"') // depending on DOS or Unix line endings we can be one off
1392          fseek(tf, -(strlen(p)+2), SEEK_END);
1393     else fseek(tf, -(strlen(p)+3), SEEK_END);
1394     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1395     DisplayMessage(buf, "");
1396     free(p); appData.results = q;
1397     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1398        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1399         UnloadEngine(&first);  // next game belongs to other pairing;
1400         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1401     }
1402 }
1403
1404 void
1405 MatchEvent(int mode)
1406 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1407         int dummy;
1408         if(matchMode) { // already in match mode: switch it off
1409             abortMatch = TRUE;
1410             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1411             return;
1412         }
1413 //      if(gameMode != BeginningOfGame) {
1414 //          DisplayError(_("You can only start a match from the initial position."), 0);
1415 //          return;
1416 //      }
1417         abortMatch = FALSE;
1418         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1419         /* Set up machine vs. machine match */
1420         nextGame = 0;
1421         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1422         if(appData.tourneyFile[0]) {
1423             ReserveGame(-1, 0);
1424             if(nextGame > appData.matchGames) {
1425                 char buf[MSG_SIZ];
1426                 if(strchr(appData.results, '*') == NULL) {
1427                     FILE *f;
1428                     appData.tourneyCycles++;
1429                     if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1430                         fclose(f);
1431                         NextTourneyGame(-1, &dummy);
1432                         ReserveGame(-1, 0);
1433                         if(nextGame <= appData.matchGames) {
1434                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1435                             matchMode = mode;
1436                             ScheduleDelayedEvent(NextMatchGame, 10000);
1437                             return;
1438                         }
1439                     }
1440                 }
1441                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1442                 DisplayError(buf, 0);
1443                 appData.tourneyFile[0] = 0;
1444                 return;
1445             }
1446         } else
1447         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1448             DisplayFatalError(_("Can't have a match with no chess programs"),
1449                               0, 2);
1450             return;
1451         }
1452         matchMode = mode;
1453         matchGame = roundNr = 1;
1454         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1455         NextMatchGame();
1456 }
1457
1458 void
1459 InitBackEnd3 P((void))
1460 {
1461     GameMode initialMode;
1462     char buf[MSG_SIZ];
1463     int err, len;
1464
1465     InitChessProgram(&first, startedFromSetupPosition);
1466
1467     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1468         free(programVersion);
1469         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1470         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1471     }
1472
1473     if (appData.icsActive) {
1474 #ifdef WIN32
1475         /* [DM] Make a console window if needed [HGM] merged ifs */
1476         ConsoleCreate();
1477 #endif
1478         err = establish();
1479         if (err != 0)
1480           {
1481             if (*appData.icsCommPort != NULLCHAR)
1482               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1483                              appData.icsCommPort);
1484             else
1485               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1486                         appData.icsHost, appData.icsPort);
1487
1488             if( (len > MSG_SIZ) && appData.debugMode )
1489               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1490
1491             DisplayFatalError(buf, err, 1);
1492             return;
1493         }
1494         SetICSMode();
1495         telnetISR =
1496           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1497         fromUserISR =
1498           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1499         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1500             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1501     } else if (appData.noChessProgram) {
1502         SetNCPMode();
1503     } else {
1504         SetGNUMode();
1505     }
1506
1507     if (*appData.cmailGameName != NULLCHAR) {
1508         SetCmailMode();
1509         OpenLoopback(&cmailPR);
1510         cmailISR =
1511           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1512     }
1513
1514     ThawUI();
1515     DisplayMessage("", "");
1516     if (StrCaseCmp(appData.initialMode, "") == 0) {
1517       initialMode = BeginningOfGame;
1518       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1519         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1520         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1521         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1522         ModeHighlight();
1523       }
1524     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1525       initialMode = TwoMachinesPlay;
1526     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1527       initialMode = AnalyzeFile;
1528     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1529       initialMode = AnalyzeMode;
1530     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1531       initialMode = MachinePlaysWhite;
1532     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1533       initialMode = MachinePlaysBlack;
1534     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1535       initialMode = EditGame;
1536     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1537       initialMode = EditPosition;
1538     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1539       initialMode = Training;
1540     } else {
1541       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1542       if( (len > MSG_SIZ) && appData.debugMode )
1543         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1544
1545       DisplayFatalError(buf, 0, 2);
1546       return;
1547     }
1548
1549     if (appData.matchMode) {
1550         if(appData.tourneyFile[0]) { // start tourney from command line
1551             FILE *f;
1552             if(f = fopen(appData.tourneyFile, "r")) {
1553                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1554                 fclose(f);
1555                 appData.clockMode = TRUE;
1556                 SetGNUMode();
1557             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1558         }
1559         MatchEvent(TRUE);
1560     } else if (*appData.cmailGameName != NULLCHAR) {
1561         /* Set up cmail mode */
1562         ReloadCmailMsgEvent(TRUE);
1563     } else {
1564         /* Set up other modes */
1565         if (initialMode == AnalyzeFile) {
1566           if (*appData.loadGameFile == NULLCHAR) {
1567             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1568             return;
1569           }
1570         }
1571         if (*appData.loadGameFile != NULLCHAR) {
1572             (void) LoadGameFromFile(appData.loadGameFile,
1573                                     appData.loadGameIndex,
1574                                     appData.loadGameFile, TRUE);
1575         } else if (*appData.loadPositionFile != NULLCHAR) {
1576             (void) LoadPositionFromFile(appData.loadPositionFile,
1577                                         appData.loadPositionIndex,
1578                                         appData.loadPositionFile);
1579             /* [HGM] try to make self-starting even after FEN load */
1580             /* to allow automatic setup of fairy variants with wtm */
1581             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1582                 gameMode = BeginningOfGame;
1583                 setboardSpoiledMachineBlack = 1;
1584             }
1585             /* [HGM] loadPos: make that every new game uses the setup */
1586             /* from file as long as we do not switch variant          */
1587             if(!blackPlaysFirst) {
1588                 startedFromPositionFile = TRUE;
1589                 CopyBoard(filePosition, boards[0]);
1590             }
1591         }
1592         if (initialMode == AnalyzeMode) {
1593           if (appData.noChessProgram) {
1594             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1595             return;
1596           }
1597           if (appData.icsActive) {
1598             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1599             return;
1600           }
1601           AnalyzeModeEvent();
1602         } else if (initialMode == AnalyzeFile) {
1603           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1604           ShowThinkingEvent();
1605           AnalyzeFileEvent();
1606           AnalysisPeriodicEvent(1);
1607         } else if (initialMode == MachinePlaysWhite) {
1608           if (appData.noChessProgram) {
1609             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1610                               0, 2);
1611             return;
1612           }
1613           if (appData.icsActive) {
1614             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1615                               0, 2);
1616             return;
1617           }
1618           MachineWhiteEvent();
1619         } else if (initialMode == MachinePlaysBlack) {
1620           if (appData.noChessProgram) {
1621             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1622                               0, 2);
1623             return;
1624           }
1625           if (appData.icsActive) {
1626             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1627                               0, 2);
1628             return;
1629           }
1630           MachineBlackEvent();
1631         } else if (initialMode == TwoMachinesPlay) {
1632           if (appData.noChessProgram) {
1633             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1634                               0, 2);
1635             return;
1636           }
1637           if (appData.icsActive) {
1638             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1639                               0, 2);
1640             return;
1641           }
1642           TwoMachinesEvent();
1643         } else if (initialMode == EditGame) {
1644           EditGameEvent();
1645         } else if (initialMode == EditPosition) {
1646           EditPositionEvent();
1647         } else if (initialMode == Training) {
1648           if (*appData.loadGameFile == NULLCHAR) {
1649             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1650             return;
1651           }
1652           TrainingEvent();
1653         }
1654     }
1655 }
1656
1657 /*
1658  * Establish will establish a contact to a remote host.port.
1659  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1660  *  used to talk to the host.
1661  * Returns 0 if okay, error code if not.
1662  */
1663 int
1664 establish()
1665 {
1666     char buf[MSG_SIZ];
1667
1668     if (*appData.icsCommPort != NULLCHAR) {
1669         /* Talk to the host through a serial comm port */
1670         return OpenCommPort(appData.icsCommPort, &icsPR);
1671
1672     } else if (*appData.gateway != NULLCHAR) {
1673         if (*appData.remoteShell == NULLCHAR) {
1674             /* Use the rcmd protocol to run telnet program on a gateway host */
1675             snprintf(buf, sizeof(buf), "%s %s %s",
1676                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1677             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1678
1679         } else {
1680             /* Use the rsh program to run telnet program on a gateway host */
1681             if (*appData.remoteUser == NULLCHAR) {
1682                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1683                         appData.gateway, appData.telnetProgram,
1684                         appData.icsHost, appData.icsPort);
1685             } else {
1686                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1687                         appData.remoteShell, appData.gateway,
1688                         appData.remoteUser, appData.telnetProgram,
1689                         appData.icsHost, appData.icsPort);
1690             }
1691             return StartChildProcess(buf, "", &icsPR);
1692
1693         }
1694     } else if (appData.useTelnet) {
1695         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1696
1697     } else {
1698         /* TCP socket interface differs somewhat between
1699            Unix and NT; handle details in the front end.
1700            */
1701         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1702     }
1703 }
1704
1705 void EscapeExpand(char *p, char *q)
1706 {       // [HGM] initstring: routine to shape up string arguments
1707         while(*p++ = *q++) if(p[-1] == '\\')
1708             switch(*q++) {
1709                 case 'n': p[-1] = '\n'; break;
1710                 case 'r': p[-1] = '\r'; break;
1711                 case 't': p[-1] = '\t'; break;
1712                 case '\\': p[-1] = '\\'; break;
1713                 case 0: *p = 0; return;
1714                 default: p[-1] = q[-1]; break;
1715             }
1716 }
1717
1718 void
1719 show_bytes(fp, buf, count)
1720      FILE *fp;
1721      char *buf;
1722      int count;
1723 {
1724     while (count--) {
1725         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1726             fprintf(fp, "\\%03o", *buf & 0xff);
1727         } else {
1728             putc(*buf, fp);
1729         }
1730         buf++;
1731     }
1732     fflush(fp);
1733 }
1734
1735 /* Returns an errno value */
1736 int
1737 OutputMaybeTelnet(pr, message, count, outError)
1738      ProcRef pr;
1739      char *message;
1740      int count;
1741      int *outError;
1742 {
1743     char buf[8192], *p, *q, *buflim;
1744     int left, newcount, outcount;
1745
1746     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1747         *appData.gateway != NULLCHAR) {
1748         if (appData.debugMode) {
1749             fprintf(debugFP, ">ICS: ");
1750             show_bytes(debugFP, message, count);
1751             fprintf(debugFP, "\n");
1752         }
1753         return OutputToProcess(pr, message, count, outError);
1754     }
1755
1756     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1757     p = message;
1758     q = buf;
1759     left = count;
1760     newcount = 0;
1761     while (left) {
1762         if (q >= buflim) {
1763             if (appData.debugMode) {
1764                 fprintf(debugFP, ">ICS: ");
1765                 show_bytes(debugFP, buf, newcount);
1766                 fprintf(debugFP, "\n");
1767             }
1768             outcount = OutputToProcess(pr, buf, newcount, outError);
1769             if (outcount < newcount) return -1; /* to be sure */
1770             q = buf;
1771             newcount = 0;
1772         }
1773         if (*p == '\n') {
1774             *q++ = '\r';
1775             newcount++;
1776         } else if (((unsigned char) *p) == TN_IAC) {
1777             *q++ = (char) TN_IAC;
1778             newcount ++;
1779         }
1780         *q++ = *p++;
1781         newcount++;
1782         left--;
1783     }
1784     if (appData.debugMode) {
1785         fprintf(debugFP, ">ICS: ");
1786         show_bytes(debugFP, buf, newcount);
1787         fprintf(debugFP, "\n");
1788     }
1789     outcount = OutputToProcess(pr, buf, newcount, outError);
1790     if (outcount < newcount) return -1; /* to be sure */
1791     return count;
1792 }
1793
1794 void
1795 read_from_player(isr, closure, message, count, error)
1796      InputSourceRef isr;
1797      VOIDSTAR closure;
1798      char *message;
1799      int count;
1800      int error;
1801 {
1802     int outError, outCount;
1803     static int gotEof = 0;
1804
1805     /* Pass data read from player on to ICS */
1806     if (count > 0) {
1807         gotEof = 0;
1808         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1809         if (outCount < count) {
1810             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1811         }
1812     } else if (count < 0) {
1813         RemoveInputSource(isr);
1814         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1815     } else if (gotEof++ > 0) {
1816         RemoveInputSource(isr);
1817         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1818     }
1819 }
1820
1821 void
1822 KeepAlive()
1823 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1824     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1825     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1826     SendToICS("date\n");
1827     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1828 }
1829
1830 /* added routine for printf style output to ics */
1831 void ics_printf(char *format, ...)
1832 {
1833     char buffer[MSG_SIZ];
1834     va_list args;
1835
1836     va_start(args, format);
1837     vsnprintf(buffer, sizeof(buffer), format, args);
1838     buffer[sizeof(buffer)-1] = '\0';
1839     SendToICS(buffer);
1840     va_end(args);
1841 }
1842
1843 void
1844 SendToICS(s)
1845      char *s;
1846 {
1847     int count, outCount, outError;
1848
1849     if (icsPR == NULL) return;
1850
1851     count = strlen(s);
1852     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1853     if (outCount < count) {
1854         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1855     }
1856 }
1857
1858 /* This is used for sending logon scripts to the ICS. Sending
1859    without a delay causes problems when using timestamp on ICC
1860    (at least on my machine). */
1861 void
1862 SendToICSDelayed(s,msdelay)
1863      char *s;
1864      long msdelay;
1865 {
1866     int count, outCount, outError;
1867
1868     if (icsPR == NULL) return;
1869
1870     count = strlen(s);
1871     if (appData.debugMode) {
1872         fprintf(debugFP, ">ICS: ");
1873         show_bytes(debugFP, s, count);
1874         fprintf(debugFP, "\n");
1875     }
1876     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1877                                       msdelay);
1878     if (outCount < count) {
1879         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1880     }
1881 }
1882
1883
1884 /* Remove all highlighting escape sequences in s
1885    Also deletes any suffix starting with '('
1886    */
1887 char *
1888 StripHighlightAndTitle(s)
1889      char *s;
1890 {
1891     static char retbuf[MSG_SIZ];
1892     char *p = retbuf;
1893
1894     while (*s != NULLCHAR) {
1895         while (*s == '\033') {
1896             while (*s != NULLCHAR && !isalpha(*s)) s++;
1897             if (*s != NULLCHAR) s++;
1898         }
1899         while (*s != NULLCHAR && *s != '\033') {
1900             if (*s == '(' || *s == '[') {
1901                 *p = NULLCHAR;
1902                 return retbuf;
1903             }
1904             *p++ = *s++;
1905         }
1906     }
1907     *p = NULLCHAR;
1908     return retbuf;
1909 }
1910
1911 /* Remove all highlighting escape sequences in s */
1912 char *
1913 StripHighlight(s)
1914      char *s;
1915 {
1916     static char retbuf[MSG_SIZ];
1917     char *p = retbuf;
1918
1919     while (*s != NULLCHAR) {
1920         while (*s == '\033') {
1921             while (*s != NULLCHAR && !isalpha(*s)) s++;
1922             if (*s != NULLCHAR) s++;
1923         }
1924         while (*s != NULLCHAR && *s != '\033') {
1925             *p++ = *s++;
1926         }
1927     }
1928     *p = NULLCHAR;
1929     return retbuf;
1930 }
1931
1932 char *variantNames[] = VARIANT_NAMES;
1933 char *
1934 VariantName(v)
1935      VariantClass v;
1936 {
1937     return variantNames[v];
1938 }
1939
1940
1941 /* Identify a variant from the strings the chess servers use or the
1942    PGN Variant tag names we use. */
1943 VariantClass
1944 StringToVariant(e)
1945      char *e;
1946 {
1947     char *p;
1948     int wnum = -1;
1949     VariantClass v = VariantNormal;
1950     int i, found = FALSE;
1951     char buf[MSG_SIZ];
1952     int len;
1953
1954     if (!e) return v;
1955
1956     /* [HGM] skip over optional board-size prefixes */
1957     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1958         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1959         while( *e++ != '_');
1960     }
1961
1962     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1963         v = VariantNormal;
1964         found = TRUE;
1965     } else
1966     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1967       if (StrCaseStr(e, variantNames[i])) {
1968         v = (VariantClass) i;
1969         found = TRUE;
1970         break;
1971       }
1972     }
1973
1974     if (!found) {
1975       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1976           || StrCaseStr(e, "wild/fr")
1977           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1978         v = VariantFischeRandom;
1979       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1980                  (i = 1, p = StrCaseStr(e, "w"))) {
1981         p += i;
1982         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1983         if (isdigit(*p)) {
1984           wnum = atoi(p);
1985         } else {
1986           wnum = -1;
1987         }
1988         switch (wnum) {
1989         case 0: /* FICS only, actually */
1990         case 1:
1991           /* Castling legal even if K starts on d-file */
1992           v = VariantWildCastle;
1993           break;
1994         case 2:
1995         case 3:
1996         case 4:
1997           /* Castling illegal even if K & R happen to start in
1998              normal positions. */
1999           v = VariantNoCastle;
2000           break;
2001         case 5:
2002         case 7:
2003         case 8:
2004         case 10:
2005         case 11:
2006         case 12:
2007         case 13:
2008         case 14:
2009         case 15:
2010         case 18:
2011         case 19:
2012           /* Castling legal iff K & R start in normal positions */
2013           v = VariantNormal;
2014           break;
2015         case 6:
2016         case 20:
2017         case 21:
2018           /* Special wilds for position setup; unclear what to do here */
2019           v = VariantLoadable;
2020           break;
2021         case 9:
2022           /* Bizarre ICC game */
2023           v = VariantTwoKings;
2024           break;
2025         case 16:
2026           v = VariantKriegspiel;
2027           break;
2028         case 17:
2029           v = VariantLosers;
2030           break;
2031         case 22:
2032           v = VariantFischeRandom;
2033           break;
2034         case 23:
2035           v = VariantCrazyhouse;
2036           break;
2037         case 24:
2038           v = VariantBughouse;
2039           break;
2040         case 25:
2041           v = Variant3Check;
2042           break;
2043         case 26:
2044           /* Not quite the same as FICS suicide! */
2045           v = VariantGiveaway;
2046           break;
2047         case 27:
2048           v = VariantAtomic;
2049           break;
2050         case 28:
2051           v = VariantShatranj;
2052           break;
2053
2054         /* Temporary names for future ICC types.  The name *will* change in
2055            the next xboard/WinBoard release after ICC defines it. */
2056         case 29:
2057           v = Variant29;
2058           break;
2059         case 30:
2060           v = Variant30;
2061           break;
2062         case 31:
2063           v = Variant31;
2064           break;
2065         case 32:
2066           v = Variant32;
2067           break;
2068         case 33:
2069           v = Variant33;
2070           break;
2071         case 34:
2072           v = Variant34;
2073           break;
2074         case 35:
2075           v = Variant35;
2076           break;
2077         case 36:
2078           v = Variant36;
2079           break;
2080         case 37:
2081           v = VariantShogi;
2082           break;
2083         case 38:
2084           v = VariantXiangqi;
2085           break;
2086         case 39:
2087           v = VariantCourier;
2088           break;
2089         case 40:
2090           v = VariantGothic;
2091           break;
2092         case 41:
2093           v = VariantCapablanca;
2094           break;
2095         case 42:
2096           v = VariantKnightmate;
2097           break;
2098         case 43:
2099           v = VariantFairy;
2100           break;
2101         case 44:
2102           v = VariantCylinder;
2103           break;
2104         case 45:
2105           v = VariantFalcon;
2106           break;
2107         case 46:
2108           v = VariantCapaRandom;
2109           break;
2110         case 47:
2111           v = VariantBerolina;
2112           break;
2113         case 48:
2114           v = VariantJanus;
2115           break;
2116         case 49:
2117           v = VariantSuper;
2118           break;
2119         case 50:
2120           v = VariantGreat;
2121           break;
2122         case -1:
2123           /* Found "wild" or "w" in the string but no number;
2124              must assume it's normal chess. */
2125           v = VariantNormal;
2126           break;
2127         default:
2128           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2129           if( (len > MSG_SIZ) && appData.debugMode )
2130             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2131
2132           DisplayError(buf, 0);
2133           v = VariantUnknown;
2134           break;
2135         }
2136       }
2137     }
2138     if (appData.debugMode) {
2139       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2140               e, wnum, VariantName(v));
2141     }
2142     return v;
2143 }
2144
2145 static int leftover_start = 0, leftover_len = 0;
2146 char star_match[STAR_MATCH_N][MSG_SIZ];
2147
2148 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2149    advance *index beyond it, and set leftover_start to the new value of
2150    *index; else return FALSE.  If pattern contains the character '*', it
2151    matches any sequence of characters not containing '\r', '\n', or the
2152    character following the '*' (if any), and the matched sequence(s) are
2153    copied into star_match.
2154    */
2155 int
2156 looking_at(buf, index, pattern)
2157      char *buf;
2158      int *index;
2159      char *pattern;
2160 {
2161     char *bufp = &buf[*index], *patternp = pattern;
2162     int star_count = 0;
2163     char *matchp = star_match[0];
2164
2165     for (;;) {
2166         if (*patternp == NULLCHAR) {
2167             *index = leftover_start = bufp - buf;
2168             *matchp = NULLCHAR;
2169             return TRUE;
2170         }
2171         if (*bufp == NULLCHAR) return FALSE;
2172         if (*patternp == '*') {
2173             if (*bufp == *(patternp + 1)) {
2174                 *matchp = NULLCHAR;
2175                 matchp = star_match[++star_count];
2176                 patternp += 2;
2177                 bufp++;
2178                 continue;
2179             } else if (*bufp == '\n' || *bufp == '\r') {
2180                 patternp++;
2181                 if (*patternp == NULLCHAR)
2182                   continue;
2183                 else
2184                   return FALSE;
2185             } else {
2186                 *matchp++ = *bufp++;
2187                 continue;
2188             }
2189         }
2190         if (*patternp != *bufp) return FALSE;
2191         patternp++;
2192         bufp++;
2193     }
2194 }
2195
2196 void
2197 SendToPlayer(data, length)
2198      char *data;
2199      int length;
2200 {
2201     int error, outCount;
2202     outCount = OutputToProcess(NoProc, data, length, &error);
2203     if (outCount < length) {
2204         DisplayFatalError(_("Error writing to display"), error, 1);
2205     }
2206 }
2207
2208 void
2209 PackHolding(packed, holding)
2210      char packed[];
2211      char *holding;
2212 {
2213     char *p = holding;
2214     char *q = packed;
2215     int runlength = 0;
2216     int curr = 9999;
2217     do {
2218         if (*p == curr) {
2219             runlength++;
2220         } else {
2221             switch (runlength) {
2222               case 0:
2223                 break;
2224               case 1:
2225                 *q++ = curr;
2226                 break;
2227               case 2:
2228                 *q++ = curr;
2229                 *q++ = curr;
2230                 break;
2231               default:
2232                 sprintf(q, "%d", runlength);
2233                 while (*q) q++;
2234                 *q++ = curr;
2235                 break;
2236             }
2237             runlength = 1;
2238             curr = *p;
2239         }
2240     } while (*p++);
2241     *q = NULLCHAR;
2242 }
2243
2244 /* Telnet protocol requests from the front end */
2245 void
2246 TelnetRequest(ddww, option)
2247      unsigned char ddww, option;
2248 {
2249     unsigned char msg[3];
2250     int outCount, outError;
2251
2252     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2253
2254     if (appData.debugMode) {
2255         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2256         switch (ddww) {
2257           case TN_DO:
2258             ddwwStr = "DO";
2259             break;
2260           case TN_DONT:
2261             ddwwStr = "DONT";
2262             break;
2263           case TN_WILL:
2264             ddwwStr = "WILL";
2265             break;
2266           case TN_WONT:
2267             ddwwStr = "WONT";
2268             break;
2269           default:
2270             ddwwStr = buf1;
2271             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2272             break;
2273         }
2274         switch (option) {
2275           case TN_ECHO:
2276             optionStr = "ECHO";
2277             break;
2278           default:
2279             optionStr = buf2;
2280             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2281             break;
2282         }
2283         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2284     }
2285     msg[0] = TN_IAC;
2286     msg[1] = ddww;
2287     msg[2] = option;
2288     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2289     if (outCount < 3) {
2290         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2291     }
2292 }
2293
2294 void
2295 DoEcho()
2296 {
2297     if (!appData.icsActive) return;
2298     TelnetRequest(TN_DO, TN_ECHO);
2299 }
2300
2301 void
2302 DontEcho()
2303 {
2304     if (!appData.icsActive) return;
2305     TelnetRequest(TN_DONT, TN_ECHO);
2306 }
2307
2308 void
2309 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2310 {
2311     /* put the holdings sent to us by the server on the board holdings area */
2312     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2313     char p;
2314     ChessSquare piece;
2315
2316     if(gameInfo.holdingsWidth < 2)  return;
2317     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2318         return; // prevent overwriting by pre-board holdings
2319
2320     if( (int)lowestPiece >= BlackPawn ) {
2321         holdingsColumn = 0;
2322         countsColumn = 1;
2323         holdingsStartRow = BOARD_HEIGHT-1;
2324         direction = -1;
2325     } else {
2326         holdingsColumn = BOARD_WIDTH-1;
2327         countsColumn = BOARD_WIDTH-2;
2328         holdingsStartRow = 0;
2329         direction = 1;
2330     }
2331
2332     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2333         board[i][holdingsColumn] = EmptySquare;
2334         board[i][countsColumn]   = (ChessSquare) 0;
2335     }
2336     while( (p=*holdings++) != NULLCHAR ) {
2337         piece = CharToPiece( ToUpper(p) );
2338         if(piece == EmptySquare) continue;
2339         /*j = (int) piece - (int) WhitePawn;*/
2340         j = PieceToNumber(piece);
2341         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2342         if(j < 0) continue;               /* should not happen */
2343         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2344         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2345         board[holdingsStartRow+j*direction][countsColumn]++;
2346     }
2347 }
2348
2349
2350 void
2351 VariantSwitch(Board board, VariantClass newVariant)
2352 {
2353    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2354    static Board oldBoard;
2355
2356    startedFromPositionFile = FALSE;
2357    if(gameInfo.variant == newVariant) return;
2358
2359    /* [HGM] This routine is called each time an assignment is made to
2360     * gameInfo.variant during a game, to make sure the board sizes
2361     * are set to match the new variant. If that means adding or deleting
2362     * holdings, we shift the playing board accordingly
2363     * This kludge is needed because in ICS observe mode, we get boards
2364     * of an ongoing game without knowing the variant, and learn about the
2365     * latter only later. This can be because of the move list we requested,
2366     * in which case the game history is refilled from the beginning anyway,
2367     * but also when receiving holdings of a crazyhouse game. In the latter
2368     * case we want to add those holdings to the already received position.
2369     */
2370
2371
2372    if (appData.debugMode) {
2373      fprintf(debugFP, "Switch board from %s to %s\n",
2374              VariantName(gameInfo.variant), VariantName(newVariant));
2375      setbuf(debugFP, NULL);
2376    }
2377    shuffleOpenings = 0;       /* [HGM] shuffle */
2378    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2379    switch(newVariant)
2380      {
2381      case VariantShogi:
2382        newWidth = 9;  newHeight = 9;
2383        gameInfo.holdingsSize = 7;
2384      case VariantBughouse:
2385      case VariantCrazyhouse:
2386        newHoldingsWidth = 2; break;
2387      case VariantGreat:
2388        newWidth = 10;
2389      case VariantSuper:
2390        newHoldingsWidth = 2;
2391        gameInfo.holdingsSize = 8;
2392        break;
2393      case VariantGothic:
2394      case VariantCapablanca:
2395      case VariantCapaRandom:
2396        newWidth = 10;
2397      default:
2398        newHoldingsWidth = gameInfo.holdingsSize = 0;
2399      };
2400
2401    if(newWidth  != gameInfo.boardWidth  ||
2402       newHeight != gameInfo.boardHeight ||
2403       newHoldingsWidth != gameInfo.holdingsWidth ) {
2404
2405      /* shift position to new playing area, if needed */
2406      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2407        for(i=0; i<BOARD_HEIGHT; i++)
2408          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2409            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2410              board[i][j];
2411        for(i=0; i<newHeight; i++) {
2412          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2413          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2414        }
2415      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2416        for(i=0; i<BOARD_HEIGHT; i++)
2417          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2418            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2419              board[i][j];
2420      }
2421      gameInfo.boardWidth  = newWidth;
2422      gameInfo.boardHeight = newHeight;
2423      gameInfo.holdingsWidth = newHoldingsWidth;
2424      gameInfo.variant = newVariant;
2425      InitDrawingSizes(-2, 0);
2426    } else gameInfo.variant = newVariant;
2427    CopyBoard(oldBoard, board);   // remember correctly formatted board
2428      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2429    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2430 }
2431
2432 static int loggedOn = FALSE;
2433
2434 /*-- Game start info cache: --*/
2435 int gs_gamenum;
2436 char gs_kind[MSG_SIZ];
2437 static char player1Name[128] = "";
2438 static char player2Name[128] = "";
2439 static char cont_seq[] = "\n\\   ";
2440 static int player1Rating = -1;
2441 static int player2Rating = -1;
2442 /*----------------------------*/
2443
2444 ColorClass curColor = ColorNormal;
2445 int suppressKibitz = 0;
2446
2447 // [HGM] seekgraph
2448 Boolean soughtPending = FALSE;
2449 Boolean seekGraphUp;
2450 #define MAX_SEEK_ADS 200
2451 #define SQUARE 0x80
2452 char *seekAdList[MAX_SEEK_ADS];
2453 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2454 float tcList[MAX_SEEK_ADS];
2455 char colorList[MAX_SEEK_ADS];
2456 int nrOfSeekAds = 0;
2457 int minRating = 1010, maxRating = 2800;
2458 int hMargin = 10, vMargin = 20, h, w;
2459 extern int squareSize, lineGap;
2460
2461 void
2462 PlotSeekAd(int i)
2463 {
2464         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2465         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2466         if(r < minRating+100 && r >=0 ) r = minRating+100;
2467         if(r > maxRating) r = maxRating;
2468         if(tc < 1.) tc = 1.;
2469         if(tc > 95.) tc = 95.;
2470         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2471         y = ((double)r - minRating)/(maxRating - minRating)
2472             * (h-vMargin-squareSize/8-1) + vMargin;
2473         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2474         if(strstr(seekAdList[i], " u ")) color = 1;
2475         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2476            !strstr(seekAdList[i], "bullet") &&
2477            !strstr(seekAdList[i], "blitz") &&
2478            !strstr(seekAdList[i], "standard") ) color = 2;
2479         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2480         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2481 }
2482
2483 void
2484 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2485 {
2486         char buf[MSG_SIZ], *ext = "";
2487         VariantClass v = StringToVariant(type);
2488         if(strstr(type, "wild")) {
2489             ext = type + 4; // append wild number
2490             if(v == VariantFischeRandom) type = "chess960"; else
2491             if(v == VariantLoadable) type = "setup"; else
2492             type = VariantName(v);
2493         }
2494         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2495         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2496             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2497             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2498             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2499             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2500             seekNrList[nrOfSeekAds] = nr;
2501             zList[nrOfSeekAds] = 0;
2502             seekAdList[nrOfSeekAds++] = StrSave(buf);
2503             if(plot) PlotSeekAd(nrOfSeekAds-1);
2504         }
2505 }
2506
2507 void
2508 EraseSeekDot(int i)
2509 {
2510     int x = xList[i], y = yList[i], d=squareSize/4, k;
2511     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2512     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2513     // now replot every dot that overlapped
2514     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2515         int xx = xList[k], yy = yList[k];
2516         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2517             DrawSeekDot(xx, yy, colorList[k]);
2518     }
2519 }
2520
2521 void
2522 RemoveSeekAd(int nr)
2523 {
2524         int i;
2525         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2526             EraseSeekDot(i);
2527             if(seekAdList[i]) free(seekAdList[i]);
2528             seekAdList[i] = seekAdList[--nrOfSeekAds];
2529             seekNrList[i] = seekNrList[nrOfSeekAds];
2530             ratingList[i] = ratingList[nrOfSeekAds];
2531             colorList[i]  = colorList[nrOfSeekAds];
2532             tcList[i] = tcList[nrOfSeekAds];
2533             xList[i]  = xList[nrOfSeekAds];
2534             yList[i]  = yList[nrOfSeekAds];
2535             zList[i]  = zList[nrOfSeekAds];
2536             seekAdList[nrOfSeekAds] = NULL;
2537             break;
2538         }
2539 }
2540
2541 Boolean
2542 MatchSoughtLine(char *line)
2543 {
2544     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2545     int nr, base, inc, u=0; char dummy;
2546
2547     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2548        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2549        (u=1) &&
2550        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2551         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2552         // match: compact and save the line
2553         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2554         return TRUE;
2555     }
2556     return FALSE;
2557 }
2558
2559 int
2560 DrawSeekGraph()
2561 {
2562     int i;
2563     if(!seekGraphUp) return FALSE;
2564     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2565     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2566
2567     DrawSeekBackground(0, 0, w, h);
2568     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2569     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2570     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2571         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2572         yy = h-1-yy;
2573         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2574         if(i%500 == 0) {
2575             char buf[MSG_SIZ];
2576             snprintf(buf, MSG_SIZ, "%d", i);
2577             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2578         }
2579     }
2580     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2581     for(i=1; i<100; i+=(i<10?1:5)) {
2582         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2583         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2584         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2585             char buf[MSG_SIZ];
2586             snprintf(buf, MSG_SIZ, "%d", i);
2587             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2588         }
2589     }
2590     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2591     return TRUE;
2592 }
2593
2594 int SeekGraphClick(ClickType click, int x, int y, int moving)
2595 {
2596     static int lastDown = 0, displayed = 0, lastSecond;
2597     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2598         if(click == Release || moving) return FALSE;
2599         nrOfSeekAds = 0;
2600         soughtPending = TRUE;
2601         SendToICS(ics_prefix);
2602         SendToICS("sought\n"); // should this be "sought all"?
2603     } else { // issue challenge based on clicked ad
2604         int dist = 10000; int i, closest = 0, second = 0;
2605         for(i=0; i<nrOfSeekAds; i++) {
2606             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2607             if(d < dist) { dist = d; closest = i; }
2608             second += (d - zList[i] < 120); // count in-range ads
2609             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2610         }
2611         if(dist < 120) {
2612             char buf[MSG_SIZ];
2613             second = (second > 1);
2614             if(displayed != closest || second != lastSecond) {
2615                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2616                 lastSecond = second; displayed = closest;
2617             }
2618             if(click == Press) {
2619                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2620                 lastDown = closest;
2621                 return TRUE;
2622             } // on press 'hit', only show info
2623             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2624             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2625             SendToICS(ics_prefix);
2626             SendToICS(buf);
2627             return TRUE; // let incoming board of started game pop down the graph
2628         } else if(click == Release) { // release 'miss' is ignored
2629             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2630             if(moving == 2) { // right up-click
2631                 nrOfSeekAds = 0; // refresh graph
2632                 soughtPending = TRUE;
2633                 SendToICS(ics_prefix);
2634                 SendToICS("sought\n"); // should this be "sought all"?
2635             }
2636             return TRUE;
2637         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2638         // press miss or release hit 'pop down' seek graph
2639         seekGraphUp = FALSE;
2640         DrawPosition(TRUE, NULL);
2641     }
2642     return TRUE;
2643 }
2644
2645 void
2646 read_from_ics(isr, closure, data, count, error)
2647      InputSourceRef isr;
2648      VOIDSTAR closure;
2649      char *data;
2650      int count;
2651      int error;
2652 {
2653 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2654 #define STARTED_NONE 0
2655 #define STARTED_MOVES 1
2656 #define STARTED_BOARD 2
2657 #define STARTED_OBSERVE 3
2658 #define STARTED_HOLDINGS 4
2659 #define STARTED_CHATTER 5
2660 #define STARTED_COMMENT 6
2661 #define STARTED_MOVES_NOHIDE 7
2662
2663     static int started = STARTED_NONE;
2664     static char parse[20000];
2665     static int parse_pos = 0;
2666     static char buf[BUF_SIZE + 1];
2667     static int firstTime = TRUE, intfSet = FALSE;
2668     static ColorClass prevColor = ColorNormal;
2669     static int savingComment = FALSE;
2670     static int cmatch = 0; // continuation sequence match
2671     char *bp;
2672     char str[MSG_SIZ];
2673     int i, oldi;
2674     int buf_len;
2675     int next_out;
2676     int tkind;
2677     int backup;    /* [DM] For zippy color lines */
2678     char *p;
2679     char talker[MSG_SIZ]; // [HGM] chat
2680     int channel;
2681
2682     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2683
2684     if (appData.debugMode) {
2685       if (!error) {
2686         fprintf(debugFP, "<ICS: ");
2687         show_bytes(debugFP, data, count);
2688         fprintf(debugFP, "\n");
2689       }
2690     }
2691
2692     if (appData.debugMode) { int f = forwardMostMove;
2693         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2694                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2695                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2696     }
2697     if (count > 0) {
2698         /* If last read ended with a partial line that we couldn't parse,
2699            prepend it to the new read and try again. */
2700         if (leftover_len > 0) {
2701             for (i=0; i<leftover_len; i++)
2702               buf[i] = buf[leftover_start + i];
2703         }
2704
2705     /* copy new characters into the buffer */
2706     bp = buf + leftover_len;
2707     buf_len=leftover_len;
2708     for (i=0; i<count; i++)
2709     {
2710         // ignore these
2711         if (data[i] == '\r')
2712             continue;
2713
2714         // join lines split by ICS?
2715         if (!appData.noJoin)
2716         {
2717             /*
2718                 Joining just consists of finding matches against the
2719                 continuation sequence, and discarding that sequence
2720                 if found instead of copying it.  So, until a match
2721                 fails, there's nothing to do since it might be the
2722                 complete sequence, and thus, something we don't want
2723                 copied.
2724             */
2725             if (data[i] == cont_seq[cmatch])
2726             {
2727                 cmatch++;
2728                 if (cmatch == strlen(cont_seq))
2729                 {
2730                     cmatch = 0; // complete match.  just reset the counter
2731
2732                     /*
2733                         it's possible for the ICS to not include the space
2734                         at the end of the last word, making our [correct]
2735                         join operation fuse two separate words.  the server
2736                         does this when the space occurs at the width setting.
2737                     */
2738                     if (!buf_len || buf[buf_len-1] != ' ')
2739                     {
2740                         *bp++ = ' ';
2741                         buf_len++;
2742                     }
2743                 }
2744                 continue;
2745             }
2746             else if (cmatch)
2747             {
2748                 /*
2749                     match failed, so we have to copy what matched before
2750                     falling through and copying this character.  In reality,
2751                     this will only ever be just the newline character, but
2752                     it doesn't hurt to be precise.
2753                 */
2754                 strncpy(bp, cont_seq, cmatch);
2755                 bp += cmatch;
2756                 buf_len += cmatch;
2757                 cmatch = 0;
2758             }
2759         }
2760
2761         // copy this char
2762         *bp++ = data[i];
2763         buf_len++;
2764     }
2765
2766         buf[buf_len] = NULLCHAR;
2767 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2768         next_out = 0;
2769         leftover_start = 0;
2770
2771         i = 0;
2772         while (i < buf_len) {
2773             /* Deal with part of the TELNET option negotiation
2774                protocol.  We refuse to do anything beyond the
2775                defaults, except that we allow the WILL ECHO option,
2776                which ICS uses to turn off password echoing when we are
2777                directly connected to it.  We reject this option
2778                if localLineEditing mode is on (always on in xboard)
2779                and we are talking to port 23, which might be a real
2780                telnet server that will try to keep WILL ECHO on permanently.
2781              */
2782             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2783                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2784                 unsigned char option;
2785                 oldi = i;
2786                 switch ((unsigned char) buf[++i]) {
2787                   case TN_WILL:
2788                     if (appData.debugMode)
2789                       fprintf(debugFP, "\n<WILL ");
2790                     switch (option = (unsigned char) buf[++i]) {
2791                       case TN_ECHO:
2792                         if (appData.debugMode)
2793                           fprintf(debugFP, "ECHO ");
2794                         /* Reply only if this is a change, according
2795                            to the protocol rules. */
2796                         if (remoteEchoOption) break;
2797                         if (appData.localLineEditing &&
2798                             atoi(appData.icsPort) == TN_PORT) {
2799                             TelnetRequest(TN_DONT, TN_ECHO);
2800                         } else {
2801                             EchoOff();
2802                             TelnetRequest(TN_DO, TN_ECHO);
2803                             remoteEchoOption = TRUE;
2804                         }
2805                         break;
2806                       default:
2807                         if (appData.debugMode)
2808                           fprintf(debugFP, "%d ", option);
2809                         /* Whatever this is, we don't want it. */
2810                         TelnetRequest(TN_DONT, option);
2811                         break;
2812                     }
2813                     break;
2814                   case TN_WONT:
2815                     if (appData.debugMode)
2816                       fprintf(debugFP, "\n<WONT ");
2817                     switch (option = (unsigned char) buf[++i]) {
2818                       case TN_ECHO:
2819                         if (appData.debugMode)
2820                           fprintf(debugFP, "ECHO ");
2821                         /* Reply only if this is a change, according
2822                            to the protocol rules. */
2823                         if (!remoteEchoOption) break;
2824                         EchoOn();
2825                         TelnetRequest(TN_DONT, TN_ECHO);
2826                         remoteEchoOption = FALSE;
2827                         break;
2828                       default:
2829                         if (appData.debugMode)
2830                           fprintf(debugFP, "%d ", (unsigned char) option);
2831                         /* Whatever this is, it must already be turned
2832                            off, because we never agree to turn on
2833                            anything non-default, so according to the
2834                            protocol rules, we don't reply. */
2835                         break;
2836                     }
2837                     break;
2838                   case TN_DO:
2839                     if (appData.debugMode)
2840                       fprintf(debugFP, "\n<DO ");
2841                     switch (option = (unsigned char) buf[++i]) {
2842                       default:
2843                         /* Whatever this is, we refuse to do it. */
2844                         if (appData.debugMode)
2845                           fprintf(debugFP, "%d ", option);
2846                         TelnetRequest(TN_WONT, option);
2847                         break;
2848                     }
2849                     break;
2850                   case TN_DONT:
2851                     if (appData.debugMode)
2852                       fprintf(debugFP, "\n<DONT ");
2853                     switch (option = (unsigned char) buf[++i]) {
2854                       default:
2855                         if (appData.debugMode)
2856                           fprintf(debugFP, "%d ", option);
2857                         /* Whatever this is, we are already not doing
2858                            it, because we never agree to do anything
2859                            non-default, so according to the protocol
2860                            rules, we don't reply. */
2861                         break;
2862                     }
2863                     break;
2864                   case TN_IAC:
2865                     if (appData.debugMode)
2866                       fprintf(debugFP, "\n<IAC ");
2867                     /* Doubled IAC; pass it through */
2868                     i--;
2869                     break;
2870                   default:
2871                     if (appData.debugMode)
2872                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2873                     /* Drop all other telnet commands on the floor */
2874                     break;
2875                 }
2876                 if (oldi > next_out)
2877                   SendToPlayer(&buf[next_out], oldi - next_out);
2878                 if (++i > next_out)
2879                   next_out = i;
2880                 continue;
2881             }
2882
2883             /* OK, this at least will *usually* work */
2884             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2885                 loggedOn = TRUE;
2886             }
2887
2888             if (loggedOn && !intfSet) {
2889                 if (ics_type == ICS_ICC) {
2890                   snprintf(str, MSG_SIZ,
2891                           "/set-quietly interface %s\n/set-quietly style 12\n",
2892                           programVersion);
2893                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2894                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2895                 } else if (ics_type == ICS_CHESSNET) {
2896                   snprintf(str, MSG_SIZ, "/style 12\n");
2897                 } else {
2898                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2899                   strcat(str, programVersion);
2900                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2901                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2902                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2903 #ifdef WIN32
2904                   strcat(str, "$iset nohighlight 1\n");
2905 #endif
2906                   strcat(str, "$iset lock 1\n$style 12\n");
2907                 }
2908                 SendToICS(str);
2909                 NotifyFrontendLogin();
2910                 intfSet = TRUE;
2911             }
2912
2913             if (started == STARTED_COMMENT) {
2914                 /* Accumulate characters in comment */
2915                 parse[parse_pos++] = buf[i];
2916                 if (buf[i] == '\n') {
2917                     parse[parse_pos] = NULLCHAR;
2918                     if(chattingPartner>=0) {
2919                         char mess[MSG_SIZ];
2920                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2921                         OutputChatMessage(chattingPartner, mess);
2922                         chattingPartner = -1;
2923                         next_out = i+1; // [HGM] suppress printing in ICS window
2924                     } else
2925                     if(!suppressKibitz) // [HGM] kibitz
2926                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2927                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2928                         int nrDigit = 0, nrAlph = 0, j;
2929                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2930                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2931                         parse[parse_pos] = NULLCHAR;
2932                         // try to be smart: if it does not look like search info, it should go to
2933                         // ICS interaction window after all, not to engine-output window.
2934                         for(j=0; j<parse_pos; j++) { // count letters and digits
2935                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2936                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2937                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2938                         }
2939                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2940                             int depth=0; float score;
2941                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2942                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2943                                 pvInfoList[forwardMostMove-1].depth = depth;
2944                                 pvInfoList[forwardMostMove-1].score = 100*score;
2945                             }
2946                             OutputKibitz(suppressKibitz, parse);
2947                         } else {
2948                             char tmp[MSG_SIZ];
2949                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2950                             SendToPlayer(tmp, strlen(tmp));
2951                         }
2952                         next_out = i+1; // [HGM] suppress printing in ICS window
2953                     }
2954                     started = STARTED_NONE;
2955                 } else {
2956                     /* Don't match patterns against characters in comment */
2957                     i++;
2958                     continue;
2959                 }
2960             }
2961             if (started == STARTED_CHATTER) {
2962                 if (buf[i] != '\n') {
2963                     /* Don't match patterns against characters in chatter */
2964                     i++;
2965                     continue;
2966                 }
2967                 started = STARTED_NONE;
2968                 if(suppressKibitz) next_out = i+1;
2969             }
2970
2971             /* Kludge to deal with rcmd protocol */
2972             if (firstTime && looking_at(buf, &i, "\001*")) {
2973                 DisplayFatalError(&buf[1], 0, 1);
2974                 continue;
2975             } else {
2976                 firstTime = FALSE;
2977             }
2978
2979             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2980                 ics_type = ICS_ICC;
2981                 ics_prefix = "/";
2982                 if (appData.debugMode)
2983                   fprintf(debugFP, "ics_type %d\n", ics_type);
2984                 continue;
2985             }
2986             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2987                 ics_type = ICS_FICS;
2988                 ics_prefix = "$";
2989                 if (appData.debugMode)
2990                   fprintf(debugFP, "ics_type %d\n", ics_type);
2991                 continue;
2992             }
2993             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2994                 ics_type = ICS_CHESSNET;
2995                 ics_prefix = "/";
2996                 if (appData.debugMode)
2997                   fprintf(debugFP, "ics_type %d\n", ics_type);
2998                 continue;
2999             }
3000
3001             if (!loggedOn &&
3002                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3003                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3004                  looking_at(buf, &i, "will be \"*\""))) {
3005               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3006               continue;
3007             }
3008
3009             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3010               char buf[MSG_SIZ];
3011               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3012               DisplayIcsInteractionTitle(buf);
3013               have_set_title = TRUE;
3014             }
3015
3016             /* skip finger notes */
3017             if (started == STARTED_NONE &&
3018                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3019                  (buf[i] == '1' && buf[i+1] == '0')) &&
3020                 buf[i+2] == ':' && buf[i+3] == ' ') {
3021               started = STARTED_CHATTER;
3022               i += 3;
3023               continue;
3024             }
3025
3026             oldi = i;
3027             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3028             if(appData.seekGraph) {
3029                 if(soughtPending && MatchSoughtLine(buf+i)) {
3030                     i = strstr(buf+i, "rated") - buf;
3031                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3032                     next_out = leftover_start = i;
3033                     started = STARTED_CHATTER;
3034                     suppressKibitz = TRUE;
3035                     continue;
3036                 }
3037                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3038                         && looking_at(buf, &i, "* ads displayed")) {
3039                     soughtPending = FALSE;
3040                     seekGraphUp = TRUE;
3041                     DrawSeekGraph();
3042                     continue;
3043                 }
3044                 if(appData.autoRefresh) {
3045                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3046                         int s = (ics_type == ICS_ICC); // ICC format differs
3047                         if(seekGraphUp)
3048                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3049                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3050                         looking_at(buf, &i, "*% "); // eat prompt
3051                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3052                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3053                         next_out = i; // suppress
3054                         continue;
3055                     }
3056                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3057                         char *p = star_match[0];
3058                         while(*p) {
3059                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3060                             while(*p && *p++ != ' '); // next
3061                         }
3062                         looking_at(buf, &i, "*% "); // eat prompt
3063                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3064                         next_out = i;
3065                         continue;
3066                     }
3067                 }
3068             }
3069
3070             /* skip formula vars */
3071             if (started == STARTED_NONE &&
3072                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3073               started = STARTED_CHATTER;
3074               i += 3;
3075               continue;
3076             }
3077
3078             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3079             if (appData.autoKibitz && started == STARTED_NONE &&
3080                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3081                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3082                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3083                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3084                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3085                         suppressKibitz = TRUE;
3086                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3087                         next_out = i;
3088                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3089                                 && (gameMode == IcsPlayingWhite)) ||
3090                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3091                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3092                             started = STARTED_CHATTER; // own kibitz we simply discard
3093                         else {
3094                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3095                             parse_pos = 0; parse[0] = NULLCHAR;
3096                             savingComment = TRUE;
3097                             suppressKibitz = gameMode != IcsObserving ? 2 :
3098                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3099                         }
3100                         continue;
3101                 } else
3102                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3103                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3104                          && atoi(star_match[0])) {
3105                     // suppress the acknowledgements of our own autoKibitz
3106                     char *p;
3107                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3108                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3109                     SendToPlayer(star_match[0], strlen(star_match[0]));
3110                     if(looking_at(buf, &i, "*% ")) // eat prompt
3111                         suppressKibitz = FALSE;
3112                     next_out = i;
3113                     continue;
3114                 }
3115             } // [HGM] kibitz: end of patch
3116
3117             // [HGM] chat: intercept tells by users for which we have an open chat window
3118             channel = -1;
3119             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3120                                            looking_at(buf, &i, "* whispers:") ||
3121                                            looking_at(buf, &i, "* kibitzes:") ||
3122                                            looking_at(buf, &i, "* shouts:") ||
3123                                            looking_at(buf, &i, "* c-shouts:") ||
3124                                            looking_at(buf, &i, "--> * ") ||
3125                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3126                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3127                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3128                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3129                 int p;
3130                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3131                 chattingPartner = -1;
3132
3133                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3134                 for(p=0; p<MAX_CHAT; p++) {
3135                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3136                     talker[0] = '['; strcat(talker, "] ");
3137                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3138                     chattingPartner = p; break;
3139                     }
3140                 } else
3141                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3142                 for(p=0; p<MAX_CHAT; p++) {
3143                     if(!strcmp("kibitzes", chatPartner[p])) {
3144                         talker[0] = '['; strcat(talker, "] ");
3145                         chattingPartner = p; break;
3146                     }
3147                 } else
3148                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3149                 for(p=0; p<MAX_CHAT; p++) {
3150                     if(!strcmp("whispers", chatPartner[p])) {
3151                         talker[0] = '['; strcat(talker, "] ");
3152                         chattingPartner = p; break;
3153                     }
3154                 } else
3155                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3156                   if(buf[i-8] == '-' && buf[i-3] == 't')
3157                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3158                     if(!strcmp("c-shouts", chatPartner[p])) {
3159                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3160                         chattingPartner = p; break;
3161                     }
3162                   }
3163                   if(chattingPartner < 0)
3164                   for(p=0; p<MAX_CHAT; p++) {
3165                     if(!strcmp("shouts", chatPartner[p])) {
3166                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3167                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3168                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3169                         chattingPartner = p; break;
3170                     }
3171                   }
3172                 }
3173                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3174                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3175                     talker[0] = 0; Colorize(ColorTell, FALSE);
3176                     chattingPartner = p; break;
3177                 }
3178                 if(chattingPartner<0) i = oldi; else {
3179                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3180                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3181                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3182                     started = STARTED_COMMENT;
3183                     parse_pos = 0; parse[0] = NULLCHAR;
3184                     savingComment = 3 + chattingPartner; // counts as TRUE
3185                     suppressKibitz = TRUE;
3186                     continue;
3187                 }
3188             } // [HGM] chat: end of patch
3189
3190           backup = i;
3191             if (appData.zippyTalk || appData.zippyPlay) {
3192                 /* [DM] Backup address for color zippy lines */
3193 #if ZIPPY
3194                if (loggedOn == TRUE)
3195                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3196                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3197 #endif
3198             } // [DM] 'else { ' deleted
3199                 if (
3200                     /* Regular tells and says */
3201                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3202                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3203                     looking_at(buf, &i, "* says: ") ||
3204                     /* Don't color "message" or "messages" output */
3205                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3206                     looking_at(buf, &i, "*. * at *:*: ") ||
3207                     looking_at(buf, &i, "--* (*:*): ") ||
3208                     /* Message notifications (same color as tells) */
3209                     looking_at(buf, &i, "* has left a message ") ||
3210                     looking_at(buf, &i, "* just sent you a message:\n") ||
3211                     /* Whispers and kibitzes */
3212                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3213                     looking_at(buf, &i, "* kibitzes: ") ||
3214                     /* Channel tells */
3215                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3216
3217                   if (tkind == 1 && strchr(star_match[0], ':')) {
3218                       /* Avoid "tells you:" spoofs in channels */
3219                      tkind = 3;
3220                   }
3221                   if (star_match[0][0] == NULLCHAR ||
3222                       strchr(star_match[0], ' ') ||
3223                       (tkind == 3 && strchr(star_match[1], ' '))) {
3224                     /* Reject bogus matches */
3225                     i = oldi;
3226                   } else {
3227                     if (appData.colorize) {
3228                       if (oldi > next_out) {
3229                         SendToPlayer(&buf[next_out], oldi - next_out);
3230                         next_out = oldi;
3231                       }
3232                       switch (tkind) {
3233                       case 1:
3234                         Colorize(ColorTell, FALSE);
3235                         curColor = ColorTell;
3236                         break;
3237                       case 2:
3238                         Colorize(ColorKibitz, FALSE);
3239                         curColor = ColorKibitz;
3240                         break;
3241                       case 3:
3242                         p = strrchr(star_match[1], '(');
3243                         if (p == NULL) {
3244                           p = star_match[1];
3245                         } else {
3246                           p++;
3247                         }
3248                         if (atoi(p) == 1) {
3249                           Colorize(ColorChannel1, FALSE);
3250                           curColor = ColorChannel1;
3251                         } else {
3252                           Colorize(ColorChannel, FALSE);
3253                           curColor = ColorChannel;
3254                         }
3255                         break;
3256                       case 5:
3257                         curColor = ColorNormal;
3258                         break;
3259                       }
3260                     }
3261                     if (started == STARTED_NONE && appData.autoComment &&
3262                         (gameMode == IcsObserving ||
3263                          gameMode == IcsPlayingWhite ||
3264                          gameMode == IcsPlayingBlack)) {
3265                       parse_pos = i - oldi;
3266                       memcpy(parse, &buf[oldi], parse_pos);
3267                       parse[parse_pos] = NULLCHAR;
3268                       started = STARTED_COMMENT;
3269                       savingComment = TRUE;
3270                     } else {
3271                       started = STARTED_CHATTER;
3272                       savingComment = FALSE;
3273                     }
3274                     loggedOn = TRUE;
3275                     continue;
3276                   }
3277                 }
3278
3279                 if (looking_at(buf, &i, "* s-shouts: ") ||
3280                     looking_at(buf, &i, "* c-shouts: ")) {
3281                     if (appData.colorize) {
3282                         if (oldi > next_out) {
3283                             SendToPlayer(&buf[next_out], oldi - next_out);
3284                             next_out = oldi;
3285                         }
3286                         Colorize(ColorSShout, FALSE);
3287                         curColor = ColorSShout;
3288                     }
3289                     loggedOn = TRUE;
3290                     started = STARTED_CHATTER;
3291                     continue;
3292                 }
3293
3294                 if (looking_at(buf, &i, "--->")) {
3295                     loggedOn = TRUE;
3296                     continue;
3297                 }
3298
3299                 if (looking_at(buf, &i, "* shouts: ") ||
3300                     looking_at(buf, &i, "--> ")) {
3301                     if (appData.colorize) {
3302                         if (oldi > next_out) {
3303                             SendToPlayer(&buf[next_out], oldi - next_out);
3304                             next_out = oldi;
3305                         }
3306                         Colorize(ColorShout, FALSE);
3307                         curColor = ColorShout;
3308                     }
3309                     loggedOn = TRUE;
3310                     started = STARTED_CHATTER;
3311                     continue;
3312                 }
3313
3314                 if (looking_at( buf, &i, "Challenge:")) {
3315                     if (appData.colorize) {
3316                         if (oldi > next_out) {
3317                             SendToPlayer(&buf[next_out], oldi - next_out);
3318                             next_out = oldi;
3319                         }
3320                         Colorize(ColorChallenge, FALSE);
3321                         curColor = ColorChallenge;
3322                     }
3323                     loggedOn = TRUE;
3324                     continue;
3325                 }
3326
3327                 if (looking_at(buf, &i, "* offers you") ||
3328                     looking_at(buf, &i, "* offers to be") ||
3329                     looking_at(buf, &i, "* would like to") ||
3330                     looking_at(buf, &i, "* requests to") ||
3331                     looking_at(buf, &i, "Your opponent offers") ||
3332                     looking_at(buf, &i, "Your opponent requests")) {
3333
3334                     if (appData.colorize) {
3335                         if (oldi > next_out) {
3336                             SendToPlayer(&buf[next_out], oldi - next_out);
3337                             next_out = oldi;
3338                         }
3339                         Colorize(ColorRequest, FALSE);
3340                         curColor = ColorRequest;
3341                     }
3342                     continue;
3343                 }
3344
3345                 if (looking_at(buf, &i, "* (*) seeking")) {
3346                     if (appData.colorize) {
3347                         if (oldi > next_out) {
3348                             SendToPlayer(&buf[next_out], oldi - next_out);
3349                             next_out = oldi;
3350                         }
3351                         Colorize(ColorSeek, FALSE);
3352                         curColor = ColorSeek;
3353                     }
3354                     continue;
3355             }
3356
3357           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3358
3359             if (looking_at(buf, &i, "\\   ")) {
3360                 if (prevColor != ColorNormal) {
3361                     if (oldi > next_out) {
3362                         SendToPlayer(&buf[next_out], oldi - next_out);
3363                         next_out = oldi;
3364                     }
3365                     Colorize(prevColor, TRUE);
3366                     curColor = prevColor;
3367                 }
3368                 if (savingComment) {
3369                     parse_pos = i - oldi;
3370                     memcpy(parse, &buf[oldi], parse_pos);
3371                     parse[parse_pos] = NULLCHAR;
3372                     started = STARTED_COMMENT;
3373                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3374                         chattingPartner = savingComment - 3; // kludge to remember the box
3375                 } else {
3376                     started = STARTED_CHATTER;
3377                 }
3378                 continue;
3379             }
3380
3381             if (looking_at(buf, &i, "Black Strength :") ||
3382                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3383                 looking_at(buf, &i, "<10>") ||
3384                 looking_at(buf, &i, "#@#")) {
3385                 /* Wrong board style */
3386                 loggedOn = TRUE;
3387                 SendToICS(ics_prefix);
3388                 SendToICS("set style 12\n");
3389                 SendToICS(ics_prefix);
3390                 SendToICS("refresh\n");
3391                 continue;
3392             }
3393
3394             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3395                 ICSInitScript();
3396                 have_sent_ICS_logon = 1;
3397                 continue;
3398             }
3399
3400             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3401                 (looking_at(buf, &i, "\n<12> ") ||
3402                  looking_at(buf, &i, "<12> "))) {
3403                 loggedOn = TRUE;
3404                 if (oldi > next_out) {
3405                     SendToPlayer(&buf[next_out], oldi - next_out);
3406                 }
3407                 next_out = i;
3408                 started = STARTED_BOARD;
3409                 parse_pos = 0;
3410                 continue;
3411             }
3412
3413             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3414                 looking_at(buf, &i, "<b1> ")) {
3415                 if (oldi > next_out) {
3416                     SendToPlayer(&buf[next_out], oldi - next_out);
3417                 }
3418                 next_out = i;
3419                 started = STARTED_HOLDINGS;
3420                 parse_pos = 0;
3421                 continue;
3422             }
3423
3424             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3425                 loggedOn = TRUE;
3426                 /* Header for a move list -- first line */
3427
3428                 switch (ics_getting_history) {
3429                   case H_FALSE:
3430                     switch (gameMode) {
3431                       case IcsIdle:
3432                       case BeginningOfGame:
3433                         /* User typed "moves" or "oldmoves" while we
3434                            were idle.  Pretend we asked for these
3435                            moves and soak them up so user can step
3436                            through them and/or save them.
3437                            */
3438                         Reset(FALSE, TRUE);
3439                         gameMode = IcsObserving;
3440                         ModeHighlight();
3441                         ics_gamenum = -1;
3442                         ics_getting_history = H_GOT_UNREQ_HEADER;
3443                         break;
3444                       case EditGame: /*?*/
3445                       case EditPosition: /*?*/
3446                         /* Should above feature work in these modes too? */
3447                         /* For now it doesn't */
3448                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3449                         break;
3450                       default:
3451                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3452                         break;
3453                     }
3454                     break;
3455                   case H_REQUESTED:
3456                     /* Is this the right one? */
3457                     if (gameInfo.white && gameInfo.black &&
3458                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3459                         strcmp(gameInfo.black, star_match[2]) == 0) {
3460                         /* All is well */
3461                         ics_getting_history = H_GOT_REQ_HEADER;
3462                     }
3463                     break;
3464                   case H_GOT_REQ_HEADER:
3465                   case H_GOT_UNREQ_HEADER:
3466                   case H_GOT_UNWANTED_HEADER:
3467                   case H_GETTING_MOVES:
3468                     /* Should not happen */
3469                     DisplayError(_("Error gathering move list: two headers"), 0);
3470                     ics_getting_history = H_FALSE;
3471                     break;
3472                 }
3473
3474                 /* Save player ratings into gameInfo if needed */
3475                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3476                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3477                     (gameInfo.whiteRating == -1 ||
3478                      gameInfo.blackRating == -1)) {
3479
3480                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3481                     gameInfo.blackRating = string_to_rating(star_match[3]);
3482                     if (appData.debugMode)
3483                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3484                               gameInfo.whiteRating, gameInfo.blackRating);
3485                 }
3486                 continue;
3487             }
3488
3489             if (looking_at(buf, &i,
3490               "* * match, initial time: * minute*, increment: * second")) {
3491                 /* Header for a move list -- second line */
3492                 /* Initial board will follow if this is a wild game */
3493                 if (gameInfo.event != NULL) free(gameInfo.event);
3494                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3495                 gameInfo.event = StrSave(str);
3496                 /* [HGM] we switched variant. Translate boards if needed. */
3497                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3498                 continue;
3499             }
3500
3501             if (looking_at(buf, &i, "Move  ")) {
3502                 /* Beginning of a move list */
3503                 switch (ics_getting_history) {
3504                   case H_FALSE:
3505                     /* Normally should not happen */
3506                     /* Maybe user hit reset while we were parsing */
3507                     break;
3508                   case H_REQUESTED:
3509                     /* Happens if we are ignoring a move list that is not
3510                      * the one we just requested.  Common if the user
3511                      * tries to observe two games without turning off
3512                      * getMoveList */
3513                     break;
3514                   case H_GETTING_MOVES:
3515                     /* Should not happen */
3516                     DisplayError(_("Error gathering move list: nested"), 0);
3517                     ics_getting_history = H_FALSE;
3518                     break;
3519                   case H_GOT_REQ_HEADER:
3520                     ics_getting_history = H_GETTING_MOVES;
3521                     started = STARTED_MOVES;
3522                     parse_pos = 0;
3523                     if (oldi > next_out) {
3524                         SendToPlayer(&buf[next_out], oldi - next_out);
3525                     }
3526                     break;
3527                   case H_GOT_UNREQ_HEADER:
3528                     ics_getting_history = H_GETTING_MOVES;
3529                     started = STARTED_MOVES_NOHIDE;
3530                     parse_pos = 0;
3531                     break;
3532                   case H_GOT_UNWANTED_HEADER:
3533                     ics_getting_history = H_FALSE;
3534                     break;
3535                 }
3536                 continue;
3537             }
3538
3539             if (looking_at(buf, &i, "% ") ||
3540                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3541                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3542                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3543                     soughtPending = FALSE;
3544                     seekGraphUp = TRUE;
3545                     DrawSeekGraph();
3546                 }
3547                 if(suppressKibitz) next_out = i;
3548                 savingComment = FALSE;
3549                 suppressKibitz = 0;
3550                 switch (started) {
3551                   case STARTED_MOVES:
3552                   case STARTED_MOVES_NOHIDE:
3553                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3554                     parse[parse_pos + i - oldi] = NULLCHAR;
3555                     ParseGameHistory(parse);
3556 #if ZIPPY
3557                     if (appData.zippyPlay && first.initDone) {
3558                         FeedMovesToProgram(&first, forwardMostMove);
3559                         if (gameMode == IcsPlayingWhite) {
3560                             if (WhiteOnMove(forwardMostMove)) {
3561                                 if (first.sendTime) {
3562                                   if (first.useColors) {
3563                                     SendToProgram("black\n", &first);
3564                                   }
3565                                   SendTimeRemaining(&first, TRUE);
3566                                 }
3567                                 if (first.useColors) {
3568                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3569                                 }
3570                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3571                                 first.maybeThinking = TRUE;
3572                             } else {
3573                                 if (first.usePlayother) {
3574                                   if (first.sendTime) {
3575                                     SendTimeRemaining(&first, TRUE);
3576                                   }
3577                                   SendToProgram("playother\n", &first);
3578                                   firstMove = FALSE;
3579                                 } else {
3580                                   firstMove = TRUE;
3581                                 }
3582                             }
3583                         } else if (gameMode == IcsPlayingBlack) {
3584                             if (!WhiteOnMove(forwardMostMove)) {
3585                                 if (first.sendTime) {
3586                                   if (first.useColors) {
3587                                     SendToProgram("white\n", &first);
3588                                   }
3589                                   SendTimeRemaining(&first, FALSE);
3590                                 }
3591                                 if (first.useColors) {
3592                                   SendToProgram("black\n", &first);
3593                                 }
3594                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3595                                 first.maybeThinking = TRUE;
3596                             } else {
3597                                 if (first.usePlayother) {
3598                                   if (first.sendTime) {
3599                                     SendTimeRemaining(&first, FALSE);
3600                                   }
3601                                   SendToProgram("playother\n", &first);
3602                                   firstMove = FALSE;
3603                                 } else {
3604                                   firstMove = TRUE;
3605                                 }
3606                             }
3607                         }
3608                     }
3609 #endif
3610                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3611                         /* Moves came from oldmoves or moves command
3612                            while we weren't doing anything else.
3613                            */
3614                         currentMove = forwardMostMove;
3615                         ClearHighlights();/*!!could figure this out*/
3616                         flipView = appData.flipView;
3617                         DrawPosition(TRUE, boards[currentMove]);
3618                         DisplayBothClocks();
3619                         snprintf(str, MSG_SIZ, "%s vs. %s",
3620                                 gameInfo.white, gameInfo.black);
3621                         DisplayTitle(str);
3622                         gameMode = IcsIdle;
3623                     } else {
3624                         /* Moves were history of an active game */
3625                         if (gameInfo.resultDetails != NULL) {
3626                             free(gameInfo.resultDetails);
3627                             gameInfo.resultDetails = NULL;
3628                         }
3629                     }
3630                     HistorySet(parseList, backwardMostMove,
3631                                forwardMostMove, currentMove-1);
3632                     DisplayMove(currentMove - 1);
3633                     if (started == STARTED_MOVES) next_out = i;
3634                     started = STARTED_NONE;
3635                     ics_getting_history = H_FALSE;
3636                     break;
3637
3638                   case STARTED_OBSERVE:
3639                     started = STARTED_NONE;
3640                     SendToICS(ics_prefix);
3641                     SendToICS("refresh\n");
3642                     break;
3643
3644                   default:
3645                     break;
3646                 }
3647                 if(bookHit) { // [HGM] book: simulate book reply
3648                     static char bookMove[MSG_SIZ]; // a bit generous?
3649
3650                     programStats.nodes = programStats.depth = programStats.time =
3651                     programStats.score = programStats.got_only_move = 0;
3652                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3653
3654                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3655                     strcat(bookMove, bookHit);
3656                     HandleMachineMove(bookMove, &first);
3657                 }
3658                 continue;
3659             }
3660
3661             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3662                  started == STARTED_HOLDINGS ||
3663                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3664                 /* Accumulate characters in move list or board */
3665                 parse[parse_pos++] = buf[i];
3666             }
3667
3668             /* Start of game messages.  Mostly we detect start of game
3669                when the first board image arrives.  On some versions
3670                of the ICS, though, we need to do a "refresh" after starting
3671                to observe in order to get the current board right away. */
3672             if (looking_at(buf, &i, "Adding game * to observation list")) {
3673                 started = STARTED_OBSERVE;
3674                 continue;
3675             }
3676
3677             /* Handle auto-observe */
3678             if (appData.autoObserve &&
3679                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3680                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3681                 char *player;
3682                 /* Choose the player that was highlighted, if any. */
3683                 if (star_match[0][0] == '\033' ||
3684                     star_match[1][0] != '\033') {
3685                     player = star_match[0];
3686                 } else {
3687                     player = star_match[2];
3688                 }
3689                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3690                         ics_prefix, StripHighlightAndTitle(player));
3691                 SendToICS(str);
3692
3693                 /* Save ratings from notify string */
3694                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3695                 player1Rating = string_to_rating(star_match[1]);
3696                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3697                 player2Rating = string_to_rating(star_match[3]);
3698
3699                 if (appData.debugMode)
3700                   fprintf(debugFP,
3701                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3702                           player1Name, player1Rating,
3703                           player2Name, player2Rating);
3704
3705                 continue;
3706             }
3707
3708             /* Deal with automatic examine mode after a game,
3709                and with IcsObserving -> IcsExamining transition */
3710             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3711                 looking_at(buf, &i, "has made you an examiner of game *")) {
3712
3713                 int gamenum = atoi(star_match[0]);
3714                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3715                     gamenum == ics_gamenum) {
3716                     /* We were already playing or observing this game;
3717                        no need to refetch history */
3718                     gameMode = IcsExamining;
3719                     if (pausing) {
3720                         pauseExamForwardMostMove = forwardMostMove;
3721                     } else if (currentMove < forwardMostMove) {
3722                         ForwardInner(forwardMostMove);
3723                     }
3724                 } else {
3725                     /* I don't think this case really can happen */
3726                     SendToICS(ics_prefix);
3727                     SendToICS("refresh\n");
3728                 }
3729                 continue;
3730             }
3731
3732             /* Error messages */
3733 //          if (ics_user_moved) {
3734             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3735                 if (looking_at(buf, &i, "Illegal move") ||
3736                     looking_at(buf, &i, "Not a legal move") ||
3737                     looking_at(buf, &i, "Your king is in check") ||
3738                     looking_at(buf, &i, "It isn't your turn") ||
3739                     looking_at(buf, &i, "It is not your move")) {
3740                     /* Illegal move */
3741                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3742                         currentMove = forwardMostMove-1;
3743                         DisplayMove(currentMove - 1); /* before DMError */
3744                         DrawPosition(FALSE, boards[currentMove]);
3745                         SwitchClocks(forwardMostMove-1); // [HGM] race
3746                         DisplayBothClocks();
3747                     }
3748                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3749                     ics_user_moved = 0;
3750                     continue;
3751                 }
3752             }
3753
3754             if (looking_at(buf, &i, "still have time") ||
3755                 looking_at(buf, &i, "not out of time") ||
3756                 looking_at(buf, &i, "either player is out of time") ||
3757                 looking_at(buf, &i, "has timeseal; checking")) {
3758                 /* We must have called his flag a little too soon */
3759                 whiteFlag = blackFlag = FALSE;
3760                 continue;
3761             }
3762
3763             if (looking_at(buf, &i, "added * seconds to") ||
3764                 looking_at(buf, &i, "seconds were added to")) {
3765                 /* Update the clocks */
3766                 SendToICS(ics_prefix);
3767                 SendToICS("refresh\n");
3768                 continue;
3769             }
3770
3771             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3772                 ics_clock_paused = TRUE;
3773                 StopClocks();
3774                 continue;
3775             }
3776
3777             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3778                 ics_clock_paused = FALSE;
3779                 StartClocks();
3780                 continue;
3781             }
3782
3783             /* Grab player ratings from the Creating: message.
3784                Note we have to check for the special case when
3785                the ICS inserts things like [white] or [black]. */
3786             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3787                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3788                 /* star_matches:
3789                    0    player 1 name (not necessarily white)
3790                    1    player 1 rating
3791                    2    empty, white, or black (IGNORED)
3792                    3    player 2 name (not necessarily black)
3793                    4    player 2 rating
3794
3795                    The names/ratings are sorted out when the game
3796                    actually starts (below).
3797                 */
3798                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3799                 player1Rating = string_to_rating(star_match[1]);
3800                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3801                 player2Rating = string_to_rating(star_match[4]);
3802
3803                 if (appData.debugMode)
3804                   fprintf(debugFP,
3805                           "Ratings from 'Creating:' %s %d, %s %d\n",
3806                           player1Name, player1Rating,
3807                           player2Name, player2Rating);
3808
3809                 continue;
3810             }
3811
3812             /* Improved generic start/end-of-game messages */
3813             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3814                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3815                 /* If tkind == 0: */
3816                 /* star_match[0] is the game number */
3817                 /*           [1] is the white player's name */
3818                 /*           [2] is the black player's name */
3819                 /* For end-of-game: */
3820                 /*           [3] is the reason for the game end */
3821                 /*           [4] is a PGN end game-token, preceded by " " */
3822                 /* For start-of-game: */
3823                 /*           [3] begins with "Creating" or "Continuing" */
3824                 /*           [4] is " *" or empty (don't care). */
3825                 int gamenum = atoi(star_match[0]);
3826                 char *whitename, *blackname, *why, *endtoken;
3827                 ChessMove endtype = EndOfFile;
3828
3829                 if (tkind == 0) {
3830                   whitename = star_match[1];
3831                   blackname = star_match[2];
3832                   why = star_match[3];
3833                   endtoken = star_match[4];
3834                 } else {
3835                   whitename = star_match[1];
3836                   blackname = star_match[3];
3837                   why = star_match[5];
3838                   endtoken = star_match[6];
3839                 }
3840
3841                 /* Game start messages */
3842                 if (strncmp(why, "Creating ", 9) == 0 ||
3843                     strncmp(why, "Continuing ", 11) == 0) {
3844                     gs_gamenum = gamenum;
3845                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3846                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3847 #if ZIPPY
3848                     if (appData.zippyPlay) {
3849                         ZippyGameStart(whitename, blackname);
3850                     }
3851 #endif /*ZIPPY*/
3852                     partnerBoardValid = FALSE; // [HGM] bughouse
3853                     continue;
3854                 }
3855
3856                 /* Game end messages */
3857                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3858                     ics_gamenum != gamenum) {
3859                     continue;
3860                 }
3861                 while (endtoken[0] == ' ') endtoken++;
3862                 switch (endtoken[0]) {
3863                   case '*':
3864                   default:
3865                     endtype = GameUnfinished;
3866                     break;
3867                   case '0':
3868                     endtype = BlackWins;
3869                     break;
3870                   case '1':
3871                     if (endtoken[1] == '/')
3872                       endtype = GameIsDrawn;
3873                     else
3874                       endtype = WhiteWins;
3875                     break;
3876                 }
3877                 GameEnds(endtype, why, GE_ICS);
3878 #if ZIPPY
3879                 if (appData.zippyPlay && first.initDone) {
3880                     ZippyGameEnd(endtype, why);
3881                     if (first.pr == NULL) {
3882                       /* Start the next process early so that we'll
3883                          be ready for the next challenge */
3884                       StartChessProgram(&first);
3885                     }
3886                     /* Send "new" early, in case this command takes
3887                        a long time to finish, so that we'll be ready
3888                        for the next challenge. */
3889                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3890                     Reset(TRUE, TRUE);
3891                 }
3892 #endif /*ZIPPY*/
3893                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3894                 continue;
3895             }
3896
3897             if (looking_at(buf, &i, "Removing game * from observation") ||
3898                 looking_at(buf, &i, "no longer observing game *") ||
3899                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3900                 if (gameMode == IcsObserving &&
3901                     atoi(star_match[0]) == ics_gamenum)
3902                   {
3903                       /* icsEngineAnalyze */
3904                       if (appData.icsEngineAnalyze) {
3905                             ExitAnalyzeMode();
3906                             ModeHighlight();
3907                       }
3908                       StopClocks();
3909                       gameMode = IcsIdle;
3910                       ics_gamenum = -1;
3911                       ics_user_moved = FALSE;
3912                   }
3913                 continue;
3914             }
3915
3916             if (looking_at(buf, &i, "no longer examining game *")) {
3917                 if (gameMode == IcsExamining &&
3918                     atoi(star_match[0]) == ics_gamenum)
3919                   {
3920                       gameMode = IcsIdle;
3921                       ics_gamenum = -1;
3922                       ics_user_moved = FALSE;
3923                   }
3924                 continue;
3925             }
3926
3927             /* Advance leftover_start past any newlines we find,
3928                so only partial lines can get reparsed */
3929             if (looking_at(buf, &i, "\n")) {
3930                 prevColor = curColor;
3931                 if (curColor != ColorNormal) {
3932                     if (oldi > next_out) {
3933                         SendToPlayer(&buf[next_out], oldi - next_out);
3934                         next_out = oldi;
3935                     }
3936                     Colorize(ColorNormal, FALSE);
3937                     curColor = ColorNormal;
3938                 }
3939                 if (started == STARTED_BOARD) {
3940                     started = STARTED_NONE;
3941                     parse[parse_pos] = NULLCHAR;
3942                     ParseBoard12(parse);
3943                     ics_user_moved = 0;
3944
3945                     /* Send premove here */
3946                     if (appData.premove) {
3947                       char str[MSG_SIZ];
3948                       if (currentMove == 0 &&
3949                           gameMode == IcsPlayingWhite &&
3950                           appData.premoveWhite) {
3951                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3952                         if (appData.debugMode)
3953                           fprintf(debugFP, "Sending premove:\n");
3954                         SendToICS(str);
3955                       } else if (currentMove == 1 &&
3956                                  gameMode == IcsPlayingBlack &&
3957                                  appData.premoveBlack) {
3958                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3959                         if (appData.debugMode)
3960                           fprintf(debugFP, "Sending premove:\n");
3961                         SendToICS(str);
3962                       } else if (gotPremove) {
3963                         gotPremove = 0;
3964                         ClearPremoveHighlights();
3965                         if (appData.debugMode)
3966                           fprintf(debugFP, "Sending premove:\n");
3967                           UserMoveEvent(premoveFromX, premoveFromY,
3968                                         premoveToX, premoveToY,
3969                                         premovePromoChar);
3970                       }
3971                     }
3972
3973                     /* Usually suppress following prompt */
3974                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3975                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3976                         if (looking_at(buf, &i, "*% ")) {
3977                             savingComment = FALSE;
3978                             suppressKibitz = 0;
3979                         }
3980                     }
3981                     next_out = i;
3982                 } else if (started == STARTED_HOLDINGS) {
3983                     int gamenum;
3984                     char new_piece[MSG_SIZ];
3985                     started = STARTED_NONE;
3986                     parse[parse_pos] = NULLCHAR;
3987                     if (appData.debugMode)
3988                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3989                                                         parse, currentMove);
3990                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3991                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3992                         if (gameInfo.variant == VariantNormal) {
3993                           /* [HGM] We seem to switch variant during a game!
3994                            * Presumably no holdings were displayed, so we have
3995                            * to move the position two files to the right to
3996                            * create room for them!
3997                            */
3998                           VariantClass newVariant;
3999                           switch(gameInfo.boardWidth) { // base guess on board width
4000                                 case 9:  newVariant = VariantShogi; break;
4001                                 case 10: newVariant = VariantGreat; break;
4002                                 default: newVariant = VariantCrazyhouse; break;
4003                           }
4004                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4005                           /* Get a move list just to see the header, which
4006                              will tell us whether this is really bug or zh */
4007                           if (ics_getting_history == H_FALSE) {
4008                             ics_getting_history = H_REQUESTED;
4009                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4010                             SendToICS(str);
4011                           }
4012                         }
4013                         new_piece[0] = NULLCHAR;
4014                         sscanf(parse, "game %d white [%s black [%s <- %s",
4015                                &gamenum, white_holding, black_holding,
4016                                new_piece);
4017                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4018                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4019                         /* [HGM] copy holdings to board holdings area */
4020                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4021                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4022                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4023 #if ZIPPY
4024                         if (appData.zippyPlay && first.initDone) {
4025                             ZippyHoldings(white_holding, black_holding,
4026                                           new_piece);
4027                         }
4028 #endif /*ZIPPY*/
4029                         if (tinyLayout || smallLayout) {
4030                             char wh[16], bh[16];
4031                             PackHolding(wh, white_holding);
4032                             PackHolding(bh, black_holding);
4033                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4034                                     gameInfo.white, gameInfo.black);
4035                         } else {
4036                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4037                                     gameInfo.white, white_holding,
4038                                     gameInfo.black, black_holding);
4039                         }
4040                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4041                         DrawPosition(FALSE, boards[currentMove]);
4042                         DisplayTitle(str);
4043                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4044                         sscanf(parse, "game %d white [%s black [%s <- %s",
4045                                &gamenum, white_holding, black_holding,
4046                                new_piece);
4047                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4048                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4049                         /* [HGM] copy holdings to partner-board holdings area */
4050                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4051                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4052                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4053                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4054                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4055                       }
4056                     }
4057                     /* Suppress following prompt */
4058                     if (looking_at(buf, &i, "*% ")) {
4059                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4060                         savingComment = FALSE;
4061                         suppressKibitz = 0;
4062                     }
4063                     next_out = i;
4064                 }
4065                 continue;
4066             }
4067
4068             i++;                /* skip unparsed character and loop back */
4069         }
4070
4071         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4072 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4073 //          SendToPlayer(&buf[next_out], i - next_out);
4074             started != STARTED_HOLDINGS && leftover_start > next_out) {
4075             SendToPlayer(&buf[next_out], leftover_start - next_out);
4076             next_out = i;
4077         }
4078
4079         leftover_len = buf_len - leftover_start;
4080         /* if buffer ends with something we couldn't parse,
4081            reparse it after appending the next read */
4082
4083     } else if (count == 0) {
4084         RemoveInputSource(isr);
4085         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4086     } else {
4087         DisplayFatalError(_("Error reading from ICS"), error, 1);
4088     }
4089 }
4090
4091
4092 /* Board style 12 looks like this:
4093
4094    <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
4095
4096  * The "<12> " is stripped before it gets to this routine.  The two
4097  * trailing 0's (flip state and clock ticking) are later addition, and
4098  * some chess servers may not have them, or may have only the first.
4099  * Additional trailing fields may be added in the future.
4100  */
4101
4102 #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"
4103
4104 #define RELATION_OBSERVING_PLAYED    0
4105 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4106 #define RELATION_PLAYING_MYMOVE      1
4107 #define RELATION_PLAYING_NOTMYMOVE  -1
4108 #define RELATION_EXAMINING           2
4109 #define RELATION_ISOLATED_BOARD     -3
4110 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4111
4112 void
4113 ParseBoard12(string)
4114      char *string;
4115 {
4116     GameMode newGameMode;
4117     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4118     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4119     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4120     char to_play, board_chars[200];
4121     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4122     char black[32], white[32];
4123     Board board;
4124     int prevMove = currentMove;
4125     int ticking = 2;
4126     ChessMove moveType;
4127     int fromX, fromY, toX, toY;
4128     char promoChar;
4129     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4130     char *bookHit = NULL; // [HGM] book
4131     Boolean weird = FALSE, reqFlag = FALSE;
4132
4133     fromX = fromY = toX = toY = -1;
4134
4135     newGame = FALSE;
4136
4137     if (appData.debugMode)
4138       fprintf(debugFP, _("Parsing board: %s\n"), string);
4139
4140     move_str[0] = NULLCHAR;
4141     elapsed_time[0] = NULLCHAR;
4142     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4143         int  i = 0, j;
4144         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4145             if(string[i] == ' ') { ranks++; files = 0; }
4146             else files++;
4147             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4148             i++;
4149         }
4150         for(j = 0; j <i; j++) board_chars[j] = string[j];
4151         board_chars[i] = '\0';
4152         string += i + 1;
4153     }
4154     n = sscanf(string, PATTERN, &to_play, &double_push,
4155                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4156                &gamenum, white, black, &relation, &basetime, &increment,
4157                &white_stren, &black_stren, &white_time, &black_time,
4158                &moveNum, str, elapsed_time, move_str, &ics_flip,
4159                &ticking);
4160
4161     if (n < 21) {
4162         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4163         DisplayError(str, 0);
4164         return;
4165     }
4166
4167     /* Convert the move number to internal form */
4168     moveNum = (moveNum - 1) * 2;
4169     if (to_play == 'B') moveNum++;
4170     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4171       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4172                         0, 1);
4173       return;
4174     }
4175
4176     switch (relation) {
4177       case RELATION_OBSERVING_PLAYED:
4178       case RELATION_OBSERVING_STATIC:
4179         if (gamenum == -1) {
4180             /* Old ICC buglet */
4181             relation = RELATION_OBSERVING_STATIC;
4182         }
4183         newGameMode = IcsObserving;
4184         break;
4185       case RELATION_PLAYING_MYMOVE:
4186       case RELATION_PLAYING_NOTMYMOVE:
4187         newGameMode =
4188           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4189             IcsPlayingWhite : IcsPlayingBlack;
4190         break;
4191       case RELATION_EXAMINING:
4192         newGameMode = IcsExamining;
4193         break;
4194       case RELATION_ISOLATED_BOARD:
4195       default:
4196         /* Just display this board.  If user was doing something else,
4197            we will forget about it until the next board comes. */
4198         newGameMode = IcsIdle;
4199         break;
4200       case RELATION_STARTING_POSITION:
4201         newGameMode = gameMode;
4202         break;
4203     }
4204
4205     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4206          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4207       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4208       char *toSqr;
4209       for (k = 0; k < ranks; k++) {
4210         for (j = 0; j < files; j++)
4211           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4212         if(gameInfo.holdingsWidth > 1) {
4213              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4214              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4215         }
4216       }
4217       CopyBoard(partnerBoard, board);
4218       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4219         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4220         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4221       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4222       if(toSqr = strchr(str, '-')) {
4223         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4224         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4225       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4226       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4227       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4228       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4229       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4230       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4231                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4232       DisplayMessage(partnerStatus, "");
4233         partnerBoardValid = TRUE;
4234       return;
4235     }
4236
4237     /* Modify behavior for initial board display on move listing
4238        of wild games.
4239        */
4240     switch (ics_getting_history) {
4241       case H_FALSE:
4242       case H_REQUESTED:
4243         break;
4244       case H_GOT_REQ_HEADER:
4245       case H_GOT_UNREQ_HEADER:
4246         /* This is the initial position of the current game */
4247         gamenum = ics_gamenum;
4248         moveNum = 0;            /* old ICS bug workaround */
4249         if (to_play == 'B') {
4250           startedFromSetupPosition = TRUE;
4251           blackPlaysFirst = TRUE;
4252           moveNum = 1;
4253           if (forwardMostMove == 0) forwardMostMove = 1;
4254           if (backwardMostMove == 0) backwardMostMove = 1;
4255           if (currentMove == 0) currentMove = 1;
4256         }
4257         newGameMode = gameMode;
4258         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4259         break;
4260       case H_GOT_UNWANTED_HEADER:
4261         /* This is an initial board that we don't want */
4262         return;
4263       case H_GETTING_MOVES:
4264         /* Should not happen */
4265         DisplayError(_("Error gathering move list: extra board"), 0);
4266         ics_getting_history = H_FALSE;
4267         return;
4268     }
4269
4270    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4271                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4272      /* [HGM] We seem to have switched variant unexpectedly
4273       * Try to guess new variant from board size
4274       */
4275           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4276           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4277           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4278           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4279           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4280           if(!weird) newVariant = VariantNormal;
4281           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4282           /* Get a move list just to see the header, which
4283              will tell us whether this is really bug or zh */
4284           if (ics_getting_history == H_FALSE) {
4285             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4286             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4287             SendToICS(str);
4288           }
4289     }
4290
4291     /* Take action if this is the first board of a new game, or of a
4292        different game than is currently being displayed.  */
4293     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4294         relation == RELATION_ISOLATED_BOARD) {
4295
4296         /* Forget the old game and get the history (if any) of the new one */
4297         if (gameMode != BeginningOfGame) {
4298           Reset(TRUE, TRUE);
4299         }
4300         newGame = TRUE;
4301         if (appData.autoRaiseBoard) BoardToTop();
4302         prevMove = -3;
4303         if (gamenum == -1) {
4304             newGameMode = IcsIdle;
4305         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4306                    appData.getMoveList && !reqFlag) {
4307             /* Need to get game history */
4308             ics_getting_history = H_REQUESTED;
4309             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4310             SendToICS(str);
4311         }
4312
4313         /* Initially flip the board to have black on the bottom if playing
4314            black or if the ICS flip flag is set, but let the user change
4315            it with the Flip View button. */
4316         flipView = appData.autoFlipView ?
4317           (newGameMode == IcsPlayingBlack) || ics_flip :
4318           appData.flipView;
4319
4320         /* Done with values from previous mode; copy in new ones */
4321         gameMode = newGameMode;
4322         ModeHighlight();
4323         ics_gamenum = gamenum;
4324         if (gamenum == gs_gamenum) {
4325             int klen = strlen(gs_kind);
4326             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4327             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4328             gameInfo.event = StrSave(str);
4329         } else {
4330             gameInfo.event = StrSave("ICS game");
4331         }
4332         gameInfo.site = StrSave(appData.icsHost);
4333         gameInfo.date = PGNDate();
4334         gameInfo.round = StrSave("-");
4335         gameInfo.white = StrSave(white);
4336         gameInfo.black = StrSave(black);
4337         timeControl = basetime * 60 * 1000;
4338         timeControl_2 = 0;
4339         timeIncrement = increment * 1000;
4340         movesPerSession = 0;
4341         gameInfo.timeControl = TimeControlTagValue();
4342         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4343   if (appData.debugMode) {
4344     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4345     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4346     setbuf(debugFP, NULL);
4347   }
4348
4349         gameInfo.outOfBook = NULL;
4350
4351         /* Do we have the ratings? */
4352         if (strcmp(player1Name, white) == 0 &&
4353             strcmp(player2Name, black) == 0) {
4354             if (appData.debugMode)
4355               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4356                       player1Rating, player2Rating);
4357             gameInfo.whiteRating = player1Rating;
4358             gameInfo.blackRating = player2Rating;
4359         } else if (strcmp(player2Name, white) == 0 &&
4360                    strcmp(player1Name, black) == 0) {
4361             if (appData.debugMode)
4362               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4363                       player2Rating, player1Rating);
4364             gameInfo.whiteRating = player2Rating;
4365             gameInfo.blackRating = player1Rating;
4366         }
4367         player1Name[0] = player2Name[0] = NULLCHAR;
4368
4369         /* Silence shouts if requested */
4370         if (appData.quietPlay &&
4371             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4372             SendToICS(ics_prefix);
4373             SendToICS("set shout 0\n");
4374         }
4375     }
4376
4377     /* Deal with midgame name changes */
4378     if (!newGame) {
4379         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4380             if (gameInfo.white) free(gameInfo.white);
4381             gameInfo.white = StrSave(white);
4382         }
4383         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4384             if (gameInfo.black) free(gameInfo.black);
4385             gameInfo.black = StrSave(black);
4386         }
4387     }
4388
4389     /* Throw away game result if anything actually changes in examine mode */
4390     if (gameMode == IcsExamining && !newGame) {
4391         gameInfo.result = GameUnfinished;
4392         if (gameInfo.resultDetails != NULL) {
4393             free(gameInfo.resultDetails);
4394             gameInfo.resultDetails = NULL;
4395         }
4396     }
4397
4398     /* In pausing && IcsExamining mode, we ignore boards coming
4399        in if they are in a different variation than we are. */
4400     if (pauseExamInvalid) return;
4401     if (pausing && gameMode == IcsExamining) {
4402         if (moveNum <= pauseExamForwardMostMove) {
4403             pauseExamInvalid = TRUE;
4404             forwardMostMove = pauseExamForwardMostMove;
4405             return;
4406         }
4407     }
4408
4409   if (appData.debugMode) {
4410     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4411   }
4412     /* Parse the board */
4413     for (k = 0; k < ranks; k++) {
4414       for (j = 0; j < files; j++)
4415         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4416       if(gameInfo.holdingsWidth > 1) {
4417            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4418            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4419       }
4420     }
4421     CopyBoard(boards[moveNum], board);
4422     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4423     if (moveNum == 0) {
4424         startedFromSetupPosition =
4425           !CompareBoards(board, initialPosition);
4426         if(startedFromSetupPosition)
4427             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4428     }
4429
4430     /* [HGM] Set castling rights. Take the outermost Rooks,
4431        to make it also work for FRC opening positions. Note that board12
4432        is really defective for later FRC positions, as it has no way to
4433        indicate which Rook can castle if they are on the same side of King.
4434        For the initial position we grant rights to the outermost Rooks,
4435        and remember thos rights, and we then copy them on positions
4436        later in an FRC game. This means WB might not recognize castlings with
4437        Rooks that have moved back to their original position as illegal,
4438        but in ICS mode that is not its job anyway.
4439     */
4440     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4441     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4442
4443         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4444             if(board[0][i] == WhiteRook) j = i;
4445         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4446         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4447             if(board[0][i] == WhiteRook) j = i;
4448         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4449         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4450             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4451         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4452         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4453             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4454         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4455
4456         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4457         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4458             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4459         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4460             if(board[BOARD_HEIGHT-1][k] == bKing)
4461                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4462         if(gameInfo.variant == VariantTwoKings) {
4463             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4464             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4465             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4466         }
4467     } else { int r;
4468         r = boards[moveNum][CASTLING][0] = initialRights[0];
4469         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4470         r = boards[moveNum][CASTLING][1] = initialRights[1];
4471         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4472         r = boards[moveNum][CASTLING][3] = initialRights[3];
4473         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4474         r = boards[moveNum][CASTLING][4] = initialRights[4];
4475         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4476         /* wildcastle kludge: always assume King has rights */
4477         r = boards[moveNum][CASTLING][2] = initialRights[2];
4478         r = boards[moveNum][CASTLING][5] = initialRights[5];
4479     }
4480     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4481     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4482
4483
4484     if (ics_getting_history == H_GOT_REQ_HEADER ||
4485         ics_getting_history == H_GOT_UNREQ_HEADER) {
4486         /* This was an initial position from a move list, not
4487            the current position */
4488         return;
4489     }
4490
4491     /* Update currentMove and known move number limits */
4492     newMove = newGame || moveNum > forwardMostMove;
4493
4494     if (newGame) {
4495         forwardMostMove = backwardMostMove = currentMove = moveNum;
4496         if (gameMode == IcsExamining && moveNum == 0) {
4497           /* Workaround for ICS limitation: we are not told the wild
4498              type when starting to examine a game.  But if we ask for
4499              the move list, the move list header will tell us */
4500             ics_getting_history = H_REQUESTED;
4501             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4502             SendToICS(str);
4503         }
4504     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4505                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4506 #if ZIPPY
4507         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4508         /* [HGM] applied this also to an engine that is silently watching        */
4509         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4510             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4511             gameInfo.variant == currentlyInitializedVariant) {
4512           takeback = forwardMostMove - moveNum;
4513           for (i = 0; i < takeback; i++) {
4514             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4515             SendToProgram("undo\n", &first);
4516           }
4517         }
4518 #endif
4519
4520         forwardMostMove = moveNum;
4521         if (!pausing || currentMove > forwardMostMove)
4522           currentMove = forwardMostMove;
4523     } else {
4524         /* New part of history that is not contiguous with old part */
4525         if (pausing && gameMode == IcsExamining) {
4526             pauseExamInvalid = TRUE;
4527             forwardMostMove = pauseExamForwardMostMove;
4528             return;
4529         }
4530         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4531 #if ZIPPY
4532             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4533                 // [HGM] when we will receive the move list we now request, it will be
4534                 // fed to the engine from the first move on. So if the engine is not
4535                 // in the initial position now, bring it there.
4536                 InitChessProgram(&first, 0);
4537             }
4538 #endif
4539             ics_getting_history = H_REQUESTED;
4540             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4541             SendToICS(str);
4542         }
4543         forwardMostMove = backwardMostMove = currentMove = moveNum;
4544     }
4545
4546     /* Update the clocks */
4547     if (strchr(elapsed_time, '.')) {
4548       /* Time is in ms */
4549       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4550       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4551     } else {
4552       /* Time is in seconds */
4553       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4554       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4555     }
4556
4557
4558 #if ZIPPY
4559     if (appData.zippyPlay && newGame &&
4560         gameMode != IcsObserving && gameMode != IcsIdle &&
4561         gameMode != IcsExamining)
4562       ZippyFirstBoard(moveNum, basetime, increment);
4563 #endif
4564
4565     /* Put the move on the move list, first converting
4566        to canonical algebraic form. */
4567     if (moveNum > 0) {
4568   if (appData.debugMode) {
4569     if (appData.debugMode) { int f = forwardMostMove;
4570         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4571                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4572                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4573     }
4574     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4575     fprintf(debugFP, "moveNum = %d\n", moveNum);
4576     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4577     setbuf(debugFP, NULL);
4578   }
4579         if (moveNum <= backwardMostMove) {
4580             /* We don't know what the board looked like before
4581                this move.  Punt. */
4582           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4583             strcat(parseList[moveNum - 1], " ");
4584             strcat(parseList[moveNum - 1], elapsed_time);
4585             moveList[moveNum - 1][0] = NULLCHAR;
4586         } else if (strcmp(move_str, "none") == 0) {
4587             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4588             /* Again, we don't know what the board looked like;
4589                this is really the start of the game. */
4590             parseList[moveNum - 1][0] = NULLCHAR;
4591             moveList[moveNum - 1][0] = NULLCHAR;
4592             backwardMostMove = moveNum;
4593             startedFromSetupPosition = TRUE;
4594             fromX = fromY = toX = toY = -1;
4595         } else {
4596           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4597           //                 So we parse the long-algebraic move string in stead of the SAN move
4598           int valid; char buf[MSG_SIZ], *prom;
4599
4600           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4601                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4602           // str looks something like "Q/a1-a2"; kill the slash
4603           if(str[1] == '/')
4604             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4605           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4606           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4607                 strcat(buf, prom); // long move lacks promo specification!
4608           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4609                 if(appData.debugMode)
4610                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4611                 safeStrCpy(move_str, buf, MSG_SIZ);
4612           }
4613           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4614                                 &fromX, &fromY, &toX, &toY, &promoChar)
4615                || ParseOneMove(buf, moveNum - 1, &moveType,
4616                                 &fromX, &fromY, &toX, &toY, &promoChar);
4617           // end of long SAN patch
4618           if (valid) {
4619             (void) CoordsToAlgebraic(boards[moveNum - 1],
4620                                      PosFlags(moveNum - 1),
4621                                      fromY, fromX, toY, toX, promoChar,
4622                                      parseList[moveNum-1]);
4623             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4624               case MT_NONE:
4625               case MT_STALEMATE:
4626               default:
4627                 break;
4628               case MT_CHECK:
4629                 if(gameInfo.variant != VariantShogi)
4630                     strcat(parseList[moveNum - 1], "+");
4631                 break;
4632               case MT_CHECKMATE:
4633               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4634                 strcat(parseList[moveNum - 1], "#");
4635                 break;
4636             }
4637             strcat(parseList[moveNum - 1], " ");
4638             strcat(parseList[moveNum - 1], elapsed_time);
4639             /* currentMoveString is set as a side-effect of ParseOneMove */
4640             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4641             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4642             strcat(moveList[moveNum - 1], "\n");
4643
4644             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4645                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4646               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4647                 ChessSquare old, new = boards[moveNum][k][j];
4648                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4649                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4650                   if(old == new) continue;
4651                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4652                   else if(new == WhiteWazir || new == BlackWazir) {
4653                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4654                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4655                       else boards[moveNum][k][j] = old; // preserve type of Gold
4656                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4657                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4658               }
4659           } else {
4660             /* Move from ICS was illegal!?  Punt. */
4661             if (appData.debugMode) {
4662               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4663               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4664             }
4665             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4666             strcat(parseList[moveNum - 1], " ");
4667             strcat(parseList[moveNum - 1], elapsed_time);
4668             moveList[moveNum - 1][0] = NULLCHAR;
4669             fromX = fromY = toX = toY = -1;
4670           }
4671         }
4672   if (appData.debugMode) {
4673     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4674     setbuf(debugFP, NULL);
4675   }
4676
4677 #if ZIPPY
4678         /* Send move to chess program (BEFORE animating it). */
4679         if (appData.zippyPlay && !newGame && newMove &&
4680            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4681
4682             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4683                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4684                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4685                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4686                             move_str);
4687                     DisplayError(str, 0);
4688                 } else {
4689                     if (first.sendTime) {
4690                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4691                     }
4692                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4693                     if (firstMove && !bookHit) {
4694                         firstMove = FALSE;
4695                         if (first.useColors) {
4696                           SendToProgram(gameMode == IcsPlayingWhite ?
4697                                         "white\ngo\n" :
4698                                         "black\ngo\n", &first);
4699                         } else {
4700                           SendToProgram("go\n", &first);
4701                         }
4702                         first.maybeThinking = TRUE;
4703                     }
4704                 }
4705             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4706               if (moveList[moveNum - 1][0] == NULLCHAR) {
4707                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4708                 DisplayError(str, 0);
4709               } else {
4710                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4711                 SendMoveToProgram(moveNum - 1, &first);
4712               }
4713             }
4714         }
4715 #endif
4716     }
4717
4718     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4719         /* If move comes from a remote source, animate it.  If it
4720            isn't remote, it will have already been animated. */
4721         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4722             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4723         }
4724         if (!pausing && appData.highlightLastMove) {
4725             SetHighlights(fromX, fromY, toX, toY);
4726         }
4727     }
4728
4729     /* Start the clocks */
4730     whiteFlag = blackFlag = FALSE;
4731     appData.clockMode = !(basetime == 0 && increment == 0);
4732     if (ticking == 0) {
4733       ics_clock_paused = TRUE;
4734       StopClocks();
4735     } else if (ticking == 1) {
4736       ics_clock_paused = FALSE;
4737     }
4738     if (gameMode == IcsIdle ||
4739         relation == RELATION_OBSERVING_STATIC ||
4740         relation == RELATION_EXAMINING ||
4741         ics_clock_paused)
4742       DisplayBothClocks();
4743     else
4744       StartClocks();
4745
4746     /* Display opponents and material strengths */
4747     if (gameInfo.variant != VariantBughouse &&
4748         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4749         if (tinyLayout || smallLayout) {
4750             if(gameInfo.variant == VariantNormal)
4751               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4752                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4753                     basetime, increment);
4754             else
4755               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4756                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4757                     basetime, increment, (int) gameInfo.variant);
4758         } else {
4759             if(gameInfo.variant == VariantNormal)
4760               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4761                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4762                     basetime, increment);
4763             else
4764               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4765                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4766                     basetime, increment, VariantName(gameInfo.variant));
4767         }
4768         DisplayTitle(str);
4769   if (appData.debugMode) {
4770     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4771   }
4772     }
4773
4774
4775     /* Display the board */
4776     if (!pausing && !appData.noGUI) {
4777
4778       if (appData.premove)
4779           if (!gotPremove ||
4780              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4781              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4782               ClearPremoveHighlights();
4783
4784       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4785         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4786       DrawPosition(j, boards[currentMove]);
4787
4788       DisplayMove(moveNum - 1);
4789       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4790             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4791               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4792         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4793       }
4794     }
4795
4796     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4797 #if ZIPPY
4798     if(bookHit) { // [HGM] book: simulate book reply
4799         static char bookMove[MSG_SIZ]; // a bit generous?
4800
4801         programStats.nodes = programStats.depth = programStats.time =
4802         programStats.score = programStats.got_only_move = 0;
4803         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4804
4805         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4806         strcat(bookMove, bookHit);
4807         HandleMachineMove(bookMove, &first);
4808     }
4809 #endif
4810 }
4811
4812 void
4813 GetMoveListEvent()
4814 {
4815     char buf[MSG_SIZ];
4816     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4817         ics_getting_history = H_REQUESTED;
4818         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4819         SendToICS(buf);
4820     }
4821 }
4822
4823 void
4824 AnalysisPeriodicEvent(force)
4825      int force;
4826 {
4827     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4828          && !force) || !appData.periodicUpdates)
4829       return;
4830
4831     /* Send . command to Crafty to collect stats */
4832     SendToProgram(".\n", &first);
4833
4834     /* Don't send another until we get a response (this makes
4835        us stop sending to old Crafty's which don't understand
4836        the "." command (sending illegal cmds resets node count & time,
4837        which looks bad)) */
4838     programStats.ok_to_send = 0;
4839 }
4840
4841 void ics_update_width(new_width)
4842         int new_width;
4843 {
4844         ics_printf("set width %d\n", new_width);
4845 }
4846
4847 void
4848 SendMoveToProgram(moveNum, cps)
4849      int moveNum;
4850      ChessProgramState *cps;
4851 {
4852     char buf[MSG_SIZ];
4853
4854     if (cps->useUsermove) {
4855       SendToProgram("usermove ", cps);
4856     }
4857     if (cps->useSAN) {
4858       char *space;
4859       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4860         int len = space - parseList[moveNum];
4861         memcpy(buf, parseList[moveNum], len);
4862         buf[len++] = '\n';
4863         buf[len] = NULLCHAR;
4864       } else {
4865         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4866       }
4867       SendToProgram(buf, cps);
4868     } else {
4869       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4870         AlphaRank(moveList[moveNum], 4);
4871         SendToProgram(moveList[moveNum], cps);
4872         AlphaRank(moveList[moveNum], 4); // and back
4873       } else
4874       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4875        * the engine. It would be nice to have a better way to identify castle
4876        * moves here. */
4877       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4878                                                                          && cps->useOOCastle) {
4879         int fromX = moveList[moveNum][0] - AAA;
4880         int fromY = moveList[moveNum][1] - ONE;
4881         int toX = moveList[moveNum][2] - AAA;
4882         int toY = moveList[moveNum][3] - ONE;
4883         if((boards[moveNum][fromY][fromX] == WhiteKing
4884             && boards[moveNum][toY][toX] == WhiteRook)
4885            || (boards[moveNum][fromY][fromX] == BlackKing
4886                && boards[moveNum][toY][toX] == BlackRook)) {
4887           if(toX > fromX) SendToProgram("O-O\n", cps);
4888           else SendToProgram("O-O-O\n", cps);
4889         }
4890         else SendToProgram(moveList[moveNum], cps);
4891       } else
4892       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4893         snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4894                                              moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4895         SendToProgram(buf, cps);
4896       }
4897       else SendToProgram(moveList[moveNum], cps);
4898       /* End of additions by Tord */
4899     }
4900
4901     /* [HGM] setting up the opening has brought engine in force mode! */
4902     /*       Send 'go' if we are in a mode where machine should play. */
4903     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4904         (gameMode == TwoMachinesPlay   ||
4905 #if ZIPPY
4906          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4907 #endif
4908          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4909         SendToProgram("go\n", cps);
4910   if (appData.debugMode) {
4911     fprintf(debugFP, "(extra)\n");
4912   }
4913     }
4914     setboardSpoiledMachineBlack = 0;
4915 }
4916
4917 void
4918 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4919      ChessMove moveType;
4920      int fromX, fromY, toX, toY;
4921      char promoChar;
4922 {
4923     char user_move[MSG_SIZ];
4924
4925     switch (moveType) {
4926       default:
4927         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4928                 (int)moveType, fromX, fromY, toX, toY);
4929         DisplayError(user_move + strlen("say "), 0);
4930         break;
4931       case WhiteKingSideCastle:
4932       case BlackKingSideCastle:
4933       case WhiteQueenSideCastleWild:
4934       case BlackQueenSideCastleWild:
4935       /* PUSH Fabien */
4936       case WhiteHSideCastleFR:
4937       case BlackHSideCastleFR:
4938       /* POP Fabien */
4939         snprintf(user_move, MSG_SIZ, "o-o\n");
4940         break;
4941       case WhiteQueenSideCastle:
4942       case BlackQueenSideCastle:
4943       case WhiteKingSideCastleWild:
4944       case BlackKingSideCastleWild:
4945       /* PUSH Fabien */
4946       case WhiteASideCastleFR:
4947       case BlackASideCastleFR:
4948       /* POP Fabien */
4949         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4950         break;
4951       case WhiteNonPromotion:
4952       case BlackNonPromotion:
4953         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4954         break;
4955       case WhitePromotion:
4956       case BlackPromotion:
4957         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4958           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4959                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4960                 PieceToChar(WhiteFerz));
4961         else if(gameInfo.variant == VariantGreat)
4962           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4963                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4964                 PieceToChar(WhiteMan));
4965         else
4966           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4967                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4968                 promoChar);
4969         break;
4970       case WhiteDrop:
4971       case BlackDrop:
4972       drop:
4973         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4974                  ToUpper(PieceToChar((ChessSquare) fromX)),
4975                  AAA + toX, ONE + toY);
4976         break;
4977       case IllegalMove:  /* could be a variant we don't quite understand */
4978         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4979       case NormalMove:
4980       case WhiteCapturesEnPassant:
4981       case BlackCapturesEnPassant:
4982         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4983                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4984         break;
4985     }
4986     SendToICS(user_move);
4987     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4988         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4989 }
4990
4991 void
4992 UploadGameEvent()
4993 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4994     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4995     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4996     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4997         DisplayError("You cannot do this while you are playing or observing", 0);
4998         return;
4999     }
5000     if(gameMode != IcsExamining) { // is this ever not the case?
5001         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5002
5003         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5004           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5005         } else { // on FICS we must first go to general examine mode
5006           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5007         }
5008         if(gameInfo.variant != VariantNormal) {
5009             // try figure out wild number, as xboard names are not always valid on ICS
5010             for(i=1; i<=36; i++) {
5011               snprintf(buf, MSG_SIZ, "wild/%d", i);
5012                 if(StringToVariant(buf) == gameInfo.variant) break;
5013             }
5014             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5015             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5016             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5017         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5018         SendToICS(ics_prefix);
5019         SendToICS(buf);
5020         if(startedFromSetupPosition || backwardMostMove != 0) {
5021           fen = PositionToFEN(backwardMostMove, NULL);
5022           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5023             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5024             SendToICS(buf);
5025           } else { // FICS: everything has to set by separate bsetup commands
5026             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5027             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5028             SendToICS(buf);
5029             if(!WhiteOnMove(backwardMostMove)) {
5030                 SendToICS("bsetup tomove black\n");
5031             }
5032             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5033             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5034             SendToICS(buf);
5035             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5036             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5037             SendToICS(buf);
5038             i = boards[backwardMostMove][EP_STATUS];
5039             if(i >= 0) { // set e.p.
5040               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5041                 SendToICS(buf);
5042             }
5043             bsetup++;
5044           }
5045         }
5046       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5047             SendToICS("bsetup done\n"); // switch to normal examining.
5048     }
5049     for(i = backwardMostMove; i<last; i++) {
5050         char buf[20];
5051         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5052         SendToICS(buf);
5053     }
5054     SendToICS(ics_prefix);
5055     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5056 }
5057
5058 void
5059 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5060      int rf, ff, rt, ft;
5061      char promoChar;
5062      char move[7];
5063 {
5064     if (rf == DROP_RANK) {
5065       sprintf(move, "%c@%c%c\n",
5066                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5067     } else {
5068         if (promoChar == 'x' || promoChar == NULLCHAR) {
5069           sprintf(move, "%c%c%c%c\n",
5070                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5071         } else {
5072             sprintf(move, "%c%c%c%c%c\n",
5073                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5074         }
5075     }
5076 }
5077
5078 void
5079 ProcessICSInitScript(f)
5080      FILE *f;
5081 {
5082     char buf[MSG_SIZ];
5083
5084     while (fgets(buf, MSG_SIZ, f)) {
5085         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5086     }
5087
5088     fclose(f);
5089 }
5090
5091
5092 static int lastX, lastY, selectFlag, dragging;
5093
5094 void
5095 Sweep(int step)
5096 {
5097     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5098     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5099     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5100     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5101     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5102     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5103     do {
5104         promoSweep -= step;
5105         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5106         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5107         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5108         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5109         if(!step) step = 1;
5110     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5111             appData.testLegality && (promoSweep == king ||
5112             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5113     ChangeDragPiece(promoSweep);
5114 }
5115
5116 int PromoScroll(int x, int y)
5117 {
5118   int step = 0;
5119
5120   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5121   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5122   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5123   if(!step) return FALSE;
5124   lastX = x; lastY = y;
5125   if((promoSweep < BlackPawn) == flipView) step = -step;
5126   if(step > 0) selectFlag = 1;
5127   if(!selectFlag) Sweep(step);
5128   return FALSE;
5129 }
5130
5131 void
5132 NextPiece(int step)
5133 {
5134     ChessSquare piece = boards[currentMove][toY][toX];
5135     do {
5136         pieceSweep -= step;
5137         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5138         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5139         if(!step) step = -1;
5140     } while(PieceToChar(pieceSweep) == '.');
5141     boards[currentMove][toY][toX] = pieceSweep;
5142     DrawPosition(FALSE, boards[currentMove]);
5143     boards[currentMove][toY][toX] = piece;
5144 }
5145 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5146 void
5147 AlphaRank(char *move, int n)
5148 {
5149 //    char *p = move, c; int x, y;
5150
5151     if (appData.debugMode) {
5152         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5153     }
5154
5155     if(move[1]=='*' &&
5156        move[2]>='0' && move[2]<='9' &&
5157        move[3]>='a' && move[3]<='x'    ) {
5158         move[1] = '@';
5159         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5160         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5161     } else
5162     if(move[0]>='0' && move[0]<='9' &&
5163        move[1]>='a' && move[1]<='x' &&
5164        move[2]>='0' && move[2]<='9' &&
5165        move[3]>='a' && move[3]<='x'    ) {
5166         /* input move, Shogi -> normal */
5167         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5168         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5169         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5170         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5171     } else
5172     if(move[1]=='@' &&
5173        move[3]>='0' && move[3]<='9' &&
5174        move[2]>='a' && move[2]<='x'    ) {
5175         move[1] = '*';
5176         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5177         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5178     } else
5179     if(
5180        move[0]>='a' && move[0]<='x' &&
5181        move[3]>='0' && move[3]<='9' &&
5182        move[2]>='a' && move[2]<='x'    ) {
5183          /* output move, normal -> Shogi */
5184         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5185         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5186         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5187         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5188         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5189     }
5190     if (appData.debugMode) {
5191         fprintf(debugFP, "   out = '%s'\n", move);
5192     }
5193 }
5194
5195 char yy_textstr[8000];
5196
5197 /* Parser for moves from gnuchess, ICS, or user typein box */
5198 Boolean
5199 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5200      char *move;
5201      int moveNum;
5202      ChessMove *moveType;
5203      int *fromX, *fromY, *toX, *toY;
5204      char *promoChar;
5205 {
5206     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5207
5208     switch (*moveType) {
5209       case WhitePromotion:
5210       case BlackPromotion:
5211       case WhiteNonPromotion:
5212       case BlackNonPromotion:
5213       case NormalMove:
5214       case WhiteCapturesEnPassant:
5215       case BlackCapturesEnPassant:
5216       case WhiteKingSideCastle:
5217       case WhiteQueenSideCastle:
5218       case BlackKingSideCastle:
5219       case BlackQueenSideCastle:
5220       case WhiteKingSideCastleWild:
5221       case WhiteQueenSideCastleWild:
5222       case BlackKingSideCastleWild:
5223       case BlackQueenSideCastleWild:
5224       /* Code added by Tord: */
5225       case WhiteHSideCastleFR:
5226       case WhiteASideCastleFR:
5227       case BlackHSideCastleFR:
5228       case BlackASideCastleFR:
5229       /* End of code added by Tord */
5230       case IllegalMove:         /* bug or odd chess variant */
5231         *fromX = currentMoveString[0] - AAA;
5232         *fromY = currentMoveString[1] - ONE;
5233         *toX = currentMoveString[2] - AAA;
5234         *toY = currentMoveString[3] - ONE;
5235         *promoChar = currentMoveString[4];
5236         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5237             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5238     if (appData.debugMode) {
5239         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5240     }
5241             *fromX = *fromY = *toX = *toY = 0;
5242             return FALSE;
5243         }
5244         if (appData.testLegality) {
5245           return (*moveType != IllegalMove);
5246         } else {
5247           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5248                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5249         }
5250
5251       case WhiteDrop:
5252       case BlackDrop:
5253         *fromX = *moveType == WhiteDrop ?
5254           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5255           (int) CharToPiece(ToLower(currentMoveString[0]));
5256         *fromY = DROP_RANK;
5257         *toX = currentMoveString[2] - AAA;
5258         *toY = currentMoveString[3] - ONE;
5259         *promoChar = NULLCHAR;
5260         return TRUE;
5261
5262       case AmbiguousMove:
5263       case ImpossibleMove:
5264       case EndOfFile:
5265       case ElapsedTime:
5266       case Comment:
5267       case PGNTag:
5268       case NAG:
5269       case WhiteWins:
5270       case BlackWins:
5271       case GameIsDrawn:
5272       default:
5273     if (appData.debugMode) {
5274         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5275     }
5276         /* bug? */
5277         *fromX = *fromY = *toX = *toY = 0;
5278         *promoChar = NULLCHAR;
5279         return FALSE;
5280     }
5281 }
5282
5283 Boolean pushed = FALSE;
5284 char *lastParseAttempt;
5285
5286 void
5287 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5288 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5289   int fromX, fromY, toX, toY; char promoChar;
5290   ChessMove moveType;
5291   Boolean valid;
5292   int nr = 0;
5293
5294   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5295     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5296     pushed = TRUE;
5297   }
5298   endPV = forwardMostMove;
5299   do {
5300     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5301     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5302     lastParseAttempt = pv;
5303     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5304 if(appData.debugMode){
5305 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);
5306 }
5307     if(!valid && nr == 0 &&
5308        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5309         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5310         // Hande case where played move is different from leading PV move
5311         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5312         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5313         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5314         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5315           endPV += 2; // if position different, keep this
5316           moveList[endPV-1][0] = fromX + AAA;
5317           moveList[endPV-1][1] = fromY + ONE;
5318           moveList[endPV-1][2] = toX + AAA;
5319           moveList[endPV-1][3] = toY + ONE;
5320           parseList[endPV-1][0] = NULLCHAR;
5321           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5322         }
5323       }
5324     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5325     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5326     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5327     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5328         valid++; // allow comments in PV
5329         continue;
5330     }
5331     nr++;
5332     if(endPV+1 > framePtr) break; // no space, truncate
5333     if(!valid) break;
5334     endPV++;
5335     CopyBoard(boards[endPV], boards[endPV-1]);
5336     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5337     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5338     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5339     CoordsToAlgebraic(boards[endPV - 1],
5340                              PosFlags(endPV - 1),
5341                              fromY, fromX, toY, toX, promoChar,
5342                              parseList[endPV - 1]);
5343   } while(valid);
5344   if(atEnd == 2) return; // used hidden, for PV conversion
5345   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5346   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5347   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5348                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5349   DrawPosition(TRUE, boards[currentMove]);
5350 }
5351
5352 int
5353 MultiPV(ChessProgramState *cps)
5354 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5355         int i;
5356         for(i=0; i<cps->nrOptions; i++)
5357             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5358                 return i;
5359         return -1;
5360 }
5361
5362 Boolean
5363 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5364 {
5365         int startPV, multi, lineStart, origIndex = index;
5366         char *p, buf2[MSG_SIZ];
5367
5368         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5369         lastX = x; lastY = y;
5370         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5371         lineStart = startPV = index;
5372         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5373         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5374         index = startPV;
5375         do{ while(buf[index] && buf[index] != '\n') index++;
5376         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5377         buf[index] = 0;
5378         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5379                 int n = first.option[multi].value;
5380                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5381                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5382                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5383                 first.option[multi].value = n;
5384                 *start = *end = 0;
5385                 return FALSE;
5386         }
5387         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5388         *start = startPV; *end = index-1;
5389         return TRUE;
5390 }
5391
5392 char *
5393 PvToSAN(char *pv)
5394 {
5395         static char buf[10*MSG_SIZ];
5396         int i, k=0, savedEnd=endPV;
5397         *buf = NULLCHAR;
5398         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5399         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5400         for(i = forwardMostMove; i<endPV; i++){
5401             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5402             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5403             k += strlen(buf+k);
5404         }
5405         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5406         if(forwardMostMove < savedEnd) PopInner(0);
5407         endPV = savedEnd;
5408         return buf;
5409 }
5410
5411 Boolean
5412 LoadPV(int x, int y)
5413 { // called on right mouse click to load PV
5414   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5415   lastX = x; lastY = y;
5416   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5417   return TRUE;
5418 }
5419
5420 void
5421 UnLoadPV()
5422 {
5423   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5424   if(endPV < 0) return;
5425   endPV = -1;
5426   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5427         Boolean saveAnimate = appData.animate;
5428         if(pushed) {
5429             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5430                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5431             } else storedGames--; // abandon shelved tail of original game
5432         }
5433         pushed = FALSE;
5434         forwardMostMove = currentMove;
5435         currentMove = oldFMM;
5436         appData.animate = FALSE;
5437         ToNrEvent(forwardMostMove);
5438         appData.animate = saveAnimate;
5439   }
5440   currentMove = forwardMostMove;
5441   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5442   ClearPremoveHighlights();
5443   DrawPosition(TRUE, boards[currentMove]);
5444 }
5445
5446 void
5447 MovePV(int x, int y, int h)
5448 { // step through PV based on mouse coordinates (called on mouse move)
5449   int margin = h>>3, step = 0;
5450
5451   // we must somehow check if right button is still down (might be released off board!)
5452   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5453   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5454   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5455   if(!step) return;
5456   lastX = x; lastY = y;
5457
5458   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5459   if(endPV < 0) return;
5460   if(y < margin) step = 1; else
5461   if(y > h - margin) step = -1;
5462   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5463   currentMove += step;
5464   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5465   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5466                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5467   DrawPosition(FALSE, boards[currentMove]);
5468 }
5469
5470
5471 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5472 // All positions will have equal probability, but the current method will not provide a unique
5473 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5474 #define DARK 1
5475 #define LITE 2
5476 #define ANY 3
5477
5478 int squaresLeft[4];
5479 int piecesLeft[(int)BlackPawn];
5480 int seed, nrOfShuffles;
5481
5482 void GetPositionNumber()
5483 {       // sets global variable seed
5484         int i;
5485
5486         seed = appData.defaultFrcPosition;
5487         if(seed < 0) { // randomize based on time for negative FRC position numbers
5488                 for(i=0; i<50; i++) seed += random();
5489                 seed = random() ^ random() >> 8 ^ random() << 8;
5490                 if(seed<0) seed = -seed;
5491         }
5492 }
5493
5494 int put(Board board, int pieceType, int rank, int n, int shade)
5495 // put the piece on the (n-1)-th empty squares of the given shade
5496 {
5497         int i;
5498
5499         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5500                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5501                         board[rank][i] = (ChessSquare) pieceType;
5502                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5503                         squaresLeft[ANY]--;
5504                         piecesLeft[pieceType]--;
5505                         return i;
5506                 }
5507         }
5508         return -1;
5509 }
5510
5511
5512 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5513 // calculate where the next piece goes, (any empty square), and put it there
5514 {
5515         int i;
5516
5517         i = seed % squaresLeft[shade];
5518         nrOfShuffles *= squaresLeft[shade];
5519         seed /= squaresLeft[shade];
5520         put(board, pieceType, rank, i, shade);
5521 }
5522
5523 void AddTwoPieces(Board board, int pieceType, int rank)
5524 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5525 {
5526         int i, n=squaresLeft[ANY], j=n-1, k;
5527
5528         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5529         i = seed % k;  // pick one
5530         nrOfShuffles *= k;
5531         seed /= k;
5532         while(i >= j) i -= j--;
5533         j = n - 1 - j; i += j;
5534         put(board, pieceType, rank, j, ANY);
5535         put(board, pieceType, rank, i, ANY);
5536 }
5537
5538 void SetUpShuffle(Board board, int number)
5539 {
5540         int i, p, first=1;
5541
5542         GetPositionNumber(); nrOfShuffles = 1;
5543
5544         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5545         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5546         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5547
5548         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5549
5550         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5551             p = (int) board[0][i];
5552             if(p < (int) BlackPawn) piecesLeft[p] ++;
5553             board[0][i] = EmptySquare;
5554         }
5555
5556         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5557             // shuffles restricted to allow normal castling put KRR first
5558             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5559                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5560             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5561                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5562             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5563                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5564             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5565                 put(board, WhiteRook, 0, 0, ANY);
5566             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5567         }
5568
5569         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5570             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5571             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5572                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5573                 while(piecesLeft[p] >= 2) {
5574                     AddOnePiece(board, p, 0, LITE);
5575                     AddOnePiece(board, p, 0, DARK);
5576                 }
5577                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5578             }
5579
5580         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5581             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5582             // but we leave King and Rooks for last, to possibly obey FRC restriction
5583             if(p == (int)WhiteRook) continue;
5584             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5585             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5586         }
5587
5588         // now everything is placed, except perhaps King (Unicorn) and Rooks
5589
5590         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5591             // Last King gets castling rights
5592             while(piecesLeft[(int)WhiteUnicorn]) {
5593                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5594                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5595             }
5596
5597             while(piecesLeft[(int)WhiteKing]) {
5598                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5599                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5600             }
5601
5602
5603         } else {
5604             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5605             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5606         }
5607
5608         // Only Rooks can be left; simply place them all
5609         while(piecesLeft[(int)WhiteRook]) {
5610                 i = put(board, WhiteRook, 0, 0, ANY);
5611                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5612                         if(first) {
5613                                 first=0;
5614                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5615                         }
5616                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5617                 }
5618         }
5619         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5620             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5621         }
5622
5623         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5624 }
5625
5626 int SetCharTable( char *table, const char * map )
5627 /* [HGM] moved here from winboard.c because of its general usefulness */
5628 /*       Basically a safe strcpy that uses the last character as King */
5629 {
5630     int result = FALSE; int NrPieces;
5631
5632     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5633                     && NrPieces >= 12 && !(NrPieces&1)) {
5634         int i; /* [HGM] Accept even length from 12 to 34 */
5635
5636         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5637         for( i=0; i<NrPieces/2-1; i++ ) {
5638             table[i] = map[i];
5639             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5640         }
5641         table[(int) WhiteKing]  = map[NrPieces/2-1];
5642         table[(int) BlackKing]  = map[NrPieces-1];
5643
5644         result = TRUE;
5645     }
5646
5647     return result;
5648 }
5649
5650 void Prelude(Board board)
5651 {       // [HGM] superchess: random selection of exo-pieces
5652         int i, j, k; ChessSquare p;
5653         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5654
5655         GetPositionNumber(); // use FRC position number
5656
5657         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5658             SetCharTable(pieceToChar, appData.pieceToCharTable);
5659             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5660                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5661         }
5662
5663         j = seed%4;                 seed /= 4;
5664         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5665         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5666         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5667         j = seed%3 + (seed%3 >= j); seed /= 3;
5668         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5669         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5670         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5671         j = seed%3;                 seed /= 3;
5672         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5673         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5674         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5675         j = seed%2 + (seed%2 >= j); seed /= 2;
5676         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5677         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5678         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5679         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5680         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5681         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5682         put(board, exoPieces[0],    0, 0, ANY);
5683         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5684 }
5685
5686 void
5687 InitPosition(redraw)
5688      int redraw;
5689 {
5690     ChessSquare (* pieces)[BOARD_FILES];
5691     int i, j, pawnRow, overrule,
5692     oldx = gameInfo.boardWidth,
5693     oldy = gameInfo.boardHeight,
5694     oldh = gameInfo.holdingsWidth;
5695     static int oldv;
5696
5697     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5698
5699     /* [AS] Initialize pv info list [HGM] and game status */
5700     {
5701         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5702             pvInfoList[i].depth = 0;
5703             boards[i][EP_STATUS] = EP_NONE;
5704             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5705         }
5706
5707         initialRulePlies = 0; /* 50-move counter start */
5708
5709         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5710         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5711     }
5712
5713
5714     /* [HGM] logic here is completely changed. In stead of full positions */
5715     /* the initialized data only consist of the two backranks. The switch */
5716     /* selects which one we will use, which is than copied to the Board   */
5717     /* initialPosition, which for the rest is initialized by Pawns and    */
5718     /* empty squares. This initial position is then copied to boards[0],  */
5719     /* possibly after shuffling, so that it remains available.            */
5720
5721     gameInfo.holdingsWidth = 0; /* default board sizes */
5722     gameInfo.boardWidth    = 8;
5723     gameInfo.boardHeight   = 8;
5724     gameInfo.holdingsSize  = 0;
5725     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5726     for(i=0; i<BOARD_FILES-2; i++)
5727       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5728     initialPosition[EP_STATUS] = EP_NONE;
5729     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5730     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5731          SetCharTable(pieceNickName, appData.pieceNickNames);
5732     else SetCharTable(pieceNickName, "............");
5733     pieces = FIDEArray;
5734
5735     switch (gameInfo.variant) {
5736     case VariantFischeRandom:
5737       shuffleOpenings = TRUE;
5738     default:
5739       break;
5740     case VariantShatranj:
5741       pieces = ShatranjArray;
5742       nrCastlingRights = 0;
5743       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5744       break;
5745     case VariantMakruk:
5746       pieces = makrukArray;
5747       nrCastlingRights = 0;
5748       startedFromSetupPosition = TRUE;
5749       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5750       break;
5751     case VariantTwoKings:
5752       pieces = twoKingsArray;
5753       break;
5754     case VariantCapaRandom:
5755       shuffleOpenings = TRUE;
5756     case VariantCapablanca:
5757       pieces = CapablancaArray;
5758       gameInfo.boardWidth = 10;
5759       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5760       break;
5761     case VariantGothic:
5762       pieces = GothicArray;
5763       gameInfo.boardWidth = 10;
5764       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5765       break;
5766     case VariantSChess:
5767       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5768       gameInfo.holdingsSize = 7;
5769       break;
5770     case VariantJanus:
5771       pieces = JanusArray;
5772       gameInfo.boardWidth = 10;
5773       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5774       nrCastlingRights = 6;
5775         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5776         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5777         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5778         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5779         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5780         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5781       break;
5782     case VariantFalcon:
5783       pieces = FalconArray;
5784       gameInfo.boardWidth = 10;
5785       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5786       break;
5787     case VariantXiangqi:
5788       pieces = XiangqiArray;
5789       gameInfo.boardWidth  = 9;
5790       gameInfo.boardHeight = 10;
5791       nrCastlingRights = 0;
5792       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5793       break;
5794     case VariantShogi:
5795       pieces = ShogiArray;
5796       gameInfo.boardWidth  = 9;
5797       gameInfo.boardHeight = 9;
5798       gameInfo.holdingsSize = 7;
5799       nrCastlingRights = 0;
5800       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5801       break;
5802     case VariantCourier:
5803       pieces = CourierArray;
5804       gameInfo.boardWidth  = 12;
5805       nrCastlingRights = 0;
5806       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5807       break;
5808     case VariantKnightmate:
5809       pieces = KnightmateArray;
5810       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5811       break;
5812     case VariantSpartan:
5813       pieces = SpartanArray;
5814       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5815       break;
5816     case VariantFairy:
5817       pieces = fairyArray;
5818       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5819       break;
5820     case VariantGreat:
5821       pieces = GreatArray;
5822       gameInfo.boardWidth = 10;
5823       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5824       gameInfo.holdingsSize = 8;
5825       break;
5826     case VariantSuper:
5827       pieces = FIDEArray;
5828       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5829       gameInfo.holdingsSize = 8;
5830       startedFromSetupPosition = TRUE;
5831       break;
5832     case VariantCrazyhouse:
5833     case VariantBughouse:
5834       pieces = FIDEArray;
5835       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5836       gameInfo.holdingsSize = 5;
5837       break;
5838     case VariantWildCastle:
5839       pieces = FIDEArray;
5840       /* !!?shuffle with kings guaranteed to be on d or e file */
5841       shuffleOpenings = 1;
5842       break;
5843     case VariantNoCastle:
5844       pieces = FIDEArray;
5845       nrCastlingRights = 0;
5846       /* !!?unconstrained back-rank shuffle */
5847       shuffleOpenings = 1;
5848       break;
5849     }
5850
5851     overrule = 0;
5852     if(appData.NrFiles >= 0) {
5853         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5854         gameInfo.boardWidth = appData.NrFiles;
5855     }
5856     if(appData.NrRanks >= 0) {
5857         gameInfo.boardHeight = appData.NrRanks;
5858     }
5859     if(appData.holdingsSize >= 0) {
5860         i = appData.holdingsSize;
5861         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5862         gameInfo.holdingsSize = i;
5863     }
5864     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5865     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5866         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5867
5868     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5869     if(pawnRow < 1) pawnRow = 1;
5870     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5871
5872     /* User pieceToChar list overrules defaults */
5873     if(appData.pieceToCharTable != NULL)
5874         SetCharTable(pieceToChar, appData.pieceToCharTable);
5875
5876     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5877
5878         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5879             s = (ChessSquare) 0; /* account holding counts in guard band */
5880         for( i=0; i<BOARD_HEIGHT; i++ )
5881             initialPosition[i][j] = s;
5882
5883         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5884         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5885         initialPosition[pawnRow][j] = WhitePawn;
5886         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5887         if(gameInfo.variant == VariantXiangqi) {
5888             if(j&1) {
5889                 initialPosition[pawnRow][j] =
5890                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5891                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5892                    initialPosition[2][j] = WhiteCannon;
5893                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5894                 }
5895             }
5896         }
5897         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5898     }
5899     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5900
5901             j=BOARD_LEFT+1;
5902             initialPosition[1][j] = WhiteBishop;
5903             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5904             j=BOARD_RGHT-2;
5905             initialPosition[1][j] = WhiteRook;
5906             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5907     }
5908
5909     if( nrCastlingRights == -1) {
5910         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5911         /*       This sets default castling rights from none to normal corners   */
5912         /* Variants with other castling rights must set them themselves above    */
5913         nrCastlingRights = 6;
5914
5915         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5916         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5917         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5918         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5919         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5920         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5921      }
5922
5923      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5924      if(gameInfo.variant == VariantGreat) { // promotion commoners
5925         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5926         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5927         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5928         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5929      }
5930      if( gameInfo.variant == VariantSChess ) {
5931       initialPosition[1][0] = BlackMarshall;
5932       initialPosition[2][0] = BlackAngel;
5933       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5934       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5935       initialPosition[1][1] = initialPosition[2][1] = 
5936       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5937      }
5938   if (appData.debugMode) {
5939     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5940   }
5941     if(shuffleOpenings) {
5942         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5943         startedFromSetupPosition = TRUE;
5944     }
5945     if(startedFromPositionFile) {
5946       /* [HGM] loadPos: use PositionFile for every new game */
5947       CopyBoard(initialPosition, filePosition);
5948       for(i=0; i<nrCastlingRights; i++)
5949           initialRights[i] = filePosition[CASTLING][i];
5950       startedFromSetupPosition = TRUE;
5951     }
5952
5953     CopyBoard(boards[0], initialPosition);
5954
5955     if(oldx != gameInfo.boardWidth ||
5956        oldy != gameInfo.boardHeight ||
5957        oldv != gameInfo.variant ||
5958        oldh != gameInfo.holdingsWidth
5959                                          )
5960             InitDrawingSizes(-2 ,0);
5961
5962     oldv = gameInfo.variant;
5963     if (redraw)
5964       DrawPosition(TRUE, boards[currentMove]);
5965 }
5966
5967 void
5968 SendBoard(cps, moveNum)
5969      ChessProgramState *cps;
5970      int moveNum;
5971 {
5972     char message[MSG_SIZ];
5973
5974     if (cps->useSetboard) {
5975       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5976       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5977       SendToProgram(message, cps);
5978       free(fen);
5979
5980     } else {
5981       ChessSquare *bp;
5982       int i, j;
5983       /* Kludge to set black to move, avoiding the troublesome and now
5984        * deprecated "black" command.
5985        */
5986       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5987         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5988
5989       SendToProgram("edit\n", cps);
5990       SendToProgram("#\n", cps);
5991       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5992         bp = &boards[moveNum][i][BOARD_LEFT];
5993         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5994           if ((int) *bp < (int) BlackPawn) {
5995             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5996                     AAA + j, ONE + i);
5997             if(message[0] == '+' || message[0] == '~') {
5998               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5999                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6000                         AAA + j, ONE + i);
6001             }
6002             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6003                 message[1] = BOARD_RGHT   - 1 - j + '1';
6004                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6005             }
6006             SendToProgram(message, cps);
6007           }
6008         }
6009       }
6010
6011       SendToProgram("c\n", cps);
6012       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6013         bp = &boards[moveNum][i][BOARD_LEFT];
6014         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6015           if (((int) *bp != (int) EmptySquare)
6016               && ((int) *bp >= (int) BlackPawn)) {
6017             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6018                     AAA + j, ONE + i);
6019             if(message[0] == '+' || message[0] == '~') {
6020               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6021                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6022                         AAA + j, ONE + i);
6023             }
6024             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6025                 message[1] = BOARD_RGHT   - 1 - j + '1';
6026                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6027             }
6028             SendToProgram(message, cps);
6029           }
6030         }
6031       }
6032
6033       SendToProgram(".\n", cps);
6034     }
6035     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6036 }
6037
6038 ChessSquare
6039 DefaultPromoChoice(int white)
6040 {
6041     ChessSquare result;
6042     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6043         result = WhiteFerz; // no choice
6044     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6045         result= WhiteKing; // in Suicide Q is the last thing we want
6046     else if(gameInfo.variant == VariantSpartan)
6047         result = white ? WhiteQueen : WhiteAngel;
6048     else result = WhiteQueen;
6049     if(!white) result = WHITE_TO_BLACK result;
6050     return result;
6051 }
6052
6053 static int autoQueen; // [HGM] oneclick
6054
6055 int
6056 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6057 {
6058     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6059     /* [HGM] add Shogi promotions */
6060     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6061     ChessSquare piece;
6062     ChessMove moveType;
6063     Boolean premove;
6064
6065     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6066     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6067
6068     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6069       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6070         return FALSE;
6071
6072     piece = boards[currentMove][fromY][fromX];
6073     if(gameInfo.variant == VariantShogi) {
6074         promotionZoneSize = BOARD_HEIGHT/3;
6075         highestPromotingPiece = (int)WhiteFerz;
6076     } else if(gameInfo.variant == VariantMakruk) {
6077         promotionZoneSize = 3;
6078     }
6079
6080     // Treat Lance as Pawn when it is not representing Amazon
6081     if(gameInfo.variant != VariantSuper) {
6082         if(piece == WhiteLance) piece = WhitePawn; else
6083         if(piece == BlackLance) piece = BlackPawn;
6084     }
6085
6086     // next weed out all moves that do not touch the promotion zone at all
6087     if((int)piece >= BlackPawn) {
6088         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6089              return FALSE;
6090         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6091     } else {
6092         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6093            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6094     }
6095
6096     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6097
6098     // weed out mandatory Shogi promotions
6099     if(gameInfo.variant == VariantShogi) {
6100         if(piece >= BlackPawn) {
6101             if(toY == 0 && piece == BlackPawn ||
6102                toY == 0 && piece == BlackQueen ||
6103                toY <= 1 && piece == BlackKnight) {
6104                 *promoChoice = '+';
6105                 return FALSE;
6106             }
6107         } else {
6108             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6109                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6110                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6111                 *promoChoice = '+';
6112                 return FALSE;
6113             }
6114         }
6115     }
6116
6117     // weed out obviously illegal Pawn moves
6118     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6119         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6120         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6121         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6122         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6123         // note we are not allowed to test for valid (non-)capture, due to premove
6124     }
6125
6126     // we either have a choice what to promote to, or (in Shogi) whether to promote
6127     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6128         *promoChoice = PieceToChar(BlackFerz);  // no choice
6129         return FALSE;
6130     }
6131     // no sense asking what we must promote to if it is going to explode...
6132     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6133         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6134         return FALSE;
6135     }
6136     // give caller the default choice even if we will not make it
6137     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6138     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6139     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6140                            && gameInfo.variant != VariantShogi
6141                            && gameInfo.variant != VariantSuper) return FALSE;
6142     if(autoQueen) return FALSE; // predetermined
6143
6144     // suppress promotion popup on illegal moves that are not premoves
6145     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6146               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6147     if(appData.testLegality && !premove) {
6148         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6149                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6150         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6151             return FALSE;
6152     }
6153
6154     return TRUE;
6155 }
6156
6157 int
6158 InPalace(row, column)
6159      int row, column;
6160 {   /* [HGM] for Xiangqi */
6161     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6162          column < (BOARD_WIDTH + 4)/2 &&
6163          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6164     return FALSE;
6165 }
6166
6167 int
6168 PieceForSquare (x, y)
6169      int x;
6170      int y;
6171 {
6172   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6173      return -1;
6174   else
6175      return boards[currentMove][y][x];
6176 }
6177
6178 int
6179 OKToStartUserMove(x, y)
6180      int x, y;
6181 {
6182     ChessSquare from_piece;
6183     int white_piece;
6184
6185     if (matchMode) return FALSE;
6186     if (gameMode == EditPosition) return TRUE;
6187
6188     if (x >= 0 && y >= 0)
6189       from_piece = boards[currentMove][y][x];
6190     else
6191       from_piece = EmptySquare;
6192
6193     if (from_piece == EmptySquare) return FALSE;
6194
6195     white_piece = (int)from_piece >= (int)WhitePawn &&
6196       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6197
6198     switch (gameMode) {
6199       case PlayFromGameFile:
6200       case AnalyzeFile:
6201       case TwoMachinesPlay:
6202       case EndOfGame:
6203         return FALSE;
6204
6205       case IcsObserving:
6206       case IcsIdle:
6207         return FALSE;
6208
6209       case MachinePlaysWhite:
6210       case IcsPlayingBlack:
6211         if (appData.zippyPlay) return FALSE;
6212         if (white_piece) {
6213             DisplayMoveError(_("You are playing Black"));
6214             return FALSE;
6215         }
6216         break;
6217
6218       case MachinePlaysBlack:
6219       case IcsPlayingWhite:
6220         if (appData.zippyPlay) return FALSE;
6221         if (!white_piece) {
6222             DisplayMoveError(_("You are playing White"));
6223             return FALSE;
6224         }
6225         break;
6226
6227       case EditGame:
6228         if (!white_piece && WhiteOnMove(currentMove)) {
6229             DisplayMoveError(_("It is White's turn"));
6230             return FALSE;
6231         }
6232         if (white_piece && !WhiteOnMove(currentMove)) {
6233             DisplayMoveError(_("It is Black's turn"));
6234             return FALSE;
6235         }
6236         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6237             /* Editing correspondence game history */
6238             /* Could disallow this or prompt for confirmation */
6239             cmailOldMove = -1;
6240         }
6241         break;
6242
6243       case BeginningOfGame:
6244         if (appData.icsActive) return FALSE;
6245         if (!appData.noChessProgram) {
6246             if (!white_piece) {
6247                 DisplayMoveError(_("You are playing White"));
6248                 return FALSE;
6249             }
6250         }
6251         break;
6252
6253       case Training:
6254         if (!white_piece && WhiteOnMove(currentMove)) {
6255             DisplayMoveError(_("It is White's turn"));
6256             return FALSE;
6257         }
6258         if (white_piece && !WhiteOnMove(currentMove)) {
6259             DisplayMoveError(_("It is Black's turn"));
6260             return FALSE;
6261         }
6262         break;
6263
6264       default:
6265       case IcsExamining:
6266         break;
6267     }
6268     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6269         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6270         && gameMode != AnalyzeFile && gameMode != Training) {
6271         DisplayMoveError(_("Displayed position is not current"));
6272         return FALSE;
6273     }
6274     return TRUE;
6275 }
6276
6277 Boolean
6278 OnlyMove(int *x, int *y, Boolean captures) {
6279     DisambiguateClosure cl;
6280     if (appData.zippyPlay) return FALSE;
6281     switch(gameMode) {
6282       case MachinePlaysBlack:
6283       case IcsPlayingWhite:
6284       case BeginningOfGame:
6285         if(!WhiteOnMove(currentMove)) return FALSE;
6286         break;
6287       case MachinePlaysWhite:
6288       case IcsPlayingBlack:
6289         if(WhiteOnMove(currentMove)) return FALSE;
6290         break;
6291       case EditGame:
6292         break;
6293       default:
6294         return FALSE;
6295     }
6296     cl.pieceIn = EmptySquare;
6297     cl.rfIn = *y;
6298     cl.ffIn = *x;
6299     cl.rtIn = -1;
6300     cl.ftIn = -1;
6301     cl.promoCharIn = NULLCHAR;
6302     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6303     if( cl.kind == NormalMove ||
6304         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6305         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6306         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6307       fromX = cl.ff;
6308       fromY = cl.rf;
6309       *x = cl.ft;
6310       *y = cl.rt;
6311       return TRUE;
6312     }
6313     if(cl.kind != ImpossibleMove) return FALSE;
6314     cl.pieceIn = EmptySquare;
6315     cl.rfIn = -1;
6316     cl.ffIn = -1;
6317     cl.rtIn = *y;
6318     cl.ftIn = *x;
6319     cl.promoCharIn = NULLCHAR;
6320     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6321     if( cl.kind == NormalMove ||
6322         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6323         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6324         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6325       fromX = cl.ff;
6326       fromY = cl.rf;
6327       *x = cl.ft;
6328       *y = cl.rt;
6329       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6330       return TRUE;
6331     }
6332     return FALSE;
6333 }
6334
6335 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6336 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6337 int lastLoadGameUseList = FALSE;
6338 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6339 ChessMove lastLoadGameStart = EndOfFile;
6340
6341 void
6342 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6343      int fromX, fromY, toX, toY;
6344      int promoChar;
6345 {
6346     ChessMove moveType;
6347     ChessSquare pdown, pup;
6348
6349     /* Check if the user is playing in turn.  This is complicated because we
6350        let the user "pick up" a piece before it is his turn.  So the piece he
6351        tried to pick up may have been captured by the time he puts it down!
6352        Therefore we use the color the user is supposed to be playing in this
6353        test, not the color of the piece that is currently on the starting
6354        square---except in EditGame mode, where the user is playing both
6355        sides; fortunately there the capture race can't happen.  (It can
6356        now happen in IcsExamining mode, but that's just too bad.  The user
6357        will get a somewhat confusing message in that case.)
6358        */
6359
6360     switch (gameMode) {
6361       case PlayFromGameFile:
6362       case AnalyzeFile:
6363       case TwoMachinesPlay:
6364       case EndOfGame:
6365       case IcsObserving:
6366       case IcsIdle:
6367         /* We switched into a game mode where moves are not accepted,
6368            perhaps while the mouse button was down. */
6369         return;
6370
6371       case MachinePlaysWhite:
6372         /* User is moving for Black */
6373         if (WhiteOnMove(currentMove)) {
6374             DisplayMoveError(_("It is White's turn"));
6375             return;
6376         }
6377         break;
6378
6379       case MachinePlaysBlack:
6380         /* User is moving for White */
6381         if (!WhiteOnMove(currentMove)) {
6382             DisplayMoveError(_("It is Black's turn"));
6383             return;
6384         }
6385         break;
6386
6387       case EditGame:
6388       case IcsExamining:
6389       case BeginningOfGame:
6390       case AnalyzeMode:
6391       case Training:
6392         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6393         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6394             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6395             /* User is moving for Black */
6396             if (WhiteOnMove(currentMove)) {
6397                 DisplayMoveError(_("It is White's turn"));
6398                 return;
6399             }
6400         } else {
6401             /* User is moving for White */
6402             if (!WhiteOnMove(currentMove)) {
6403                 DisplayMoveError(_("It is Black's turn"));
6404                 return;
6405             }
6406         }
6407         break;
6408
6409       case IcsPlayingBlack:
6410         /* User is moving for Black */
6411         if (WhiteOnMove(currentMove)) {
6412             if (!appData.premove) {
6413                 DisplayMoveError(_("It is White's turn"));
6414             } else if (toX >= 0 && toY >= 0) {
6415                 premoveToX = toX;
6416                 premoveToY = toY;
6417                 premoveFromX = fromX;
6418                 premoveFromY = fromY;
6419                 premovePromoChar = promoChar;
6420                 gotPremove = 1;
6421                 if (appData.debugMode)
6422                     fprintf(debugFP, "Got premove: fromX %d,"
6423                             "fromY %d, toX %d, toY %d\n",
6424                             fromX, fromY, toX, toY);
6425             }
6426             return;
6427         }
6428         break;
6429
6430       case IcsPlayingWhite:
6431         /* User is moving for White */
6432         if (!WhiteOnMove(currentMove)) {
6433             if (!appData.premove) {
6434                 DisplayMoveError(_("It is Black's turn"));
6435             } else if (toX >= 0 && toY >= 0) {
6436                 premoveToX = toX;
6437                 premoveToY = toY;
6438                 premoveFromX = fromX;
6439                 premoveFromY = fromY;
6440                 premovePromoChar = promoChar;
6441                 gotPremove = 1;
6442                 if (appData.debugMode)
6443                     fprintf(debugFP, "Got premove: fromX %d,"
6444                             "fromY %d, toX %d, toY %d\n",
6445                             fromX, fromY, toX, toY);
6446             }
6447             return;
6448         }
6449         break;
6450
6451       default:
6452         break;
6453
6454       case EditPosition:
6455         /* EditPosition, empty square, or different color piece;
6456            click-click move is possible */
6457         if (toX == -2 || toY == -2) {
6458             boards[0][fromY][fromX] = EmptySquare;
6459             DrawPosition(FALSE, boards[currentMove]);
6460             return;
6461         } else if (toX >= 0 && toY >= 0) {
6462             boards[0][toY][toX] = boards[0][fromY][fromX];
6463             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6464                 if(boards[0][fromY][0] != EmptySquare) {
6465                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6466                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6467                 }
6468             } else
6469             if(fromX == BOARD_RGHT+1) {
6470                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6471                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6472                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6473                 }
6474             } else
6475             boards[0][fromY][fromX] = EmptySquare;
6476             DrawPosition(FALSE, boards[currentMove]);
6477             return;
6478         }
6479         return;
6480     }
6481
6482     if(toX < 0 || toY < 0) return;
6483     pdown = boards[currentMove][fromY][fromX];
6484     pup = boards[currentMove][toY][toX];
6485
6486     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6487     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6488          if( pup != EmptySquare ) return;
6489          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6490            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6491                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6492            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6493            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6494            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6495            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6496          fromY = DROP_RANK;
6497     }
6498
6499     /* [HGM] always test for legality, to get promotion info */
6500     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6501                                          fromY, fromX, toY, toX, promoChar);
6502     /* [HGM] but possibly ignore an IllegalMove result */
6503     if (appData.testLegality) {
6504         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6505             DisplayMoveError(_("Illegal move"));
6506             return;
6507         }
6508     }
6509
6510     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6511 }
6512
6513 /* Common tail of UserMoveEvent and DropMenuEvent */
6514 int
6515 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6516      ChessMove moveType;
6517      int fromX, fromY, toX, toY;
6518      /*char*/int promoChar;
6519 {
6520     char *bookHit = 0;
6521
6522     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6523         // [HGM] superchess: suppress promotions to non-available piece
6524         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6525         if(WhiteOnMove(currentMove)) {
6526             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6527         } else {
6528             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6529         }
6530     }
6531
6532     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6533        move type in caller when we know the move is a legal promotion */
6534     if(moveType == NormalMove && promoChar)
6535         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6536
6537     /* [HGM] <popupFix> The following if has been moved here from
6538        UserMoveEvent(). Because it seemed to belong here (why not allow
6539        piece drops in training games?), and because it can only be
6540        performed after it is known to what we promote. */
6541     if (gameMode == Training) {
6542       /* compare the move played on the board to the next move in the
6543        * game. If they match, display the move and the opponent's response.
6544        * If they don't match, display an error message.
6545        */
6546       int saveAnimate;
6547       Board testBoard;
6548       CopyBoard(testBoard, boards[currentMove]);
6549       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6550
6551       if (CompareBoards(testBoard, boards[currentMove+1])) {
6552         ForwardInner(currentMove+1);
6553
6554         /* Autoplay the opponent's response.
6555          * if appData.animate was TRUE when Training mode was entered,
6556          * the response will be animated.
6557          */
6558         saveAnimate = appData.animate;
6559         appData.animate = animateTraining;
6560         ForwardInner(currentMove+1);
6561         appData.animate = saveAnimate;
6562
6563         /* check for the end of the game */
6564         if (currentMove >= forwardMostMove) {
6565           gameMode = PlayFromGameFile;
6566           ModeHighlight();
6567           SetTrainingModeOff();
6568           DisplayInformation(_("End of game"));
6569         }
6570       } else {
6571         DisplayError(_("Incorrect move"), 0);
6572       }
6573       return 1;
6574     }
6575
6576   /* Ok, now we know that the move is good, so we can kill
6577      the previous line in Analysis Mode */
6578   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6579                                 && currentMove < forwardMostMove) {
6580     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6581     else forwardMostMove = currentMove;
6582   }
6583
6584   /* If we need the chess program but it's dead, restart it */
6585   ResurrectChessProgram();
6586
6587   /* A user move restarts a paused game*/
6588   if (pausing)
6589     PauseEvent();
6590
6591   thinkOutput[0] = NULLCHAR;
6592
6593   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6594
6595   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6596     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6597     return 1;
6598   }
6599
6600   if (gameMode == BeginningOfGame) {
6601     if (appData.noChessProgram) {
6602       gameMode = EditGame;
6603       SetGameInfo();
6604     } else {
6605       char buf[MSG_SIZ];
6606       gameMode = MachinePlaysBlack;
6607       StartClocks();
6608       SetGameInfo();
6609       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6610       DisplayTitle(buf);
6611       if (first.sendName) {
6612         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6613         SendToProgram(buf, &first);
6614       }
6615       StartClocks();
6616     }
6617     ModeHighlight();
6618   }
6619
6620   /* Relay move to ICS or chess engine */
6621   if (appData.icsActive) {
6622     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6623         gameMode == IcsExamining) {
6624       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6625         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6626         SendToICS("draw ");
6627         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6628       }
6629       // also send plain move, in case ICS does not understand atomic claims
6630       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6631       ics_user_moved = 1;
6632     }
6633   } else {
6634     if (first.sendTime && (gameMode == BeginningOfGame ||
6635                            gameMode == MachinePlaysWhite ||
6636                            gameMode == MachinePlaysBlack)) {
6637       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6638     }
6639     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6640          // [HGM] book: if program might be playing, let it use book
6641         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6642         first.maybeThinking = TRUE;
6643     } else SendMoveToProgram(forwardMostMove-1, &first);
6644     if (currentMove == cmailOldMove + 1) {
6645       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6646     }
6647   }
6648
6649   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6650
6651   switch (gameMode) {
6652   case EditGame:
6653     if(appData.testLegality)
6654     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6655     case MT_NONE:
6656     case MT_CHECK:
6657       break;
6658     case MT_CHECKMATE:
6659     case MT_STAINMATE:
6660       if (WhiteOnMove(currentMove)) {
6661         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6662       } else {
6663         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6664       }
6665       break;
6666     case MT_STALEMATE:
6667       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6668       break;
6669     }
6670     break;
6671
6672   case MachinePlaysBlack:
6673   case MachinePlaysWhite:
6674     /* disable certain menu options while machine is thinking */
6675     SetMachineThinkingEnables();
6676     break;
6677
6678   default:
6679     break;
6680   }
6681
6682   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6683   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6684
6685   if(bookHit) { // [HGM] book: simulate book reply
6686         static char bookMove[MSG_SIZ]; // a bit generous?
6687
6688         programStats.nodes = programStats.depth = programStats.time =
6689         programStats.score = programStats.got_only_move = 0;
6690         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6691
6692         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6693         strcat(bookMove, bookHit);
6694         HandleMachineMove(bookMove, &first);
6695   }
6696   return 1;
6697 }
6698
6699 void
6700 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6701      Board board;
6702      int flags;
6703      ChessMove kind;
6704      int rf, ff, rt, ft;
6705      VOIDSTAR closure;
6706 {
6707     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6708     Markers *m = (Markers *) closure;
6709     if(rf == fromY && ff == fromX)
6710         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6711                          || kind == WhiteCapturesEnPassant
6712                          || kind == BlackCapturesEnPassant);
6713     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6714 }
6715
6716 void
6717 MarkTargetSquares(int clear)
6718 {
6719   int x, y;
6720   if(!appData.markers || !appData.highlightDragging ||
6721      !appData.testLegality || gameMode == EditPosition) return;
6722   if(clear) {
6723     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6724   } else {
6725     int capt = 0;
6726     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6727     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6728       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6729       if(capt)
6730       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6731     }
6732   }
6733   DrawPosition(TRUE, NULL);
6734 }
6735
6736 int
6737 Explode(Board board, int fromX, int fromY, int toX, int toY)
6738 {
6739     if(gameInfo.variant == VariantAtomic &&
6740        (board[toY][toX] != EmptySquare ||                     // capture?
6741         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6742                          board[fromY][fromX] == BlackPawn   )
6743       )) {
6744         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6745         return TRUE;
6746     }
6747     return FALSE;
6748 }
6749
6750 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6751
6752 int CanPromote(ChessSquare piece, int y)
6753 {
6754         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6755         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6756         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6757            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6758            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6759                                                   gameInfo.variant == VariantMakruk) return FALSE;
6760         return (piece == BlackPawn && y == 1 ||
6761                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6762                 piece == BlackLance && y == 1 ||
6763                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6764 }
6765
6766 void LeftClick(ClickType clickType, int xPix, int yPix)
6767 {
6768     int x, y;
6769     Boolean saveAnimate;
6770     static int second = 0, promotionChoice = 0, clearFlag = 0;
6771     char promoChoice = NULLCHAR;
6772     ChessSquare piece;
6773
6774     if(appData.seekGraph && appData.icsActive && loggedOn &&
6775         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6776         SeekGraphClick(clickType, xPix, yPix, 0);
6777         return;
6778     }
6779
6780     if (clickType == Press) ErrorPopDown();
6781     MarkTargetSquares(1);
6782
6783     x = EventToSquare(xPix, BOARD_WIDTH);
6784     y = EventToSquare(yPix, BOARD_HEIGHT);
6785     if (!flipView && y >= 0) {
6786         y = BOARD_HEIGHT - 1 - y;
6787     }
6788     if (flipView && x >= 0) {
6789         x = BOARD_WIDTH - 1 - x;
6790     }
6791
6792     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6793         defaultPromoChoice = promoSweep;
6794         promoSweep = EmptySquare;   // terminate sweep
6795         promoDefaultAltered = TRUE;
6796         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6797     }
6798
6799     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6800         if(clickType == Release) return; // ignore upclick of click-click destination
6801         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6802         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6803         if(gameInfo.holdingsWidth &&
6804                 (WhiteOnMove(currentMove)
6805                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6806                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6807             // click in right holdings, for determining promotion piece
6808             ChessSquare p = boards[currentMove][y][x];
6809             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6810             if(p != EmptySquare) {
6811                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6812                 fromX = fromY = -1;
6813                 return;
6814             }
6815         }
6816         DrawPosition(FALSE, boards[currentMove]);
6817         return;
6818     }
6819
6820     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6821     if(clickType == Press
6822             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6823               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6824               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6825         return;
6826
6827     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6828         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6829
6830     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6831         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6832                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6833         defaultPromoChoice = DefaultPromoChoice(side);
6834     }
6835
6836     autoQueen = appData.alwaysPromoteToQueen;
6837
6838     if (fromX == -1) {
6839       int originalY = y;
6840       gatingPiece = EmptySquare;
6841       if (clickType != Press) {
6842         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6843             DragPieceEnd(xPix, yPix); dragging = 0;
6844             DrawPosition(FALSE, NULL);
6845         }
6846         return;
6847       }
6848       fromX = x; fromY = y;
6849       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6850          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6851          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6852             /* First square */
6853             if (OKToStartUserMove(fromX, fromY)) {
6854                 second = 0;
6855                 MarkTargetSquares(0);
6856                 DragPieceBegin(xPix, yPix); dragging = 1;
6857                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6858                     promoSweep = defaultPromoChoice;
6859                     selectFlag = 0; lastX = xPix; lastY = yPix;
6860                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6861                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6862                 }
6863                 if (appData.highlightDragging) {
6864                     SetHighlights(fromX, fromY, -1, -1);
6865                 }
6866             } else fromX = fromY = -1;
6867             return;
6868         }
6869     }
6870
6871     /* fromX != -1 */
6872     if (clickType == Press && gameMode != EditPosition) {
6873         ChessSquare fromP;
6874         ChessSquare toP;
6875         int frc;
6876
6877         // ignore off-board to clicks
6878         if(y < 0 || x < 0) return;
6879
6880         /* Check if clicking again on the same color piece */
6881         fromP = boards[currentMove][fromY][fromX];
6882         toP = boards[currentMove][y][x];
6883         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6884         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6885              WhitePawn <= toP && toP <= WhiteKing &&
6886              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6887              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6888             (BlackPawn <= fromP && fromP <= BlackKing &&
6889              BlackPawn <= toP && toP <= BlackKing &&
6890              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6891              !(fromP == BlackKing && toP == BlackRook && frc))) {
6892             /* Clicked again on same color piece -- changed his mind */
6893             second = (x == fromX && y == fromY);
6894             promoDefaultAltered = FALSE;
6895            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6896             if (appData.highlightDragging) {
6897                 SetHighlights(x, y, -1, -1);
6898             } else {
6899                 ClearHighlights();
6900             }
6901             if (OKToStartUserMove(x, y)) {
6902                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6903                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6904                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6905                  gatingPiece = boards[currentMove][fromY][fromX];
6906                 else gatingPiece = EmptySquare;
6907                 fromX = x;
6908                 fromY = y; dragging = 1;
6909                 MarkTargetSquares(0);
6910                 DragPieceBegin(xPix, yPix);
6911                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6912                     promoSweep = defaultPromoChoice;
6913                     selectFlag = 0; lastX = xPix; lastY = yPix;
6914                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6915                 }
6916             }
6917            }
6918            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6919            second = FALSE; 
6920         }
6921         // ignore clicks on holdings
6922         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6923     }
6924
6925     if (clickType == Release && x == fromX && y == fromY) {
6926         DragPieceEnd(xPix, yPix); dragging = 0;
6927         if(clearFlag) {
6928             // a deferred attempt to click-click move an empty square on top of a piece
6929             boards[currentMove][y][x] = EmptySquare;
6930             ClearHighlights();
6931             DrawPosition(FALSE, boards[currentMove]);
6932             fromX = fromY = -1; clearFlag = 0;
6933             return;
6934         }
6935         if (appData.animateDragging) {
6936             /* Undo animation damage if any */
6937             DrawPosition(FALSE, NULL);
6938         }
6939         if (second) {
6940             /* Second up/down in same square; just abort move */
6941             second = 0;
6942             fromX = fromY = -1;
6943             gatingPiece = EmptySquare;
6944             ClearHighlights();
6945             gotPremove = 0;
6946             ClearPremoveHighlights();
6947         } else {
6948             /* First upclick in same square; start click-click mode */
6949             SetHighlights(x, y, -1, -1);
6950         }
6951         return;
6952     }
6953
6954     clearFlag = 0;
6955
6956     /* we now have a different from- and (possibly off-board) to-square */
6957     /* Completed move */
6958     toX = x;
6959     toY = y;
6960     saveAnimate = appData.animate;
6961     if (clickType == Press) {
6962         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6963             // must be Edit Position mode with empty-square selected
6964             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6965             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6966             return;
6967         }
6968         /* Finish clickclick move */
6969         if (appData.animate || appData.highlightLastMove) {
6970             SetHighlights(fromX, fromY, toX, toY);
6971         } else {
6972             ClearHighlights();
6973         }
6974     } else {
6975         /* Finish drag move */
6976         if (appData.highlightLastMove) {
6977             SetHighlights(fromX, fromY, toX, toY);
6978         } else {
6979             ClearHighlights();
6980         }
6981         DragPieceEnd(xPix, yPix); dragging = 0;
6982         /* Don't animate move and drag both */
6983         appData.animate = FALSE;
6984     }
6985
6986     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6987     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6988         ChessSquare piece = boards[currentMove][fromY][fromX];
6989         if(gameMode == EditPosition && piece != EmptySquare &&
6990            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6991             int n;
6992
6993             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6994                 n = PieceToNumber(piece - (int)BlackPawn);
6995                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6996                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6997                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6998             } else
6999             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7000                 n = PieceToNumber(piece);
7001                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7002                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7003                 boards[currentMove][n][BOARD_WIDTH-2]++;
7004             }
7005             boards[currentMove][fromY][fromX] = EmptySquare;
7006         }
7007         ClearHighlights();
7008         fromX = fromY = -1;
7009         DrawPosition(TRUE, boards[currentMove]);
7010         return;
7011     }
7012
7013     // off-board moves should not be highlighted
7014     if(x < 0 || y < 0) ClearHighlights();
7015
7016     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7017
7018     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
7019         SetHighlights(fromX, fromY, toX, toY);
7020         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7021             // [HGM] super: promotion to captured piece selected from holdings
7022             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7023             promotionChoice = TRUE;
7024             // kludge follows to temporarily execute move on display, without promoting yet
7025             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7026             boards[currentMove][toY][toX] = p;
7027             DrawPosition(FALSE, boards[currentMove]);
7028             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7029             boards[currentMove][toY][toX] = q;
7030             DisplayMessage("Click in holdings to choose piece", "");
7031             return;
7032         }
7033         PromotionPopUp();
7034     } else {
7035         int oldMove = currentMove;
7036         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7037         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7038         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7039         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7040            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7041             DrawPosition(TRUE, boards[currentMove]);
7042         fromX = fromY = -1;
7043     }
7044     appData.animate = saveAnimate;
7045     if (appData.animate || appData.animateDragging) {
7046         /* Undo animation damage if needed */
7047         DrawPosition(FALSE, NULL);
7048     }
7049 }
7050
7051 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7052 {   // front-end-free part taken out of PieceMenuPopup
7053     int whichMenu; int xSqr, ySqr;
7054
7055     if(seekGraphUp) { // [HGM] seekgraph
7056         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7057         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7058         return -2;
7059     }
7060
7061     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7062          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7063         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7064         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7065         if(action == Press)   {
7066             originalFlip = flipView;
7067             flipView = !flipView; // temporarily flip board to see game from partners perspective
7068             DrawPosition(TRUE, partnerBoard);
7069             DisplayMessage(partnerStatus, "");
7070             partnerUp = TRUE;
7071         } else if(action == Release) {
7072             flipView = originalFlip;
7073             DrawPosition(TRUE, boards[currentMove]);
7074             partnerUp = FALSE;
7075         }
7076         return -2;
7077     }
7078
7079     xSqr = EventToSquare(x, BOARD_WIDTH);
7080     ySqr = EventToSquare(y, BOARD_HEIGHT);
7081     if (action == Release) {
7082         if(pieceSweep != EmptySquare) {
7083             EditPositionMenuEvent(pieceSweep, toX, toY);
7084             pieceSweep = EmptySquare;
7085         } else UnLoadPV(); // [HGM] pv
7086     }
7087     if (action != Press) return -2; // return code to be ignored
7088     switch (gameMode) {
7089       case IcsExamining:
7090         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7091       case EditPosition:
7092         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7093         if (xSqr < 0 || ySqr < 0) return -1;
7094         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7095         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7096         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7097         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7098         NextPiece(0);
7099         return -2;
7100       case IcsObserving:
7101         if(!appData.icsEngineAnalyze) return -1;
7102       case IcsPlayingWhite:
7103       case IcsPlayingBlack:
7104         if(!appData.zippyPlay) goto noZip;
7105       case AnalyzeMode:
7106       case AnalyzeFile:
7107       case MachinePlaysWhite:
7108       case MachinePlaysBlack:
7109       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7110         if (!appData.dropMenu) {
7111           LoadPV(x, y);
7112           return 2; // flag front-end to grab mouse events
7113         }
7114         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7115            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7116       case EditGame:
7117       noZip:
7118         if (xSqr < 0 || ySqr < 0) return -1;
7119         if (!appData.dropMenu || appData.testLegality &&
7120             gameInfo.variant != VariantBughouse &&
7121             gameInfo.variant != VariantCrazyhouse) return -1;
7122         whichMenu = 1; // drop menu
7123         break;
7124       default:
7125         return -1;
7126     }
7127
7128     if (((*fromX = xSqr) < 0) ||
7129         ((*fromY = ySqr) < 0)) {
7130         *fromX = *fromY = -1;
7131         return -1;
7132     }
7133     if (flipView)
7134       *fromX = BOARD_WIDTH - 1 - *fromX;
7135     else
7136       *fromY = BOARD_HEIGHT - 1 - *fromY;
7137
7138     return whichMenu;
7139 }
7140
7141 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7142 {
7143 //    char * hint = lastHint;
7144     FrontEndProgramStats stats;
7145
7146     stats.which = cps == &first ? 0 : 1;
7147     stats.depth = cpstats->depth;
7148     stats.nodes = cpstats->nodes;
7149     stats.score = cpstats->score;
7150     stats.time = cpstats->time;
7151     stats.pv = cpstats->movelist;
7152     stats.hint = lastHint;
7153     stats.an_move_index = 0;
7154     stats.an_move_count = 0;
7155
7156     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7157         stats.hint = cpstats->move_name;
7158         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7159         stats.an_move_count = cpstats->nr_moves;
7160     }
7161
7162     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
7163
7164     SetProgramStats( &stats );
7165 }
7166
7167 void
7168 ClearEngineOutputPane(int which)
7169 {
7170     static FrontEndProgramStats dummyStats;
7171     dummyStats.which = which;
7172     dummyStats.pv = "#";
7173     SetProgramStats( &dummyStats );
7174 }
7175
7176 #define MAXPLAYERS 500
7177
7178 char *
7179 TourneyStandings(int display)
7180 {
7181     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7182     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7183     char result, *p, *names[MAXPLAYERS];
7184
7185     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7186         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7187     names[0] = p = strdup(appData.participants);
7188     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7189
7190     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7191
7192     while(result = appData.results[nr]) {
7193         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7194         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7195         wScore = bScore = 0;
7196         switch(result) {
7197           case '+': wScore = 2; break;
7198           case '-': bScore = 2; break;
7199           case '=': wScore = bScore = 1; break;
7200           case ' ':
7201           case '*': return strdup("busy"); // tourney not finished
7202         }
7203         score[w] += wScore;
7204         score[b] += bScore;
7205         games[w]++;
7206         games[b]++;
7207         nr++;
7208     }
7209     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7210     for(w=0; w<nPlayers; w++) {
7211         bScore = -1;
7212         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7213         ranking[w] = b; points[w] = bScore; score[b] = -2;
7214     }
7215     p = malloc(nPlayers*34+1);
7216     for(w=0; w<nPlayers && w<display; w++)
7217         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7218     free(names[0]);
7219     return p;
7220 }
7221
7222 void
7223 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7224 {       // count all piece types
7225         int p, f, r;
7226         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7227         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7228         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7229                 p = board[r][f];
7230                 pCnt[p]++;
7231                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7232                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7233                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7234                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7235                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7236                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7237         }
7238 }
7239
7240 int
7241 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7242 {
7243         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7244         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7245
7246         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7247         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7248         if(myPawns == 2 && nMine == 3) // KPP
7249             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7250         if(myPawns == 1 && nMine == 2) // KP
7251             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7252         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7253             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7254         if(myPawns) return FALSE;
7255         if(pCnt[WhiteRook+side])
7256             return pCnt[BlackRook-side] ||
7257                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7258                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7259                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7260         if(pCnt[WhiteCannon+side]) {
7261             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7262             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7263         }
7264         if(pCnt[WhiteKnight+side])
7265             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7266         return FALSE;
7267 }
7268
7269 int
7270 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7271 {
7272         VariantClass v = gameInfo.variant;
7273
7274         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7275         if(v == VariantShatranj) return TRUE; // always winnable through baring
7276         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7277         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7278
7279         if(v == VariantXiangqi) {
7280                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7281
7282                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7283                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7284                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7285                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7286                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7287                 if(stale) // we have at least one last-rank P plus perhaps C
7288                     return majors // KPKX
7289                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7290                 else // KCA*E*
7291                     return pCnt[WhiteFerz+side] // KCAK
7292                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7293                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7294                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7295
7296         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7297                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7298
7299                 if(nMine == 1) return FALSE; // bare King
7300                 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
7301                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7302                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7303                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7304                 if(pCnt[WhiteKnight+side])
7305                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7306                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7307                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7308                 if(nBishops)
7309                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7310                 if(pCnt[WhiteAlfil+side])
7311                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7312                 if(pCnt[WhiteWazir+side])
7313                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7314         }
7315
7316         return TRUE;
7317 }
7318
7319 int
7320 Adjudicate(ChessProgramState *cps)
7321 {       // [HGM] some adjudications useful with buggy engines
7322         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7323         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7324         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7325         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7326         int k, count = 0; static int bare = 1;
7327         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7328         Boolean canAdjudicate = !appData.icsActive;
7329
7330         // most tests only when we understand the game, i.e. legality-checking on
7331             if( appData.testLegality )
7332             {   /* [HGM] Some more adjudications for obstinate engines */
7333                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7334                 static int moveCount = 6;
7335                 ChessMove result;
7336                 char *reason = NULL;
7337
7338                 /* Count what is on board. */
7339                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7340
7341                 /* Some material-based adjudications that have to be made before stalemate test */
7342                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7343                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7344                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7345                      if(canAdjudicate && appData.checkMates) {
7346                          if(engineOpponent)
7347                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7348                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7349                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7350                          return 1;
7351                      }
7352                 }
7353
7354                 /* Bare King in Shatranj (loses) or Losers (wins) */
7355                 if( nrW == 1 || nrB == 1) {
7356                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7357                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7358                      if(canAdjudicate && appData.checkMates) {
7359                          if(engineOpponent)
7360                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7361                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7362                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7363                          return 1;
7364                      }
7365                   } else
7366                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7367                   {    /* bare King */
7368                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7369                         if(canAdjudicate && appData.checkMates) {
7370                             /* but only adjudicate if adjudication enabled */
7371                             if(engineOpponent)
7372                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7373                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7374                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7375                             return 1;
7376                         }
7377                   }
7378                 } else bare = 1;
7379
7380
7381             // don't wait for engine to announce game end if we can judge ourselves
7382             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7383               case MT_CHECK:
7384                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7385                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7386                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7387                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7388                             checkCnt++;
7389                         if(checkCnt >= 2) {
7390                             reason = "Xboard adjudication: 3rd check";
7391                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7392                             break;
7393                         }
7394                     }
7395                 }
7396               case MT_NONE:
7397               default:
7398                 break;
7399               case MT_STALEMATE:
7400               case MT_STAINMATE:
7401                 reason = "Xboard adjudication: Stalemate";
7402                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7403                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7404                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7405                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7406                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7407                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7408                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7409                                                                         EP_CHECKMATE : EP_WINS);
7410                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7411                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7412                 }
7413                 break;
7414               case MT_CHECKMATE:
7415                 reason = "Xboard adjudication: Checkmate";
7416                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7417                 break;
7418             }
7419
7420                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7421                     case EP_STALEMATE:
7422                         result = GameIsDrawn; break;
7423                     case EP_CHECKMATE:
7424                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7425                     case EP_WINS:
7426                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7427                     default:
7428                         result = EndOfFile;
7429                 }
7430                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7431                     if(engineOpponent)
7432                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7433                     GameEnds( result, reason, GE_XBOARD );
7434                     return 1;
7435                 }
7436
7437                 /* Next absolutely insufficient mating material. */
7438                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7439                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7440                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7441
7442                      /* always flag draws, for judging claims */
7443                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7444
7445                      if(canAdjudicate && appData.materialDraws) {
7446                          /* but only adjudicate them if adjudication enabled */
7447                          if(engineOpponent) {
7448                            SendToProgram("force\n", engineOpponent); // suppress reply
7449                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7450                          }
7451                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7452                          return 1;
7453                      }
7454                 }
7455
7456                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7457                 if(gameInfo.variant == VariantXiangqi ?
7458                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7459                  : nrW + nrB == 4 &&
7460                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7461                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7462                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7463                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7464                    ) ) {
7465                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7466                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7467                           if(engineOpponent) {
7468                             SendToProgram("force\n", engineOpponent); // suppress reply
7469                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7470                           }
7471                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7472                           return 1;
7473                      }
7474                 } else moveCount = 6;
7475             }
7476         if (appData.debugMode) { int i;
7477             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7478                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7479                     appData.drawRepeats);
7480             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7481               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7482
7483         }
7484
7485         // Repetition draws and 50-move rule can be applied independently of legality testing
7486
7487                 /* Check for rep-draws */
7488                 count = 0;
7489                 for(k = forwardMostMove-2;
7490                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7491                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7492                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7493                     k-=2)
7494                 {   int rights=0;
7495                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7496                         /* compare castling rights */
7497                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7498                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7499                                 rights++; /* King lost rights, while rook still had them */
7500                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7501                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7502                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7503                                    rights++; /* but at least one rook lost them */
7504                         }
7505                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7506                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7507                                 rights++;
7508                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7509                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7510                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7511                                    rights++;
7512                         }
7513                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7514                             && appData.drawRepeats > 1) {
7515                              /* adjudicate after user-specified nr of repeats */
7516                              int result = GameIsDrawn;
7517                              char *details = "XBoard adjudication: repetition draw";
7518                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7519                                 // [HGM] xiangqi: check for forbidden perpetuals
7520                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7521                                 for(m=forwardMostMove; m>k; m-=2) {
7522                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7523                                         ourPerpetual = 0; // the current mover did not always check
7524                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7525                                         hisPerpetual = 0; // the opponent did not always check
7526                                 }
7527                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7528                                                                         ourPerpetual, hisPerpetual);
7529                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7530                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7531                                     details = "Xboard adjudication: perpetual checking";
7532                                 } else
7533                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7534                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7535                                 } else
7536                                 // Now check for perpetual chases
7537                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7538                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7539                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7540                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7541                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7542                                         details = "Xboard adjudication: perpetual chasing";
7543                                     } else
7544                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7545                                         break; // Abort repetition-checking loop.
7546                                 }
7547                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7548                              }
7549                              if(engineOpponent) {
7550                                SendToProgram("force\n", engineOpponent); // suppress reply
7551                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7552                              }
7553                              GameEnds( result, details, GE_XBOARD );
7554                              return 1;
7555                         }
7556                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7557                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7558                     }
7559                 }
7560
7561                 /* Now we test for 50-move draws. Determine ply count */
7562                 count = forwardMostMove;
7563                 /* look for last irreversble move */
7564                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7565                     count--;
7566                 /* if we hit starting position, add initial plies */
7567                 if( count == backwardMostMove )
7568                     count -= initialRulePlies;
7569                 count = forwardMostMove - count;
7570                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7571                         // adjust reversible move counter for checks in Xiangqi
7572                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7573                         if(i < backwardMostMove) i = backwardMostMove;
7574                         while(i <= forwardMostMove) {
7575                                 lastCheck = inCheck; // check evasion does not count
7576                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7577                                 if(inCheck || lastCheck) count--; // check does not count
7578                                 i++;
7579                         }
7580                 }
7581                 if( count >= 100)
7582                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7583                          /* this is used to judge if draw claims are legal */
7584                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7585                          if(engineOpponent) {
7586                            SendToProgram("force\n", engineOpponent); // suppress reply
7587                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7588                          }
7589                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7590                          return 1;
7591                 }
7592
7593                 /* if draw offer is pending, treat it as a draw claim
7594                  * when draw condition present, to allow engines a way to
7595                  * claim draws before making their move to avoid a race
7596                  * condition occurring after their move
7597                  */
7598                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7599                          char *p = NULL;
7600                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7601                              p = "Draw claim: 50-move rule";
7602                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7603                              p = "Draw claim: 3-fold repetition";
7604                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7605                              p = "Draw claim: insufficient mating material";
7606                          if( p != NULL && canAdjudicate) {
7607                              if(engineOpponent) {
7608                                SendToProgram("force\n", engineOpponent); // suppress reply
7609                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7610                              }
7611                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7612                              return 1;
7613                          }
7614                 }
7615
7616                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7617                     if(engineOpponent) {
7618                       SendToProgram("force\n", engineOpponent); // suppress reply
7619                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7620                     }
7621                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7622                     return 1;
7623                 }
7624         return 0;
7625 }
7626
7627 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7628 {   // [HGM] book: this routine intercepts moves to simulate book replies
7629     char *bookHit = NULL;
7630
7631     //first determine if the incoming move brings opponent into his book
7632     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7633         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7634     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7635     if(bookHit != NULL && !cps->bookSuspend) {
7636         // make sure opponent is not going to reply after receiving move to book position
7637         SendToProgram("force\n", cps);
7638         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7639     }
7640     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7641     // now arrange restart after book miss
7642     if(bookHit) {
7643         // after a book hit we never send 'go', and the code after the call to this routine
7644         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7645         char buf[MSG_SIZ], *move = bookHit;
7646         if(cps->useSAN) {
7647             int fromX, fromY, toX, toY;
7648             char promoChar;
7649             ChessMove moveType;
7650             move = buf + 30;
7651             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7652                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7653                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7654                                     PosFlags(forwardMostMove),
7655                                     fromY, fromX, toY, toX, promoChar, move);
7656             } else {
7657                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7658                 bookHit = NULL;
7659             }
7660         }
7661         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7662         SendToProgram(buf, cps);
7663         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7664     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7665         SendToProgram("go\n", cps);
7666         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7667     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7668         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7669             SendToProgram("go\n", cps);
7670         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7671     }
7672     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7673 }
7674
7675 char *savedMessage;
7676 ChessProgramState *savedState;
7677 void DeferredBookMove(void)
7678 {
7679         if(savedState->lastPing != savedState->lastPong)
7680                     ScheduleDelayedEvent(DeferredBookMove, 10);
7681         else
7682         HandleMachineMove(savedMessage, savedState);
7683 }
7684
7685 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7686
7687 void
7688 HandleMachineMove(message, cps)
7689      char *message;
7690      ChessProgramState *cps;
7691 {
7692     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7693     char realname[MSG_SIZ];
7694     int fromX, fromY, toX, toY;
7695     ChessMove moveType;
7696     char promoChar;
7697     char *p, *pv=buf1;
7698     int machineWhite;
7699     char *bookHit;
7700
7701     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7702         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7703         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7704             DisplayError(_("Invalid pairing from pairing engine"), 0);
7705             return;
7706         }
7707         pairingReceived = 1;
7708         NextMatchGame();
7709         return; // Skim the pairing messages here.
7710     }
7711
7712     cps->userError = 0;
7713
7714 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7715     /*
7716      * Kludge to ignore BEL characters
7717      */
7718     while (*message == '\007') message++;
7719
7720     /*
7721      * [HGM] engine debug message: ignore lines starting with '#' character
7722      */
7723     if(cps->debug && *message == '#') return;
7724
7725     /*
7726      * Look for book output
7727      */
7728     if (cps == &first && bookRequested) {
7729         if (message[0] == '\t' || message[0] == ' ') {
7730             /* Part of the book output is here; append it */
7731             strcat(bookOutput, message);
7732             strcat(bookOutput, "  \n");
7733             return;
7734         } else if (bookOutput[0] != NULLCHAR) {
7735             /* All of book output has arrived; display it */
7736             char *p = bookOutput;
7737             while (*p != NULLCHAR) {
7738                 if (*p == '\t') *p = ' ';
7739                 p++;
7740             }
7741             DisplayInformation(bookOutput);
7742             bookRequested = FALSE;
7743             /* Fall through to parse the current output */
7744         }
7745     }
7746
7747     /*
7748      * Look for machine move.
7749      */
7750     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7751         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7752     {
7753         /* This method is only useful on engines that support ping */
7754         if (cps->lastPing != cps->lastPong) {
7755           if (gameMode == BeginningOfGame) {
7756             /* Extra move from before last new; ignore */
7757             if (appData.debugMode) {
7758                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7759             }
7760           } else {
7761             if (appData.debugMode) {
7762                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7763                         cps->which, gameMode);
7764             }
7765
7766             SendToProgram("undo\n", cps);
7767           }
7768           return;
7769         }
7770
7771         switch (gameMode) {
7772           case BeginningOfGame:
7773             /* Extra move from before last reset; ignore */
7774             if (appData.debugMode) {
7775                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7776             }
7777             return;
7778
7779           case EndOfGame:
7780           case IcsIdle:
7781           default:
7782             /* Extra move after we tried to stop.  The mode test is
7783                not a reliable way of detecting this problem, but it's
7784                the best we can do on engines that don't support ping.
7785             */
7786             if (appData.debugMode) {
7787                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7788                         cps->which, gameMode);
7789             }
7790             SendToProgram("undo\n", cps);
7791             return;
7792
7793           case MachinePlaysWhite:
7794           case IcsPlayingWhite:
7795             machineWhite = TRUE;
7796             break;
7797
7798           case MachinePlaysBlack:
7799           case IcsPlayingBlack:
7800             machineWhite = FALSE;
7801             break;
7802
7803           case TwoMachinesPlay:
7804             machineWhite = (cps->twoMachinesColor[0] == 'w');
7805             break;
7806         }
7807         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7808             if (appData.debugMode) {
7809                 fprintf(debugFP,
7810                         "Ignoring move out of turn by %s, gameMode %d"
7811                         ", forwardMost %d\n",
7812                         cps->which, gameMode, forwardMostMove);
7813             }
7814             return;
7815         }
7816
7817     if (appData.debugMode) { int f = forwardMostMove;
7818         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7819                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7820                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7821     }
7822         if(cps->alphaRank) AlphaRank(machineMove, 4);
7823         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7824                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7825             /* Machine move could not be parsed; ignore it. */
7826           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7827                     machineMove, _(cps->which));
7828             DisplayError(buf1, 0);
7829             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7830                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7831             if (gameMode == TwoMachinesPlay) {
7832               GameEnds(machineWhite ? BlackWins : WhiteWins,
7833                        buf1, GE_XBOARD);
7834             }
7835             return;
7836         }
7837
7838         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7839         /* So we have to redo legality test with true e.p. status here,  */
7840         /* to make sure an illegal e.p. capture does not slip through,   */
7841         /* to cause a forfeit on a justified illegal-move complaint      */
7842         /* of the opponent.                                              */
7843         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7844            ChessMove moveType;
7845            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7846                              fromY, fromX, toY, toX, promoChar);
7847             if (appData.debugMode) {
7848                 int i;
7849                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7850                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7851                 fprintf(debugFP, "castling rights\n");
7852             }
7853             if(moveType == IllegalMove) {
7854               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7855                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7856                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7857                            buf1, GE_XBOARD);
7858                 return;
7859            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7860            /* [HGM] Kludge to handle engines that send FRC-style castling
7861               when they shouldn't (like TSCP-Gothic) */
7862            switch(moveType) {
7863              case WhiteASideCastleFR:
7864              case BlackASideCastleFR:
7865                toX+=2;
7866                currentMoveString[2]++;
7867                break;
7868              case WhiteHSideCastleFR:
7869              case BlackHSideCastleFR:
7870                toX--;
7871                currentMoveString[2]--;
7872                break;
7873              default: ; // nothing to do, but suppresses warning of pedantic compilers
7874            }
7875         }
7876         hintRequested = FALSE;
7877         lastHint[0] = NULLCHAR;
7878         bookRequested = FALSE;
7879         /* Program may be pondering now */
7880         cps->maybeThinking = TRUE;
7881         if (cps->sendTime == 2) cps->sendTime = 1;
7882         if (cps->offeredDraw) cps->offeredDraw--;
7883
7884         /* [AS] Save move info*/
7885         pvInfoList[ forwardMostMove ].score = programStats.score;
7886         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7887         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7888
7889         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7890
7891         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7892         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7893             int count = 0;
7894
7895             while( count < adjudicateLossPlies ) {
7896                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7897
7898                 if( count & 1 ) {
7899                     score = -score; /* Flip score for winning side */
7900                 }
7901
7902                 if( score > adjudicateLossThreshold ) {
7903                     break;
7904                 }
7905
7906                 count++;
7907             }
7908
7909             if( count >= adjudicateLossPlies ) {
7910                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7911
7912                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7913                     "Xboard adjudication",
7914                     GE_XBOARD );
7915
7916                 return;
7917             }
7918         }
7919
7920         if(Adjudicate(cps)) {
7921             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7922             return; // [HGM] adjudicate: for all automatic game ends
7923         }
7924
7925 #if ZIPPY
7926         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7927             first.initDone) {
7928           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7929                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7930                 SendToICS("draw ");
7931                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7932           }
7933           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7934           ics_user_moved = 1;
7935           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7936                 char buf[3*MSG_SIZ];
7937
7938                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7939                         programStats.score / 100.,
7940                         programStats.depth,
7941                         programStats.time / 100.,
7942                         (unsigned int)programStats.nodes,
7943                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7944                         programStats.movelist);
7945                 SendToICS(buf);
7946 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7947           }
7948         }
7949 #endif
7950
7951         /* [AS] Clear stats for next move */
7952         ClearProgramStats();
7953         thinkOutput[0] = NULLCHAR;
7954         hiddenThinkOutputState = 0;
7955
7956         bookHit = NULL;
7957         if (gameMode == TwoMachinesPlay) {
7958             /* [HGM] relaying draw offers moved to after reception of move */
7959             /* and interpreting offer as claim if it brings draw condition */
7960             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7961                 SendToProgram("draw\n", cps->other);
7962             }
7963             if (cps->other->sendTime) {
7964                 SendTimeRemaining(cps->other,
7965                                   cps->other->twoMachinesColor[0] == 'w');
7966             }
7967             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7968             if (firstMove && !bookHit) {
7969                 firstMove = FALSE;
7970                 if (cps->other->useColors) {
7971                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7972                 }
7973                 SendToProgram("go\n", cps->other);
7974             }
7975             cps->other->maybeThinking = TRUE;
7976         }
7977
7978         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7979
7980         if (!pausing && appData.ringBellAfterMoves) {
7981             RingBell();
7982         }
7983
7984         /*
7985          * Reenable menu items that were disabled while
7986          * machine was thinking
7987          */
7988         if (gameMode != TwoMachinesPlay)
7989             SetUserThinkingEnables();
7990
7991         // [HGM] book: after book hit opponent has received move and is now in force mode
7992         // force the book reply into it, and then fake that it outputted this move by jumping
7993         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7994         if(bookHit) {
7995                 static char bookMove[MSG_SIZ]; // a bit generous?
7996
7997                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7998                 strcat(bookMove, bookHit);
7999                 message = bookMove;
8000                 cps = cps->other;
8001                 programStats.nodes = programStats.depth = programStats.time =
8002                 programStats.score = programStats.got_only_move = 0;
8003                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8004
8005                 if(cps->lastPing != cps->lastPong) {
8006                     savedMessage = message; // args for deferred call
8007                     savedState = cps;
8008                     ScheduleDelayedEvent(DeferredBookMove, 10);
8009                     return;
8010                 }
8011                 goto FakeBookMove;
8012         }
8013
8014         return;
8015     }
8016
8017     /* Set special modes for chess engines.  Later something general
8018      *  could be added here; for now there is just one kludge feature,
8019      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8020      *  when "xboard" is given as an interactive command.
8021      */
8022     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8023         cps->useSigint = FALSE;
8024         cps->useSigterm = FALSE;
8025     }
8026     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8027       ParseFeatures(message+8, cps);
8028       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8029     }
8030
8031     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8032       int dummy, s=6; char buf[MSG_SIZ];
8033       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8034       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8035       ParseFEN(boards[0], &dummy, message+s);
8036       DrawPosition(TRUE, boards[0]);
8037       startedFromSetupPosition = TRUE;
8038       return;
8039     }
8040     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8041      * want this, I was asked to put it in, and obliged.
8042      */
8043     if (!strncmp(message, "setboard ", 9)) {
8044         Board initial_position;
8045
8046         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8047
8048         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8049             DisplayError(_("Bad FEN received from engine"), 0);
8050             return ;
8051         } else {
8052            Reset(TRUE, FALSE);
8053            CopyBoard(boards[0], initial_position);
8054            initialRulePlies = FENrulePlies;
8055            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8056            else gameMode = MachinePlaysBlack;
8057            DrawPosition(FALSE, boards[currentMove]);
8058         }
8059         return;
8060     }
8061
8062     /*
8063      * Look for communication commands
8064      */
8065     if (!strncmp(message, "telluser ", 9)) {
8066         if(message[9] == '\\' && message[10] == '\\')
8067             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8068         PlayTellSound();
8069         DisplayNote(message + 9);
8070         return;
8071     }
8072     if (!strncmp(message, "tellusererror ", 14)) {
8073         cps->userError = 1;
8074         if(message[14] == '\\' && message[15] == '\\')
8075             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8076         PlayTellSound();
8077         DisplayError(message + 14, 0);
8078         return;
8079     }
8080     if (!strncmp(message, "tellopponent ", 13)) {
8081       if (appData.icsActive) {
8082         if (loggedOn) {
8083           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8084           SendToICS(buf1);
8085         }
8086       } else {
8087         DisplayNote(message + 13);
8088       }
8089       return;
8090     }
8091     if (!strncmp(message, "tellothers ", 11)) {
8092       if (appData.icsActive) {
8093         if (loggedOn) {
8094           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8095           SendToICS(buf1);
8096         }
8097       }
8098       return;
8099     }
8100     if (!strncmp(message, "tellall ", 8)) {
8101       if (appData.icsActive) {
8102         if (loggedOn) {
8103           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8104           SendToICS(buf1);
8105         }
8106       } else {
8107         DisplayNote(message + 8);
8108       }
8109       return;
8110     }
8111     if (strncmp(message, "warning", 7) == 0) {
8112         /* Undocumented feature, use tellusererror in new code */
8113         DisplayError(message, 0);
8114         return;
8115     }
8116     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8117         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8118         strcat(realname, " query");
8119         AskQuestion(realname, buf2, buf1, cps->pr);
8120         return;
8121     }
8122     /* Commands from the engine directly to ICS.  We don't allow these to be
8123      *  sent until we are logged on. Crafty kibitzes have been known to
8124      *  interfere with the login process.
8125      */
8126     if (loggedOn) {
8127         if (!strncmp(message, "tellics ", 8)) {
8128             SendToICS(message + 8);
8129             SendToICS("\n");
8130             return;
8131         }
8132         if (!strncmp(message, "tellicsnoalias ", 15)) {
8133             SendToICS(ics_prefix);
8134             SendToICS(message + 15);
8135             SendToICS("\n");
8136             return;
8137         }
8138         /* The following are for backward compatibility only */
8139         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8140             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8141             SendToICS(ics_prefix);
8142             SendToICS(message);
8143             SendToICS("\n");
8144             return;
8145         }
8146     }
8147     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8148         return;
8149     }
8150     /*
8151      * If the move is illegal, cancel it and redraw the board.
8152      * Also deal with other error cases.  Matching is rather loose
8153      * here to accommodate engines written before the spec.
8154      */
8155     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8156         strncmp(message, "Error", 5) == 0) {
8157         if (StrStr(message, "name") ||
8158             StrStr(message, "rating") || StrStr(message, "?") ||
8159             StrStr(message, "result") || StrStr(message, "board") ||
8160             StrStr(message, "bk") || StrStr(message, "computer") ||
8161             StrStr(message, "variant") || StrStr(message, "hint") ||
8162             StrStr(message, "random") || StrStr(message, "depth") ||
8163             StrStr(message, "accepted")) {
8164             return;
8165         }
8166         if (StrStr(message, "protover")) {
8167           /* Program is responding to input, so it's apparently done
8168              initializing, and this error message indicates it is
8169              protocol version 1.  So we don't need to wait any longer
8170              for it to initialize and send feature commands. */
8171           FeatureDone(cps, 1);
8172           cps->protocolVersion = 1;
8173           return;
8174         }
8175         cps->maybeThinking = FALSE;
8176
8177         if (StrStr(message, "draw")) {
8178             /* Program doesn't have "draw" command */
8179             cps->sendDrawOffers = 0;
8180             return;
8181         }
8182         if (cps->sendTime != 1 &&
8183             (StrStr(message, "time") || StrStr(message, "otim"))) {
8184           /* Program apparently doesn't have "time" or "otim" command */
8185           cps->sendTime = 0;
8186           return;
8187         }
8188         if (StrStr(message, "analyze")) {
8189             cps->analysisSupport = FALSE;
8190             cps->analyzing = FALSE;
8191             Reset(FALSE, TRUE);
8192             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8193             DisplayError(buf2, 0);
8194             return;
8195         }
8196         if (StrStr(message, "(no matching move)st")) {
8197           /* Special kludge for GNU Chess 4 only */
8198           cps->stKludge = TRUE;
8199           SendTimeControl(cps, movesPerSession, timeControl,
8200                           timeIncrement, appData.searchDepth,
8201                           searchTime);
8202           return;
8203         }
8204         if (StrStr(message, "(no matching move)sd")) {
8205           /* Special kludge for GNU Chess 4 only */
8206           cps->sdKludge = TRUE;
8207           SendTimeControl(cps, movesPerSession, timeControl,
8208                           timeIncrement, appData.searchDepth,
8209                           searchTime);
8210           return;
8211         }
8212         if (!StrStr(message, "llegal")) {
8213             return;
8214         }
8215         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8216             gameMode == IcsIdle) return;
8217         if (forwardMostMove <= backwardMostMove) return;
8218         if (pausing) PauseEvent();
8219       if(appData.forceIllegal) {
8220             // [HGM] illegal: machine refused move; force position after move into it
8221           SendToProgram("force\n", cps);
8222           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8223                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8224                 // when black is to move, while there might be nothing on a2 or black
8225                 // might already have the move. So send the board as if white has the move.
8226                 // But first we must change the stm of the engine, as it refused the last move
8227                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8228                 if(WhiteOnMove(forwardMostMove)) {
8229                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8230                     SendBoard(cps, forwardMostMove); // kludgeless board
8231                 } else {
8232                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8233                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8234                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8235                 }
8236           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8237             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8238                  gameMode == TwoMachinesPlay)
8239               SendToProgram("go\n", cps);
8240             return;
8241       } else
8242         if (gameMode == PlayFromGameFile) {
8243             /* Stop reading this game file */
8244             gameMode = EditGame;
8245             ModeHighlight();
8246         }
8247         /* [HGM] illegal-move claim should forfeit game when Xboard */
8248         /* only passes fully legal moves                            */
8249         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8250             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8251                                 "False illegal-move claim", GE_XBOARD );
8252             return; // do not take back move we tested as valid
8253         }
8254         currentMove = forwardMostMove-1;
8255         DisplayMove(currentMove-1); /* before DisplayMoveError */
8256         SwitchClocks(forwardMostMove-1); // [HGM] race
8257         DisplayBothClocks();
8258         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8259                 parseList[currentMove], _(cps->which));
8260         DisplayMoveError(buf1);
8261         DrawPosition(FALSE, boards[currentMove]);
8262         return;
8263     }
8264     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8265         /* Program has a broken "time" command that
8266            outputs a string not ending in newline.
8267            Don't use it. */
8268         cps->sendTime = 0;
8269     }
8270
8271     /*
8272      * If chess program startup fails, exit with an error message.
8273      * Attempts to recover here are futile.
8274      */
8275     if ((StrStr(message, "unknown host") != NULL)
8276         || (StrStr(message, "No remote directory") != NULL)
8277         || (StrStr(message, "not found") != NULL)
8278         || (StrStr(message, "No such file") != NULL)
8279         || (StrStr(message, "can't alloc") != NULL)
8280         || (StrStr(message, "Permission denied") != NULL)) {
8281
8282         cps->maybeThinking = FALSE;
8283         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8284                 _(cps->which), cps->program, cps->host, message);
8285         RemoveInputSource(cps->isr);
8286         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8287             if(cps == &first) appData.noChessProgram = TRUE;
8288             DisplayError(buf1, 0);
8289         }
8290         return;
8291     }
8292
8293     /*
8294      * Look for hint output
8295      */
8296     if (sscanf(message, "Hint: %s", buf1) == 1) {
8297         if (cps == &first && hintRequested) {
8298             hintRequested = FALSE;
8299             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8300                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8301                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8302                                     PosFlags(forwardMostMove),
8303                                     fromY, fromX, toY, toX, promoChar, buf1);
8304                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8305                 DisplayInformation(buf2);
8306             } else {
8307                 /* Hint move could not be parsed!? */
8308               snprintf(buf2, sizeof(buf2),
8309                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8310                         buf1, _(cps->which));
8311                 DisplayError(buf2, 0);
8312             }
8313         } else {
8314           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8315         }
8316         return;
8317     }
8318
8319     /*
8320      * Ignore other messages if game is not in progress
8321      */
8322     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8323         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8324
8325     /*
8326      * look for win, lose, draw, or draw offer
8327      */
8328     if (strncmp(message, "1-0", 3) == 0) {
8329         char *p, *q, *r = "";
8330         p = strchr(message, '{');
8331         if (p) {
8332             q = strchr(p, '}');
8333             if (q) {
8334                 *q = NULLCHAR;
8335                 r = p + 1;
8336             }
8337         }
8338         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8339         return;
8340     } else if (strncmp(message, "0-1", 3) == 0) {
8341         char *p, *q, *r = "";
8342         p = strchr(message, '{');
8343         if (p) {
8344             q = strchr(p, '}');
8345             if (q) {
8346                 *q = NULLCHAR;
8347                 r = p + 1;
8348             }
8349         }
8350         /* Kludge for Arasan 4.1 bug */
8351         if (strcmp(r, "Black resigns") == 0) {
8352             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8353             return;
8354         }
8355         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8356         return;
8357     } else if (strncmp(message, "1/2", 3) == 0) {
8358         char *p, *q, *r = "";
8359         p = strchr(message, '{');
8360         if (p) {
8361             q = strchr(p, '}');
8362             if (q) {
8363                 *q = NULLCHAR;
8364                 r = p + 1;
8365             }
8366         }
8367
8368         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8369         return;
8370
8371     } else if (strncmp(message, "White resign", 12) == 0) {
8372         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8373         return;
8374     } else if (strncmp(message, "Black resign", 12) == 0) {
8375         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8376         return;
8377     } else if (strncmp(message, "White matches", 13) == 0 ||
8378                strncmp(message, "Black matches", 13) == 0   ) {
8379         /* [HGM] ignore GNUShogi noises */
8380         return;
8381     } else if (strncmp(message, "White", 5) == 0 &&
8382                message[5] != '(' &&
8383                StrStr(message, "Black") == NULL) {
8384         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8385         return;
8386     } else if (strncmp(message, "Black", 5) == 0 &&
8387                message[5] != '(') {
8388         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8389         return;
8390     } else if (strcmp(message, "resign") == 0 ||
8391                strcmp(message, "computer resigns") == 0) {
8392         switch (gameMode) {
8393           case MachinePlaysBlack:
8394           case IcsPlayingBlack:
8395             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8396             break;
8397           case MachinePlaysWhite:
8398           case IcsPlayingWhite:
8399             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8400             break;
8401           case TwoMachinesPlay:
8402             if (cps->twoMachinesColor[0] == 'w')
8403               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8404             else
8405               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8406             break;
8407           default:
8408             /* can't happen */
8409             break;
8410         }
8411         return;
8412     } else if (strncmp(message, "opponent mates", 14) == 0) {
8413         switch (gameMode) {
8414           case MachinePlaysBlack:
8415           case IcsPlayingBlack:
8416             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8417             break;
8418           case MachinePlaysWhite:
8419           case IcsPlayingWhite:
8420             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8421             break;
8422           case TwoMachinesPlay:
8423             if (cps->twoMachinesColor[0] == 'w')
8424               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8425             else
8426               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8427             break;
8428           default:
8429             /* can't happen */
8430             break;
8431         }
8432         return;
8433     } else if (strncmp(message, "computer mates", 14) == 0) {
8434         switch (gameMode) {
8435           case MachinePlaysBlack:
8436           case IcsPlayingBlack:
8437             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8438             break;
8439           case MachinePlaysWhite:
8440           case IcsPlayingWhite:
8441             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8442             break;
8443           case TwoMachinesPlay:
8444             if (cps->twoMachinesColor[0] == 'w')
8445               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8446             else
8447               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8448             break;
8449           default:
8450             /* can't happen */
8451             break;
8452         }
8453         return;
8454     } else if (strncmp(message, "checkmate", 9) == 0) {
8455         if (WhiteOnMove(forwardMostMove)) {
8456             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8457         } else {
8458             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8459         }
8460         return;
8461     } else if (strstr(message, "Draw") != NULL ||
8462                strstr(message, "game is a draw") != NULL) {
8463         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8464         return;
8465     } else if (strstr(message, "offer") != NULL &&
8466                strstr(message, "draw") != NULL) {
8467 #if ZIPPY
8468         if (appData.zippyPlay && first.initDone) {
8469             /* Relay offer to ICS */
8470             SendToICS(ics_prefix);
8471             SendToICS("draw\n");
8472         }
8473 #endif
8474         cps->offeredDraw = 2; /* valid until this engine moves twice */
8475         if (gameMode == TwoMachinesPlay) {
8476             if (cps->other->offeredDraw) {
8477                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8478             /* [HGM] in two-machine mode we delay relaying draw offer      */
8479             /* until after we also have move, to see if it is really claim */
8480             }
8481         } else if (gameMode == MachinePlaysWhite ||
8482                    gameMode == MachinePlaysBlack) {
8483           if (userOfferedDraw) {
8484             DisplayInformation(_("Machine accepts your draw offer"));
8485             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8486           } else {
8487             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8488           }
8489         }
8490     }
8491
8492
8493     /*
8494      * Look for thinking output
8495      */
8496     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8497           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8498                                 ) {
8499         int plylev, mvleft, mvtot, curscore, time;
8500         char mvname[MOVE_LEN];
8501         u64 nodes; // [DM]
8502         char plyext;
8503         int ignore = FALSE;
8504         int prefixHint = FALSE;
8505         mvname[0] = NULLCHAR;
8506
8507         switch (gameMode) {
8508           case MachinePlaysBlack:
8509           case IcsPlayingBlack:
8510             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8511             break;
8512           case MachinePlaysWhite:
8513           case IcsPlayingWhite:
8514             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8515             break;
8516           case AnalyzeMode:
8517           case AnalyzeFile:
8518             break;
8519           case IcsObserving: /* [DM] icsEngineAnalyze */
8520             if (!appData.icsEngineAnalyze) ignore = TRUE;
8521             break;
8522           case TwoMachinesPlay:
8523             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8524                 ignore = TRUE;
8525             }
8526             break;
8527           default:
8528             ignore = TRUE;
8529             break;
8530         }
8531
8532         if (!ignore) {
8533             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8534             buf1[0] = NULLCHAR;
8535             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8536                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8537
8538                 if (plyext != ' ' && plyext != '\t') {
8539                     time *= 100;
8540                 }
8541
8542                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8543                 if( cps->scoreIsAbsolute &&
8544                     ( gameMode == MachinePlaysBlack ||
8545                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8546                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8547                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8548                      !WhiteOnMove(currentMove)
8549                     ) )
8550                 {
8551                     curscore = -curscore;
8552                 }
8553
8554                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8555
8556                 tempStats.depth = plylev;
8557                 tempStats.nodes = nodes;
8558                 tempStats.time = time;
8559                 tempStats.score = curscore;
8560                 tempStats.got_only_move = 0;
8561
8562                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8563                         int ticklen;
8564
8565                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8566                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8567                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8568                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8569                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8570                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8571                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8572                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8573                 }
8574
8575                 /* Buffer overflow protection */
8576                 if (pv[0] != NULLCHAR) {
8577                     if (strlen(pv) >= sizeof(tempStats.movelist)
8578                         && appData.debugMode) {
8579                         fprintf(debugFP,
8580                                 "PV is too long; using the first %u bytes.\n",
8581                                 (unsigned) sizeof(tempStats.movelist) - 1);
8582                     }
8583
8584                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8585                 } else {
8586                     sprintf(tempStats.movelist, " no PV\n");
8587                 }
8588
8589                 if (tempStats.seen_stat) {
8590                     tempStats.ok_to_send = 1;
8591                 }
8592
8593                 if (strchr(tempStats.movelist, '(') != NULL) {
8594                     tempStats.line_is_book = 1;
8595                     tempStats.nr_moves = 0;
8596                     tempStats.moves_left = 0;
8597                 } else {
8598                     tempStats.line_is_book = 0;
8599                 }
8600
8601                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8602                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8603
8604                 SendProgramStatsToFrontend( cps, &tempStats );
8605
8606                 /*
8607                     [AS] Protect the thinkOutput buffer from overflow... this
8608                     is only useful if buf1 hasn't overflowed first!
8609                 */
8610                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8611                          plylev,
8612                          (gameMode == TwoMachinesPlay ?
8613                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8614                          ((double) curscore) / 100.0,
8615                          prefixHint ? lastHint : "",
8616                          prefixHint ? " " : "" );
8617
8618                 if( buf1[0] != NULLCHAR ) {
8619                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8620
8621                     if( strlen(pv) > max_len ) {
8622                         if( appData.debugMode) {
8623                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8624                         }
8625                         pv[max_len+1] = '\0';
8626                     }
8627
8628                     strcat( thinkOutput, pv);
8629                 }
8630
8631                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8632                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8633                     DisplayMove(currentMove - 1);
8634                 }
8635                 return;
8636
8637             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8638                 /* crafty (9.25+) says "(only move) <move>"
8639                  * if there is only 1 legal move
8640                  */
8641                 sscanf(p, "(only move) %s", buf1);
8642                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8643                 sprintf(programStats.movelist, "%s (only move)", buf1);
8644                 programStats.depth = 1;
8645                 programStats.nr_moves = 1;
8646                 programStats.moves_left = 1;
8647                 programStats.nodes = 1;
8648                 programStats.time = 1;
8649                 programStats.got_only_move = 1;
8650
8651                 /* Not really, but we also use this member to
8652                    mean "line isn't going to change" (Crafty
8653                    isn't searching, so stats won't change) */
8654                 programStats.line_is_book = 1;
8655
8656                 SendProgramStatsToFrontend( cps, &programStats );
8657
8658                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8659                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8660                     DisplayMove(currentMove - 1);
8661                 }
8662                 return;
8663             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8664                               &time, &nodes, &plylev, &mvleft,
8665                               &mvtot, mvname) >= 5) {
8666                 /* The stat01: line is from Crafty (9.29+) in response
8667                    to the "." command */
8668                 programStats.seen_stat = 1;
8669                 cps->maybeThinking = TRUE;
8670
8671                 if (programStats.got_only_move || !appData.periodicUpdates)
8672                   return;
8673
8674                 programStats.depth = plylev;
8675                 programStats.time = time;
8676                 programStats.nodes = nodes;
8677                 programStats.moves_left = mvleft;
8678                 programStats.nr_moves = mvtot;
8679                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8680                 programStats.ok_to_send = 1;
8681                 programStats.movelist[0] = '\0';
8682
8683                 SendProgramStatsToFrontend( cps, &programStats );
8684
8685                 return;
8686
8687             } else if (strncmp(message,"++",2) == 0) {
8688                 /* Crafty 9.29+ outputs this */
8689                 programStats.got_fail = 2;
8690                 return;
8691
8692             } else if (strncmp(message,"--",2) == 0) {
8693                 /* Crafty 9.29+ outputs this */
8694                 programStats.got_fail = 1;
8695                 return;
8696
8697             } else if (thinkOutput[0] != NULLCHAR &&
8698                        strncmp(message, "    ", 4) == 0) {
8699                 unsigned message_len;
8700
8701                 p = message;
8702                 while (*p && *p == ' ') p++;
8703
8704                 message_len = strlen( p );
8705
8706                 /* [AS] Avoid buffer overflow */
8707                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8708                     strcat(thinkOutput, " ");
8709                     strcat(thinkOutput, p);
8710                 }
8711
8712                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8713                     strcat(programStats.movelist, " ");
8714                     strcat(programStats.movelist, p);
8715                 }
8716
8717                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8718                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8719                     DisplayMove(currentMove - 1);
8720                 }
8721                 return;
8722             }
8723         }
8724         else {
8725             buf1[0] = NULLCHAR;
8726
8727             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8728                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8729             {
8730                 ChessProgramStats cpstats;
8731
8732                 if (plyext != ' ' && plyext != '\t') {
8733                     time *= 100;
8734                 }
8735
8736                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8737                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8738                     curscore = -curscore;
8739                 }
8740
8741                 cpstats.depth = plylev;
8742                 cpstats.nodes = nodes;
8743                 cpstats.time = time;
8744                 cpstats.score = curscore;
8745                 cpstats.got_only_move = 0;
8746                 cpstats.movelist[0] = '\0';
8747
8748                 if (buf1[0] != NULLCHAR) {
8749                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8750                 }
8751
8752                 cpstats.ok_to_send = 0;
8753                 cpstats.line_is_book = 0;
8754                 cpstats.nr_moves = 0;
8755                 cpstats.moves_left = 0;
8756
8757                 SendProgramStatsToFrontend( cps, &cpstats );
8758             }
8759         }
8760     }
8761 }
8762
8763
8764 /* Parse a game score from the character string "game", and
8765    record it as the history of the current game.  The game
8766    score is NOT assumed to start from the standard position.
8767    The display is not updated in any way.
8768    */
8769 void
8770 ParseGameHistory(game)
8771      char *game;
8772 {
8773     ChessMove moveType;
8774     int fromX, fromY, toX, toY, boardIndex;
8775     char promoChar;
8776     char *p, *q;
8777     char buf[MSG_SIZ];
8778
8779     if (appData.debugMode)
8780       fprintf(debugFP, "Parsing game history: %s\n", game);
8781
8782     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8783     gameInfo.site = StrSave(appData.icsHost);
8784     gameInfo.date = PGNDate();
8785     gameInfo.round = StrSave("-");
8786
8787     /* Parse out names of players */
8788     while (*game == ' ') game++;
8789     p = buf;
8790     while (*game != ' ') *p++ = *game++;
8791     *p = NULLCHAR;
8792     gameInfo.white = StrSave(buf);
8793     while (*game == ' ') game++;
8794     p = buf;
8795     while (*game != ' ' && *game != '\n') *p++ = *game++;
8796     *p = NULLCHAR;
8797     gameInfo.black = StrSave(buf);
8798
8799     /* Parse moves */
8800     boardIndex = blackPlaysFirst ? 1 : 0;
8801     yynewstr(game);
8802     for (;;) {
8803         yyboardindex = boardIndex;
8804         moveType = (ChessMove) Myylex();
8805         switch (moveType) {
8806           case IllegalMove:             /* maybe suicide chess, etc. */
8807   if (appData.debugMode) {
8808     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8809     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8810     setbuf(debugFP, NULL);
8811   }
8812           case WhitePromotion:
8813           case BlackPromotion:
8814           case WhiteNonPromotion:
8815           case BlackNonPromotion:
8816           case NormalMove:
8817           case WhiteCapturesEnPassant:
8818           case BlackCapturesEnPassant:
8819           case WhiteKingSideCastle:
8820           case WhiteQueenSideCastle:
8821           case BlackKingSideCastle:
8822           case BlackQueenSideCastle:
8823           case WhiteKingSideCastleWild:
8824           case WhiteQueenSideCastleWild:
8825           case BlackKingSideCastleWild:
8826           case BlackQueenSideCastleWild:
8827           /* PUSH Fabien */
8828           case WhiteHSideCastleFR:
8829           case WhiteASideCastleFR:
8830           case BlackHSideCastleFR:
8831           case BlackASideCastleFR:
8832           /* POP Fabien */
8833             fromX = currentMoveString[0] - AAA;
8834             fromY = currentMoveString[1] - ONE;
8835             toX = currentMoveString[2] - AAA;
8836             toY = currentMoveString[3] - ONE;
8837             promoChar = currentMoveString[4];
8838             break;
8839           case WhiteDrop:
8840           case BlackDrop:
8841             fromX = moveType == WhiteDrop ?
8842               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8843             (int) CharToPiece(ToLower(currentMoveString[0]));
8844             fromY = DROP_RANK;
8845             toX = currentMoveString[2] - AAA;
8846             toY = currentMoveString[3] - ONE;
8847             promoChar = NULLCHAR;
8848             break;
8849           case AmbiguousMove:
8850             /* bug? */
8851             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8852   if (appData.debugMode) {
8853     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8854     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8855     setbuf(debugFP, NULL);
8856   }
8857             DisplayError(buf, 0);
8858             return;
8859           case ImpossibleMove:
8860             /* bug? */
8861             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8862   if (appData.debugMode) {
8863     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8864     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8865     setbuf(debugFP, NULL);
8866   }
8867             DisplayError(buf, 0);
8868             return;
8869           case EndOfFile:
8870             if (boardIndex < backwardMostMove) {
8871                 /* Oops, gap.  How did that happen? */
8872                 DisplayError(_("Gap in move list"), 0);
8873                 return;
8874             }
8875             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8876             if (boardIndex > forwardMostMove) {
8877                 forwardMostMove = boardIndex;
8878             }
8879             return;
8880           case ElapsedTime:
8881             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8882                 strcat(parseList[boardIndex-1], " ");
8883                 strcat(parseList[boardIndex-1], yy_text);
8884             }
8885             continue;
8886           case Comment:
8887           case PGNTag:
8888           case NAG:
8889           default:
8890             /* ignore */
8891             continue;
8892           case WhiteWins:
8893           case BlackWins:
8894           case GameIsDrawn:
8895           case GameUnfinished:
8896             if (gameMode == IcsExamining) {
8897                 if (boardIndex < backwardMostMove) {
8898                     /* Oops, gap.  How did that happen? */
8899                     return;
8900                 }
8901                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8902                 return;
8903             }
8904             gameInfo.result = moveType;
8905             p = strchr(yy_text, '{');
8906             if (p == NULL) p = strchr(yy_text, '(');
8907             if (p == NULL) {
8908                 p = yy_text;
8909                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8910             } else {
8911                 q = strchr(p, *p == '{' ? '}' : ')');
8912                 if (q != NULL) *q = NULLCHAR;
8913                 p++;
8914             }
8915             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8916             gameInfo.resultDetails = StrSave(p);
8917             continue;
8918         }
8919         if (boardIndex >= forwardMostMove &&
8920             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8921             backwardMostMove = blackPlaysFirst ? 1 : 0;
8922             return;
8923         }
8924         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8925                                  fromY, fromX, toY, toX, promoChar,
8926                                  parseList[boardIndex]);
8927         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8928         /* currentMoveString is set as a side-effect of yylex */
8929         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8930         strcat(moveList[boardIndex], "\n");
8931         boardIndex++;
8932         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8933         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8934           case MT_NONE:
8935           case MT_STALEMATE:
8936           default:
8937             break;
8938           case MT_CHECK:
8939             if(gameInfo.variant != VariantShogi)
8940                 strcat(parseList[boardIndex - 1], "+");
8941             break;
8942           case MT_CHECKMATE:
8943           case MT_STAINMATE:
8944             strcat(parseList[boardIndex - 1], "#");
8945             break;
8946         }
8947     }
8948 }
8949
8950
8951 /* Apply a move to the given board  */
8952 void
8953 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8954      int fromX, fromY, toX, toY;
8955      int promoChar;
8956      Board board;
8957 {
8958   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8959   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8960
8961     /* [HGM] compute & store e.p. status and castling rights for new position */
8962     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8963
8964       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8965       oldEP = (signed char)board[EP_STATUS];
8966       board[EP_STATUS] = EP_NONE;
8967
8968       if( board[toY][toX] != EmptySquare )
8969            board[EP_STATUS] = EP_CAPTURE;
8970
8971   if (fromY == DROP_RANK) {
8972         /* must be first */
8973         piece = board[toY][toX] = (ChessSquare) fromX;
8974   } else {
8975       int i;
8976
8977       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8978            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8979                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8980       } else
8981       if( board[fromY][fromX] == WhitePawn ) {
8982            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8983                board[EP_STATUS] = EP_PAWN_MOVE;
8984            if( toY-fromY==2) {
8985                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8986                         gameInfo.variant != VariantBerolina || toX < fromX)
8987                       board[EP_STATUS] = toX | berolina;
8988                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8989                         gameInfo.variant != VariantBerolina || toX > fromX)
8990                       board[EP_STATUS] = toX;
8991            }
8992       } else
8993       if( board[fromY][fromX] == BlackPawn ) {
8994            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8995                board[EP_STATUS] = EP_PAWN_MOVE;
8996            if( toY-fromY== -2) {
8997                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8998                         gameInfo.variant != VariantBerolina || toX < fromX)
8999                       board[EP_STATUS] = toX | berolina;
9000                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9001                         gameInfo.variant != VariantBerolina || toX > fromX)
9002                       board[EP_STATUS] = toX;
9003            }
9004        }
9005
9006        for(i=0; i<nrCastlingRights; i++) {
9007            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9008               board[CASTLING][i] == toX   && castlingRank[i] == toY
9009              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9010        }
9011
9012      if (fromX == toX && fromY == toY) return;
9013
9014      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9015      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9016      if(gameInfo.variant == VariantKnightmate)
9017          king += (int) WhiteUnicorn - (int) WhiteKing;
9018
9019     /* Code added by Tord: */
9020     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9021     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9022         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9023       board[fromY][fromX] = EmptySquare;
9024       board[toY][toX] = EmptySquare;
9025       if((toX > fromX) != (piece == WhiteRook)) {
9026         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9027       } else {
9028         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9029       }
9030     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9031                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9032       board[fromY][fromX] = EmptySquare;
9033       board[toY][toX] = EmptySquare;
9034       if((toX > fromX) != (piece == BlackRook)) {
9035         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9036       } else {
9037         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9038       }
9039     /* End of code added by Tord */
9040
9041     } else if (board[fromY][fromX] == king
9042         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9043         && toY == fromY && toX > fromX+1) {
9044         board[fromY][fromX] = EmptySquare;
9045         board[toY][toX] = king;
9046         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9047         board[fromY][BOARD_RGHT-1] = EmptySquare;
9048     } else if (board[fromY][fromX] == king
9049         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9050                && toY == fromY && toX < fromX-1) {
9051         board[fromY][fromX] = EmptySquare;
9052         board[toY][toX] = king;
9053         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9054         board[fromY][BOARD_LEFT] = EmptySquare;
9055     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9056                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9057                && toY >= BOARD_HEIGHT-promoRank
9058                ) {
9059         /* white pawn promotion */
9060         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9061         if (board[toY][toX] == EmptySquare) {
9062             board[toY][toX] = WhiteQueen;
9063         }
9064         if(gameInfo.variant==VariantBughouse ||
9065            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9066             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9067         board[fromY][fromX] = EmptySquare;
9068     } else if ((fromY == BOARD_HEIGHT-4)
9069                && (toX != fromX)
9070                && gameInfo.variant != VariantXiangqi
9071                && gameInfo.variant != VariantBerolina
9072                && (board[fromY][fromX] == WhitePawn)
9073                && (board[toY][toX] == EmptySquare)) {
9074         board[fromY][fromX] = EmptySquare;
9075         board[toY][toX] = WhitePawn;
9076         captured = board[toY - 1][toX];
9077         board[toY - 1][toX] = EmptySquare;
9078     } else if ((fromY == BOARD_HEIGHT-4)
9079                && (toX == fromX)
9080                && gameInfo.variant == VariantBerolina
9081                && (board[fromY][fromX] == WhitePawn)
9082                && (board[toY][toX] == EmptySquare)) {
9083         board[fromY][fromX] = EmptySquare;
9084         board[toY][toX] = WhitePawn;
9085         if(oldEP & EP_BEROLIN_A) {
9086                 captured = board[fromY][fromX-1];
9087                 board[fromY][fromX-1] = EmptySquare;
9088         }else{  captured = board[fromY][fromX+1];
9089                 board[fromY][fromX+1] = EmptySquare;
9090         }
9091     } else if (board[fromY][fromX] == king
9092         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9093                && toY == fromY && toX > fromX+1) {
9094         board[fromY][fromX] = EmptySquare;
9095         board[toY][toX] = king;
9096         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9097         board[fromY][BOARD_RGHT-1] = EmptySquare;
9098     } else if (board[fromY][fromX] == king
9099         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9100                && toY == fromY && toX < fromX-1) {
9101         board[fromY][fromX] = EmptySquare;
9102         board[toY][toX] = king;
9103         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9104         board[fromY][BOARD_LEFT] = EmptySquare;
9105     } else if (fromY == 7 && fromX == 3
9106                && board[fromY][fromX] == BlackKing
9107                && toY == 7 && toX == 5) {
9108         board[fromY][fromX] = EmptySquare;
9109         board[toY][toX] = BlackKing;
9110         board[fromY][7] = EmptySquare;
9111         board[toY][4] = BlackRook;
9112     } else if (fromY == 7 && fromX == 3
9113                && board[fromY][fromX] == BlackKing
9114                && toY == 7 && toX == 1) {
9115         board[fromY][fromX] = EmptySquare;
9116         board[toY][toX] = BlackKing;
9117         board[fromY][0] = EmptySquare;
9118         board[toY][2] = BlackRook;
9119     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9120                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9121                && toY < promoRank
9122                ) {
9123         /* black pawn promotion */
9124         board[toY][toX] = CharToPiece(ToLower(promoChar));
9125         if (board[toY][toX] == EmptySquare) {
9126             board[toY][toX] = BlackQueen;
9127         }
9128         if(gameInfo.variant==VariantBughouse ||
9129            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9130             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9131         board[fromY][fromX] = EmptySquare;
9132     } else if ((fromY == 3)
9133                && (toX != fromX)
9134                && gameInfo.variant != VariantXiangqi
9135                && gameInfo.variant != VariantBerolina
9136                && (board[fromY][fromX] == BlackPawn)
9137                && (board[toY][toX] == EmptySquare)) {
9138         board[fromY][fromX] = EmptySquare;
9139         board[toY][toX] = BlackPawn;
9140         captured = board[toY + 1][toX];
9141         board[toY + 1][toX] = EmptySquare;
9142     } else if ((fromY == 3)
9143                && (toX == fromX)
9144                && gameInfo.variant == VariantBerolina
9145                && (board[fromY][fromX] == BlackPawn)
9146                && (board[toY][toX] == EmptySquare)) {
9147         board[fromY][fromX] = EmptySquare;
9148         board[toY][toX] = BlackPawn;
9149         if(oldEP & EP_BEROLIN_A) {
9150                 captured = board[fromY][fromX-1];
9151                 board[fromY][fromX-1] = EmptySquare;
9152         }else{  captured = board[fromY][fromX+1];
9153                 board[fromY][fromX+1] = EmptySquare;
9154         }
9155     } else {
9156         board[toY][toX] = board[fromY][fromX];
9157         board[fromY][fromX] = EmptySquare;
9158     }
9159   }
9160
9161     if (gameInfo.holdingsWidth != 0) {
9162
9163       /* !!A lot more code needs to be written to support holdings  */
9164       /* [HGM] OK, so I have written it. Holdings are stored in the */
9165       /* penultimate board files, so they are automaticlly stored   */
9166       /* in the game history.                                       */
9167       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9168                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9169         /* Delete from holdings, by decreasing count */
9170         /* and erasing image if necessary            */
9171         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9172         if(p < (int) BlackPawn) { /* white drop */
9173              p -= (int)WhitePawn;
9174                  p = PieceToNumber((ChessSquare)p);
9175              if(p >= gameInfo.holdingsSize) p = 0;
9176              if(--board[p][BOARD_WIDTH-2] <= 0)
9177                   board[p][BOARD_WIDTH-1] = EmptySquare;
9178              if((int)board[p][BOARD_WIDTH-2] < 0)
9179                         board[p][BOARD_WIDTH-2] = 0;
9180         } else {                  /* black drop */
9181              p -= (int)BlackPawn;
9182                  p = PieceToNumber((ChessSquare)p);
9183              if(p >= gameInfo.holdingsSize) p = 0;
9184              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9185                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9186              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9187                         board[BOARD_HEIGHT-1-p][1] = 0;
9188         }
9189       }
9190       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9191           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9192         /* [HGM] holdings: Add to holdings, if holdings exist */
9193         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9194                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9195                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9196         }
9197         p = (int) captured;
9198         if (p >= (int) BlackPawn) {
9199           p -= (int)BlackPawn;
9200           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9201                   /* in Shogi restore piece to its original  first */
9202                   captured = (ChessSquare) (DEMOTED captured);
9203                   p = DEMOTED p;
9204           }
9205           p = PieceToNumber((ChessSquare)p);
9206           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9207           board[p][BOARD_WIDTH-2]++;
9208           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9209         } else {
9210           p -= (int)WhitePawn;
9211           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9212                   captured = (ChessSquare) (DEMOTED captured);
9213                   p = DEMOTED p;
9214           }
9215           p = PieceToNumber((ChessSquare)p);
9216           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9217           board[BOARD_HEIGHT-1-p][1]++;
9218           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9219         }
9220       }
9221     } else if (gameInfo.variant == VariantAtomic) {
9222       if (captured != EmptySquare) {
9223         int y, x;
9224         for (y = toY-1; y <= toY+1; y++) {
9225           for (x = toX-1; x <= toX+1; x++) {
9226             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9227                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9228               board[y][x] = EmptySquare;
9229             }
9230           }
9231         }
9232         board[toY][toX] = EmptySquare;
9233       }
9234     }
9235     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9236         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9237     } else
9238     if(promoChar == '+') {
9239         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9240         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9241     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9242         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9243     }
9244     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9245                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9246         // [HGM] superchess: take promotion piece out of holdings
9247         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9248         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9249             if(!--board[k][BOARD_WIDTH-2])
9250                 board[k][BOARD_WIDTH-1] = EmptySquare;
9251         } else {
9252             if(!--board[BOARD_HEIGHT-1-k][1])
9253                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9254         }
9255     }
9256
9257 }
9258
9259 /* Updates forwardMostMove */
9260 void
9261 MakeMove(fromX, fromY, toX, toY, promoChar)
9262      int fromX, fromY, toX, toY;
9263      int promoChar;
9264 {
9265 //    forwardMostMove++; // [HGM] bare: moved downstream
9266
9267     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9268         int timeLeft; static int lastLoadFlag=0; int king, piece;
9269         piece = boards[forwardMostMove][fromY][fromX];
9270         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9271         if(gameInfo.variant == VariantKnightmate)
9272             king += (int) WhiteUnicorn - (int) WhiteKing;
9273         if(forwardMostMove == 0) {
9274             if(blackPlaysFirst)
9275                 fprintf(serverMoves, "%s;", second.tidy);
9276             fprintf(serverMoves, "%s;", first.tidy);
9277             if(!blackPlaysFirst)
9278                 fprintf(serverMoves, "%s;", second.tidy);
9279         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9280         lastLoadFlag = loadFlag;
9281         // print base move
9282         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9283         // print castling suffix
9284         if( toY == fromY && piece == king ) {
9285             if(toX-fromX > 1)
9286                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9287             if(fromX-toX >1)
9288                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9289         }
9290         // e.p. suffix
9291         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9292              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9293              boards[forwardMostMove][toY][toX] == EmptySquare
9294              && fromX != toX && fromY != toY)
9295                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9296         // promotion suffix
9297         if(promoChar != NULLCHAR)
9298                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9299         if(!loadFlag) {
9300             fprintf(serverMoves, "/%d/%d",
9301                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9302             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9303             else                      timeLeft = blackTimeRemaining/1000;
9304             fprintf(serverMoves, "/%d", timeLeft);
9305         }
9306         fflush(serverMoves);
9307     }
9308
9309     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9310       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9311                         0, 1);
9312       return;
9313     }
9314     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9315     if (commentList[forwardMostMove+1] != NULL) {
9316         free(commentList[forwardMostMove+1]);
9317         commentList[forwardMostMove+1] = NULL;
9318     }
9319     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9320     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9321     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9322     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9323     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9324     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9325     gameInfo.result = GameUnfinished;
9326     if (gameInfo.resultDetails != NULL) {
9327         free(gameInfo.resultDetails);
9328         gameInfo.resultDetails = NULL;
9329     }
9330     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9331                               moveList[forwardMostMove - 1]);
9332     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9333                              PosFlags(forwardMostMove - 1),
9334                              fromY, fromX, toY, toX, promoChar,
9335                              parseList[forwardMostMove - 1]);
9336     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9337       case MT_NONE:
9338       case MT_STALEMATE:
9339       default:
9340         break;
9341       case MT_CHECK:
9342         if(gameInfo.variant != VariantShogi)
9343             strcat(parseList[forwardMostMove - 1], "+");
9344         break;
9345       case MT_CHECKMATE:
9346       case MT_STAINMATE:
9347         strcat(parseList[forwardMostMove - 1], "#");
9348         break;
9349     }
9350     if (appData.debugMode) {
9351         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9352     }
9353
9354 }
9355
9356 /* Updates currentMove if not pausing */
9357 void
9358 ShowMove(fromX, fromY, toX, toY)
9359 {
9360     int instant = (gameMode == PlayFromGameFile) ?
9361         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9362     if(appData.noGUI) return;
9363     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9364         if (!instant) {
9365             if (forwardMostMove == currentMove + 1) {
9366                 AnimateMove(boards[forwardMostMove - 1],
9367                             fromX, fromY, toX, toY);
9368             }
9369             if (appData.highlightLastMove) {
9370                 SetHighlights(fromX, fromY, toX, toY);
9371             }
9372         }
9373         currentMove = forwardMostMove;
9374     }
9375
9376     if (instant) return;
9377
9378     DisplayMove(currentMove - 1);
9379     DrawPosition(FALSE, boards[currentMove]);
9380     DisplayBothClocks();
9381     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9382     DisplayBook(currentMove);
9383 }
9384
9385 void SendEgtPath(ChessProgramState *cps)
9386 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9387         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9388
9389         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9390
9391         while(*p) {
9392             char c, *q = name+1, *r, *s;
9393
9394             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9395             while(*p && *p != ',') *q++ = *p++;
9396             *q++ = ':'; *q = 0;
9397             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9398                 strcmp(name, ",nalimov:") == 0 ) {
9399                 // take nalimov path from the menu-changeable option first, if it is defined
9400               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9401                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9402             } else
9403             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9404                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9405                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9406                 s = r = StrStr(s, ":") + 1; // beginning of path info
9407                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9408                 c = *r; *r = 0;             // temporarily null-terminate path info
9409                     *--q = 0;               // strip of trailig ':' from name
9410                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9411                 *r = c;
9412                 SendToProgram(buf,cps);     // send egtbpath command for this format
9413             }
9414             if(*p == ',') p++; // read away comma to position for next format name
9415         }
9416 }
9417
9418 void
9419 InitChessProgram(cps, setup)
9420      ChessProgramState *cps;
9421      int setup; /* [HGM] needed to setup FRC opening position */
9422 {
9423     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9424     if (appData.noChessProgram) return;
9425     hintRequested = FALSE;
9426     bookRequested = FALSE;
9427
9428     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9429     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9430     if(cps->memSize) { /* [HGM] memory */
9431       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9432         SendToProgram(buf, cps);
9433     }
9434     SendEgtPath(cps); /* [HGM] EGT */
9435     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9436       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9437         SendToProgram(buf, cps);
9438     }
9439
9440     SendToProgram(cps->initString, cps);
9441     if (gameInfo.variant != VariantNormal &&
9442         gameInfo.variant != VariantLoadable
9443         /* [HGM] also send variant if board size non-standard */
9444         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9445                                             ) {
9446       char *v = VariantName(gameInfo.variant);
9447       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9448         /* [HGM] in protocol 1 we have to assume all variants valid */
9449         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9450         DisplayFatalError(buf, 0, 1);
9451         return;
9452       }
9453
9454       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9455       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9456       if( gameInfo.variant == VariantXiangqi )
9457            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9458       if( gameInfo.variant == VariantShogi )
9459            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9460       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9461            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9462       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9463           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9464            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9465       if( gameInfo.variant == VariantCourier )
9466            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9467       if( gameInfo.variant == VariantSuper )
9468            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9469       if( gameInfo.variant == VariantGreat )
9470            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9471       if( gameInfo.variant == VariantSChess )
9472            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9473
9474       if(overruled) {
9475         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9476                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9477            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9478            if(StrStr(cps->variants, b) == NULL) {
9479                // specific sized variant not known, check if general sizing allowed
9480                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9481                    if(StrStr(cps->variants, "boardsize") == NULL) {
9482                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9483                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9484                        DisplayFatalError(buf, 0, 1);
9485                        return;
9486                    }
9487                    /* [HGM] here we really should compare with the maximum supported board size */
9488                }
9489            }
9490       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9491       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9492       SendToProgram(buf, cps);
9493     }
9494     currentlyInitializedVariant = gameInfo.variant;
9495
9496     /* [HGM] send opening position in FRC to first engine */
9497     if(setup) {
9498           SendToProgram("force\n", cps);
9499           SendBoard(cps, 0);
9500           /* engine is now in force mode! Set flag to wake it up after first move. */
9501           setboardSpoiledMachineBlack = 1;
9502     }
9503
9504     if (cps->sendICS) {
9505       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9506       SendToProgram(buf, cps);
9507     }
9508     cps->maybeThinking = FALSE;
9509     cps->offeredDraw = 0;
9510     if (!appData.icsActive) {
9511         SendTimeControl(cps, movesPerSession, timeControl,
9512                         timeIncrement, appData.searchDepth,
9513                         searchTime);
9514     }
9515     if (appData.showThinking
9516         // [HGM] thinking: four options require thinking output to be sent
9517         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9518                                 ) {
9519         SendToProgram("post\n", cps);
9520     }
9521     SendToProgram("hard\n", cps);
9522     if (!appData.ponderNextMove) {
9523         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9524            it without being sure what state we are in first.  "hard"
9525            is not a toggle, so that one is OK.
9526          */
9527         SendToProgram("easy\n", cps);
9528     }
9529     if (cps->usePing) {
9530       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9531       SendToProgram(buf, cps);
9532     }
9533     cps->initDone = TRUE;
9534     ClearEngineOutputPane(cps == &second);
9535 }
9536
9537
9538 void
9539 StartChessProgram(cps)
9540      ChessProgramState *cps;
9541 {
9542     char buf[MSG_SIZ];
9543     int err;
9544
9545     if (appData.noChessProgram) return;
9546     cps->initDone = FALSE;
9547
9548     if (strcmp(cps->host, "localhost") == 0) {
9549         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9550     } else if (*appData.remoteShell == NULLCHAR) {
9551         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9552     } else {
9553         if (*appData.remoteUser == NULLCHAR) {
9554           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9555                     cps->program);
9556         } else {
9557           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9558                     cps->host, appData.remoteUser, cps->program);
9559         }
9560         err = StartChildProcess(buf, "", &cps->pr);
9561     }
9562
9563     if (err != 0) {
9564       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9565         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9566         if(cps != &first) return;
9567         appData.noChessProgram = TRUE;
9568         ThawUI();
9569         SetNCPMode();
9570 //      DisplayFatalError(buf, err, 1);
9571 //      cps->pr = NoProc;
9572 //      cps->isr = NULL;
9573         return;
9574     }
9575
9576     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9577     if (cps->protocolVersion > 1) {
9578       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9579       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9580       cps->comboCnt = 0;  //                and values of combo boxes
9581       SendToProgram(buf, cps);
9582     } else {
9583       SendToProgram("xboard\n", cps);
9584     }
9585 }
9586
9587 void
9588 TwoMachinesEventIfReady P((void))
9589 {
9590   static int curMess = 0;
9591   if (first.lastPing != first.lastPong) {
9592     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9593     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9594     return;
9595   }
9596   if (second.lastPing != second.lastPong) {
9597     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9598     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9599     return;
9600   }
9601   DisplayMessage("", ""); curMess = 0;
9602   ThawUI();
9603   TwoMachinesEvent();
9604 }
9605
9606 char *
9607 MakeName(char *template)
9608 {
9609     time_t clock;
9610     struct tm *tm;
9611     static char buf[MSG_SIZ];
9612     char *p = buf;
9613     int i;
9614
9615     clock = time((time_t *)NULL);
9616     tm = localtime(&clock);
9617
9618     while(*p++ = *template++) if(p[-1] == '%') {
9619         switch(*template++) {
9620           case 0:   *p = 0; return buf;
9621           case 'Y': i = tm->tm_year+1900; break;
9622           case 'y': i = tm->tm_year-100; break;
9623           case 'M': i = tm->tm_mon+1; break;
9624           case 'd': i = tm->tm_mday; break;
9625           case 'h': i = tm->tm_hour; break;
9626           case 'm': i = tm->tm_min; break;
9627           case 's': i = tm->tm_sec; break;
9628           default:  i = 0;
9629         }
9630         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9631     }
9632     return buf;
9633 }
9634
9635 int
9636 CountPlayers(char *p)
9637 {
9638     int n = 0;
9639     while(p = strchr(p, '\n')) p++, n++; // count participants
9640     return n;
9641 }
9642
9643 FILE *
9644 WriteTourneyFile(char *results)
9645 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9646     FILE *f = fopen(appData.tourneyFile, "w");
9647     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9648         // create a file with tournament description
9649         fprintf(f, "-participants {%s}\n", appData.participants);
9650         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9651         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9652         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9653         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9654         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9655         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9656         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9657         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9658         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9659         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9660         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9661         if(searchTime > 0)
9662                 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9663         else {
9664                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9665                 fprintf(f, "-tc %s\n", appData.timeControl);
9666                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9667         }
9668         fprintf(f, "-results \"%s\"\n", results);
9669     }
9670     return f;
9671 }
9672
9673 int
9674 CreateTourney(char *name)
9675 {
9676         FILE *f;
9677         if(name[0] == NULLCHAR) {
9678             if(appData.participants[0])
9679                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9680             return 0;
9681         }
9682         f = fopen(name, "r");
9683         if(f) { // file exists
9684             ASSIGN(appData.tourneyFile, name);
9685             ParseArgsFromFile(f); // parse it
9686         } else {
9687             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9688             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9689                 DisplayError(_("Not enough participants"), 0);
9690                 return 0;
9691             }
9692             ASSIGN(appData.tourneyFile, name);
9693             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9694             if((f = WriteTourneyFile("")) == NULL) return 0;
9695         }
9696         fclose(f);
9697         appData.noChessProgram = FALSE;
9698         appData.clockMode = TRUE;
9699         SetGNUMode();
9700         return 1;
9701 }
9702
9703 #define MAXENGINES 1000
9704 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9705
9706 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9707 {
9708     char buf[MSG_SIZ], *p, *q;
9709     int i=1;
9710     while(*names) {
9711         p = names; q = buf;
9712         while(*p && *p != '\n') *q++ = *p++;
9713         *q = 0;
9714         if(engineList[i]) free(engineList[i]);
9715         engineList[i] = strdup(buf);
9716         if(*p == '\n') p++;
9717         TidyProgramName(engineList[i], "localhost", buf);
9718         if(engineMnemonic[i]) free(engineMnemonic[i]);
9719         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9720             strcat(buf, " (");
9721             sscanf(q + 8, "%s", buf + strlen(buf));
9722             strcat(buf, ")");
9723         }
9724         engineMnemonic[i] = strdup(buf);
9725         names = p; i++;
9726       if(i > MAXENGINES - 2) break;
9727     }
9728     engineList[i] = NULL;
9729 }
9730
9731 // following implemented as macro to avoid type limitations
9732 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9733
9734 void SwapEngines(int n)
9735 {   // swap settings for first engine and other engine (so far only some selected options)
9736     int h;
9737     char *p;
9738     if(n == 0) return;
9739     SWAP(directory, p)
9740     SWAP(chessProgram, p)
9741     SWAP(isUCI, h)
9742     SWAP(hasOwnBookUCI, h)
9743     SWAP(protocolVersion, h)
9744     SWAP(reuse, h)
9745     SWAP(scoreIsAbsolute, h)
9746     SWAP(timeOdds, h)
9747     SWAP(logo, p)
9748     SWAP(pgnName, p)
9749     SWAP(pvSAN, h)
9750 }
9751
9752 void
9753 SetPlayer(int player)
9754 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9755     int i;
9756     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9757     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9758     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9759     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9760     if(mnemonic[i]) {
9761         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9762         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9763         ParseArgsFromString(buf);
9764     }
9765     free(engineName);
9766 }
9767
9768 int
9769 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9770 {   // determine players from game number
9771     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9772
9773     if(appData.tourneyType == 0) {
9774         roundsPerCycle = (nPlayers - 1) | 1;
9775         pairingsPerRound = nPlayers / 2;
9776     } else if(appData.tourneyType > 0) {
9777         roundsPerCycle = nPlayers - appData.tourneyType;
9778         pairingsPerRound = appData.tourneyType;
9779     }
9780     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9781     gamesPerCycle = gamesPerRound * roundsPerCycle;
9782     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9783     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9784     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9785     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9786     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9787     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9788
9789     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9790     if(appData.roundSync) *syncInterval = gamesPerRound;
9791
9792     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9793
9794     if(appData.tourneyType == 0) {
9795         if(curPairing == (nPlayers-1)/2 ) {
9796             *whitePlayer = curRound;
9797             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9798         } else {
9799             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9800             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9801             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9802             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9803         }
9804     } else if(appData.tourneyType > 0) {
9805         *whitePlayer = curPairing;
9806         *blackPlayer = curRound + appData.tourneyType;
9807     }
9808
9809     // take care of white/black alternation per round. 
9810     // For cycles and games this is already taken care of by default, derived from matchGame!
9811     return curRound & 1;
9812 }
9813
9814 int
9815 NextTourneyGame(int nr, int *swapColors)
9816 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9817     char *p, *q;
9818     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9819     FILE *tf;
9820     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9821     tf = fopen(appData.tourneyFile, "r");
9822     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9823     ParseArgsFromFile(tf); fclose(tf);
9824     InitTimeControls(); // TC might be altered from tourney file
9825
9826     nPlayers = CountPlayers(appData.participants); // count participants
9827     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9828     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9829
9830     if(syncInterval) {
9831         p = q = appData.results;
9832         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9833         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9834             DisplayMessage(_("Waiting for other game(s)"),"");
9835             waitingForGame = TRUE;
9836             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9837             return 0;
9838         }
9839         waitingForGame = FALSE;
9840     }
9841
9842     if(appData.tourneyType < 0) {
9843         if(nr>=0 && !pairingReceived) {
9844             char buf[1<<16];
9845             if(pairing.pr == NoProc) {
9846                 if(!appData.pairingEngine[0]) {
9847                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9848                     return 0;
9849                 }
9850                 StartChessProgram(&pairing); // starts the pairing engine
9851             }
9852             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9853             SendToProgram(buf, &pairing);
9854             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9855             SendToProgram(buf, &pairing);
9856             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9857         }
9858         pairingReceived = 0;                              // ... so we continue here 
9859         *swapColors = 0;
9860         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9861         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9862         matchGame = 1; roundNr = nr / syncInterval + 1;
9863     }
9864
9865     if(first.pr != NoProc) return 1; // engines already loaded
9866
9867     // redefine engines, engine dir, etc.
9868     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9869     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9870     SwapEngines(1);
9871     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9872     SwapEngines(1);         // and make that valid for second engine by swapping
9873     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9874     InitEngine(&second, 1);
9875     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9876     UpdateLogos(FALSE);     // leave display to ModeHiglight()
9877     return 1;
9878 }
9879
9880 void
9881 NextMatchGame()
9882 {   // performs game initialization that does not invoke engines, and then tries to start the game
9883     int firstWhite, swapColors = 0;
9884     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9885     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9886     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9887     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9888     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9889     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9890     Reset(FALSE, first.pr != NoProc);
9891     appData.noChessProgram = FALSE;
9892     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9893     TwoMachinesEvent();
9894 }
9895
9896 void UserAdjudicationEvent( int result )
9897 {
9898     ChessMove gameResult = GameIsDrawn;
9899
9900     if( result > 0 ) {
9901         gameResult = WhiteWins;
9902     }
9903     else if( result < 0 ) {
9904         gameResult = BlackWins;
9905     }
9906
9907     if( gameMode == TwoMachinesPlay ) {
9908         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9909     }
9910 }
9911
9912
9913 // [HGM] save: calculate checksum of game to make games easily identifiable
9914 int StringCheckSum(char *s)
9915 {
9916         int i = 0;
9917         if(s==NULL) return 0;
9918         while(*s) i = i*259 + *s++;
9919         return i;
9920 }
9921
9922 int GameCheckSum()
9923 {
9924         int i, sum=0;
9925         for(i=backwardMostMove; i<forwardMostMove; i++) {
9926                 sum += pvInfoList[i].depth;
9927                 sum += StringCheckSum(parseList[i]);
9928                 sum += StringCheckSum(commentList[i]);
9929                 sum *= 261;
9930         }
9931         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9932         return sum + StringCheckSum(commentList[i]);
9933 } // end of save patch
9934
9935 void
9936 GameEnds(result, resultDetails, whosays)
9937      ChessMove result;
9938      char *resultDetails;
9939      int whosays;
9940 {
9941     GameMode nextGameMode;
9942     int isIcsGame;
9943     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9944
9945     if(endingGame) return; /* [HGM] crash: forbid recursion */
9946     endingGame = 1;
9947     if(twoBoards) { // [HGM] dual: switch back to one board
9948         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9949         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9950     }
9951     if (appData.debugMode) {
9952       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9953               result, resultDetails ? resultDetails : "(null)", whosays);
9954     }
9955
9956     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9957
9958     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9959         /* If we are playing on ICS, the server decides when the
9960            game is over, but the engine can offer to draw, claim
9961            a draw, or resign.
9962          */
9963 #if ZIPPY
9964         if (appData.zippyPlay && first.initDone) {
9965             if (result == GameIsDrawn) {
9966                 /* In case draw still needs to be claimed */
9967                 SendToICS(ics_prefix);
9968                 SendToICS("draw\n");
9969             } else if (StrCaseStr(resultDetails, "resign")) {
9970                 SendToICS(ics_prefix);
9971                 SendToICS("resign\n");
9972             }
9973         }
9974 #endif
9975         endingGame = 0; /* [HGM] crash */
9976         return;
9977     }
9978
9979     /* If we're loading the game from a file, stop */
9980     if (whosays == GE_FILE) {
9981       (void) StopLoadGameTimer();
9982       gameFileFP = NULL;
9983     }
9984
9985     /* Cancel draw offers */
9986     first.offeredDraw = second.offeredDraw = 0;
9987
9988     /* If this is an ICS game, only ICS can really say it's done;
9989        if not, anyone can. */
9990     isIcsGame = (gameMode == IcsPlayingWhite ||
9991                  gameMode == IcsPlayingBlack ||
9992                  gameMode == IcsObserving    ||
9993                  gameMode == IcsExamining);
9994
9995     if (!isIcsGame || whosays == GE_ICS) {
9996         /* OK -- not an ICS game, or ICS said it was done */
9997         StopClocks();
9998         if (!isIcsGame && !appData.noChessProgram)
9999           SetUserThinkingEnables();
10000
10001         /* [HGM] if a machine claims the game end we verify this claim */
10002         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10003             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10004                 char claimer;
10005                 ChessMove trueResult = (ChessMove) -1;
10006
10007                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10008                                             first.twoMachinesColor[0] :
10009                                             second.twoMachinesColor[0] ;
10010
10011                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10012                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10013                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10014                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10015                 } else
10016                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10017                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10018                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10019                 } else
10020                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10021                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10022                 }
10023
10024                 // now verify win claims, but not in drop games, as we don't understand those yet
10025                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10026                                                  || gameInfo.variant == VariantGreat) &&
10027                     (result == WhiteWins && claimer == 'w' ||
10028                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10029                       if (appData.debugMode) {
10030                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10031                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10032                       }
10033                       if(result != trueResult) {
10034                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10035                               result = claimer == 'w' ? BlackWins : WhiteWins;
10036                               resultDetails = buf;
10037                       }
10038                 } else
10039                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10040                     && (forwardMostMove <= backwardMostMove ||
10041                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10042                         (claimer=='b')==(forwardMostMove&1))
10043                                                                                   ) {
10044                       /* [HGM] verify: draws that were not flagged are false claims */
10045                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10046                       result = claimer == 'w' ? BlackWins : WhiteWins;
10047                       resultDetails = buf;
10048                 }
10049                 /* (Claiming a loss is accepted no questions asked!) */
10050             }
10051             /* [HGM] bare: don't allow bare King to win */
10052             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
10053                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10054                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10055                && result != GameIsDrawn)
10056             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10057                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10058                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10059                         if(p >= 0 && p <= (int)WhiteKing) k++;
10060                 }
10061                 if (appData.debugMode) {
10062                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10063                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10064                 }
10065                 if(k <= 1) {
10066                         result = GameIsDrawn;
10067                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10068                         resultDetails = buf;
10069                 }
10070             }
10071         }
10072
10073
10074         if(serverMoves != NULL && !loadFlag) { char c = '=';
10075             if(result==WhiteWins) c = '+';
10076             if(result==BlackWins) c = '-';
10077             if(resultDetails != NULL)
10078                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10079         }
10080         if (resultDetails != NULL) {
10081             gameInfo.result = result;
10082             gameInfo.resultDetails = StrSave(resultDetails);
10083
10084             /* display last move only if game was not loaded from file */
10085             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10086                 DisplayMove(currentMove - 1);
10087
10088             if (forwardMostMove != 0) {
10089                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10090                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10091                                                                 ) {
10092                     if (*appData.saveGameFile != NULLCHAR) {
10093                         SaveGameToFile(appData.saveGameFile, TRUE);
10094                     } else if (appData.autoSaveGames) {
10095                         AutoSaveGame();
10096                     }
10097                     if (*appData.savePositionFile != NULLCHAR) {
10098                         SavePositionToFile(appData.savePositionFile);
10099                     }
10100                 }
10101             }
10102
10103             /* Tell program how game ended in case it is learning */
10104             /* [HGM] Moved this to after saving the PGN, just in case */
10105             /* engine died and we got here through time loss. In that */
10106             /* case we will get a fatal error writing the pipe, which */
10107             /* would otherwise lose us the PGN.                       */
10108             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10109             /* output during GameEnds should never be fatal anymore   */
10110             if (gameMode == MachinePlaysWhite ||
10111                 gameMode == MachinePlaysBlack ||
10112                 gameMode == TwoMachinesPlay ||
10113                 gameMode == IcsPlayingWhite ||
10114                 gameMode == IcsPlayingBlack ||
10115                 gameMode == BeginningOfGame) {
10116                 char buf[MSG_SIZ];
10117                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10118                         resultDetails);
10119                 if (first.pr != NoProc) {
10120                     SendToProgram(buf, &first);
10121                 }
10122                 if (second.pr != NoProc &&
10123                     gameMode == TwoMachinesPlay) {
10124                     SendToProgram(buf, &second);
10125                 }
10126             }
10127         }
10128
10129         if (appData.icsActive) {
10130             if (appData.quietPlay &&
10131                 (gameMode == IcsPlayingWhite ||
10132                  gameMode == IcsPlayingBlack)) {
10133                 SendToICS(ics_prefix);
10134                 SendToICS("set shout 1\n");
10135             }
10136             nextGameMode = IcsIdle;
10137             ics_user_moved = FALSE;
10138             /* clean up premove.  It's ugly when the game has ended and the
10139              * premove highlights are still on the board.
10140              */
10141             if (gotPremove) {
10142               gotPremove = FALSE;
10143               ClearPremoveHighlights();
10144               DrawPosition(FALSE, boards[currentMove]);
10145             }
10146             if (whosays == GE_ICS) {
10147                 switch (result) {
10148                 case WhiteWins:
10149                     if (gameMode == IcsPlayingWhite)
10150                         PlayIcsWinSound();
10151                     else if(gameMode == IcsPlayingBlack)
10152                         PlayIcsLossSound();
10153                     break;
10154                 case BlackWins:
10155                     if (gameMode == IcsPlayingBlack)
10156                         PlayIcsWinSound();
10157                     else if(gameMode == IcsPlayingWhite)
10158                         PlayIcsLossSound();
10159                     break;
10160                 case GameIsDrawn:
10161                     PlayIcsDrawSound();
10162                     break;
10163                 default:
10164                     PlayIcsUnfinishedSound();
10165                 }
10166             }
10167         } else if (gameMode == EditGame ||
10168                    gameMode == PlayFromGameFile ||
10169                    gameMode == AnalyzeMode ||
10170                    gameMode == AnalyzeFile) {
10171             nextGameMode = gameMode;
10172         } else {
10173             nextGameMode = EndOfGame;
10174         }
10175         pausing = FALSE;
10176         ModeHighlight();
10177     } else {
10178         nextGameMode = gameMode;
10179     }
10180
10181     if (appData.noChessProgram) {
10182         gameMode = nextGameMode;
10183         ModeHighlight();
10184         endingGame = 0; /* [HGM] crash */
10185         return;
10186     }
10187
10188     if (first.reuse) {
10189         /* Put first chess program into idle state */
10190         if (first.pr != NoProc &&
10191             (gameMode == MachinePlaysWhite ||
10192              gameMode == MachinePlaysBlack ||
10193              gameMode == TwoMachinesPlay ||
10194              gameMode == IcsPlayingWhite ||
10195              gameMode == IcsPlayingBlack ||
10196              gameMode == BeginningOfGame)) {
10197             SendToProgram("force\n", &first);
10198             if (first.usePing) {
10199               char buf[MSG_SIZ];
10200               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10201               SendToProgram(buf, &first);
10202             }
10203         }
10204     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10205         /* Kill off first chess program */
10206         if (first.isr != NULL)
10207           RemoveInputSource(first.isr);
10208         first.isr = NULL;
10209
10210         if (first.pr != NoProc) {
10211             ExitAnalyzeMode();
10212             DoSleep( appData.delayBeforeQuit );
10213             SendToProgram("quit\n", &first);
10214             DoSleep( appData.delayAfterQuit );
10215             DestroyChildProcess(first.pr, first.useSigterm);
10216         }
10217         first.pr = NoProc;
10218     }
10219     if (second.reuse) {
10220         /* Put second chess program into idle state */
10221         if (second.pr != NoProc &&
10222             gameMode == TwoMachinesPlay) {
10223             SendToProgram("force\n", &second);
10224             if (second.usePing) {
10225               char buf[MSG_SIZ];
10226               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10227               SendToProgram(buf, &second);
10228             }
10229         }
10230     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10231         /* Kill off second chess program */
10232         if (second.isr != NULL)
10233           RemoveInputSource(second.isr);
10234         second.isr = NULL;
10235
10236         if (second.pr != NoProc) {
10237             DoSleep( appData.delayBeforeQuit );
10238             SendToProgram("quit\n", &second);
10239             DoSleep( appData.delayAfterQuit );
10240             DestroyChildProcess(second.pr, second.useSigterm);
10241         }
10242         second.pr = NoProc;
10243     }
10244
10245     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10246         char resChar = '=';
10247         switch (result) {
10248         case WhiteWins:
10249           resChar = '+';
10250           if (first.twoMachinesColor[0] == 'w') {
10251             first.matchWins++;
10252           } else {
10253             second.matchWins++;
10254           }
10255           break;
10256         case BlackWins:
10257           resChar = '-';
10258           if (first.twoMachinesColor[0] == 'b') {
10259             first.matchWins++;
10260           } else {
10261             second.matchWins++;
10262           }
10263           break;
10264         case GameUnfinished:
10265           resChar = ' ';
10266         default:
10267           break;
10268         }
10269
10270         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10271         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10272             ReserveGame(nextGame, resChar); // sets nextGame
10273             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10274             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10275         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10276
10277         if (nextGame <= appData.matchGames && !abortMatch) {
10278             gameMode = nextGameMode;
10279             matchGame = nextGame; // this will be overruled in tourney mode!
10280             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10281             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10282             endingGame = 0; /* [HGM] crash */
10283             return;
10284         } else {
10285             gameMode = nextGameMode;
10286             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10287                      first.tidy, second.tidy,
10288                      first.matchWins, second.matchWins,
10289                      appData.matchGames - (first.matchWins + second.matchWins));
10290             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10291             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10292             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10293                 first.twoMachinesColor = "black\n";
10294                 second.twoMachinesColor = "white\n";
10295             } else {
10296                 first.twoMachinesColor = "white\n";
10297                 second.twoMachinesColor = "black\n";
10298             }
10299         }
10300     }
10301     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10302         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10303       ExitAnalyzeMode();
10304     gameMode = nextGameMode;
10305     ModeHighlight();
10306     endingGame = 0;  /* [HGM] crash */
10307     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10308         if(matchMode == TRUE) { // match through command line: exit with or without popup
10309             if(ranking) {
10310                 ToNrEvent(forwardMostMove);
10311                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10312                 else ExitEvent(0);
10313             } else DisplayFatalError(buf, 0, 0);
10314         } else { // match through menu; just stop, with or without popup
10315             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10316             ModeHighlight();
10317             if(ranking){
10318                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10319             } else DisplayNote(buf);
10320       }
10321       if(ranking) free(ranking);
10322     }
10323 }
10324
10325 /* Assumes program was just initialized (initString sent).
10326    Leaves program in force mode. */
10327 void
10328 FeedMovesToProgram(cps, upto)
10329      ChessProgramState *cps;
10330      int upto;
10331 {
10332     int i;
10333
10334     if (appData.debugMode)
10335       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10336               startedFromSetupPosition ? "position and " : "",
10337               backwardMostMove, upto, cps->which);
10338     if(currentlyInitializedVariant != gameInfo.variant) {
10339       char buf[MSG_SIZ];
10340         // [HGM] variantswitch: make engine aware of new variant
10341         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10342                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10343         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10344         SendToProgram(buf, cps);
10345         currentlyInitializedVariant = gameInfo.variant;
10346     }
10347     SendToProgram("force\n", cps);
10348     if (startedFromSetupPosition) {
10349         SendBoard(cps, backwardMostMove);
10350     if (appData.debugMode) {
10351         fprintf(debugFP, "feedMoves\n");
10352     }
10353     }
10354     for (i = backwardMostMove; i < upto; i++) {
10355         SendMoveToProgram(i, cps);
10356     }
10357 }
10358
10359
10360 int
10361 ResurrectChessProgram()
10362 {
10363      /* The chess program may have exited.
10364         If so, restart it and feed it all the moves made so far. */
10365     static int doInit = 0;
10366
10367     if (appData.noChessProgram) return 1;
10368
10369     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10370         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10371         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10372         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10373     } else {
10374         if (first.pr != NoProc) return 1;
10375         StartChessProgram(&first);
10376     }
10377     InitChessProgram(&first, FALSE);
10378     FeedMovesToProgram(&first, currentMove);
10379
10380     if (!first.sendTime) {
10381         /* can't tell gnuchess what its clock should read,
10382            so we bow to its notion. */
10383         ResetClocks();
10384         timeRemaining[0][currentMove] = whiteTimeRemaining;
10385         timeRemaining[1][currentMove] = blackTimeRemaining;
10386     }
10387
10388     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10389                 appData.icsEngineAnalyze) && first.analysisSupport) {
10390       SendToProgram("analyze\n", &first);
10391       first.analyzing = TRUE;
10392     }
10393     return 1;
10394 }
10395
10396 /*
10397  * Button procedures
10398  */
10399 void
10400 Reset(redraw, init)
10401      int redraw, init;
10402 {
10403     int i;
10404
10405     if (appData.debugMode) {
10406         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10407                 redraw, init, gameMode);
10408     }
10409     CleanupTail(); // [HGM] vari: delete any stored variations
10410     pausing = pauseExamInvalid = FALSE;
10411     startedFromSetupPosition = blackPlaysFirst = FALSE;
10412     firstMove = TRUE;
10413     whiteFlag = blackFlag = FALSE;
10414     userOfferedDraw = FALSE;
10415     hintRequested = bookRequested = FALSE;
10416     first.maybeThinking = FALSE;
10417     second.maybeThinking = FALSE;
10418     first.bookSuspend = FALSE; // [HGM] book
10419     second.bookSuspend = FALSE;
10420     thinkOutput[0] = NULLCHAR;
10421     lastHint[0] = NULLCHAR;
10422     ClearGameInfo(&gameInfo);
10423     gameInfo.variant = StringToVariant(appData.variant);
10424     ics_user_moved = ics_clock_paused = FALSE;
10425     ics_getting_history = H_FALSE;
10426     ics_gamenum = -1;
10427     white_holding[0] = black_holding[0] = NULLCHAR;
10428     ClearProgramStats();
10429     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10430
10431     ResetFrontEnd();
10432     ClearHighlights();
10433     flipView = appData.flipView;
10434     ClearPremoveHighlights();
10435     gotPremove = FALSE;
10436     alarmSounded = FALSE;
10437
10438     GameEnds(EndOfFile, NULL, GE_PLAYER);
10439     if(appData.serverMovesName != NULL) {
10440         /* [HGM] prepare to make moves file for broadcasting */
10441         clock_t t = clock();
10442         if(serverMoves != NULL) fclose(serverMoves);
10443         serverMoves = fopen(appData.serverMovesName, "r");
10444         if(serverMoves != NULL) {
10445             fclose(serverMoves);
10446             /* delay 15 sec before overwriting, so all clients can see end */
10447             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10448         }
10449         serverMoves = fopen(appData.serverMovesName, "w");
10450     }
10451
10452     ExitAnalyzeMode();
10453     gameMode = BeginningOfGame;
10454     ModeHighlight();
10455     if(appData.icsActive) gameInfo.variant = VariantNormal;
10456     currentMove = forwardMostMove = backwardMostMove = 0;
10457     InitPosition(redraw);
10458     for (i = 0; i < MAX_MOVES; i++) {
10459         if (commentList[i] != NULL) {
10460             free(commentList[i]);
10461             commentList[i] = NULL;
10462         }
10463     }
10464     ResetClocks();
10465     timeRemaining[0][0] = whiteTimeRemaining;
10466     timeRemaining[1][0] = blackTimeRemaining;
10467
10468     if (first.pr == NULL) {
10469         StartChessProgram(&first);
10470     }
10471     if (init) {
10472             InitChessProgram(&first, startedFromSetupPosition);
10473     }
10474     DisplayTitle("");
10475     DisplayMessage("", "");
10476     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10477     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10478 }
10479
10480 void
10481 AutoPlayGameLoop()
10482 {
10483     for (;;) {
10484         if (!AutoPlayOneMove())
10485           return;
10486         if (matchMode || appData.timeDelay == 0)
10487           continue;
10488         if (appData.timeDelay < 0)
10489           return;
10490         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10491         break;
10492     }
10493 }
10494
10495
10496 int
10497 AutoPlayOneMove()
10498 {
10499     int fromX, fromY, toX, toY;
10500
10501     if (appData.debugMode) {
10502       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10503     }
10504
10505     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10506       return FALSE;
10507
10508     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10509       pvInfoList[currentMove].depth = programStats.depth;
10510       pvInfoList[currentMove].score = programStats.score;
10511       pvInfoList[currentMove].time  = 0;
10512       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10513     }
10514
10515     if (currentMove >= forwardMostMove) {
10516       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10517       gameMode = EditGame;
10518       ModeHighlight();
10519
10520       /* [AS] Clear current move marker at the end of a game */
10521       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10522
10523       return FALSE;
10524     }
10525
10526     toX = moveList[currentMove][2] - AAA;
10527     toY = moveList[currentMove][3] - ONE;
10528
10529     if (moveList[currentMove][1] == '@') {
10530         if (appData.highlightLastMove) {
10531             SetHighlights(-1, -1, toX, toY);
10532         }
10533     } else {
10534         fromX = moveList[currentMove][0] - AAA;
10535         fromY = moveList[currentMove][1] - ONE;
10536
10537         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10538
10539         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10540
10541         if (appData.highlightLastMove) {
10542             SetHighlights(fromX, fromY, toX, toY);
10543         }
10544     }
10545     DisplayMove(currentMove);
10546     SendMoveToProgram(currentMove++, &first);
10547     DisplayBothClocks();
10548     DrawPosition(FALSE, boards[currentMove]);
10549     // [HGM] PV info: always display, routine tests if empty
10550     DisplayComment(currentMove - 1, commentList[currentMove]);
10551     return TRUE;
10552 }
10553
10554
10555 int
10556 LoadGameOneMove(readAhead)
10557      ChessMove readAhead;
10558 {
10559     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10560     char promoChar = NULLCHAR;
10561     ChessMove moveType;
10562     char move[MSG_SIZ];
10563     char *p, *q;
10564
10565     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10566         gameMode != AnalyzeMode && gameMode != Training) {
10567         gameFileFP = NULL;
10568         return FALSE;
10569     }
10570
10571     yyboardindex = forwardMostMove;
10572     if (readAhead != EndOfFile) {
10573       moveType = readAhead;
10574     } else {
10575       if (gameFileFP == NULL)
10576           return FALSE;
10577       moveType = (ChessMove) Myylex();
10578     }
10579
10580     done = FALSE;
10581     switch (moveType) {
10582       case Comment:
10583         if (appData.debugMode)
10584           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10585         p = yy_text;
10586
10587         /* append the comment but don't display it */
10588         AppendComment(currentMove, p, FALSE);
10589         return TRUE;
10590
10591       case WhiteCapturesEnPassant:
10592       case BlackCapturesEnPassant:
10593       case WhitePromotion:
10594       case BlackPromotion:
10595       case WhiteNonPromotion:
10596       case BlackNonPromotion:
10597       case NormalMove:
10598       case WhiteKingSideCastle:
10599       case WhiteQueenSideCastle:
10600       case BlackKingSideCastle:
10601       case BlackQueenSideCastle:
10602       case WhiteKingSideCastleWild:
10603       case WhiteQueenSideCastleWild:
10604       case BlackKingSideCastleWild:
10605       case BlackQueenSideCastleWild:
10606       /* PUSH Fabien */
10607       case WhiteHSideCastleFR:
10608       case WhiteASideCastleFR:
10609       case BlackHSideCastleFR:
10610       case BlackASideCastleFR:
10611       /* POP Fabien */
10612         if (appData.debugMode)
10613           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10614         fromX = currentMoveString[0] - AAA;
10615         fromY = currentMoveString[1] - ONE;
10616         toX = currentMoveString[2] - AAA;
10617         toY = currentMoveString[3] - ONE;
10618         promoChar = currentMoveString[4];
10619         break;
10620
10621       case WhiteDrop:
10622       case BlackDrop:
10623         if (appData.debugMode)
10624           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10625         fromX = moveType == WhiteDrop ?
10626           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10627         (int) CharToPiece(ToLower(currentMoveString[0]));
10628         fromY = DROP_RANK;
10629         toX = currentMoveString[2] - AAA;
10630         toY = currentMoveString[3] - ONE;
10631         break;
10632
10633       case WhiteWins:
10634       case BlackWins:
10635       case GameIsDrawn:
10636       case GameUnfinished:
10637         if (appData.debugMode)
10638           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10639         p = strchr(yy_text, '{');
10640         if (p == NULL) p = strchr(yy_text, '(');
10641         if (p == NULL) {
10642             p = yy_text;
10643             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10644         } else {
10645             q = strchr(p, *p == '{' ? '}' : ')');
10646             if (q != NULL) *q = NULLCHAR;
10647             p++;
10648         }
10649         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10650         GameEnds(moveType, p, GE_FILE);
10651         done = TRUE;
10652         if (cmailMsgLoaded) {
10653             ClearHighlights();
10654             flipView = WhiteOnMove(currentMove);
10655             if (moveType == GameUnfinished) flipView = !flipView;
10656             if (appData.debugMode)
10657               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10658         }
10659         break;
10660
10661       case EndOfFile:
10662         if (appData.debugMode)
10663           fprintf(debugFP, "Parser hit end of file\n");
10664         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10665           case MT_NONE:
10666           case MT_CHECK:
10667             break;
10668           case MT_CHECKMATE:
10669           case MT_STAINMATE:
10670             if (WhiteOnMove(currentMove)) {
10671                 GameEnds(BlackWins, "Black mates", GE_FILE);
10672             } else {
10673                 GameEnds(WhiteWins, "White mates", GE_FILE);
10674             }
10675             break;
10676           case MT_STALEMATE:
10677             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10678             break;
10679         }
10680         done = TRUE;
10681         break;
10682
10683       case MoveNumberOne:
10684         if (lastLoadGameStart == GNUChessGame) {
10685             /* GNUChessGames have numbers, but they aren't move numbers */
10686             if (appData.debugMode)
10687               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10688                       yy_text, (int) moveType);
10689             return LoadGameOneMove(EndOfFile); /* tail recursion */
10690         }
10691         /* else fall thru */
10692
10693       case XBoardGame:
10694       case GNUChessGame:
10695       case PGNTag:
10696         /* Reached start of next game in file */
10697         if (appData.debugMode)
10698           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10699         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10700           case MT_NONE:
10701           case MT_CHECK:
10702             break;
10703           case MT_CHECKMATE:
10704           case MT_STAINMATE:
10705             if (WhiteOnMove(currentMove)) {
10706                 GameEnds(BlackWins, "Black mates", GE_FILE);
10707             } else {
10708                 GameEnds(WhiteWins, "White mates", GE_FILE);
10709             }
10710             break;
10711           case MT_STALEMATE:
10712             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10713             break;
10714         }
10715         done = TRUE;
10716         break;
10717
10718       case PositionDiagram:     /* should not happen; ignore */
10719       case ElapsedTime:         /* ignore */
10720       case NAG:                 /* ignore */
10721         if (appData.debugMode)
10722           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10723                   yy_text, (int) moveType);
10724         return LoadGameOneMove(EndOfFile); /* tail recursion */
10725
10726       case IllegalMove:
10727         if (appData.testLegality) {
10728             if (appData.debugMode)
10729               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10730             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10731                     (forwardMostMove / 2) + 1,
10732                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10733             DisplayError(move, 0);
10734             done = TRUE;
10735         } else {
10736             if (appData.debugMode)
10737               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10738                       yy_text, currentMoveString);
10739             fromX = currentMoveString[0] - AAA;
10740             fromY = currentMoveString[1] - ONE;
10741             toX = currentMoveString[2] - AAA;
10742             toY = currentMoveString[3] - ONE;
10743             promoChar = currentMoveString[4];
10744         }
10745         break;
10746
10747       case AmbiguousMove:
10748         if (appData.debugMode)
10749           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10750         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10751                 (forwardMostMove / 2) + 1,
10752                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10753         DisplayError(move, 0);
10754         done = TRUE;
10755         break;
10756
10757       default:
10758       case ImpossibleMove:
10759         if (appData.debugMode)
10760           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10761         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10762                 (forwardMostMove / 2) + 1,
10763                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10764         DisplayError(move, 0);
10765         done = TRUE;
10766         break;
10767     }
10768
10769     if (done) {
10770         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10771             DrawPosition(FALSE, boards[currentMove]);
10772             DisplayBothClocks();
10773             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10774               DisplayComment(currentMove - 1, commentList[currentMove]);
10775         }
10776         (void) StopLoadGameTimer();
10777         gameFileFP = NULL;
10778         cmailOldMove = forwardMostMove;
10779         return FALSE;
10780     } else {
10781         /* currentMoveString is set as a side-effect of yylex */
10782
10783         thinkOutput[0] = NULLCHAR;
10784         MakeMove(fromX, fromY, toX, toY, promoChar);
10785         currentMove = forwardMostMove;
10786         return TRUE;
10787     }
10788 }
10789
10790 /* Load the nth game from the given file */
10791 int
10792 LoadGameFromFile(filename, n, title, useList)
10793      char *filename;
10794      int n;
10795      char *title;
10796      /*Boolean*/ int useList;
10797 {
10798     FILE *f;
10799     char buf[MSG_SIZ];
10800
10801     if (strcmp(filename, "-") == 0) {
10802         f = stdin;
10803         title = "stdin";
10804     } else {
10805         f = fopen(filename, "rb");
10806         if (f == NULL) {
10807           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10808             DisplayError(buf, errno);
10809             return FALSE;
10810         }
10811     }
10812     if (fseek(f, 0, 0) == -1) {
10813         /* f is not seekable; probably a pipe */
10814         useList = FALSE;
10815     }
10816     if (useList && n == 0) {
10817         int error = GameListBuild(f);
10818         if (error) {
10819             DisplayError(_("Cannot build game list"), error);
10820         } else if (!ListEmpty(&gameList) &&
10821                    ((ListGame *) gameList.tailPred)->number > 1) {
10822             GameListPopUp(f, title);
10823             return TRUE;
10824         }
10825         GameListDestroy();
10826         n = 1;
10827     }
10828     if (n == 0) n = 1;
10829     return LoadGame(f, n, title, FALSE);
10830 }
10831
10832
10833 void
10834 MakeRegisteredMove()
10835 {
10836     int fromX, fromY, toX, toY;
10837     char promoChar;
10838     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10839         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10840           case CMAIL_MOVE:
10841           case CMAIL_DRAW:
10842             if (appData.debugMode)
10843               fprintf(debugFP, "Restoring %s for game %d\n",
10844                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10845
10846             thinkOutput[0] = NULLCHAR;
10847             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10848             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10849             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10850             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10851             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10852             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10853             MakeMove(fromX, fromY, toX, toY, promoChar);
10854             ShowMove(fromX, fromY, toX, toY);
10855
10856             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10857               case MT_NONE:
10858               case MT_CHECK:
10859                 break;
10860
10861               case MT_CHECKMATE:
10862               case MT_STAINMATE:
10863                 if (WhiteOnMove(currentMove)) {
10864                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10865                 } else {
10866                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10867                 }
10868                 break;
10869
10870               case MT_STALEMATE:
10871                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10872                 break;
10873             }
10874
10875             break;
10876
10877           case CMAIL_RESIGN:
10878             if (WhiteOnMove(currentMove)) {
10879                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10880             } else {
10881                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10882             }
10883             break;
10884
10885           case CMAIL_ACCEPT:
10886             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10887             break;
10888
10889           default:
10890             break;
10891         }
10892     }
10893
10894     return;
10895 }
10896
10897 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10898 int
10899 CmailLoadGame(f, gameNumber, title, useList)
10900      FILE *f;
10901      int gameNumber;
10902      char *title;
10903      int useList;
10904 {
10905     int retVal;
10906
10907     if (gameNumber > nCmailGames) {
10908         DisplayError(_("No more games in this message"), 0);
10909         return FALSE;
10910     }
10911     if (f == lastLoadGameFP) {
10912         int offset = gameNumber - lastLoadGameNumber;
10913         if (offset == 0) {
10914             cmailMsg[0] = NULLCHAR;
10915             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10916                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10917                 nCmailMovesRegistered--;
10918             }
10919             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10920             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10921                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10922             }
10923         } else {
10924             if (! RegisterMove()) return FALSE;
10925         }
10926     }
10927
10928     retVal = LoadGame(f, gameNumber, title, useList);
10929
10930     /* Make move registered during previous look at this game, if any */
10931     MakeRegisteredMove();
10932
10933     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10934         commentList[currentMove]
10935           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10936         DisplayComment(currentMove - 1, commentList[currentMove]);
10937     }
10938
10939     return retVal;
10940 }
10941
10942 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10943 int
10944 ReloadGame(offset)
10945      int offset;
10946 {
10947     int gameNumber = lastLoadGameNumber + offset;
10948     if (lastLoadGameFP == NULL) {
10949         DisplayError(_("No game has been loaded yet"), 0);
10950         return FALSE;
10951     }
10952     if (gameNumber <= 0) {
10953         DisplayError(_("Can't back up any further"), 0);
10954         return FALSE;
10955     }
10956     if (cmailMsgLoaded) {
10957         return CmailLoadGame(lastLoadGameFP, gameNumber,
10958                              lastLoadGameTitle, lastLoadGameUseList);
10959     } else {
10960         return LoadGame(lastLoadGameFP, gameNumber,
10961                         lastLoadGameTitle, lastLoadGameUseList);
10962     }
10963 }
10964
10965
10966
10967 /* Load the nth game from open file f */
10968 int
10969 LoadGame(f, gameNumber, title, useList)
10970      FILE *f;
10971      int gameNumber;
10972      char *title;
10973      int useList;
10974 {
10975     ChessMove cm;
10976     char buf[MSG_SIZ];
10977     int gn = gameNumber;
10978     ListGame *lg = NULL;
10979     int numPGNTags = 0;
10980     int err;
10981     GameMode oldGameMode;
10982     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10983
10984     if (appData.debugMode)
10985         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10986
10987     if (gameMode == Training )
10988         SetTrainingModeOff();
10989
10990     oldGameMode = gameMode;
10991     if (gameMode != BeginningOfGame) {
10992       Reset(FALSE, TRUE);
10993     }
10994
10995     gameFileFP = f;
10996     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10997         fclose(lastLoadGameFP);
10998     }
10999
11000     if (useList) {
11001         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11002
11003         if (lg) {
11004             fseek(f, lg->offset, 0);
11005             GameListHighlight(gameNumber);
11006             gn = 1;
11007         }
11008         else {
11009             DisplayError(_("Game number out of range"), 0);
11010             return FALSE;
11011         }
11012     } else {
11013         GameListDestroy();
11014         if (fseek(f, 0, 0) == -1) {
11015             if (f == lastLoadGameFP ?
11016                 gameNumber == lastLoadGameNumber + 1 :
11017                 gameNumber == 1) {
11018                 gn = 1;
11019             } else {
11020                 DisplayError(_("Can't seek on game file"), 0);
11021                 return FALSE;
11022             }
11023         }
11024     }
11025     lastLoadGameFP = f;
11026     lastLoadGameNumber = gameNumber;
11027     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11028     lastLoadGameUseList = useList;
11029
11030     yynewfile(f);
11031
11032     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11033       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11034                 lg->gameInfo.black);
11035             DisplayTitle(buf);
11036     } else if (*title != NULLCHAR) {
11037         if (gameNumber > 1) {
11038           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11039             DisplayTitle(buf);
11040         } else {
11041             DisplayTitle(title);
11042         }
11043     }
11044
11045     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11046         gameMode = PlayFromGameFile;
11047         ModeHighlight();
11048     }
11049
11050     currentMove = forwardMostMove = backwardMostMove = 0;
11051     CopyBoard(boards[0], initialPosition);
11052     StopClocks();
11053
11054     /*
11055      * Skip the first gn-1 games in the file.
11056      * Also skip over anything that precedes an identifiable
11057      * start of game marker, to avoid being confused by
11058      * garbage at the start of the file.  Currently
11059      * recognized start of game markers are the move number "1",
11060      * the pattern "gnuchess .* game", the pattern
11061      * "^[#;%] [^ ]* game file", and a PGN tag block.
11062      * A game that starts with one of the latter two patterns
11063      * will also have a move number 1, possibly
11064      * following a position diagram.
11065      * 5-4-02: Let's try being more lenient and allowing a game to
11066      * start with an unnumbered move.  Does that break anything?
11067      */
11068     cm = lastLoadGameStart = EndOfFile;
11069     while (gn > 0) {
11070         yyboardindex = forwardMostMove;
11071         cm = (ChessMove) Myylex();
11072         switch (cm) {
11073           case EndOfFile:
11074             if (cmailMsgLoaded) {
11075                 nCmailGames = CMAIL_MAX_GAMES - gn;
11076             } else {
11077                 Reset(TRUE, TRUE);
11078                 DisplayError(_("Game not found in file"), 0);
11079             }
11080             return FALSE;
11081
11082           case GNUChessGame:
11083           case XBoardGame:
11084             gn--;
11085             lastLoadGameStart = cm;
11086             break;
11087
11088           case MoveNumberOne:
11089             switch (lastLoadGameStart) {
11090               case GNUChessGame:
11091               case XBoardGame:
11092               case PGNTag:
11093                 break;
11094               case MoveNumberOne:
11095               case EndOfFile:
11096                 gn--;           /* count this game */
11097                 lastLoadGameStart = cm;
11098                 break;
11099               default:
11100                 /* impossible */
11101                 break;
11102             }
11103             break;
11104
11105           case PGNTag:
11106             switch (lastLoadGameStart) {
11107               case GNUChessGame:
11108               case PGNTag:
11109               case MoveNumberOne:
11110               case EndOfFile:
11111                 gn--;           /* count this game */
11112                 lastLoadGameStart = cm;
11113                 break;
11114               case XBoardGame:
11115                 lastLoadGameStart = cm; /* game counted already */
11116                 break;
11117               default:
11118                 /* impossible */
11119                 break;
11120             }
11121             if (gn > 0) {
11122                 do {
11123                     yyboardindex = forwardMostMove;
11124                     cm = (ChessMove) Myylex();
11125                 } while (cm == PGNTag || cm == Comment);
11126             }
11127             break;
11128
11129           case WhiteWins:
11130           case BlackWins:
11131           case GameIsDrawn:
11132             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11133                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11134                     != CMAIL_OLD_RESULT) {
11135                     nCmailResults ++ ;
11136                     cmailResult[  CMAIL_MAX_GAMES
11137                                 - gn - 1] = CMAIL_OLD_RESULT;
11138                 }
11139             }
11140             break;
11141
11142           case NormalMove:
11143             /* Only a NormalMove can be at the start of a game
11144              * without a position diagram. */
11145             if (lastLoadGameStart == EndOfFile ) {
11146               gn--;
11147               lastLoadGameStart = MoveNumberOne;
11148             }
11149             break;
11150
11151           default:
11152             break;
11153         }
11154     }
11155
11156     if (appData.debugMode)
11157       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11158
11159     if (cm == XBoardGame) {
11160         /* Skip any header junk before position diagram and/or move 1 */
11161         for (;;) {
11162             yyboardindex = forwardMostMove;
11163             cm = (ChessMove) Myylex();
11164
11165             if (cm == EndOfFile ||
11166                 cm == GNUChessGame || cm == XBoardGame) {
11167                 /* Empty game; pretend end-of-file and handle later */
11168                 cm = EndOfFile;
11169                 break;
11170             }
11171
11172             if (cm == MoveNumberOne || cm == PositionDiagram ||
11173                 cm == PGNTag || cm == Comment)
11174               break;
11175         }
11176     } else if (cm == GNUChessGame) {
11177         if (gameInfo.event != NULL) {
11178             free(gameInfo.event);
11179         }
11180         gameInfo.event = StrSave(yy_text);
11181     }
11182
11183     startedFromSetupPosition = FALSE;
11184     while (cm == PGNTag) {
11185         if (appData.debugMode)
11186           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11187         err = ParsePGNTag(yy_text, &gameInfo);
11188         if (!err) numPGNTags++;
11189
11190         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11191         if(gameInfo.variant != oldVariant) {
11192             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11193             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11194             InitPosition(TRUE);
11195             oldVariant = gameInfo.variant;
11196             if (appData.debugMode)
11197               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11198         }
11199
11200
11201         if (gameInfo.fen != NULL) {
11202           Board initial_position;
11203           startedFromSetupPosition = TRUE;
11204           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11205             Reset(TRUE, TRUE);
11206             DisplayError(_("Bad FEN position in file"), 0);
11207             return FALSE;
11208           }
11209           CopyBoard(boards[0], initial_position);
11210           if (blackPlaysFirst) {
11211             currentMove = forwardMostMove = backwardMostMove = 1;
11212             CopyBoard(boards[1], initial_position);
11213             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11214             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11215             timeRemaining[0][1] = whiteTimeRemaining;
11216             timeRemaining[1][1] = blackTimeRemaining;
11217             if (commentList[0] != NULL) {
11218               commentList[1] = commentList[0];
11219               commentList[0] = NULL;
11220             }
11221           } else {
11222             currentMove = forwardMostMove = backwardMostMove = 0;
11223           }
11224           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11225           {   int i;
11226               initialRulePlies = FENrulePlies;
11227               for( i=0; i< nrCastlingRights; i++ )
11228                   initialRights[i] = initial_position[CASTLING][i];
11229           }
11230           yyboardindex = forwardMostMove;
11231           free(gameInfo.fen);
11232           gameInfo.fen = NULL;
11233         }
11234
11235         yyboardindex = forwardMostMove;
11236         cm = (ChessMove) Myylex();
11237
11238         /* Handle comments interspersed among the tags */
11239         while (cm == Comment) {
11240             char *p;
11241             if (appData.debugMode)
11242               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11243             p = yy_text;
11244             AppendComment(currentMove, p, FALSE);
11245             yyboardindex = forwardMostMove;
11246             cm = (ChessMove) Myylex();
11247         }
11248     }
11249
11250     /* don't rely on existence of Event tag since if game was
11251      * pasted from clipboard the Event tag may not exist
11252      */
11253     if (numPGNTags > 0){
11254         char *tags;
11255         if (gameInfo.variant == VariantNormal) {
11256           VariantClass v = StringToVariant(gameInfo.event);
11257           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11258           if(v < VariantShogi) gameInfo.variant = v;
11259         }
11260         if (!matchMode) {
11261           if( appData.autoDisplayTags ) {
11262             tags = PGNTags(&gameInfo);
11263             TagsPopUp(tags, CmailMsg());
11264             free(tags);
11265           }
11266         }
11267     } else {
11268         /* Make something up, but don't display it now */
11269         SetGameInfo();
11270         TagsPopDown();
11271     }
11272
11273     if (cm == PositionDiagram) {
11274         int i, j;
11275         char *p;
11276         Board initial_position;
11277
11278         if (appData.debugMode)
11279           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11280
11281         if (!startedFromSetupPosition) {
11282             p = yy_text;
11283             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11284               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11285                 switch (*p) {
11286                   case '{':
11287                   case '[':
11288                   case '-':
11289                   case ' ':
11290                   case '\t':
11291                   case '\n':
11292                   case '\r':
11293                     break;
11294                   default:
11295                     initial_position[i][j++] = CharToPiece(*p);
11296                     break;
11297                 }
11298             while (*p == ' ' || *p == '\t' ||
11299                    *p == '\n' || *p == '\r') p++;
11300
11301             if (strncmp(p, "black", strlen("black"))==0)
11302               blackPlaysFirst = TRUE;
11303             else
11304               blackPlaysFirst = FALSE;
11305             startedFromSetupPosition = TRUE;
11306
11307             CopyBoard(boards[0], initial_position);
11308             if (blackPlaysFirst) {
11309                 currentMove = forwardMostMove = backwardMostMove = 1;
11310                 CopyBoard(boards[1], initial_position);
11311                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11312                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11313                 timeRemaining[0][1] = whiteTimeRemaining;
11314                 timeRemaining[1][1] = blackTimeRemaining;
11315                 if (commentList[0] != NULL) {
11316                     commentList[1] = commentList[0];
11317                     commentList[0] = NULL;
11318                 }
11319             } else {
11320                 currentMove = forwardMostMove = backwardMostMove = 0;
11321             }
11322         }
11323         yyboardindex = forwardMostMove;
11324         cm = (ChessMove) Myylex();
11325     }
11326
11327     if (first.pr == NoProc) {
11328         StartChessProgram(&first);
11329     }
11330     InitChessProgram(&first, FALSE);
11331     SendToProgram("force\n", &first);
11332     if (startedFromSetupPosition) {
11333         SendBoard(&first, forwardMostMove);
11334     if (appData.debugMode) {
11335         fprintf(debugFP, "Load Game\n");
11336     }
11337         DisplayBothClocks();
11338     }
11339
11340     /* [HGM] server: flag to write setup moves in broadcast file as one */
11341     loadFlag = appData.suppressLoadMoves;
11342
11343     while (cm == Comment) {
11344         char *p;
11345         if (appData.debugMode)
11346           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11347         p = yy_text;
11348         AppendComment(currentMove, p, FALSE);
11349         yyboardindex = forwardMostMove;
11350         cm = (ChessMove) Myylex();
11351     }
11352
11353     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11354         cm == WhiteWins || cm == BlackWins ||
11355         cm == GameIsDrawn || cm == GameUnfinished) {
11356         DisplayMessage("", _("No moves in game"));
11357         if (cmailMsgLoaded) {
11358             if (appData.debugMode)
11359               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11360             ClearHighlights();
11361             flipView = FALSE;
11362         }
11363         DrawPosition(FALSE, boards[currentMove]);
11364         DisplayBothClocks();
11365         gameMode = EditGame;
11366         ModeHighlight();
11367         gameFileFP = NULL;
11368         cmailOldMove = 0;
11369         return TRUE;
11370     }
11371
11372     // [HGM] PV info: routine tests if comment empty
11373     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11374         DisplayComment(currentMove - 1, commentList[currentMove]);
11375     }
11376     if (!matchMode && appData.timeDelay != 0)
11377       DrawPosition(FALSE, boards[currentMove]);
11378
11379     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11380       programStats.ok_to_send = 1;
11381     }
11382
11383     /* if the first token after the PGN tags is a move
11384      * and not move number 1, retrieve it from the parser
11385      */
11386     if (cm != MoveNumberOne)
11387         LoadGameOneMove(cm);
11388
11389     /* load the remaining moves from the file */
11390     while (LoadGameOneMove(EndOfFile)) {
11391       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11392       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11393     }
11394
11395     /* rewind to the start of the game */
11396     currentMove = backwardMostMove;
11397
11398     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11399
11400     if (oldGameMode == AnalyzeFile ||
11401         oldGameMode == AnalyzeMode) {
11402       AnalyzeFileEvent();
11403     }
11404
11405     if (matchMode || appData.timeDelay == 0) {
11406       ToEndEvent();
11407       gameMode = EditGame;
11408       ModeHighlight();
11409     } else if (appData.timeDelay > 0) {
11410       AutoPlayGameLoop();
11411     }
11412
11413     if (appData.debugMode)
11414         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11415
11416     loadFlag = 0; /* [HGM] true game starts */
11417     return TRUE;
11418 }
11419
11420 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11421 int
11422 ReloadPosition(offset)
11423      int offset;
11424 {
11425     int positionNumber = lastLoadPositionNumber + offset;
11426     if (lastLoadPositionFP == NULL) {
11427         DisplayError(_("No position has been loaded yet"), 0);
11428         return FALSE;
11429     }
11430     if (positionNumber <= 0) {
11431         DisplayError(_("Can't back up any further"), 0);
11432         return FALSE;
11433     }
11434     return LoadPosition(lastLoadPositionFP, positionNumber,
11435                         lastLoadPositionTitle);
11436 }
11437
11438 /* Load the nth position from the given file */
11439 int
11440 LoadPositionFromFile(filename, n, title)
11441      char *filename;
11442      int n;
11443      char *title;
11444 {
11445     FILE *f;
11446     char buf[MSG_SIZ];
11447
11448     if (strcmp(filename, "-") == 0) {
11449         return LoadPosition(stdin, n, "stdin");
11450     } else {
11451         f = fopen(filename, "rb");
11452         if (f == NULL) {
11453             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11454             DisplayError(buf, errno);
11455             return FALSE;
11456         } else {
11457             return LoadPosition(f, n, title);
11458         }
11459     }
11460 }
11461
11462 /* Load the nth position from the given open file, and close it */
11463 int
11464 LoadPosition(f, positionNumber, title)
11465      FILE *f;
11466      int positionNumber;
11467      char *title;
11468 {
11469     char *p, line[MSG_SIZ];
11470     Board initial_position;
11471     int i, j, fenMode, pn;
11472
11473     if (gameMode == Training )
11474         SetTrainingModeOff();
11475
11476     if (gameMode != BeginningOfGame) {
11477         Reset(FALSE, TRUE);
11478     }
11479     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11480         fclose(lastLoadPositionFP);
11481     }
11482     if (positionNumber == 0) positionNumber = 1;
11483     lastLoadPositionFP = f;
11484     lastLoadPositionNumber = positionNumber;
11485     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11486     if (first.pr == NoProc) {
11487       StartChessProgram(&first);
11488       InitChessProgram(&first, FALSE);
11489     }
11490     pn = positionNumber;
11491     if (positionNumber < 0) {
11492         /* Negative position number means to seek to that byte offset */
11493         if (fseek(f, -positionNumber, 0) == -1) {
11494             DisplayError(_("Can't seek on position file"), 0);
11495             return FALSE;
11496         };
11497         pn = 1;
11498     } else {
11499         if (fseek(f, 0, 0) == -1) {
11500             if (f == lastLoadPositionFP ?
11501                 positionNumber == lastLoadPositionNumber + 1 :
11502                 positionNumber == 1) {
11503                 pn = 1;
11504             } else {
11505                 DisplayError(_("Can't seek on position file"), 0);
11506                 return FALSE;
11507             }
11508         }
11509     }
11510     /* See if this file is FEN or old-style xboard */
11511     if (fgets(line, MSG_SIZ, f) == NULL) {
11512         DisplayError(_("Position not found in file"), 0);
11513         return FALSE;
11514     }
11515     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11516     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11517
11518     if (pn >= 2) {
11519         if (fenMode || line[0] == '#') pn--;
11520         while (pn > 0) {
11521             /* skip positions before number pn */
11522             if (fgets(line, MSG_SIZ, f) == NULL) {
11523                 Reset(TRUE, TRUE);
11524                 DisplayError(_("Position not found in file"), 0);
11525                 return FALSE;
11526             }
11527             if (fenMode || line[0] == '#') pn--;
11528         }
11529     }
11530
11531     if (fenMode) {
11532         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11533             DisplayError(_("Bad FEN position in file"), 0);
11534             return FALSE;
11535         }
11536     } else {
11537         (void) fgets(line, MSG_SIZ, f);
11538         (void) fgets(line, MSG_SIZ, f);
11539
11540         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11541             (void) fgets(line, MSG_SIZ, f);
11542             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11543                 if (*p == ' ')
11544                   continue;
11545                 initial_position[i][j++] = CharToPiece(*p);
11546             }
11547         }
11548
11549         blackPlaysFirst = FALSE;
11550         if (!feof(f)) {
11551             (void) fgets(line, MSG_SIZ, f);
11552             if (strncmp(line, "black", strlen("black"))==0)
11553               blackPlaysFirst = TRUE;
11554         }
11555     }
11556     startedFromSetupPosition = TRUE;
11557
11558     SendToProgram("force\n", &first);
11559     CopyBoard(boards[0], initial_position);
11560     if (blackPlaysFirst) {
11561         currentMove = forwardMostMove = backwardMostMove = 1;
11562         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11563         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11564         CopyBoard(boards[1], initial_position);
11565         DisplayMessage("", _("Black to play"));
11566     } else {
11567         currentMove = forwardMostMove = backwardMostMove = 0;
11568         DisplayMessage("", _("White to play"));
11569     }
11570     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11571     SendBoard(&first, forwardMostMove);
11572     if (appData.debugMode) {
11573 int i, j;
11574   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11575   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11576         fprintf(debugFP, "Load Position\n");
11577     }
11578
11579     if (positionNumber > 1) {
11580       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11581         DisplayTitle(line);
11582     } else {
11583         DisplayTitle(title);
11584     }
11585     gameMode = EditGame;
11586     ModeHighlight();
11587     ResetClocks();
11588     timeRemaining[0][1] = whiteTimeRemaining;
11589     timeRemaining[1][1] = blackTimeRemaining;
11590     DrawPosition(FALSE, boards[currentMove]);
11591
11592     return TRUE;
11593 }
11594
11595
11596 void
11597 CopyPlayerNameIntoFileName(dest, src)
11598      char **dest, *src;
11599 {
11600     while (*src != NULLCHAR && *src != ',') {
11601         if (*src == ' ') {
11602             *(*dest)++ = '_';
11603             src++;
11604         } else {
11605             *(*dest)++ = *src++;
11606         }
11607     }
11608 }
11609
11610 char *DefaultFileName(ext)
11611      char *ext;
11612 {
11613     static char def[MSG_SIZ];
11614     char *p;
11615
11616     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11617         p = def;
11618         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11619         *p++ = '-';
11620         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11621         *p++ = '.';
11622         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11623     } else {
11624         def[0] = NULLCHAR;
11625     }
11626     return def;
11627 }
11628
11629 /* Save the current game to the given file */
11630 int
11631 SaveGameToFile(filename, append)
11632      char *filename;
11633      int append;
11634 {
11635     FILE *f;
11636     char buf[MSG_SIZ];
11637     int result;
11638
11639     if (strcmp(filename, "-") == 0) {
11640         return SaveGame(stdout, 0, NULL);
11641     } else {
11642         f = fopen(filename, append ? "a" : "w");
11643         if (f == NULL) {
11644             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11645             DisplayError(buf, errno);
11646             return FALSE;
11647         } else {
11648             safeStrCpy(buf, lastMsg, MSG_SIZ);
11649             DisplayMessage(_("Waiting for access to save file"), "");
11650             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11651             DisplayMessage(_("Saving game"), "");
11652             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11653             result = SaveGame(f, 0, NULL);
11654             DisplayMessage(buf, "");
11655             return result;
11656         }
11657     }
11658 }
11659
11660 char *
11661 SavePart(str)
11662      char *str;
11663 {
11664     static char buf[MSG_SIZ];
11665     char *p;
11666
11667     p = strchr(str, ' ');
11668     if (p == NULL) return str;
11669     strncpy(buf, str, p - str);
11670     buf[p - str] = NULLCHAR;
11671     return buf;
11672 }
11673
11674 #define PGN_MAX_LINE 75
11675
11676 #define PGN_SIDE_WHITE  0
11677 #define PGN_SIDE_BLACK  1
11678
11679 /* [AS] */
11680 static int FindFirstMoveOutOfBook( int side )
11681 {
11682     int result = -1;
11683
11684     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11685         int index = backwardMostMove;
11686         int has_book_hit = 0;
11687
11688         if( (index % 2) != side ) {
11689             index++;
11690         }
11691
11692         while( index < forwardMostMove ) {
11693             /* Check to see if engine is in book */
11694             int depth = pvInfoList[index].depth;
11695             int score = pvInfoList[index].score;
11696             int in_book = 0;
11697
11698             if( depth <= 2 ) {
11699                 in_book = 1;
11700             }
11701             else if( score == 0 && depth == 63 ) {
11702                 in_book = 1; /* Zappa */
11703             }
11704             else if( score == 2 && depth == 99 ) {
11705                 in_book = 1; /* Abrok */
11706             }
11707
11708             has_book_hit += in_book;
11709
11710             if( ! in_book ) {
11711                 result = index;
11712
11713                 break;
11714             }
11715
11716             index += 2;
11717         }
11718     }
11719
11720     return result;
11721 }
11722
11723 /* [AS] */
11724 void GetOutOfBookInfo( char * buf )
11725 {
11726     int oob[2];
11727     int i;
11728     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11729
11730     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11731     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11732
11733     *buf = '\0';
11734
11735     if( oob[0] >= 0 || oob[1] >= 0 ) {
11736         for( i=0; i<2; i++ ) {
11737             int idx = oob[i];
11738
11739             if( idx >= 0 ) {
11740                 if( i > 0 && oob[0] >= 0 ) {
11741                     strcat( buf, "   " );
11742                 }
11743
11744                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11745                 sprintf( buf+strlen(buf), "%s%.2f",
11746                     pvInfoList[idx].score >= 0 ? "+" : "",
11747                     pvInfoList[idx].score / 100.0 );
11748             }
11749         }
11750     }
11751 }
11752
11753 /* Save game in PGN style and close the file */
11754 int
11755 SaveGamePGN(f)
11756      FILE *f;
11757 {
11758     int i, offset, linelen, newblock;
11759     time_t tm;
11760 //    char *movetext;
11761     char numtext[32];
11762     int movelen, numlen, blank;
11763     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11764
11765     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11766
11767     tm = time((time_t *) NULL);
11768
11769     PrintPGNTags(f, &gameInfo);
11770
11771     if (backwardMostMove > 0 || startedFromSetupPosition) {
11772         char *fen = PositionToFEN(backwardMostMove, NULL);
11773         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11774         fprintf(f, "\n{--------------\n");
11775         PrintPosition(f, backwardMostMove);
11776         fprintf(f, "--------------}\n");
11777         free(fen);
11778     }
11779     else {
11780         /* [AS] Out of book annotation */
11781         if( appData.saveOutOfBookInfo ) {
11782             char buf[64];
11783
11784             GetOutOfBookInfo( buf );
11785
11786             if( buf[0] != '\0' ) {
11787                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11788             }
11789         }
11790
11791         fprintf(f, "\n");
11792     }
11793
11794     i = backwardMostMove;
11795     linelen = 0;
11796     newblock = TRUE;
11797
11798     while (i < forwardMostMove) {
11799         /* Print comments preceding this move */
11800         if (commentList[i] != NULL) {
11801             if (linelen > 0) fprintf(f, "\n");
11802             fprintf(f, "%s", commentList[i]);
11803             linelen = 0;
11804             newblock = TRUE;
11805         }
11806
11807         /* Format move number */
11808         if ((i % 2) == 0)
11809           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11810         else
11811           if (newblock)
11812             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11813           else
11814             numtext[0] = NULLCHAR;
11815
11816         numlen = strlen(numtext);
11817         newblock = FALSE;
11818
11819         /* Print move number */
11820         blank = linelen > 0 && numlen > 0;
11821         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11822             fprintf(f, "\n");
11823             linelen = 0;
11824             blank = 0;
11825         }
11826         if (blank) {
11827             fprintf(f, " ");
11828             linelen++;
11829         }
11830         fprintf(f, "%s", numtext);
11831         linelen += numlen;
11832
11833         /* Get move */
11834         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11835         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11836
11837         /* Print move */
11838         blank = linelen > 0 && movelen > 0;
11839         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11840             fprintf(f, "\n");
11841             linelen = 0;
11842             blank = 0;
11843         }
11844         if (blank) {
11845             fprintf(f, " ");
11846             linelen++;
11847         }
11848         fprintf(f, "%s", move_buffer);
11849         linelen += movelen;
11850
11851         /* [AS] Add PV info if present */
11852         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11853             /* [HGM] add time */
11854             char buf[MSG_SIZ]; int seconds;
11855
11856             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11857
11858             if( seconds <= 0)
11859               buf[0] = 0;
11860             else
11861               if( seconds < 30 )
11862                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11863               else
11864                 {
11865                   seconds = (seconds + 4)/10; // round to full seconds
11866                   if( seconds < 60 )
11867                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11868                   else
11869                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11870                 }
11871
11872             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11873                       pvInfoList[i].score >= 0 ? "+" : "",
11874                       pvInfoList[i].score / 100.0,
11875                       pvInfoList[i].depth,
11876                       buf );
11877
11878             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11879
11880             /* Print score/depth */
11881             blank = linelen > 0 && movelen > 0;
11882             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11883                 fprintf(f, "\n");
11884                 linelen = 0;
11885                 blank = 0;
11886             }
11887             if (blank) {
11888                 fprintf(f, " ");
11889                 linelen++;
11890             }
11891             fprintf(f, "%s", move_buffer);
11892             linelen += movelen;
11893         }
11894
11895         i++;
11896     }
11897
11898     /* Start a new line */
11899     if (linelen > 0) fprintf(f, "\n");
11900
11901     /* Print comments after last move */
11902     if (commentList[i] != NULL) {
11903         fprintf(f, "%s\n", commentList[i]);
11904     }
11905
11906     /* Print result */
11907     if (gameInfo.resultDetails != NULL &&
11908         gameInfo.resultDetails[0] != NULLCHAR) {
11909         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11910                 PGNResult(gameInfo.result));
11911     } else {
11912         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11913     }
11914
11915     fclose(f);
11916     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11917     return TRUE;
11918 }
11919
11920 /* Save game in old style and close the file */
11921 int
11922 SaveGameOldStyle(f)
11923      FILE *f;
11924 {
11925     int i, offset;
11926     time_t tm;
11927
11928     tm = time((time_t *) NULL);
11929
11930     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11931     PrintOpponents(f);
11932
11933     if (backwardMostMove > 0 || startedFromSetupPosition) {
11934         fprintf(f, "\n[--------------\n");
11935         PrintPosition(f, backwardMostMove);
11936         fprintf(f, "--------------]\n");
11937     } else {
11938         fprintf(f, "\n");
11939     }
11940
11941     i = backwardMostMove;
11942     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11943
11944     while (i < forwardMostMove) {
11945         if (commentList[i] != NULL) {
11946             fprintf(f, "[%s]\n", commentList[i]);
11947         }
11948
11949         if ((i % 2) == 1) {
11950             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11951             i++;
11952         } else {
11953             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11954             i++;
11955             if (commentList[i] != NULL) {
11956                 fprintf(f, "\n");
11957                 continue;
11958             }
11959             if (i >= forwardMostMove) {
11960                 fprintf(f, "\n");
11961                 break;
11962             }
11963             fprintf(f, "%s\n", parseList[i]);
11964             i++;
11965         }
11966     }
11967
11968     if (commentList[i] != NULL) {
11969         fprintf(f, "[%s]\n", commentList[i]);
11970     }
11971
11972     /* This isn't really the old style, but it's close enough */
11973     if (gameInfo.resultDetails != NULL &&
11974         gameInfo.resultDetails[0] != NULLCHAR) {
11975         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11976                 gameInfo.resultDetails);
11977     } else {
11978         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11979     }
11980
11981     fclose(f);
11982     return TRUE;
11983 }
11984
11985 /* Save the current game to open file f and close the file */
11986 int
11987 SaveGame(f, dummy, dummy2)
11988      FILE *f;
11989      int dummy;
11990      char *dummy2;
11991 {
11992     if (gameMode == EditPosition) EditPositionDone(TRUE);
11993     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11994     if (appData.oldSaveStyle)
11995       return SaveGameOldStyle(f);
11996     else
11997       return SaveGamePGN(f);
11998 }
11999
12000 /* Save the current position to the given file */
12001 int
12002 SavePositionToFile(filename)
12003      char *filename;
12004 {
12005     FILE *f;
12006     char buf[MSG_SIZ];
12007
12008     if (strcmp(filename, "-") == 0) {
12009         return SavePosition(stdout, 0, NULL);
12010     } else {
12011         f = fopen(filename, "a");
12012         if (f == NULL) {
12013             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12014             DisplayError(buf, errno);
12015             return FALSE;
12016         } else {
12017             safeStrCpy(buf, lastMsg, MSG_SIZ);
12018             DisplayMessage(_("Waiting for access to save file"), "");
12019             flock(fileno(f), LOCK_EX); // [HGM] lock
12020             DisplayMessage(_("Saving position"), "");
12021             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12022             SavePosition(f, 0, NULL);
12023             DisplayMessage(buf, "");
12024             return TRUE;
12025         }
12026     }
12027 }
12028
12029 /* Save the current position to the given open file and close the file */
12030 int
12031 SavePosition(f, dummy, dummy2)
12032      FILE *f;
12033      int dummy;
12034      char *dummy2;
12035 {
12036     time_t tm;
12037     char *fen;
12038
12039     if (gameMode == EditPosition) EditPositionDone(TRUE);
12040     if (appData.oldSaveStyle) {
12041         tm = time((time_t *) NULL);
12042
12043         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12044         PrintOpponents(f);
12045         fprintf(f, "[--------------\n");
12046         PrintPosition(f, currentMove);
12047         fprintf(f, "--------------]\n");
12048     } else {
12049         fen = PositionToFEN(currentMove, NULL);
12050         fprintf(f, "%s\n", fen);
12051         free(fen);
12052     }
12053     fclose(f);
12054     return TRUE;
12055 }
12056
12057 void
12058 ReloadCmailMsgEvent(unregister)
12059      int unregister;
12060 {
12061 #if !WIN32
12062     static char *inFilename = NULL;
12063     static char *outFilename;
12064     int i;
12065     struct stat inbuf, outbuf;
12066     int status;
12067
12068     /* Any registered moves are unregistered if unregister is set, */
12069     /* i.e. invoked by the signal handler */
12070     if (unregister) {
12071         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12072             cmailMoveRegistered[i] = FALSE;
12073             if (cmailCommentList[i] != NULL) {
12074                 free(cmailCommentList[i]);
12075                 cmailCommentList[i] = NULL;
12076             }
12077         }
12078         nCmailMovesRegistered = 0;
12079     }
12080
12081     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12082         cmailResult[i] = CMAIL_NOT_RESULT;
12083     }
12084     nCmailResults = 0;
12085
12086     if (inFilename == NULL) {
12087         /* Because the filenames are static they only get malloced once  */
12088         /* and they never get freed                                      */
12089         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12090         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12091
12092         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12093         sprintf(outFilename, "%s.out", appData.cmailGameName);
12094     }
12095
12096     status = stat(outFilename, &outbuf);
12097     if (status < 0) {
12098         cmailMailedMove = FALSE;
12099     } else {
12100         status = stat(inFilename, &inbuf);
12101         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12102     }
12103
12104     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12105        counts the games, notes how each one terminated, etc.
12106
12107        It would be nice to remove this kludge and instead gather all
12108        the information while building the game list.  (And to keep it
12109        in the game list nodes instead of having a bunch of fixed-size
12110        parallel arrays.)  Note this will require getting each game's
12111        termination from the PGN tags, as the game list builder does
12112        not process the game moves.  --mann
12113        */
12114     cmailMsgLoaded = TRUE;
12115     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12116
12117     /* Load first game in the file or popup game menu */
12118     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12119
12120 #endif /* !WIN32 */
12121     return;
12122 }
12123
12124 int
12125 RegisterMove()
12126 {
12127     FILE *f;
12128     char string[MSG_SIZ];
12129
12130     if (   cmailMailedMove
12131         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12132         return TRUE;            /* Allow free viewing  */
12133     }
12134
12135     /* Unregister move to ensure that we don't leave RegisterMove        */
12136     /* with the move registered when the conditions for registering no   */
12137     /* longer hold                                                       */
12138     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12139         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12140         nCmailMovesRegistered --;
12141
12142         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12143           {
12144               free(cmailCommentList[lastLoadGameNumber - 1]);
12145               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12146           }
12147     }
12148
12149     if (cmailOldMove == -1) {
12150         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12151         return FALSE;
12152     }
12153
12154     if (currentMove > cmailOldMove + 1) {
12155         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12156         return FALSE;
12157     }
12158
12159     if (currentMove < cmailOldMove) {
12160         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12161         return FALSE;
12162     }
12163
12164     if (forwardMostMove > currentMove) {
12165         /* Silently truncate extra moves */
12166         TruncateGame();
12167     }
12168
12169     if (   (currentMove == cmailOldMove + 1)
12170         || (   (currentMove == cmailOldMove)
12171             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12172                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12173         if (gameInfo.result != GameUnfinished) {
12174             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12175         }
12176
12177         if (commentList[currentMove] != NULL) {
12178             cmailCommentList[lastLoadGameNumber - 1]
12179               = StrSave(commentList[currentMove]);
12180         }
12181         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12182
12183         if (appData.debugMode)
12184           fprintf(debugFP, "Saving %s for game %d\n",
12185                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12186
12187         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12188
12189         f = fopen(string, "w");
12190         if (appData.oldSaveStyle) {
12191             SaveGameOldStyle(f); /* also closes the file */
12192
12193             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12194             f = fopen(string, "w");
12195             SavePosition(f, 0, NULL); /* also closes the file */
12196         } else {
12197             fprintf(f, "{--------------\n");
12198             PrintPosition(f, currentMove);
12199             fprintf(f, "--------------}\n\n");
12200
12201             SaveGame(f, 0, NULL); /* also closes the file*/
12202         }
12203
12204         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12205         nCmailMovesRegistered ++;
12206     } else if (nCmailGames == 1) {
12207         DisplayError(_("You have not made a move yet"), 0);
12208         return FALSE;
12209     }
12210
12211     return TRUE;
12212 }
12213
12214 void
12215 MailMoveEvent()
12216 {
12217 #if !WIN32
12218     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12219     FILE *commandOutput;
12220     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12221     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12222     int nBuffers;
12223     int i;
12224     int archived;
12225     char *arcDir;
12226
12227     if (! cmailMsgLoaded) {
12228         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12229         return;
12230     }
12231
12232     if (nCmailGames == nCmailResults) {
12233         DisplayError(_("No unfinished games"), 0);
12234         return;
12235     }
12236
12237 #if CMAIL_PROHIBIT_REMAIL
12238     if (cmailMailedMove) {
12239       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);
12240         DisplayError(msg, 0);
12241         return;
12242     }
12243 #endif
12244
12245     if (! (cmailMailedMove || RegisterMove())) return;
12246
12247     if (   cmailMailedMove
12248         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12249       snprintf(string, MSG_SIZ, partCommandString,
12250                appData.debugMode ? " -v" : "", appData.cmailGameName);
12251         commandOutput = popen(string, "r");
12252
12253         if (commandOutput == NULL) {
12254             DisplayError(_("Failed to invoke cmail"), 0);
12255         } else {
12256             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12257                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12258             }
12259             if (nBuffers > 1) {
12260                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12261                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12262                 nBytes = MSG_SIZ - 1;
12263             } else {
12264                 (void) memcpy(msg, buffer, nBytes);
12265             }
12266             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12267
12268             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12269                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12270
12271                 archived = TRUE;
12272                 for (i = 0; i < nCmailGames; i ++) {
12273                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12274                         archived = FALSE;
12275                     }
12276                 }
12277                 if (   archived
12278                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12279                         != NULL)) {
12280                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12281                            arcDir,
12282                            appData.cmailGameName,
12283                            gameInfo.date);
12284                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12285                     cmailMsgLoaded = FALSE;
12286                 }
12287             }
12288
12289             DisplayInformation(msg);
12290             pclose(commandOutput);
12291         }
12292     } else {
12293         if ((*cmailMsg) != '\0') {
12294             DisplayInformation(cmailMsg);
12295         }
12296     }
12297
12298     return;
12299 #endif /* !WIN32 */
12300 }
12301
12302 char *
12303 CmailMsg()
12304 {
12305 #if WIN32
12306     return NULL;
12307 #else
12308     int  prependComma = 0;
12309     char number[5];
12310     char string[MSG_SIZ];       /* Space for game-list */
12311     int  i;
12312
12313     if (!cmailMsgLoaded) return "";
12314
12315     if (cmailMailedMove) {
12316       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12317     } else {
12318         /* Create a list of games left */
12319       snprintf(string, MSG_SIZ, "[");
12320         for (i = 0; i < nCmailGames; i ++) {
12321             if (! (   cmailMoveRegistered[i]
12322                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12323                 if (prependComma) {
12324                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12325                 } else {
12326                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12327                     prependComma = 1;
12328                 }
12329
12330                 strcat(string, number);
12331             }
12332         }
12333         strcat(string, "]");
12334
12335         if (nCmailMovesRegistered + nCmailResults == 0) {
12336             switch (nCmailGames) {
12337               case 1:
12338                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12339                 break;
12340
12341               case 2:
12342                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12343                 break;
12344
12345               default:
12346                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12347                          nCmailGames);
12348                 break;
12349             }
12350         } else {
12351             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12352               case 1:
12353                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12354                          string);
12355                 break;
12356
12357               case 0:
12358                 if (nCmailResults == nCmailGames) {
12359                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12360                 } else {
12361                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12362                 }
12363                 break;
12364
12365               default:
12366                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12367                          string);
12368             }
12369         }
12370     }
12371     return cmailMsg;
12372 #endif /* WIN32 */
12373 }
12374
12375 void
12376 ResetGameEvent()
12377 {
12378     if (gameMode == Training)
12379       SetTrainingModeOff();
12380
12381     Reset(TRUE, TRUE);
12382     cmailMsgLoaded = FALSE;
12383     if (appData.icsActive) {
12384       SendToICS(ics_prefix);
12385       SendToICS("refresh\n");
12386     }
12387 }
12388
12389 void
12390 ExitEvent(status)
12391      int status;
12392 {
12393     exiting++;
12394     if (exiting > 2) {
12395       /* Give up on clean exit */
12396       exit(status);
12397     }
12398     if (exiting > 1) {
12399       /* Keep trying for clean exit */
12400       return;
12401     }
12402
12403     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12404
12405     if (telnetISR != NULL) {
12406       RemoveInputSource(telnetISR);
12407     }
12408     if (icsPR != NoProc) {
12409       DestroyChildProcess(icsPR, TRUE);
12410     }
12411
12412     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12413     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12414
12415     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12416     /* make sure this other one finishes before killing it!                  */
12417     if(endingGame) { int count = 0;
12418         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12419         while(endingGame && count++ < 10) DoSleep(1);
12420         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12421     }
12422
12423     /* Kill off chess programs */
12424     if (first.pr != NoProc) {
12425         ExitAnalyzeMode();
12426
12427         DoSleep( appData.delayBeforeQuit );
12428         SendToProgram("quit\n", &first);
12429         DoSleep( appData.delayAfterQuit );
12430         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12431     }
12432     if (second.pr != NoProc) {
12433         DoSleep( appData.delayBeforeQuit );
12434         SendToProgram("quit\n", &second);
12435         DoSleep( appData.delayAfterQuit );
12436         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12437     }
12438     if (first.isr != NULL) {
12439         RemoveInputSource(first.isr);
12440     }
12441     if (second.isr != NULL) {
12442         RemoveInputSource(second.isr);
12443     }
12444
12445     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12446     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12447
12448     ShutDownFrontEnd();
12449     exit(status);
12450 }
12451
12452 void
12453 PauseEvent()
12454 {
12455     if (appData.debugMode)
12456         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12457     if (pausing) {
12458         pausing = FALSE;
12459         ModeHighlight();
12460         if (gameMode == MachinePlaysWhite ||
12461             gameMode == MachinePlaysBlack) {
12462             StartClocks();
12463         } else {
12464             DisplayBothClocks();
12465         }
12466         if (gameMode == PlayFromGameFile) {
12467             if (appData.timeDelay >= 0)
12468                 AutoPlayGameLoop();
12469         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12470             Reset(FALSE, TRUE);
12471             SendToICS(ics_prefix);
12472             SendToICS("refresh\n");
12473         } else if (currentMove < forwardMostMove) {
12474             ForwardInner(forwardMostMove);
12475         }
12476         pauseExamInvalid = FALSE;
12477     } else {
12478         switch (gameMode) {
12479           default:
12480             return;
12481           case IcsExamining:
12482             pauseExamForwardMostMove = forwardMostMove;
12483             pauseExamInvalid = FALSE;
12484             /* fall through */
12485           case IcsObserving:
12486           case IcsPlayingWhite:
12487           case IcsPlayingBlack:
12488             pausing = TRUE;
12489             ModeHighlight();
12490             return;
12491           case PlayFromGameFile:
12492             (void) StopLoadGameTimer();
12493             pausing = TRUE;
12494             ModeHighlight();
12495             break;
12496           case BeginningOfGame:
12497             if (appData.icsActive) return;
12498             /* else fall through */
12499           case MachinePlaysWhite:
12500           case MachinePlaysBlack:
12501           case TwoMachinesPlay:
12502             if (forwardMostMove == 0)
12503               return;           /* don't pause if no one has moved */
12504             if ((gameMode == MachinePlaysWhite &&
12505                  !WhiteOnMove(forwardMostMove)) ||
12506                 (gameMode == MachinePlaysBlack &&
12507                  WhiteOnMove(forwardMostMove))) {
12508                 StopClocks();
12509             }
12510             pausing = TRUE;
12511             ModeHighlight();
12512             break;
12513         }
12514     }
12515 }
12516
12517 void
12518 EditCommentEvent()
12519 {
12520     char title[MSG_SIZ];
12521
12522     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12523       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12524     } else {
12525       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12526                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12527                parseList[currentMove - 1]);
12528     }
12529
12530     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12531 }
12532
12533
12534 void
12535 EditTagsEvent()
12536 {
12537     char *tags = PGNTags(&gameInfo);
12538     bookUp = FALSE;
12539     EditTagsPopUp(tags, NULL);
12540     free(tags);
12541 }
12542
12543 void
12544 AnalyzeModeEvent()
12545 {
12546     if (appData.noChessProgram || gameMode == AnalyzeMode)
12547       return;
12548
12549     if (gameMode != AnalyzeFile) {
12550         if (!appData.icsEngineAnalyze) {
12551                EditGameEvent();
12552                if (gameMode != EditGame) return;
12553         }
12554         ResurrectChessProgram();
12555         SendToProgram("analyze\n", &first);
12556         first.analyzing = TRUE;
12557         /*first.maybeThinking = TRUE;*/
12558         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12559         EngineOutputPopUp();
12560     }
12561     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12562     pausing = FALSE;
12563     ModeHighlight();
12564     SetGameInfo();
12565
12566     StartAnalysisClock();
12567     GetTimeMark(&lastNodeCountTime);
12568     lastNodeCount = 0;
12569 }
12570
12571 void
12572 AnalyzeFileEvent()
12573 {
12574     if (appData.noChessProgram || gameMode == AnalyzeFile)
12575       return;
12576
12577     if (gameMode != AnalyzeMode) {
12578         EditGameEvent();
12579         if (gameMode != EditGame) return;
12580         ResurrectChessProgram();
12581         SendToProgram("analyze\n", &first);
12582         first.analyzing = TRUE;
12583         /*first.maybeThinking = TRUE;*/
12584         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12585         EngineOutputPopUp();
12586     }
12587     gameMode = AnalyzeFile;
12588     pausing = FALSE;
12589     ModeHighlight();
12590     SetGameInfo();
12591
12592     StartAnalysisClock();
12593     GetTimeMark(&lastNodeCountTime);
12594     lastNodeCount = 0;
12595 }
12596
12597 void
12598 MachineWhiteEvent()
12599 {
12600     char buf[MSG_SIZ];
12601     char *bookHit = NULL;
12602
12603     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12604       return;
12605
12606
12607     if (gameMode == PlayFromGameFile ||
12608         gameMode == TwoMachinesPlay  ||
12609         gameMode == Training         ||
12610         gameMode == AnalyzeMode      ||
12611         gameMode == EndOfGame)
12612         EditGameEvent();
12613
12614     if (gameMode == EditPosition)
12615         EditPositionDone(TRUE);
12616
12617     if (!WhiteOnMove(currentMove)) {
12618         DisplayError(_("It is not White's turn"), 0);
12619         return;
12620     }
12621
12622     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12623       ExitAnalyzeMode();
12624
12625     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12626         gameMode == AnalyzeFile)
12627         TruncateGame();
12628
12629     ResurrectChessProgram();    /* in case it isn't running */
12630     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12631         gameMode = MachinePlaysWhite;
12632         ResetClocks();
12633     } else
12634     gameMode = MachinePlaysWhite;
12635     pausing = FALSE;
12636     ModeHighlight();
12637     SetGameInfo();
12638     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12639     DisplayTitle(buf);
12640     if (first.sendName) {
12641       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12642       SendToProgram(buf, &first);
12643     }
12644     if (first.sendTime) {
12645       if (first.useColors) {
12646         SendToProgram("black\n", &first); /*gnu kludge*/
12647       }
12648       SendTimeRemaining(&first, TRUE);
12649     }
12650     if (first.useColors) {
12651       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12652     }
12653     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12654     SetMachineThinkingEnables();
12655     first.maybeThinking = TRUE;
12656     StartClocks();
12657     firstMove = FALSE;
12658
12659     if (appData.autoFlipView && !flipView) {
12660       flipView = !flipView;
12661       DrawPosition(FALSE, NULL);
12662       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12663     }
12664
12665     if(bookHit) { // [HGM] book: simulate book reply
12666         static char bookMove[MSG_SIZ]; // a bit generous?
12667
12668         programStats.nodes = programStats.depth = programStats.time =
12669         programStats.score = programStats.got_only_move = 0;
12670         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12671
12672         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12673         strcat(bookMove, bookHit);
12674         HandleMachineMove(bookMove, &first);
12675     }
12676 }
12677
12678 void
12679 MachineBlackEvent()
12680 {
12681   char buf[MSG_SIZ];
12682   char *bookHit = NULL;
12683
12684     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12685         return;
12686
12687
12688     if (gameMode == PlayFromGameFile ||
12689         gameMode == TwoMachinesPlay  ||
12690         gameMode == Training         ||
12691         gameMode == AnalyzeMode      ||
12692         gameMode == EndOfGame)
12693         EditGameEvent();
12694
12695     if (gameMode == EditPosition)
12696         EditPositionDone(TRUE);
12697
12698     if (WhiteOnMove(currentMove)) {
12699         DisplayError(_("It is not Black's turn"), 0);
12700         return;
12701     }
12702
12703     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12704       ExitAnalyzeMode();
12705
12706     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12707         gameMode == AnalyzeFile)
12708         TruncateGame();
12709
12710     ResurrectChessProgram();    /* in case it isn't running */
12711     gameMode = MachinePlaysBlack;
12712     pausing = FALSE;
12713     ModeHighlight();
12714     SetGameInfo();
12715     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12716     DisplayTitle(buf);
12717     if (first.sendName) {
12718       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12719       SendToProgram(buf, &first);
12720     }
12721     if (first.sendTime) {
12722       if (first.useColors) {
12723         SendToProgram("white\n", &first); /*gnu kludge*/
12724       }
12725       SendTimeRemaining(&first, FALSE);
12726     }
12727     if (first.useColors) {
12728       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12729     }
12730     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12731     SetMachineThinkingEnables();
12732     first.maybeThinking = TRUE;
12733     StartClocks();
12734
12735     if (appData.autoFlipView && flipView) {
12736       flipView = !flipView;
12737       DrawPosition(FALSE, NULL);
12738       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12739     }
12740     if(bookHit) { // [HGM] book: simulate book reply
12741         static char bookMove[MSG_SIZ]; // a bit generous?
12742
12743         programStats.nodes = programStats.depth = programStats.time =
12744         programStats.score = programStats.got_only_move = 0;
12745         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12746
12747         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12748         strcat(bookMove, bookHit);
12749         HandleMachineMove(bookMove, &first);
12750     }
12751 }
12752
12753
12754 void
12755 DisplayTwoMachinesTitle()
12756 {
12757     char buf[MSG_SIZ];
12758     if (appData.matchGames > 0) {
12759         if(appData.tourneyFile[0]) {
12760           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12761                    gameInfo.white, gameInfo.black,
12762                    nextGame+1, appData.matchGames+1,
12763                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12764         } else 
12765         if (first.twoMachinesColor[0] == 'w') {
12766           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12767                    gameInfo.white, gameInfo.black,
12768                    first.matchWins, second.matchWins,
12769                    matchGame - 1 - (first.matchWins + second.matchWins));
12770         } else {
12771           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12772                    gameInfo.white, gameInfo.black,
12773                    second.matchWins, first.matchWins,
12774                    matchGame - 1 - (first.matchWins + second.matchWins));
12775         }
12776     } else {
12777       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12778     }
12779     DisplayTitle(buf);
12780 }
12781
12782 void
12783 SettingsMenuIfReady()
12784 {
12785   if (second.lastPing != second.lastPong) {
12786     DisplayMessage("", _("Waiting for second chess program"));
12787     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12788     return;
12789   }
12790   ThawUI();
12791   DisplayMessage("", "");
12792   SettingsPopUp(&second);
12793 }
12794
12795 int
12796 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12797 {
12798     char buf[MSG_SIZ];
12799     if (cps->pr == NULL) {
12800         StartChessProgram(cps);
12801         if (cps->protocolVersion == 1) {
12802           retry();
12803         } else {
12804           /* kludge: allow timeout for initial "feature" command */
12805           FreezeUI();
12806           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12807           DisplayMessage("", buf);
12808           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12809         }
12810         return 1;
12811     }
12812     return 0;
12813 }
12814
12815 void
12816 TwoMachinesEvent P((void))
12817 {
12818     int i;
12819     char buf[MSG_SIZ];
12820     ChessProgramState *onmove;
12821     char *bookHit = NULL;
12822     static int stalling = 0;
12823     TimeMark now;
12824     long wait;
12825
12826     if (appData.noChessProgram) return;
12827
12828     switch (gameMode) {
12829       case TwoMachinesPlay:
12830         return;
12831       case MachinePlaysWhite:
12832       case MachinePlaysBlack:
12833         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12834             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12835             return;
12836         }
12837         /* fall through */
12838       case BeginningOfGame:
12839       case PlayFromGameFile:
12840       case EndOfGame:
12841         EditGameEvent();
12842         if (gameMode != EditGame) return;
12843         break;
12844       case EditPosition:
12845         EditPositionDone(TRUE);
12846         break;
12847       case AnalyzeMode:
12848       case AnalyzeFile:
12849         ExitAnalyzeMode();
12850         break;
12851       case EditGame:
12852       default:
12853         break;
12854     }
12855
12856 //    forwardMostMove = currentMove;
12857     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12858
12859     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12860
12861     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12862     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12863       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12864       return;
12865     }
12866     if(!stalling) {
12867       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12868       SendToProgram("force\n", &second);
12869       stalling = 1;
12870       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12871       return;
12872     }
12873     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12874     if(appData.matchPause>10000 || appData.matchPause<10)
12875                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12876     wait = SubtractTimeMarks(&now, &pauseStart);
12877     if(wait < appData.matchPause) {
12878         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12879         return;
12880     }
12881     stalling = 0;
12882     DisplayMessage("", "");
12883     if (startedFromSetupPosition) {
12884         SendBoard(&second, backwardMostMove);
12885     if (appData.debugMode) {
12886         fprintf(debugFP, "Two Machines\n");
12887     }
12888     }
12889     for (i = backwardMostMove; i < forwardMostMove; i++) {
12890         SendMoveToProgram(i, &second);
12891     }
12892
12893     gameMode = TwoMachinesPlay;
12894     pausing = FALSE;
12895     ModeHighlight(); // [HGM] logo: this triggers display update of logos
12896     SetGameInfo();
12897     DisplayTwoMachinesTitle();
12898     firstMove = TRUE;
12899     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12900         onmove = &first;
12901     } else {
12902         onmove = &second;
12903     }
12904     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12905     SendToProgram(first.computerString, &first);
12906     if (first.sendName) {
12907       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12908       SendToProgram(buf, &first);
12909     }
12910     SendToProgram(second.computerString, &second);
12911     if (second.sendName) {
12912       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12913       SendToProgram(buf, &second);
12914     }
12915
12916     ResetClocks();
12917     if (!first.sendTime || !second.sendTime) {
12918         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12919         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12920     }
12921     if (onmove->sendTime) {
12922       if (onmove->useColors) {
12923         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12924       }
12925       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12926     }
12927     if (onmove->useColors) {
12928       SendToProgram(onmove->twoMachinesColor, onmove);
12929     }
12930     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12931 //    SendToProgram("go\n", onmove);
12932     onmove->maybeThinking = TRUE;
12933     SetMachineThinkingEnables();
12934
12935     StartClocks();
12936
12937     if(bookHit) { // [HGM] book: simulate book reply
12938         static char bookMove[MSG_SIZ]; // a bit generous?
12939
12940         programStats.nodes = programStats.depth = programStats.time =
12941         programStats.score = programStats.got_only_move = 0;
12942         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12943
12944         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12945         strcat(bookMove, bookHit);
12946         savedMessage = bookMove; // args for deferred call
12947         savedState = onmove;
12948         ScheduleDelayedEvent(DeferredBookMove, 1);
12949     }
12950 }
12951
12952 void
12953 TrainingEvent()
12954 {
12955     if (gameMode == Training) {
12956       SetTrainingModeOff();
12957       gameMode = PlayFromGameFile;
12958       DisplayMessage("", _("Training mode off"));
12959     } else {
12960       gameMode = Training;
12961       animateTraining = appData.animate;
12962
12963       /* make sure we are not already at the end of the game */
12964       if (currentMove < forwardMostMove) {
12965         SetTrainingModeOn();
12966         DisplayMessage("", _("Training mode on"));
12967       } else {
12968         gameMode = PlayFromGameFile;
12969         DisplayError(_("Already at end of game"), 0);
12970       }
12971     }
12972     ModeHighlight();
12973 }
12974
12975 void
12976 IcsClientEvent()
12977 {
12978     if (!appData.icsActive) return;
12979     switch (gameMode) {
12980       case IcsPlayingWhite:
12981       case IcsPlayingBlack:
12982       case IcsObserving:
12983       case IcsIdle:
12984       case BeginningOfGame:
12985       case IcsExamining:
12986         return;
12987
12988       case EditGame:
12989         break;
12990
12991       case EditPosition:
12992         EditPositionDone(TRUE);
12993         break;
12994
12995       case AnalyzeMode:
12996       case AnalyzeFile:
12997         ExitAnalyzeMode();
12998         break;
12999
13000       default:
13001         EditGameEvent();
13002         break;
13003     }
13004
13005     gameMode = IcsIdle;
13006     ModeHighlight();
13007     return;
13008 }
13009
13010
13011 void
13012 EditGameEvent()
13013 {
13014     int i;
13015
13016     switch (gameMode) {
13017       case Training:
13018         SetTrainingModeOff();
13019         break;
13020       case MachinePlaysWhite:
13021       case MachinePlaysBlack:
13022       case BeginningOfGame:
13023         SendToProgram("force\n", &first);
13024         SetUserThinkingEnables();
13025         break;
13026       case PlayFromGameFile:
13027         (void) StopLoadGameTimer();
13028         if (gameFileFP != NULL) {
13029             gameFileFP = NULL;
13030         }
13031         break;
13032       case EditPosition:
13033         EditPositionDone(TRUE);
13034         break;
13035       case AnalyzeMode:
13036       case AnalyzeFile:
13037         ExitAnalyzeMode();
13038         SendToProgram("force\n", &first);
13039         break;
13040       case TwoMachinesPlay:
13041         GameEnds(EndOfFile, NULL, GE_PLAYER);
13042         ResurrectChessProgram();
13043         SetUserThinkingEnables();
13044         break;
13045       case EndOfGame:
13046         ResurrectChessProgram();
13047         break;
13048       case IcsPlayingBlack:
13049       case IcsPlayingWhite:
13050         DisplayError(_("Warning: You are still playing a game"), 0);
13051         break;
13052       case IcsObserving:
13053         DisplayError(_("Warning: You are still observing a game"), 0);
13054         break;
13055       case IcsExamining:
13056         DisplayError(_("Warning: You are still examining a game"), 0);
13057         break;
13058       case IcsIdle:
13059         break;
13060       case EditGame:
13061       default:
13062         return;
13063     }
13064
13065     pausing = FALSE;
13066     StopClocks();
13067     first.offeredDraw = second.offeredDraw = 0;
13068
13069     if (gameMode == PlayFromGameFile) {
13070         whiteTimeRemaining = timeRemaining[0][currentMove];
13071         blackTimeRemaining = timeRemaining[1][currentMove];
13072         DisplayTitle("");
13073     }
13074
13075     if (gameMode == MachinePlaysWhite ||
13076         gameMode == MachinePlaysBlack ||
13077         gameMode == TwoMachinesPlay ||
13078         gameMode == EndOfGame) {
13079         i = forwardMostMove;
13080         while (i > currentMove) {
13081             SendToProgram("undo\n", &first);
13082             i--;
13083         }
13084         whiteTimeRemaining = timeRemaining[0][currentMove];
13085         blackTimeRemaining = timeRemaining[1][currentMove];
13086         DisplayBothClocks();
13087         if (whiteFlag || blackFlag) {
13088             whiteFlag = blackFlag = 0;
13089         }
13090         DisplayTitle("");
13091     }
13092
13093     gameMode = EditGame;
13094     ModeHighlight();
13095     SetGameInfo();
13096 }
13097
13098
13099 void
13100 EditPositionEvent()
13101 {
13102     if (gameMode == EditPosition) {
13103         EditGameEvent();
13104         return;
13105     }
13106
13107     EditGameEvent();
13108     if (gameMode != EditGame) return;
13109
13110     gameMode = EditPosition;
13111     ModeHighlight();
13112     SetGameInfo();
13113     if (currentMove > 0)
13114       CopyBoard(boards[0], boards[currentMove]);
13115
13116     blackPlaysFirst = !WhiteOnMove(currentMove);
13117     ResetClocks();
13118     currentMove = forwardMostMove = backwardMostMove = 0;
13119     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13120     DisplayMove(-1);
13121 }
13122
13123 void
13124 ExitAnalyzeMode()
13125 {
13126     /* [DM] icsEngineAnalyze - possible call from other functions */
13127     if (appData.icsEngineAnalyze) {
13128         appData.icsEngineAnalyze = FALSE;
13129
13130         DisplayMessage("",_("Close ICS engine analyze..."));
13131     }
13132     if (first.analysisSupport && first.analyzing) {
13133       SendToProgram("exit\n", &first);
13134       first.analyzing = FALSE;
13135     }
13136     thinkOutput[0] = NULLCHAR;
13137 }
13138
13139 void
13140 EditPositionDone(Boolean fakeRights)
13141 {
13142     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13143
13144     startedFromSetupPosition = TRUE;
13145     InitChessProgram(&first, FALSE);
13146     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13147       boards[0][EP_STATUS] = EP_NONE;
13148       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13149     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13150         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13151         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13152       } else boards[0][CASTLING][2] = NoRights;
13153     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13154         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13155         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13156       } else boards[0][CASTLING][5] = NoRights;
13157     }
13158     SendToProgram("force\n", &first);
13159     if (blackPlaysFirst) {
13160         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13161         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13162         currentMove = forwardMostMove = backwardMostMove = 1;
13163         CopyBoard(boards[1], boards[0]);
13164     } else {
13165         currentMove = forwardMostMove = backwardMostMove = 0;
13166     }
13167     SendBoard(&first, forwardMostMove);
13168     if (appData.debugMode) {
13169         fprintf(debugFP, "EditPosDone\n");
13170     }
13171     DisplayTitle("");
13172     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13173     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13174     gameMode = EditGame;
13175     ModeHighlight();
13176     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13177     ClearHighlights(); /* [AS] */
13178 }
13179
13180 /* Pause for `ms' milliseconds */
13181 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13182 void
13183 TimeDelay(ms)
13184      long ms;
13185 {
13186     TimeMark m1, m2;
13187
13188     GetTimeMark(&m1);
13189     do {
13190         GetTimeMark(&m2);
13191     } while (SubtractTimeMarks(&m2, &m1) < ms);
13192 }
13193
13194 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13195 void
13196 SendMultiLineToICS(buf)
13197      char *buf;
13198 {
13199     char temp[MSG_SIZ+1], *p;
13200     int len;
13201
13202     len = strlen(buf);
13203     if (len > MSG_SIZ)
13204       len = MSG_SIZ;
13205
13206     strncpy(temp, buf, len);
13207     temp[len] = 0;
13208
13209     p = temp;
13210     while (*p) {
13211         if (*p == '\n' || *p == '\r')
13212           *p = ' ';
13213         ++p;
13214     }
13215
13216     strcat(temp, "\n");
13217     SendToICS(temp);
13218     SendToPlayer(temp, strlen(temp));
13219 }
13220
13221 void
13222 SetWhiteToPlayEvent()
13223 {
13224     if (gameMode == EditPosition) {
13225         blackPlaysFirst = FALSE;
13226         DisplayBothClocks();    /* works because currentMove is 0 */
13227     } else if (gameMode == IcsExamining) {
13228         SendToICS(ics_prefix);
13229         SendToICS("tomove white\n");
13230     }
13231 }
13232
13233 void
13234 SetBlackToPlayEvent()
13235 {
13236     if (gameMode == EditPosition) {
13237         blackPlaysFirst = TRUE;
13238         currentMove = 1;        /* kludge */
13239         DisplayBothClocks();
13240         currentMove = 0;
13241     } else if (gameMode == IcsExamining) {
13242         SendToICS(ics_prefix);
13243         SendToICS("tomove black\n");
13244     }
13245 }
13246
13247 void
13248 EditPositionMenuEvent(selection, x, y)
13249      ChessSquare selection;
13250      int x, y;
13251 {
13252     char buf[MSG_SIZ];
13253     ChessSquare piece = boards[0][y][x];
13254
13255     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13256
13257     switch (selection) {
13258       case ClearBoard:
13259         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13260             SendToICS(ics_prefix);
13261             SendToICS("bsetup clear\n");
13262         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13263             SendToICS(ics_prefix);
13264             SendToICS("clearboard\n");
13265         } else {
13266             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13267                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13268                 for (y = 0; y < BOARD_HEIGHT; y++) {
13269                     if (gameMode == IcsExamining) {
13270                         if (boards[currentMove][y][x] != EmptySquare) {
13271                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13272                                     AAA + x, ONE + y);
13273                             SendToICS(buf);
13274                         }
13275                     } else {
13276                         boards[0][y][x] = p;
13277                     }
13278                 }
13279             }
13280         }
13281         if (gameMode == EditPosition) {
13282             DrawPosition(FALSE, boards[0]);
13283         }
13284         break;
13285
13286       case WhitePlay:
13287         SetWhiteToPlayEvent();
13288         break;
13289
13290       case BlackPlay:
13291         SetBlackToPlayEvent();
13292         break;
13293
13294       case EmptySquare:
13295         if (gameMode == IcsExamining) {
13296             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13297             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13298             SendToICS(buf);
13299         } else {
13300             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13301                 if(x == BOARD_LEFT-2) {
13302                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13303                     boards[0][y][1] = 0;
13304                 } else
13305                 if(x == BOARD_RGHT+1) {
13306                     if(y >= gameInfo.holdingsSize) break;
13307                     boards[0][y][BOARD_WIDTH-2] = 0;
13308                 } else break;
13309             }
13310             boards[0][y][x] = EmptySquare;
13311             DrawPosition(FALSE, boards[0]);
13312         }
13313         break;
13314
13315       case PromotePiece:
13316         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13317            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13318             selection = (ChessSquare) (PROMOTED piece);
13319         } else if(piece == EmptySquare) selection = WhiteSilver;
13320         else selection = (ChessSquare)((int)piece - 1);
13321         goto defaultlabel;
13322
13323       case DemotePiece:
13324         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13325            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13326             selection = (ChessSquare) (DEMOTED piece);
13327         } else if(piece == EmptySquare) selection = BlackSilver;
13328         else selection = (ChessSquare)((int)piece + 1);
13329         goto defaultlabel;
13330
13331       case WhiteQueen:
13332       case BlackQueen:
13333         if(gameInfo.variant == VariantShatranj ||
13334            gameInfo.variant == VariantXiangqi  ||
13335            gameInfo.variant == VariantCourier  ||
13336            gameInfo.variant == VariantMakruk     )
13337             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13338         goto defaultlabel;
13339
13340       case WhiteKing:
13341       case BlackKing:
13342         if(gameInfo.variant == VariantXiangqi)
13343             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13344         if(gameInfo.variant == VariantKnightmate)
13345             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13346       default:
13347         defaultlabel:
13348         if (gameMode == IcsExamining) {
13349             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13350             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13351                      PieceToChar(selection), AAA + x, ONE + y);
13352             SendToICS(buf);
13353         } else {
13354             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13355                 int n;
13356                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13357                     n = PieceToNumber(selection - BlackPawn);
13358                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13359                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13360                     boards[0][BOARD_HEIGHT-1-n][1]++;
13361                 } else
13362                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13363                     n = PieceToNumber(selection);
13364                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13365                     boards[0][n][BOARD_WIDTH-1] = selection;
13366                     boards[0][n][BOARD_WIDTH-2]++;
13367                 }
13368             } else
13369             boards[0][y][x] = selection;
13370             DrawPosition(TRUE, boards[0]);
13371         }
13372         break;
13373     }
13374 }
13375
13376
13377 void
13378 DropMenuEvent(selection, x, y)
13379      ChessSquare selection;
13380      int x, y;
13381 {
13382     ChessMove moveType;
13383
13384     switch (gameMode) {
13385       case IcsPlayingWhite:
13386       case MachinePlaysBlack:
13387         if (!WhiteOnMove(currentMove)) {
13388             DisplayMoveError(_("It is Black's turn"));
13389             return;
13390         }
13391         moveType = WhiteDrop;
13392         break;
13393       case IcsPlayingBlack:
13394       case MachinePlaysWhite:
13395         if (WhiteOnMove(currentMove)) {
13396             DisplayMoveError(_("It is White's turn"));
13397             return;
13398         }
13399         moveType = BlackDrop;
13400         break;
13401       case EditGame:
13402         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13403         break;
13404       default:
13405         return;
13406     }
13407
13408     if (moveType == BlackDrop && selection < BlackPawn) {
13409       selection = (ChessSquare) ((int) selection
13410                                  + (int) BlackPawn - (int) WhitePawn);
13411     }
13412     if (boards[currentMove][y][x] != EmptySquare) {
13413         DisplayMoveError(_("That square is occupied"));
13414         return;
13415     }
13416
13417     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13418 }
13419
13420 void
13421 AcceptEvent()
13422 {
13423     /* Accept a pending offer of any kind from opponent */
13424
13425     if (appData.icsActive) {
13426         SendToICS(ics_prefix);
13427         SendToICS("accept\n");
13428     } else if (cmailMsgLoaded) {
13429         if (currentMove == cmailOldMove &&
13430             commentList[cmailOldMove] != NULL &&
13431             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13432                    "Black offers a draw" : "White offers a draw")) {
13433             TruncateGame();
13434             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13435             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13436         } else {
13437             DisplayError(_("There is no pending offer on this move"), 0);
13438             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13439         }
13440     } else {
13441         /* Not used for offers from chess program */
13442     }
13443 }
13444
13445 void
13446 DeclineEvent()
13447 {
13448     /* Decline a pending offer of any kind from opponent */
13449
13450     if (appData.icsActive) {
13451         SendToICS(ics_prefix);
13452         SendToICS("decline\n");
13453     } else if (cmailMsgLoaded) {
13454         if (currentMove == cmailOldMove &&
13455             commentList[cmailOldMove] != NULL &&
13456             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13457                    "Black offers a draw" : "White offers a draw")) {
13458 #ifdef NOTDEF
13459             AppendComment(cmailOldMove, "Draw declined", TRUE);
13460             DisplayComment(cmailOldMove - 1, "Draw declined");
13461 #endif /*NOTDEF*/
13462         } else {
13463             DisplayError(_("There is no pending offer on this move"), 0);
13464         }
13465     } else {
13466         /* Not used for offers from chess program */
13467     }
13468 }
13469
13470 void
13471 RematchEvent()
13472 {
13473     /* Issue ICS rematch command */
13474     if (appData.icsActive) {
13475         SendToICS(ics_prefix);
13476         SendToICS("rematch\n");
13477     }
13478 }
13479
13480 void
13481 CallFlagEvent()
13482 {
13483     /* Call your opponent's flag (claim a win on time) */
13484     if (appData.icsActive) {
13485         SendToICS(ics_prefix);
13486         SendToICS("flag\n");
13487     } else {
13488         switch (gameMode) {
13489           default:
13490             return;
13491           case MachinePlaysWhite:
13492             if (whiteFlag) {
13493                 if (blackFlag)
13494                   GameEnds(GameIsDrawn, "Both players ran out of time",
13495                            GE_PLAYER);
13496                 else
13497                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13498             } else {
13499                 DisplayError(_("Your opponent is not out of time"), 0);
13500             }
13501             break;
13502           case MachinePlaysBlack:
13503             if (blackFlag) {
13504                 if (whiteFlag)
13505                   GameEnds(GameIsDrawn, "Both players ran out of time",
13506                            GE_PLAYER);
13507                 else
13508                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13509             } else {
13510                 DisplayError(_("Your opponent is not out of time"), 0);
13511             }
13512             break;
13513         }
13514     }
13515 }
13516
13517 void
13518 ClockClick(int which)
13519 {       // [HGM] code moved to back-end from winboard.c
13520         if(which) { // black clock
13521           if (gameMode == EditPosition || gameMode == IcsExamining) {
13522             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13523             SetBlackToPlayEvent();
13524           } else if (gameMode == EditGame || shiftKey) {
13525             AdjustClock(which, -1);
13526           } else if (gameMode == IcsPlayingWhite ||
13527                      gameMode == MachinePlaysBlack) {
13528             CallFlagEvent();
13529           }
13530         } else { // white clock
13531           if (gameMode == EditPosition || gameMode == IcsExamining) {
13532             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13533             SetWhiteToPlayEvent();
13534           } else if (gameMode == EditGame || shiftKey) {
13535             AdjustClock(which, -1);
13536           } else if (gameMode == IcsPlayingBlack ||
13537                    gameMode == MachinePlaysWhite) {
13538             CallFlagEvent();
13539           }
13540         }
13541 }
13542
13543 void
13544 DrawEvent()
13545 {
13546     /* Offer draw or accept pending draw offer from opponent */
13547
13548     if (appData.icsActive) {
13549         /* Note: tournament rules require draw offers to be
13550            made after you make your move but before you punch
13551            your clock.  Currently ICS doesn't let you do that;
13552            instead, you immediately punch your clock after making
13553            a move, but you can offer a draw at any time. */
13554
13555         SendToICS(ics_prefix);
13556         SendToICS("draw\n");
13557         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13558     } else if (cmailMsgLoaded) {
13559         if (currentMove == cmailOldMove &&
13560             commentList[cmailOldMove] != NULL &&
13561             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13562                    "Black offers a draw" : "White offers a draw")) {
13563             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13564             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13565         } else if (currentMove == cmailOldMove + 1) {
13566             char *offer = WhiteOnMove(cmailOldMove) ?
13567               "White offers a draw" : "Black offers a draw";
13568             AppendComment(currentMove, offer, TRUE);
13569             DisplayComment(currentMove - 1, offer);
13570             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13571         } else {
13572             DisplayError(_("You must make your move before offering a draw"), 0);
13573             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13574         }
13575     } else if (first.offeredDraw) {
13576         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13577     } else {
13578         if (first.sendDrawOffers) {
13579             SendToProgram("draw\n", &first);
13580             userOfferedDraw = TRUE;
13581         }
13582     }
13583 }
13584
13585 void
13586 AdjournEvent()
13587 {
13588     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13589
13590     if (appData.icsActive) {
13591         SendToICS(ics_prefix);
13592         SendToICS("adjourn\n");
13593     } else {
13594         /* Currently GNU Chess doesn't offer or accept Adjourns */
13595     }
13596 }
13597
13598
13599 void
13600 AbortEvent()
13601 {
13602     /* Offer Abort or accept pending Abort offer from opponent */
13603
13604     if (appData.icsActive) {
13605         SendToICS(ics_prefix);
13606         SendToICS("abort\n");
13607     } else {
13608         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13609     }
13610 }
13611
13612 void
13613 ResignEvent()
13614 {
13615     /* Resign.  You can do this even if it's not your turn. */
13616
13617     if (appData.icsActive) {
13618         SendToICS(ics_prefix);
13619         SendToICS("resign\n");
13620     } else {
13621         switch (gameMode) {
13622           case MachinePlaysWhite:
13623             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13624             break;
13625           case MachinePlaysBlack:
13626             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13627             break;
13628           case EditGame:
13629             if (cmailMsgLoaded) {
13630                 TruncateGame();
13631                 if (WhiteOnMove(cmailOldMove)) {
13632                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13633                 } else {
13634                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13635                 }
13636                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13637             }
13638             break;
13639           default:
13640             break;
13641         }
13642     }
13643 }
13644
13645
13646 void
13647 StopObservingEvent()
13648 {
13649     /* Stop observing current games */
13650     SendToICS(ics_prefix);
13651     SendToICS("unobserve\n");
13652 }
13653
13654 void
13655 StopExaminingEvent()
13656 {
13657     /* Stop observing current game */
13658     SendToICS(ics_prefix);
13659     SendToICS("unexamine\n");
13660 }
13661
13662 void
13663 ForwardInner(target)
13664      int target;
13665 {
13666     int limit;
13667
13668     if (appData.debugMode)
13669         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13670                 target, currentMove, forwardMostMove);
13671
13672     if (gameMode == EditPosition)
13673       return;
13674
13675     if (gameMode == PlayFromGameFile && !pausing)
13676       PauseEvent();
13677
13678     if (gameMode == IcsExamining && pausing)
13679       limit = pauseExamForwardMostMove;
13680     else
13681       limit = forwardMostMove;
13682
13683     if (target > limit) target = limit;
13684
13685     if (target > 0 && moveList[target - 1][0]) {
13686         int fromX, fromY, toX, toY;
13687         toX = moveList[target - 1][2] - AAA;
13688         toY = moveList[target - 1][3] - ONE;
13689         if (moveList[target - 1][1] == '@') {
13690             if (appData.highlightLastMove) {
13691                 SetHighlights(-1, -1, toX, toY);
13692             }
13693         } else {
13694             fromX = moveList[target - 1][0] - AAA;
13695             fromY = moveList[target - 1][1] - ONE;
13696             if (target == currentMove + 1) {
13697                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13698             }
13699             if (appData.highlightLastMove) {
13700                 SetHighlights(fromX, fromY, toX, toY);
13701             }
13702         }
13703     }
13704     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13705         gameMode == Training || gameMode == PlayFromGameFile ||
13706         gameMode == AnalyzeFile) {
13707         while (currentMove < target) {
13708             SendMoveToProgram(currentMove++, &first);
13709         }
13710     } else {
13711         currentMove = target;
13712     }
13713
13714     if (gameMode == EditGame || gameMode == EndOfGame) {
13715         whiteTimeRemaining = timeRemaining[0][currentMove];
13716         blackTimeRemaining = timeRemaining[1][currentMove];
13717     }
13718     DisplayBothClocks();
13719     DisplayMove(currentMove - 1);
13720     DrawPosition(FALSE, boards[currentMove]);
13721     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13722     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13723         DisplayComment(currentMove - 1, commentList[currentMove]);
13724     }
13725     DisplayBook(currentMove);
13726 }
13727
13728
13729 void
13730 ForwardEvent()
13731 {
13732     if (gameMode == IcsExamining && !pausing) {
13733         SendToICS(ics_prefix);
13734         SendToICS("forward\n");
13735     } else {
13736         ForwardInner(currentMove + 1);
13737     }
13738 }
13739
13740 void
13741 ToEndEvent()
13742 {
13743     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13744         /* to optimze, we temporarily turn off analysis mode while we feed
13745          * the remaining moves to the engine. Otherwise we get analysis output
13746          * after each move.
13747          */
13748         if (first.analysisSupport) {
13749           SendToProgram("exit\nforce\n", &first);
13750           first.analyzing = FALSE;
13751         }
13752     }
13753
13754     if (gameMode == IcsExamining && !pausing) {
13755         SendToICS(ics_prefix);
13756         SendToICS("forward 999999\n");
13757     } else {
13758         ForwardInner(forwardMostMove);
13759     }
13760
13761     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13762         /* we have fed all the moves, so reactivate analysis mode */
13763         SendToProgram("analyze\n", &first);
13764         first.analyzing = TRUE;
13765         /*first.maybeThinking = TRUE;*/
13766         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13767     }
13768 }
13769
13770 void
13771 BackwardInner(target)
13772      int target;
13773 {
13774     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13775
13776     if (appData.debugMode)
13777         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13778                 target, currentMove, forwardMostMove);
13779
13780     if (gameMode == EditPosition) return;
13781     if (currentMove <= backwardMostMove) {
13782         ClearHighlights();
13783         DrawPosition(full_redraw, boards[currentMove]);
13784         return;
13785     }
13786     if (gameMode == PlayFromGameFile && !pausing)
13787       PauseEvent();
13788
13789     if (moveList[target][0]) {
13790         int fromX, fromY, toX, toY;
13791         toX = moveList[target][2] - AAA;
13792         toY = moveList[target][3] - ONE;
13793         if (moveList[target][1] == '@') {
13794             if (appData.highlightLastMove) {
13795                 SetHighlights(-1, -1, toX, toY);
13796             }
13797         } else {
13798             fromX = moveList[target][0] - AAA;
13799             fromY = moveList[target][1] - ONE;
13800             if (target == currentMove - 1) {
13801                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13802             }
13803             if (appData.highlightLastMove) {
13804                 SetHighlights(fromX, fromY, toX, toY);
13805             }
13806         }
13807     }
13808     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13809         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13810         while (currentMove > target) {
13811             SendToProgram("undo\n", &first);
13812             currentMove--;
13813         }
13814     } else {
13815         currentMove = target;
13816     }
13817
13818     if (gameMode == EditGame || gameMode == EndOfGame) {
13819         whiteTimeRemaining = timeRemaining[0][currentMove];
13820         blackTimeRemaining = timeRemaining[1][currentMove];
13821     }
13822     DisplayBothClocks();
13823     DisplayMove(currentMove - 1);
13824     DrawPosition(full_redraw, boards[currentMove]);
13825     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13826     // [HGM] PV info: routine tests if comment empty
13827     DisplayComment(currentMove - 1, commentList[currentMove]);
13828     DisplayBook(currentMove);
13829 }
13830
13831 void
13832 BackwardEvent()
13833 {
13834     if (gameMode == IcsExamining && !pausing) {
13835         SendToICS(ics_prefix);
13836         SendToICS("backward\n");
13837     } else {
13838         BackwardInner(currentMove - 1);
13839     }
13840 }
13841
13842 void
13843 ToStartEvent()
13844 {
13845     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13846         /* to optimize, we temporarily turn off analysis mode while we undo
13847          * all the moves. Otherwise we get analysis output after each undo.
13848          */
13849         if (first.analysisSupport) {
13850           SendToProgram("exit\nforce\n", &first);
13851           first.analyzing = FALSE;
13852         }
13853     }
13854
13855     if (gameMode == IcsExamining && !pausing) {
13856         SendToICS(ics_prefix);
13857         SendToICS("backward 999999\n");
13858     } else {
13859         BackwardInner(backwardMostMove);
13860     }
13861
13862     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13863         /* we have fed all the moves, so reactivate analysis mode */
13864         SendToProgram("analyze\n", &first);
13865         first.analyzing = TRUE;
13866         /*first.maybeThinking = TRUE;*/
13867         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13868     }
13869 }
13870
13871 void
13872 ToNrEvent(int to)
13873 {
13874   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13875   if (to >= forwardMostMove) to = forwardMostMove;
13876   if (to <= backwardMostMove) to = backwardMostMove;
13877   if (to < currentMove) {
13878     BackwardInner(to);
13879   } else {
13880     ForwardInner(to);
13881   }
13882 }
13883
13884 void
13885 RevertEvent(Boolean annotate)
13886 {
13887     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13888         return;
13889     }
13890     if (gameMode != IcsExamining) {
13891         DisplayError(_("You are not examining a game"), 0);
13892         return;
13893     }
13894     if (pausing) {
13895         DisplayError(_("You can't revert while pausing"), 0);
13896         return;
13897     }
13898     SendToICS(ics_prefix);
13899     SendToICS("revert\n");
13900 }
13901
13902 void
13903 RetractMoveEvent()
13904 {
13905     switch (gameMode) {
13906       case MachinePlaysWhite:
13907       case MachinePlaysBlack:
13908         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13909             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13910             return;
13911         }
13912         if (forwardMostMove < 2) return;
13913         currentMove = forwardMostMove = forwardMostMove - 2;
13914         whiteTimeRemaining = timeRemaining[0][currentMove];
13915         blackTimeRemaining = timeRemaining[1][currentMove];
13916         DisplayBothClocks();
13917         DisplayMove(currentMove - 1);
13918         ClearHighlights();/*!! could figure this out*/
13919         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13920         SendToProgram("remove\n", &first);
13921         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13922         break;
13923
13924       case BeginningOfGame:
13925       default:
13926         break;
13927
13928       case IcsPlayingWhite:
13929       case IcsPlayingBlack:
13930         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13931             SendToICS(ics_prefix);
13932             SendToICS("takeback 2\n");
13933         } else {
13934             SendToICS(ics_prefix);
13935             SendToICS("takeback 1\n");
13936         }
13937         break;
13938     }
13939 }
13940
13941 void
13942 MoveNowEvent()
13943 {
13944     ChessProgramState *cps;
13945
13946     switch (gameMode) {
13947       case MachinePlaysWhite:
13948         if (!WhiteOnMove(forwardMostMove)) {
13949             DisplayError(_("It is your turn"), 0);
13950             return;
13951         }
13952         cps = &first;
13953         break;
13954       case MachinePlaysBlack:
13955         if (WhiteOnMove(forwardMostMove)) {
13956             DisplayError(_("It is your turn"), 0);
13957             return;
13958         }
13959         cps = &first;
13960         break;
13961       case TwoMachinesPlay:
13962         if (WhiteOnMove(forwardMostMove) ==
13963             (first.twoMachinesColor[0] == 'w')) {
13964             cps = &first;
13965         } else {
13966             cps = &second;
13967         }
13968         break;
13969       case BeginningOfGame:
13970       default:
13971         return;
13972     }
13973     SendToProgram("?\n", cps);
13974 }
13975
13976 void
13977 TruncateGameEvent()
13978 {
13979     EditGameEvent();
13980     if (gameMode != EditGame) return;
13981     TruncateGame();
13982 }
13983
13984 void
13985 TruncateGame()
13986 {
13987     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13988     if (forwardMostMove > currentMove) {
13989         if (gameInfo.resultDetails != NULL) {
13990             free(gameInfo.resultDetails);
13991             gameInfo.resultDetails = NULL;
13992             gameInfo.result = GameUnfinished;
13993         }
13994         forwardMostMove = currentMove;
13995         HistorySet(parseList, backwardMostMove, forwardMostMove,
13996                    currentMove-1);
13997     }
13998 }
13999
14000 void
14001 HintEvent()
14002 {
14003     if (appData.noChessProgram) return;
14004     switch (gameMode) {
14005       case MachinePlaysWhite:
14006         if (WhiteOnMove(forwardMostMove)) {
14007             DisplayError(_("Wait until your turn"), 0);
14008             return;
14009         }
14010         break;
14011       case BeginningOfGame:
14012       case MachinePlaysBlack:
14013         if (!WhiteOnMove(forwardMostMove)) {
14014             DisplayError(_("Wait until your turn"), 0);
14015             return;
14016         }
14017         break;
14018       default:
14019         DisplayError(_("No hint available"), 0);
14020         return;
14021     }
14022     SendToProgram("hint\n", &first);
14023     hintRequested = TRUE;
14024 }
14025
14026 void
14027 BookEvent()
14028 {
14029     if (appData.noChessProgram) return;
14030     switch (gameMode) {
14031       case MachinePlaysWhite:
14032         if (WhiteOnMove(forwardMostMove)) {
14033             DisplayError(_("Wait until your turn"), 0);
14034             return;
14035         }
14036         break;
14037       case BeginningOfGame:
14038       case MachinePlaysBlack:
14039         if (!WhiteOnMove(forwardMostMove)) {
14040             DisplayError(_("Wait until your turn"), 0);
14041             return;
14042         }
14043         break;
14044       case EditPosition:
14045         EditPositionDone(TRUE);
14046         break;
14047       case TwoMachinesPlay:
14048         return;
14049       default:
14050         break;
14051     }
14052     SendToProgram("bk\n", &first);
14053     bookOutput[0] = NULLCHAR;
14054     bookRequested = TRUE;
14055 }
14056
14057 void
14058 AboutGameEvent()
14059 {
14060     char *tags = PGNTags(&gameInfo);
14061     TagsPopUp(tags, CmailMsg());
14062     free(tags);
14063 }
14064
14065 /* end button procedures */
14066
14067 void
14068 PrintPosition(fp, move)
14069      FILE *fp;
14070      int move;
14071 {
14072     int i, j;
14073
14074     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14075         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14076             char c = PieceToChar(boards[move][i][j]);
14077             fputc(c == 'x' ? '.' : c, fp);
14078             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14079         }
14080     }
14081     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14082       fprintf(fp, "white to play\n");
14083     else
14084       fprintf(fp, "black to play\n");
14085 }
14086
14087 void
14088 PrintOpponents(fp)
14089      FILE *fp;
14090 {
14091     if (gameInfo.white != NULL) {
14092         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14093     } else {
14094         fprintf(fp, "\n");
14095     }
14096 }
14097
14098 /* Find last component of program's own name, using some heuristics */
14099 void
14100 TidyProgramName(prog, host, buf)
14101      char *prog, *host, buf[MSG_SIZ];
14102 {
14103     char *p, *q;
14104     int local = (strcmp(host, "localhost") == 0);
14105     while (!local && (p = strchr(prog, ';')) != NULL) {
14106         p++;
14107         while (*p == ' ') p++;
14108         prog = p;
14109     }
14110     if (*prog == '"' || *prog == '\'') {
14111         q = strchr(prog + 1, *prog);
14112     } else {
14113         q = strchr(prog, ' ');
14114     }
14115     if (q == NULL) q = prog + strlen(prog);
14116     p = q;
14117     while (p >= prog && *p != '/' && *p != '\\') p--;
14118     p++;
14119     if(p == prog && *p == '"') p++;
14120     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14121     memcpy(buf, p, q - p);
14122     buf[q - p] = NULLCHAR;
14123     if (!local) {
14124         strcat(buf, "@");
14125         strcat(buf, host);
14126     }
14127 }
14128
14129 char *
14130 TimeControlTagValue()
14131 {
14132     char buf[MSG_SIZ];
14133     if (!appData.clockMode) {
14134       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14135     } else if (movesPerSession > 0) {
14136       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14137     } else if (timeIncrement == 0) {
14138       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14139     } else {
14140       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14141     }
14142     return StrSave(buf);
14143 }
14144
14145 void
14146 SetGameInfo()
14147 {
14148     /* This routine is used only for certain modes */
14149     VariantClass v = gameInfo.variant;
14150     ChessMove r = GameUnfinished;
14151     char *p = NULL;
14152
14153     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14154         r = gameInfo.result;
14155         p = gameInfo.resultDetails;
14156         gameInfo.resultDetails = NULL;
14157     }
14158     ClearGameInfo(&gameInfo);
14159     gameInfo.variant = v;
14160
14161     switch (gameMode) {
14162       case MachinePlaysWhite:
14163         gameInfo.event = StrSave( appData.pgnEventHeader );
14164         gameInfo.site = StrSave(HostName());
14165         gameInfo.date = PGNDate();
14166         gameInfo.round = StrSave("-");
14167         gameInfo.white = StrSave(first.tidy);
14168         gameInfo.black = StrSave(UserName());
14169         gameInfo.timeControl = TimeControlTagValue();
14170         break;
14171
14172       case MachinePlaysBlack:
14173         gameInfo.event = StrSave( appData.pgnEventHeader );
14174         gameInfo.site = StrSave(HostName());
14175         gameInfo.date = PGNDate();
14176         gameInfo.round = StrSave("-");
14177         gameInfo.white = StrSave(UserName());
14178         gameInfo.black = StrSave(first.tidy);
14179         gameInfo.timeControl = TimeControlTagValue();
14180         break;
14181
14182       case TwoMachinesPlay:
14183         gameInfo.event = StrSave( appData.pgnEventHeader );
14184         gameInfo.site = StrSave(HostName());
14185         gameInfo.date = PGNDate();
14186         if (roundNr > 0) {
14187             char buf[MSG_SIZ];
14188             snprintf(buf, MSG_SIZ, "%d", roundNr);
14189             gameInfo.round = StrSave(buf);
14190         } else {
14191             gameInfo.round = StrSave("-");
14192         }
14193         if (first.twoMachinesColor[0] == 'w') {
14194             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14195             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14196         } else {
14197             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14198             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14199         }
14200         gameInfo.timeControl = TimeControlTagValue();
14201         break;
14202
14203       case EditGame:
14204         gameInfo.event = StrSave("Edited game");
14205         gameInfo.site = StrSave(HostName());
14206         gameInfo.date = PGNDate();
14207         gameInfo.round = StrSave("-");
14208         gameInfo.white = StrSave("-");
14209         gameInfo.black = StrSave("-");
14210         gameInfo.result = r;
14211         gameInfo.resultDetails = p;
14212         break;
14213
14214       case EditPosition:
14215         gameInfo.event = StrSave("Edited position");
14216         gameInfo.site = StrSave(HostName());
14217         gameInfo.date = PGNDate();
14218         gameInfo.round = StrSave("-");
14219         gameInfo.white = StrSave("-");
14220         gameInfo.black = StrSave("-");
14221         break;
14222
14223       case IcsPlayingWhite:
14224       case IcsPlayingBlack:
14225       case IcsObserving:
14226       case IcsExamining:
14227         break;
14228
14229       case PlayFromGameFile:
14230         gameInfo.event = StrSave("Game from non-PGN file");
14231         gameInfo.site = StrSave(HostName());
14232         gameInfo.date = PGNDate();
14233         gameInfo.round = StrSave("-");
14234         gameInfo.white = StrSave("?");
14235         gameInfo.black = StrSave("?");
14236         break;
14237
14238       default:
14239         break;
14240     }
14241 }
14242
14243 void
14244 ReplaceComment(index, text)
14245      int index;
14246      char *text;
14247 {
14248     int len;
14249     char *p;
14250     float score;
14251
14252     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14253        pvInfoList[index-1].depth == len &&
14254        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14255        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14256     while (*text == '\n') text++;
14257     len = strlen(text);
14258     while (len > 0 && text[len - 1] == '\n') len--;
14259
14260     if (commentList[index] != NULL)
14261       free(commentList[index]);
14262
14263     if (len == 0) {
14264         commentList[index] = NULL;
14265         return;
14266     }
14267   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14268       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14269       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14270     commentList[index] = (char *) malloc(len + 2);
14271     strncpy(commentList[index], text, len);
14272     commentList[index][len] = '\n';
14273     commentList[index][len + 1] = NULLCHAR;
14274   } else {
14275     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14276     char *p;
14277     commentList[index] = (char *) malloc(len + 7);
14278     safeStrCpy(commentList[index], "{\n", 3);
14279     safeStrCpy(commentList[index]+2, text, len+1);
14280     commentList[index][len+2] = NULLCHAR;
14281     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14282     strcat(commentList[index], "\n}\n");
14283   }
14284 }
14285
14286 void
14287 CrushCRs(text)
14288      char *text;
14289 {
14290   char *p = text;
14291   char *q = text;
14292   char ch;
14293
14294   do {
14295     ch = *p++;
14296     if (ch == '\r') continue;
14297     *q++ = ch;
14298   } while (ch != '\0');
14299 }
14300
14301 void
14302 AppendComment(index, text, addBraces)
14303      int index;
14304      char *text;
14305      Boolean addBraces; // [HGM] braces: tells if we should add {}
14306 {
14307     int oldlen, len;
14308     char *old;
14309
14310 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14311     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14312
14313     CrushCRs(text);
14314     while (*text == '\n') text++;
14315     len = strlen(text);
14316     while (len > 0 && text[len - 1] == '\n') len--;
14317
14318     if (len == 0) return;
14319
14320     if (commentList[index] != NULL) {
14321         old = commentList[index];
14322         oldlen = strlen(old);
14323         while(commentList[index][oldlen-1] ==  '\n')
14324           commentList[index][--oldlen] = NULLCHAR;
14325         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14326         safeStrCpy(commentList[index], old, oldlen + len + 6);
14327         free(old);
14328         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14329         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14330           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14331           while (*text == '\n') { text++; len--; }
14332           commentList[index][--oldlen] = NULLCHAR;
14333       }
14334         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14335         else          strcat(commentList[index], "\n");
14336         strcat(commentList[index], text);
14337         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14338         else          strcat(commentList[index], "\n");
14339     } else {
14340         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14341         if(addBraces)
14342           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14343         else commentList[index][0] = NULLCHAR;
14344         strcat(commentList[index], text);
14345         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14346         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14347     }
14348 }
14349
14350 static char * FindStr( char * text, char * sub_text )
14351 {
14352     char * result = strstr( text, sub_text );
14353
14354     if( result != NULL ) {
14355         result += strlen( sub_text );
14356     }
14357
14358     return result;
14359 }
14360
14361 /* [AS] Try to extract PV info from PGN comment */
14362 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14363 char *GetInfoFromComment( int index, char * text )
14364 {
14365     char * sep = text, *p;
14366
14367     if( text != NULL && index > 0 ) {
14368         int score = 0;
14369         int depth = 0;
14370         int time = -1, sec = 0, deci;
14371         char * s_eval = FindStr( text, "[%eval " );
14372         char * s_emt = FindStr( text, "[%emt " );
14373
14374         if( s_eval != NULL || s_emt != NULL ) {
14375             /* New style */
14376             char delim;
14377
14378             if( s_eval != NULL ) {
14379                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14380                     return text;
14381                 }
14382
14383                 if( delim != ']' ) {
14384                     return text;
14385                 }
14386             }
14387
14388             if( s_emt != NULL ) {
14389             }
14390                 return text;
14391         }
14392         else {
14393             /* We expect something like: [+|-]nnn.nn/dd */
14394             int score_lo = 0;
14395
14396             if(*text != '{') return text; // [HGM] braces: must be normal comment
14397
14398             sep = strchr( text, '/' );
14399             if( sep == NULL || sep < (text+4) ) {
14400                 return text;
14401             }
14402
14403             p = text;
14404             if(p[1] == '(') { // comment starts with PV
14405                p = strchr(p, ')'); // locate end of PV
14406                if(p == NULL || sep < p+5) return text;
14407                // at this point we have something like "{(.*) +0.23/6 ..."
14408                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14409                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14410                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14411             }
14412             time = -1; sec = -1; deci = -1;
14413             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14414                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14415                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14416                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14417                 return text;
14418             }
14419
14420             if( score_lo < 0 || score_lo >= 100 ) {
14421                 return text;
14422             }
14423
14424             if(sec >= 0) time = 600*time + 10*sec; else
14425             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14426
14427             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14428
14429             /* [HGM] PV time: now locate end of PV info */
14430             while( *++sep >= '0' && *sep <= '9'); // strip depth
14431             if(time >= 0)
14432             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14433             if(sec >= 0)
14434             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14435             if(deci >= 0)
14436             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14437             while(*sep == ' ') sep++;
14438         }
14439
14440         if( depth <= 0 ) {
14441             return text;
14442         }
14443
14444         if( time < 0 ) {
14445             time = -1;
14446         }
14447
14448         pvInfoList[index-1].depth = depth;
14449         pvInfoList[index-1].score = score;
14450         pvInfoList[index-1].time  = 10*time; // centi-sec
14451         if(*sep == '}') *sep = 0; else *--sep = '{';
14452         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14453     }
14454     return sep;
14455 }
14456
14457 void
14458 SendToProgram(message, cps)
14459      char *message;
14460      ChessProgramState *cps;
14461 {
14462     int count, outCount, error;
14463     char buf[MSG_SIZ];
14464
14465     if (cps->pr == NULL) return;
14466     Attention(cps);
14467
14468     if (appData.debugMode) {
14469         TimeMark now;
14470         GetTimeMark(&now);
14471         fprintf(debugFP, "%ld >%-6s: %s",
14472                 SubtractTimeMarks(&now, &programStartTime),
14473                 cps->which, message);
14474     }
14475
14476     count = strlen(message);
14477     outCount = OutputToProcess(cps->pr, message, count, &error);
14478     if (outCount < count && !exiting
14479                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14480       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14481       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14482         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14483             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14484                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14485                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14486                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14487             } else {
14488                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14489                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14490                 gameInfo.result = res;
14491             }
14492             gameInfo.resultDetails = StrSave(buf);
14493         }
14494         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14495         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14496     }
14497 }
14498
14499 void
14500 ReceiveFromProgram(isr, closure, message, count, error)
14501      InputSourceRef isr;
14502      VOIDSTAR closure;
14503      char *message;
14504      int count;
14505      int error;
14506 {
14507     char *end_str;
14508     char buf[MSG_SIZ];
14509     ChessProgramState *cps = (ChessProgramState *)closure;
14510
14511     if (isr != cps->isr) return; /* Killed intentionally */
14512     if (count <= 0) {
14513         if (count == 0) {
14514             RemoveInputSource(cps->isr);
14515             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14516             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14517                     _(cps->which), cps->program);
14518         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14519                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14520                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14521                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14522                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14523                 } else {
14524                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14525                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14526                     gameInfo.result = res;
14527                 }
14528                 gameInfo.resultDetails = StrSave(buf);
14529             }
14530             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14531             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14532         } else {
14533             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14534                     _(cps->which), cps->program);
14535             RemoveInputSource(cps->isr);
14536
14537             /* [AS] Program is misbehaving badly... kill it */
14538             if( count == -2 ) {
14539                 DestroyChildProcess( cps->pr, 9 );
14540                 cps->pr = NoProc;
14541             }
14542
14543             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14544         }
14545         return;
14546     }
14547
14548     if ((end_str = strchr(message, '\r')) != NULL)
14549       *end_str = NULLCHAR;
14550     if ((end_str = strchr(message, '\n')) != NULL)
14551       *end_str = NULLCHAR;
14552
14553     if (appData.debugMode) {
14554         TimeMark now; int print = 1;
14555         char *quote = ""; char c; int i;
14556
14557         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14558                 char start = message[0];
14559                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14560                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14561                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14562                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14563                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14564                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14565                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14566                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14567                    sscanf(message, "hint: %c", &c)!=1 && 
14568                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14569                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14570                     print = (appData.engineComments >= 2);
14571                 }
14572                 message[0] = start; // restore original message
14573         }
14574         if(print) {
14575                 GetTimeMark(&now);
14576                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14577                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14578                         quote,
14579                         message);
14580         }
14581     }
14582
14583     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14584     if (appData.icsEngineAnalyze) {
14585         if (strstr(message, "whisper") != NULL ||
14586              strstr(message, "kibitz") != NULL ||
14587             strstr(message, "tellics") != NULL) return;
14588     }
14589
14590     HandleMachineMove(message, cps);
14591 }
14592
14593
14594 void
14595 SendTimeControl(cps, mps, tc, inc, sd, st)
14596      ChessProgramState *cps;
14597      int mps, inc, sd, st;
14598      long tc;
14599 {
14600     char buf[MSG_SIZ];
14601     int seconds;
14602
14603     if( timeControl_2 > 0 ) {
14604         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14605             tc = timeControl_2;
14606         }
14607     }
14608     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14609     inc /= cps->timeOdds;
14610     st  /= cps->timeOdds;
14611
14612     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14613
14614     if (st > 0) {
14615       /* Set exact time per move, normally using st command */
14616       if (cps->stKludge) {
14617         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14618         seconds = st % 60;
14619         if (seconds == 0) {
14620           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14621         } else {
14622           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14623         }
14624       } else {
14625         snprintf(buf, MSG_SIZ, "st %d\n", st);
14626       }
14627     } else {
14628       /* Set conventional or incremental time control, using level command */
14629       if (seconds == 0) {
14630         /* Note old gnuchess bug -- minutes:seconds used to not work.
14631            Fixed in later versions, but still avoid :seconds
14632            when seconds is 0. */
14633         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14634       } else {
14635         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14636                  seconds, inc/1000.);
14637       }
14638     }
14639     SendToProgram(buf, cps);
14640
14641     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14642     /* Orthogonally, limit search to given depth */
14643     if (sd > 0) {
14644       if (cps->sdKludge) {
14645         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14646       } else {
14647         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14648       }
14649       SendToProgram(buf, cps);
14650     }
14651
14652     if(cps->nps >= 0) { /* [HGM] nps */
14653         if(cps->supportsNPS == FALSE)
14654           cps->nps = -1; // don't use if engine explicitly says not supported!
14655         else {
14656           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14657           SendToProgram(buf, cps);
14658         }
14659     }
14660 }
14661
14662 ChessProgramState *WhitePlayer()
14663 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14664 {
14665     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14666        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14667         return &second;
14668     return &first;
14669 }
14670
14671 void
14672 SendTimeRemaining(cps, machineWhite)
14673      ChessProgramState *cps;
14674      int /*boolean*/ machineWhite;
14675 {
14676     char message[MSG_SIZ];
14677     long time, otime;
14678
14679     /* Note: this routine must be called when the clocks are stopped
14680        or when they have *just* been set or switched; otherwise
14681        it will be off by the time since the current tick started.
14682     */
14683     if (machineWhite) {
14684         time = whiteTimeRemaining / 10;
14685         otime = blackTimeRemaining / 10;
14686     } else {
14687         time = blackTimeRemaining / 10;
14688         otime = whiteTimeRemaining / 10;
14689     }
14690     /* [HGM] translate opponent's time by time-odds factor */
14691     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14692     if (appData.debugMode) {
14693         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14694     }
14695
14696     if (time <= 0) time = 1;
14697     if (otime <= 0) otime = 1;
14698
14699     snprintf(message, MSG_SIZ, "time %ld\n", time);
14700     SendToProgram(message, cps);
14701
14702     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14703     SendToProgram(message, cps);
14704 }
14705
14706 int
14707 BoolFeature(p, name, loc, cps)
14708      char **p;
14709      char *name;
14710      int *loc;
14711      ChessProgramState *cps;
14712 {
14713   char buf[MSG_SIZ];
14714   int len = strlen(name);
14715   int val;
14716
14717   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14718     (*p) += len + 1;
14719     sscanf(*p, "%d", &val);
14720     *loc = (val != 0);
14721     while (**p && **p != ' ')
14722       (*p)++;
14723     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14724     SendToProgram(buf, cps);
14725     return TRUE;
14726   }
14727   return FALSE;
14728 }
14729
14730 int
14731 IntFeature(p, name, loc, cps)
14732      char **p;
14733      char *name;
14734      int *loc;
14735      ChessProgramState *cps;
14736 {
14737   char buf[MSG_SIZ];
14738   int len = strlen(name);
14739   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14740     (*p) += len + 1;
14741     sscanf(*p, "%d", loc);
14742     while (**p && **p != ' ') (*p)++;
14743     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14744     SendToProgram(buf, cps);
14745     return TRUE;
14746   }
14747   return FALSE;
14748 }
14749
14750 int
14751 StringFeature(p, name, loc, cps)
14752      char **p;
14753      char *name;
14754      char loc[];
14755      ChessProgramState *cps;
14756 {
14757   char buf[MSG_SIZ];
14758   int len = strlen(name);
14759   if (strncmp((*p), name, len) == 0
14760       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14761     (*p) += len + 2;
14762     sscanf(*p, "%[^\"]", loc);
14763     while (**p && **p != '\"') (*p)++;
14764     if (**p == '\"') (*p)++;
14765     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14766     SendToProgram(buf, cps);
14767     return TRUE;
14768   }
14769   return FALSE;
14770 }
14771
14772 int
14773 ParseOption(Option *opt, ChessProgramState *cps)
14774 // [HGM] options: process the string that defines an engine option, and determine
14775 // name, type, default value, and allowed value range
14776 {
14777         char *p, *q, buf[MSG_SIZ];
14778         int n, min = (-1)<<31, max = 1<<31, def;
14779
14780         if(p = strstr(opt->name, " -spin ")) {
14781             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14782             if(max < min) max = min; // enforce consistency
14783             if(def < min) def = min;
14784             if(def > max) def = max;
14785             opt->value = def;
14786             opt->min = min;
14787             opt->max = max;
14788             opt->type = Spin;
14789         } else if((p = strstr(opt->name, " -slider "))) {
14790             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14791             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14792             if(max < min) max = min; // enforce consistency
14793             if(def < min) def = min;
14794             if(def > max) def = max;
14795             opt->value = def;
14796             opt->min = min;
14797             opt->max = max;
14798             opt->type = Spin; // Slider;
14799         } else if((p = strstr(opt->name, " -string "))) {
14800             opt->textValue = p+9;
14801             opt->type = TextBox;
14802         } else if((p = strstr(opt->name, " -file "))) {
14803             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14804             opt->textValue = p+7;
14805             opt->type = FileName; // FileName;
14806         } else if((p = strstr(opt->name, " -path "))) {
14807             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14808             opt->textValue = p+7;
14809             opt->type = PathName; // PathName;
14810         } else if(p = strstr(opt->name, " -check ")) {
14811             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14812             opt->value = (def != 0);
14813             opt->type = CheckBox;
14814         } else if(p = strstr(opt->name, " -combo ")) {
14815             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14816             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14817             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14818             opt->value = n = 0;
14819             while(q = StrStr(q, " /// ")) {
14820                 n++; *q = 0;    // count choices, and null-terminate each of them
14821                 q += 5;
14822                 if(*q == '*') { // remember default, which is marked with * prefix
14823                     q++;
14824                     opt->value = n;
14825                 }
14826                 cps->comboList[cps->comboCnt++] = q;
14827             }
14828             cps->comboList[cps->comboCnt++] = NULL;
14829             opt->max = n + 1;
14830             opt->type = ComboBox;
14831         } else if(p = strstr(opt->name, " -button")) {
14832             opt->type = Button;
14833         } else if(p = strstr(opt->name, " -save")) {
14834             opt->type = SaveButton;
14835         } else return FALSE;
14836         *p = 0; // terminate option name
14837         // now look if the command-line options define a setting for this engine option.
14838         if(cps->optionSettings && cps->optionSettings[0])
14839             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14840         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14841           snprintf(buf, MSG_SIZ, "option %s", p);
14842                 if(p = strstr(buf, ",")) *p = 0;
14843                 if(q = strchr(buf, '=')) switch(opt->type) {
14844                     case ComboBox:
14845                         for(n=0; n<opt->max; n++)
14846                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14847                         break;
14848                     case TextBox:
14849                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14850                         break;
14851                     case Spin:
14852                     case CheckBox:
14853                         opt->value = atoi(q+1);
14854                     default:
14855                         break;
14856                 }
14857                 strcat(buf, "\n");
14858                 SendToProgram(buf, cps);
14859         }
14860         return TRUE;
14861 }
14862
14863 void
14864 FeatureDone(cps, val)
14865      ChessProgramState* cps;
14866      int val;
14867 {
14868   DelayedEventCallback cb = GetDelayedEvent();
14869   if ((cb == InitBackEnd3 && cps == &first) ||
14870       (cb == SettingsMenuIfReady && cps == &second) ||
14871       (cb == LoadEngine) ||
14872       (cb == TwoMachinesEventIfReady)) {
14873     CancelDelayedEvent();
14874     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14875   }
14876   cps->initDone = val;
14877 }
14878
14879 /* Parse feature command from engine */
14880 void
14881 ParseFeatures(args, cps)
14882      char* args;
14883      ChessProgramState *cps;
14884 {
14885   char *p = args;
14886   char *q;
14887   int val;
14888   char buf[MSG_SIZ];
14889
14890   for (;;) {
14891     while (*p == ' ') p++;
14892     if (*p == NULLCHAR) return;
14893
14894     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14895     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14896     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14897     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14898     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14899     if (BoolFeature(&p, "reuse", &val, cps)) {
14900       /* Engine can disable reuse, but can't enable it if user said no */
14901       if (!val) cps->reuse = FALSE;
14902       continue;
14903     }
14904     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14905     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14906       if (gameMode == TwoMachinesPlay) {
14907         DisplayTwoMachinesTitle();
14908       } else {
14909         DisplayTitle("");
14910       }
14911       continue;
14912     }
14913     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14914     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14915     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14916     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14917     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14918     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14919     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14920     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14921     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14922     if (IntFeature(&p, "done", &val, cps)) {
14923       FeatureDone(cps, val);
14924       continue;
14925     }
14926     /* Added by Tord: */
14927     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14928     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14929     /* End of additions by Tord */
14930
14931     /* [HGM] added features: */
14932     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14933     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14934     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14935     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14936     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14937     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14938     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14939         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14940           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14941             SendToProgram(buf, cps);
14942             continue;
14943         }
14944         if(cps->nrOptions >= MAX_OPTIONS) {
14945             cps->nrOptions--;
14946             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14947             DisplayError(buf, 0);
14948         }
14949         continue;
14950     }
14951     /* End of additions by HGM */
14952
14953     /* unknown feature: complain and skip */
14954     q = p;
14955     while (*q && *q != '=') q++;
14956     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14957     SendToProgram(buf, cps);
14958     p = q;
14959     if (*p == '=') {
14960       p++;
14961       if (*p == '\"') {
14962         p++;
14963         while (*p && *p != '\"') p++;
14964         if (*p == '\"') p++;
14965       } else {
14966         while (*p && *p != ' ') p++;
14967       }
14968     }
14969   }
14970
14971 }
14972
14973 void
14974 PeriodicUpdatesEvent(newState)
14975      int newState;
14976 {
14977     if (newState == appData.periodicUpdates)
14978       return;
14979
14980     appData.periodicUpdates=newState;
14981
14982     /* Display type changes, so update it now */
14983 //    DisplayAnalysis();
14984
14985     /* Get the ball rolling again... */
14986     if (newState) {
14987         AnalysisPeriodicEvent(1);
14988         StartAnalysisClock();
14989     }
14990 }
14991
14992 void
14993 PonderNextMoveEvent(newState)
14994      int newState;
14995 {
14996     if (newState == appData.ponderNextMove) return;
14997     if (gameMode == EditPosition) EditPositionDone(TRUE);
14998     if (newState) {
14999         SendToProgram("hard\n", &first);
15000         if (gameMode == TwoMachinesPlay) {
15001             SendToProgram("hard\n", &second);
15002         }
15003     } else {
15004         SendToProgram("easy\n", &first);
15005         thinkOutput[0] = NULLCHAR;
15006         if (gameMode == TwoMachinesPlay) {
15007             SendToProgram("easy\n", &second);
15008         }
15009     }
15010     appData.ponderNextMove = newState;
15011 }
15012
15013 void
15014 NewSettingEvent(option, feature, command, value)
15015      char *command;
15016      int option, value, *feature;
15017 {
15018     char buf[MSG_SIZ];
15019
15020     if (gameMode == EditPosition) EditPositionDone(TRUE);
15021     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15022     if(feature == NULL || *feature) SendToProgram(buf, &first);
15023     if (gameMode == TwoMachinesPlay) {
15024         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15025     }
15026 }
15027
15028 void
15029 ShowThinkingEvent()
15030 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15031 {
15032     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15033     int newState = appData.showThinking
15034         // [HGM] thinking: other features now need thinking output as well
15035         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15036
15037     if (oldState == newState) return;
15038     oldState = newState;
15039     if (gameMode == EditPosition) EditPositionDone(TRUE);
15040     if (oldState) {
15041         SendToProgram("post\n", &first);
15042         if (gameMode == TwoMachinesPlay) {
15043             SendToProgram("post\n", &second);
15044         }
15045     } else {
15046         SendToProgram("nopost\n", &first);
15047         thinkOutput[0] = NULLCHAR;
15048         if (gameMode == TwoMachinesPlay) {
15049             SendToProgram("nopost\n", &second);
15050         }
15051     }
15052 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15053 }
15054
15055 void
15056 AskQuestionEvent(title, question, replyPrefix, which)
15057      char *title; char *question; char *replyPrefix; char *which;
15058 {
15059   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15060   if (pr == NoProc) return;
15061   AskQuestion(title, question, replyPrefix, pr);
15062 }
15063
15064 void
15065 TypeInEvent(char firstChar)
15066 {
15067     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15068         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15069         gameMode == AnalyzeMode || gameMode == EditGame || 
15070         gameMode == EditPosition || gameMode == IcsExamining ||
15071         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15072         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15073                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15074                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15075         gameMode == Training) PopUpMoveDialog(firstChar);
15076 }
15077
15078 void
15079 TypeInDoneEvent(char *move)
15080 {
15081         Board board;
15082         int n, fromX, fromY, toX, toY;
15083         char promoChar;
15084         ChessMove moveType;
15085
15086         // [HGM] FENedit
15087         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15088                 EditPositionPasteFEN(move);
15089                 return;
15090         }
15091         // [HGM] movenum: allow move number to be typed in any mode
15092         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15093           ToNrEvent(2*n-1);
15094           return;
15095         }
15096
15097       if (gameMode != EditGame && currentMove != forwardMostMove && 
15098         gameMode != Training) {
15099         DisplayMoveError(_("Displayed move is not current"));
15100       } else {
15101         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15102           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15103         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15104         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15105           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15106           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15107         } else {
15108           DisplayMoveError(_("Could not parse move"));
15109         }
15110       }
15111 }
15112
15113 void
15114 DisplayMove(moveNumber)
15115      int moveNumber;
15116 {
15117     char message[MSG_SIZ];
15118     char res[MSG_SIZ];
15119     char cpThinkOutput[MSG_SIZ];
15120
15121     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15122
15123     if (moveNumber == forwardMostMove - 1 ||
15124         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15125
15126         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15127
15128         if (strchr(cpThinkOutput, '\n')) {
15129             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15130         }
15131     } else {
15132         *cpThinkOutput = NULLCHAR;
15133     }
15134
15135     /* [AS] Hide thinking from human user */
15136     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15137         *cpThinkOutput = NULLCHAR;
15138         if( thinkOutput[0] != NULLCHAR ) {
15139             int i;
15140
15141             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15142                 cpThinkOutput[i] = '.';
15143             }
15144             cpThinkOutput[i] = NULLCHAR;
15145             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15146         }
15147     }
15148
15149     if (moveNumber == forwardMostMove - 1 &&
15150         gameInfo.resultDetails != NULL) {
15151         if (gameInfo.resultDetails[0] == NULLCHAR) {
15152           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15153         } else {
15154           snprintf(res, MSG_SIZ, " {%s} %s",
15155                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15156         }
15157     } else {
15158         res[0] = NULLCHAR;
15159     }
15160
15161     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15162         DisplayMessage(res, cpThinkOutput);
15163     } else {
15164       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15165                 WhiteOnMove(moveNumber) ? " " : ".. ",
15166                 parseList[moveNumber], res);
15167         DisplayMessage(message, cpThinkOutput);
15168     }
15169 }
15170
15171 void
15172 DisplayComment(moveNumber, text)
15173      int moveNumber;
15174      char *text;
15175 {
15176     char title[MSG_SIZ];
15177     char buf[8000]; // comment can be long!
15178     int score, depth;
15179
15180     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15181       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15182     } else {
15183       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15184               WhiteOnMove(moveNumber) ? " " : ".. ",
15185               parseList[moveNumber]);
15186     }
15187     // [HGM] PV info: display PV info together with (or as) comment
15188     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15189       if(text == NULL) text = "";
15190       score = pvInfoList[moveNumber].score;
15191       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15192               depth, (pvInfoList[moveNumber].time+50)/100, text);
15193       text = buf;
15194     }
15195     if (text != NULL && (appData.autoDisplayComment || commentUp))
15196         CommentPopUp(title, text);
15197 }
15198
15199 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15200  * might be busy thinking or pondering.  It can be omitted if your
15201  * gnuchess is configured to stop thinking immediately on any user
15202  * input.  However, that gnuchess feature depends on the FIONREAD
15203  * ioctl, which does not work properly on some flavors of Unix.
15204  */
15205 void
15206 Attention(cps)
15207      ChessProgramState *cps;
15208 {
15209 #if ATTENTION
15210     if (!cps->useSigint) return;
15211     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15212     switch (gameMode) {
15213       case MachinePlaysWhite:
15214       case MachinePlaysBlack:
15215       case TwoMachinesPlay:
15216       case IcsPlayingWhite:
15217       case IcsPlayingBlack:
15218       case AnalyzeMode:
15219       case AnalyzeFile:
15220         /* Skip if we know it isn't thinking */
15221         if (!cps->maybeThinking) return;
15222         if (appData.debugMode)
15223           fprintf(debugFP, "Interrupting %s\n", cps->which);
15224         InterruptChildProcess(cps->pr);
15225         cps->maybeThinking = FALSE;
15226         break;
15227       default:
15228         break;
15229     }
15230 #endif /*ATTENTION*/
15231 }
15232
15233 int
15234 CheckFlags()
15235 {
15236     if (whiteTimeRemaining <= 0) {
15237         if (!whiteFlag) {
15238             whiteFlag = TRUE;
15239             if (appData.icsActive) {
15240                 if (appData.autoCallFlag &&
15241                     gameMode == IcsPlayingBlack && !blackFlag) {
15242                   SendToICS(ics_prefix);
15243                   SendToICS("flag\n");
15244                 }
15245             } else {
15246                 if (blackFlag) {
15247                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15248                 } else {
15249                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15250                     if (appData.autoCallFlag) {
15251                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15252                         return TRUE;
15253                     }
15254                 }
15255             }
15256         }
15257     }
15258     if (blackTimeRemaining <= 0) {
15259         if (!blackFlag) {
15260             blackFlag = TRUE;
15261             if (appData.icsActive) {
15262                 if (appData.autoCallFlag &&
15263                     gameMode == IcsPlayingWhite && !whiteFlag) {
15264                   SendToICS(ics_prefix);
15265                   SendToICS("flag\n");
15266                 }
15267             } else {
15268                 if (whiteFlag) {
15269                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15270                 } else {
15271                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15272                     if (appData.autoCallFlag) {
15273                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15274                         return TRUE;
15275                     }
15276                 }
15277             }
15278         }
15279     }
15280     return FALSE;
15281 }
15282
15283 void
15284 CheckTimeControl()
15285 {
15286     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15287         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15288
15289     /*
15290      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15291      */
15292     if ( !WhiteOnMove(forwardMostMove) ) {
15293         /* White made time control */
15294         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15295         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15296         /* [HGM] time odds: correct new time quota for time odds! */
15297                                             / WhitePlayer()->timeOdds;
15298         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15299     } else {
15300         lastBlack -= blackTimeRemaining;
15301         /* Black made time control */
15302         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15303                                             / WhitePlayer()->other->timeOdds;
15304         lastWhite = whiteTimeRemaining;
15305     }
15306 }
15307
15308 void
15309 DisplayBothClocks()
15310 {
15311     int wom = gameMode == EditPosition ?
15312       !blackPlaysFirst : WhiteOnMove(currentMove);
15313     DisplayWhiteClock(whiteTimeRemaining, wom);
15314     DisplayBlackClock(blackTimeRemaining, !wom);
15315 }
15316
15317
15318 /* Timekeeping seems to be a portability nightmare.  I think everyone
15319    has ftime(), but I'm really not sure, so I'm including some ifdefs
15320    to use other calls if you don't.  Clocks will be less accurate if
15321    you have neither ftime nor gettimeofday.
15322 */
15323
15324 /* VS 2008 requires the #include outside of the function */
15325 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15326 #include <sys/timeb.h>
15327 #endif
15328
15329 /* Get the current time as a TimeMark */
15330 void
15331 GetTimeMark(tm)
15332      TimeMark *tm;
15333 {
15334 #if HAVE_GETTIMEOFDAY
15335
15336     struct timeval timeVal;
15337     struct timezone timeZone;
15338
15339     gettimeofday(&timeVal, &timeZone);
15340     tm->sec = (long) timeVal.tv_sec;
15341     tm->ms = (int) (timeVal.tv_usec / 1000L);
15342
15343 #else /*!HAVE_GETTIMEOFDAY*/
15344 #if HAVE_FTIME
15345
15346 // include <sys/timeb.h> / moved to just above start of function
15347     struct timeb timeB;
15348
15349     ftime(&timeB);
15350     tm->sec = (long) timeB.time;
15351     tm->ms = (int) timeB.millitm;
15352
15353 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15354     tm->sec = (long) time(NULL);
15355     tm->ms = 0;
15356 #endif
15357 #endif
15358 }
15359
15360 /* Return the difference in milliseconds between two
15361    time marks.  We assume the difference will fit in a long!
15362 */
15363 long
15364 SubtractTimeMarks(tm2, tm1)
15365      TimeMark *tm2, *tm1;
15366 {
15367     return 1000L*(tm2->sec - tm1->sec) +
15368            (long) (tm2->ms - tm1->ms);
15369 }
15370
15371
15372 /*
15373  * Code to manage the game clocks.
15374  *
15375  * In tournament play, black starts the clock and then white makes a move.
15376  * We give the human user a slight advantage if he is playing white---the
15377  * clocks don't run until he makes his first move, so it takes zero time.
15378  * Also, we don't account for network lag, so we could get out of sync
15379  * with GNU Chess's clock -- but then, referees are always right.
15380  */
15381
15382 static TimeMark tickStartTM;
15383 static long intendedTickLength;
15384
15385 long
15386 NextTickLength(timeRemaining)
15387      long timeRemaining;
15388 {
15389     long nominalTickLength, nextTickLength;
15390
15391     if (timeRemaining > 0L && timeRemaining <= 10000L)
15392       nominalTickLength = 100L;
15393     else
15394       nominalTickLength = 1000L;
15395     nextTickLength = timeRemaining % nominalTickLength;
15396     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15397
15398     return nextTickLength;
15399 }
15400
15401 /* Adjust clock one minute up or down */
15402 void
15403 AdjustClock(Boolean which, int dir)
15404 {
15405     if(which) blackTimeRemaining += 60000*dir;
15406     else      whiteTimeRemaining += 60000*dir;
15407     DisplayBothClocks();
15408 }
15409
15410 /* Stop clocks and reset to a fresh time control */
15411 void
15412 ResetClocks()
15413 {
15414     (void) StopClockTimer();
15415     if (appData.icsActive) {
15416         whiteTimeRemaining = blackTimeRemaining = 0;
15417     } else if (searchTime) {
15418         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15419         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15420     } else { /* [HGM] correct new time quote for time odds */
15421         whiteTC = blackTC = fullTimeControlString;
15422         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15423         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15424     }
15425     if (whiteFlag || blackFlag) {
15426         DisplayTitle("");
15427         whiteFlag = blackFlag = FALSE;
15428     }
15429     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15430     DisplayBothClocks();
15431 }
15432
15433 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15434
15435 /* Decrement running clock by amount of time that has passed */
15436 void
15437 DecrementClocks()
15438 {
15439     long timeRemaining;
15440     long lastTickLength, fudge;
15441     TimeMark now;
15442
15443     if (!appData.clockMode) return;
15444     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15445
15446     GetTimeMark(&now);
15447
15448     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15449
15450     /* Fudge if we woke up a little too soon */
15451     fudge = intendedTickLength - lastTickLength;
15452     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15453
15454     if (WhiteOnMove(forwardMostMove)) {
15455         if(whiteNPS >= 0) lastTickLength = 0;
15456         timeRemaining = whiteTimeRemaining -= lastTickLength;
15457         if(timeRemaining < 0 && !appData.icsActive) {
15458             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15459             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15460                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15461                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15462             }
15463         }
15464         DisplayWhiteClock(whiteTimeRemaining - fudge,
15465                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15466     } else {
15467         if(blackNPS >= 0) lastTickLength = 0;
15468         timeRemaining = blackTimeRemaining -= lastTickLength;
15469         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15470             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15471             if(suddenDeath) {
15472                 blackStartMove = forwardMostMove;
15473                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15474             }
15475         }
15476         DisplayBlackClock(blackTimeRemaining - fudge,
15477                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15478     }
15479     if (CheckFlags()) return;
15480
15481     tickStartTM = now;
15482     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15483     StartClockTimer(intendedTickLength);
15484
15485     /* if the time remaining has fallen below the alarm threshold, sound the
15486      * alarm. if the alarm has sounded and (due to a takeback or time control
15487      * with increment) the time remaining has increased to a level above the
15488      * threshold, reset the alarm so it can sound again.
15489      */
15490
15491     if (appData.icsActive && appData.icsAlarm) {
15492
15493         /* make sure we are dealing with the user's clock */
15494         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15495                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15496            )) return;
15497
15498         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15499             alarmSounded = FALSE;
15500         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15501             PlayAlarmSound();
15502             alarmSounded = TRUE;
15503         }
15504     }
15505 }
15506
15507
15508 /* A player has just moved, so stop the previously running
15509    clock and (if in clock mode) start the other one.
15510    We redisplay both clocks in case we're in ICS mode, because
15511    ICS gives us an update to both clocks after every move.
15512    Note that this routine is called *after* forwardMostMove
15513    is updated, so the last fractional tick must be subtracted
15514    from the color that is *not* on move now.
15515 */
15516 void
15517 SwitchClocks(int newMoveNr)
15518 {
15519     long lastTickLength;
15520     TimeMark now;
15521     int flagged = FALSE;
15522
15523     GetTimeMark(&now);
15524
15525     if (StopClockTimer() && appData.clockMode) {
15526         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15527         if (!WhiteOnMove(forwardMostMove)) {
15528             if(blackNPS >= 0) lastTickLength = 0;
15529             blackTimeRemaining -= lastTickLength;
15530            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15531 //         if(pvInfoList[forwardMostMove].time == -1)
15532                  pvInfoList[forwardMostMove].time =               // use GUI time
15533                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15534         } else {
15535            if(whiteNPS >= 0) lastTickLength = 0;
15536            whiteTimeRemaining -= lastTickLength;
15537            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15538 //         if(pvInfoList[forwardMostMove].time == -1)
15539                  pvInfoList[forwardMostMove].time =
15540                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15541         }
15542         flagged = CheckFlags();
15543     }
15544     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15545     CheckTimeControl();
15546
15547     if (flagged || !appData.clockMode) return;
15548
15549     switch (gameMode) {
15550       case MachinePlaysBlack:
15551       case MachinePlaysWhite:
15552       case BeginningOfGame:
15553         if (pausing) return;
15554         break;
15555
15556       case EditGame:
15557       case PlayFromGameFile:
15558       case IcsExamining:
15559         return;
15560
15561       default:
15562         break;
15563     }
15564
15565     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15566         if(WhiteOnMove(forwardMostMove))
15567              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15568         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15569     }
15570
15571     tickStartTM = now;
15572     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15573       whiteTimeRemaining : blackTimeRemaining);
15574     StartClockTimer(intendedTickLength);
15575 }
15576
15577
15578 /* Stop both clocks */
15579 void
15580 StopClocks()
15581 {
15582     long lastTickLength;
15583     TimeMark now;
15584
15585     if (!StopClockTimer()) return;
15586     if (!appData.clockMode) return;
15587
15588     GetTimeMark(&now);
15589
15590     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15591     if (WhiteOnMove(forwardMostMove)) {
15592         if(whiteNPS >= 0) lastTickLength = 0;
15593         whiteTimeRemaining -= lastTickLength;
15594         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15595     } else {
15596         if(blackNPS >= 0) lastTickLength = 0;
15597         blackTimeRemaining -= lastTickLength;
15598         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15599     }
15600     CheckFlags();
15601 }
15602
15603 /* Start clock of player on move.  Time may have been reset, so
15604    if clock is already running, stop and restart it. */
15605 void
15606 StartClocks()
15607 {
15608     (void) StopClockTimer(); /* in case it was running already */
15609     DisplayBothClocks();
15610     if (CheckFlags()) return;
15611
15612     if (!appData.clockMode) return;
15613     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15614
15615     GetTimeMark(&tickStartTM);
15616     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15617       whiteTimeRemaining : blackTimeRemaining);
15618
15619    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15620     whiteNPS = blackNPS = -1;
15621     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15622        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15623         whiteNPS = first.nps;
15624     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15625        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15626         blackNPS = first.nps;
15627     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15628         whiteNPS = second.nps;
15629     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15630         blackNPS = second.nps;
15631     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15632
15633     StartClockTimer(intendedTickLength);
15634 }
15635
15636 char *
15637 TimeString(ms)
15638      long ms;
15639 {
15640     long second, minute, hour, day;
15641     char *sign = "";
15642     static char buf[32];
15643
15644     if (ms > 0 && ms <= 9900) {
15645       /* convert milliseconds to tenths, rounding up */
15646       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15647
15648       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15649       return buf;
15650     }
15651
15652     /* convert milliseconds to seconds, rounding up */
15653     /* use floating point to avoid strangeness of integer division
15654        with negative dividends on many machines */
15655     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15656
15657     if (second < 0) {
15658         sign = "-";
15659         second = -second;
15660     }
15661
15662     day = second / (60 * 60 * 24);
15663     second = second % (60 * 60 * 24);
15664     hour = second / (60 * 60);
15665     second = second % (60 * 60);
15666     minute = second / 60;
15667     second = second % 60;
15668
15669     if (day > 0)
15670       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15671               sign, day, hour, minute, second);
15672     else if (hour > 0)
15673       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15674     else
15675       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15676
15677     return buf;
15678 }
15679
15680
15681 /*
15682  * This is necessary because some C libraries aren't ANSI C compliant yet.
15683  */
15684 char *
15685 StrStr(string, match)
15686      char *string, *match;
15687 {
15688     int i, length;
15689
15690     length = strlen(match);
15691
15692     for (i = strlen(string) - length; i >= 0; i--, string++)
15693       if (!strncmp(match, string, length))
15694         return string;
15695
15696     return NULL;
15697 }
15698
15699 char *
15700 StrCaseStr(string, match)
15701      char *string, *match;
15702 {
15703     int i, j, length;
15704
15705     length = strlen(match);
15706
15707     for (i = strlen(string) - length; i >= 0; i--, string++) {
15708         for (j = 0; j < length; j++) {
15709             if (ToLower(match[j]) != ToLower(string[j]))
15710               break;
15711         }
15712         if (j == length) return string;
15713     }
15714
15715     return NULL;
15716 }
15717
15718 #ifndef _amigados
15719 int
15720 StrCaseCmp(s1, s2)
15721      char *s1, *s2;
15722 {
15723     char c1, c2;
15724
15725     for (;;) {
15726         c1 = ToLower(*s1++);
15727         c2 = ToLower(*s2++);
15728         if (c1 > c2) return 1;
15729         if (c1 < c2) return -1;
15730         if (c1 == NULLCHAR) return 0;
15731     }
15732 }
15733
15734
15735 int
15736 ToLower(c)
15737      int c;
15738 {
15739     return isupper(c) ? tolower(c) : c;
15740 }
15741
15742
15743 int
15744 ToUpper(c)
15745      int c;
15746 {
15747     return islower(c) ? toupper(c) : c;
15748 }
15749 #endif /* !_amigados    */
15750
15751 char *
15752 StrSave(s)
15753      char *s;
15754 {
15755   char *ret;
15756
15757   if ((ret = (char *) malloc(strlen(s) + 1)))
15758     {
15759       safeStrCpy(ret, s, strlen(s)+1);
15760     }
15761   return ret;
15762 }
15763
15764 char *
15765 StrSavePtr(s, savePtr)
15766      char *s, **savePtr;
15767 {
15768     if (*savePtr) {
15769         free(*savePtr);
15770     }
15771     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15772       safeStrCpy(*savePtr, s, strlen(s)+1);
15773     }
15774     return(*savePtr);
15775 }
15776
15777 char *
15778 PGNDate()
15779 {
15780     time_t clock;
15781     struct tm *tm;
15782     char buf[MSG_SIZ];
15783
15784     clock = time((time_t *)NULL);
15785     tm = localtime(&clock);
15786     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15787             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15788     return StrSave(buf);
15789 }
15790
15791
15792 char *
15793 PositionToFEN(move, overrideCastling)
15794      int move;
15795      char *overrideCastling;
15796 {
15797     int i, j, fromX, fromY, toX, toY;
15798     int whiteToPlay;
15799     char buf[128];
15800     char *p, *q;
15801     int emptycount;
15802     ChessSquare piece;
15803
15804     whiteToPlay = (gameMode == EditPosition) ?
15805       !blackPlaysFirst : (move % 2 == 0);
15806     p = buf;
15807
15808     /* Piece placement data */
15809     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15810         emptycount = 0;
15811         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15812             if (boards[move][i][j] == EmptySquare) {
15813                 emptycount++;
15814             } else { ChessSquare piece = boards[move][i][j];
15815                 if (emptycount > 0) {
15816                     if(emptycount<10) /* [HGM] can be >= 10 */
15817                         *p++ = '0' + emptycount;
15818                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15819                     emptycount = 0;
15820                 }
15821                 if(PieceToChar(piece) == '+') {
15822                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15823                     *p++ = '+';
15824                     piece = (ChessSquare)(DEMOTED piece);
15825                 }
15826                 *p++ = PieceToChar(piece);
15827                 if(p[-1] == '~') {
15828                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15829                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15830                     *p++ = '~';
15831                 }
15832             }
15833         }
15834         if (emptycount > 0) {
15835             if(emptycount<10) /* [HGM] can be >= 10 */
15836                 *p++ = '0' + emptycount;
15837             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15838             emptycount = 0;
15839         }
15840         *p++ = '/';
15841     }
15842     *(p - 1) = ' ';
15843
15844     /* [HGM] print Crazyhouse or Shogi holdings */
15845     if( gameInfo.holdingsWidth ) {
15846         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15847         q = p;
15848         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15849             piece = boards[move][i][BOARD_WIDTH-1];
15850             if( piece != EmptySquare )
15851               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15852                   *p++ = PieceToChar(piece);
15853         }
15854         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15855             piece = boards[move][BOARD_HEIGHT-i-1][0];
15856             if( piece != EmptySquare )
15857               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15858                   *p++ = PieceToChar(piece);
15859         }
15860
15861         if( q == p ) *p++ = '-';
15862         *p++ = ']';
15863         *p++ = ' ';
15864     }
15865
15866     /* Active color */
15867     *p++ = whiteToPlay ? 'w' : 'b';
15868     *p++ = ' ';
15869
15870   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15871     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15872   } else {
15873   if(nrCastlingRights) {
15874      q = p;
15875      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15876        /* [HGM] write directly from rights */
15877            if(boards[move][CASTLING][2] != NoRights &&
15878               boards[move][CASTLING][0] != NoRights   )
15879                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15880            if(boards[move][CASTLING][2] != NoRights &&
15881               boards[move][CASTLING][1] != NoRights   )
15882                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15883            if(boards[move][CASTLING][5] != NoRights &&
15884               boards[move][CASTLING][3] != NoRights   )
15885                 *p++ = boards[move][CASTLING][3] + AAA;
15886            if(boards[move][CASTLING][5] != NoRights &&
15887               boards[move][CASTLING][4] != NoRights   )
15888                 *p++ = boards[move][CASTLING][4] + AAA;
15889      } else {
15890
15891         /* [HGM] write true castling rights */
15892         if( nrCastlingRights == 6 ) {
15893             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15894                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15895             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15896                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15897             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15898                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15899             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15900                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15901         }
15902      }
15903      if (q == p) *p++ = '-'; /* No castling rights */
15904      *p++ = ' ';
15905   }
15906
15907   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15908      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15909     /* En passant target square */
15910     if (move > backwardMostMove) {
15911         fromX = moveList[move - 1][0] - AAA;
15912         fromY = moveList[move - 1][1] - ONE;
15913         toX = moveList[move - 1][2] - AAA;
15914         toY = moveList[move - 1][3] - ONE;
15915         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15916             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15917             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15918             fromX == toX) {
15919             /* 2-square pawn move just happened */
15920             *p++ = toX + AAA;
15921             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15922         } else {
15923             *p++ = '-';
15924         }
15925     } else if(move == backwardMostMove) {
15926         // [HGM] perhaps we should always do it like this, and forget the above?
15927         if((signed char)boards[move][EP_STATUS] >= 0) {
15928             *p++ = boards[move][EP_STATUS] + AAA;
15929             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15930         } else {
15931             *p++ = '-';
15932         }
15933     } else {
15934         *p++ = '-';
15935     }
15936     *p++ = ' ';
15937   }
15938   }
15939
15940     /* [HGM] find reversible plies */
15941     {   int i = 0, j=move;
15942
15943         if (appData.debugMode) { int k;
15944             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15945             for(k=backwardMostMove; k<=forwardMostMove; k++)
15946                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15947
15948         }
15949
15950         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15951         if( j == backwardMostMove ) i += initialRulePlies;
15952         sprintf(p, "%d ", i);
15953         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15954     }
15955     /* Fullmove number */
15956     sprintf(p, "%d", (move / 2) + 1);
15957
15958     return StrSave(buf);
15959 }
15960
15961 Boolean
15962 ParseFEN(board, blackPlaysFirst, fen)
15963     Board board;
15964      int *blackPlaysFirst;
15965      char *fen;
15966 {
15967     int i, j;
15968     char *p, c;
15969     int emptycount;
15970     ChessSquare piece;
15971
15972     p = fen;
15973
15974     /* [HGM] by default clear Crazyhouse holdings, if present */
15975     if(gameInfo.holdingsWidth) {
15976        for(i=0; i<BOARD_HEIGHT; i++) {
15977            board[i][0]             = EmptySquare; /* black holdings */
15978            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15979            board[i][1]             = (ChessSquare) 0; /* black counts */
15980            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15981        }
15982     }
15983
15984     /* Piece placement data */
15985     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15986         j = 0;
15987         for (;;) {
15988             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15989                 if (*p == '/') p++;
15990                 emptycount = gameInfo.boardWidth - j;
15991                 while (emptycount--)
15992                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15993                 break;
15994 #if(BOARD_FILES >= 10)
15995             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15996                 p++; emptycount=10;
15997                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15998                 while (emptycount--)
15999                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16000 #endif
16001             } else if (isdigit(*p)) {
16002                 emptycount = *p++ - '0';
16003                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16004                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16005                 while (emptycount--)
16006                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16007             } else if (*p == '+' || isalpha(*p)) {
16008                 if (j >= gameInfo.boardWidth) return FALSE;
16009                 if(*p=='+') {
16010                     piece = CharToPiece(*++p);
16011                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16012                     piece = (ChessSquare) (PROMOTED piece ); p++;
16013                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16014                 } else piece = CharToPiece(*p++);
16015
16016                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16017                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16018                     piece = (ChessSquare) (PROMOTED piece);
16019                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16020                     p++;
16021                 }
16022                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16023             } else {
16024                 return FALSE;
16025             }
16026         }
16027     }
16028     while (*p == '/' || *p == ' ') p++;
16029
16030     /* [HGM] look for Crazyhouse holdings here */
16031     while(*p==' ') p++;
16032     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16033         if(*p == '[') p++;
16034         if(*p == '-' ) p++; /* empty holdings */ else {
16035             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16036             /* if we would allow FEN reading to set board size, we would   */
16037             /* have to add holdings and shift the board read so far here   */
16038             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16039                 p++;
16040                 if((int) piece >= (int) BlackPawn ) {
16041                     i = (int)piece - (int)BlackPawn;
16042                     i = PieceToNumber((ChessSquare)i);
16043                     if( i >= gameInfo.holdingsSize ) return FALSE;
16044                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16045                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16046                 } else {
16047                     i = (int)piece - (int)WhitePawn;
16048                     i = PieceToNumber((ChessSquare)i);
16049                     if( i >= gameInfo.holdingsSize ) return FALSE;
16050                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16051                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16052                 }
16053             }
16054         }
16055         if(*p == ']') p++;
16056     }
16057
16058     while(*p == ' ') p++;
16059
16060     /* Active color */
16061     c = *p++;
16062     if(appData.colorNickNames) {
16063       if( c == appData.colorNickNames[0] ) c = 'w'; else
16064       if( c == appData.colorNickNames[1] ) c = 'b';
16065     }
16066     switch (c) {
16067       case 'w':
16068         *blackPlaysFirst = FALSE;
16069         break;
16070       case 'b':
16071         *blackPlaysFirst = TRUE;
16072         break;
16073       default:
16074         return FALSE;
16075     }
16076
16077     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16078     /* return the extra info in global variiables             */
16079
16080     /* set defaults in case FEN is incomplete */
16081     board[EP_STATUS] = EP_UNKNOWN;
16082     for(i=0; i<nrCastlingRights; i++ ) {
16083         board[CASTLING][i] =
16084             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16085     }   /* assume possible unless obviously impossible */
16086     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16087     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16088     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16089                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16090     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16091     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16092     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16093                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16094     FENrulePlies = 0;
16095
16096     while(*p==' ') p++;
16097     if(nrCastlingRights) {
16098       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16099           /* castling indicator present, so default becomes no castlings */
16100           for(i=0; i<nrCastlingRights; i++ ) {
16101                  board[CASTLING][i] = NoRights;
16102           }
16103       }
16104       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16105              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16106              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16107              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16108         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16109
16110         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16111             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16112             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16113         }
16114         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16115             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16116         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16117                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16118         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16119                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16120         switch(c) {
16121           case'K':
16122               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16123               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16124               board[CASTLING][2] = whiteKingFile;
16125               break;
16126           case'Q':
16127               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16128               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16129               board[CASTLING][2] = whiteKingFile;
16130               break;
16131           case'k':
16132               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16133               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16134               board[CASTLING][5] = blackKingFile;
16135               break;
16136           case'q':
16137               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16138               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16139               board[CASTLING][5] = blackKingFile;
16140           case '-':
16141               break;
16142           default: /* FRC castlings */
16143               if(c >= 'a') { /* black rights */
16144                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16145                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16146                   if(i == BOARD_RGHT) break;
16147                   board[CASTLING][5] = i;
16148                   c -= AAA;
16149                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16150                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16151                   if(c > i)
16152                       board[CASTLING][3] = c;
16153                   else
16154                       board[CASTLING][4] = c;
16155               } else { /* white rights */
16156                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16157                     if(board[0][i] == WhiteKing) break;
16158                   if(i == BOARD_RGHT) break;
16159                   board[CASTLING][2] = i;
16160                   c -= AAA - 'a' + 'A';
16161                   if(board[0][c] >= WhiteKing) break;
16162                   if(c > i)
16163                       board[CASTLING][0] = c;
16164                   else
16165                       board[CASTLING][1] = c;
16166               }
16167         }
16168       }
16169       for(i=0; i<nrCastlingRights; i++)
16170         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16171     if (appData.debugMode) {
16172         fprintf(debugFP, "FEN castling rights:");
16173         for(i=0; i<nrCastlingRights; i++)
16174         fprintf(debugFP, " %d", board[CASTLING][i]);
16175         fprintf(debugFP, "\n");
16176     }
16177
16178       while(*p==' ') p++;
16179     }
16180
16181     /* read e.p. field in games that know e.p. capture */
16182     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16183        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16184       if(*p=='-') {
16185         p++; board[EP_STATUS] = EP_NONE;
16186       } else {
16187          char c = *p++ - AAA;
16188
16189          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16190          if(*p >= '0' && *p <='9') p++;
16191          board[EP_STATUS] = c;
16192       }
16193     }
16194
16195
16196     if(sscanf(p, "%d", &i) == 1) {
16197         FENrulePlies = i; /* 50-move ply counter */
16198         /* (The move number is still ignored)    */
16199     }
16200
16201     return TRUE;
16202 }
16203
16204 void
16205 EditPositionPasteFEN(char *fen)
16206 {
16207   if (fen != NULL) {
16208     Board initial_position;
16209
16210     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16211       DisplayError(_("Bad FEN position in clipboard"), 0);
16212       return ;
16213     } else {
16214       int savedBlackPlaysFirst = blackPlaysFirst;
16215       EditPositionEvent();
16216       blackPlaysFirst = savedBlackPlaysFirst;
16217       CopyBoard(boards[0], initial_position);
16218       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16219       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16220       DisplayBothClocks();
16221       DrawPosition(FALSE, boards[currentMove]);
16222     }
16223   }
16224 }
16225
16226 static char cseq[12] = "\\   ";
16227
16228 Boolean set_cont_sequence(char *new_seq)
16229 {
16230     int len;
16231     Boolean ret;
16232
16233     // handle bad attempts to set the sequence
16234         if (!new_seq)
16235                 return 0; // acceptable error - no debug
16236
16237     len = strlen(new_seq);
16238     ret = (len > 0) && (len < sizeof(cseq));
16239     if (ret)
16240       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16241     else if (appData.debugMode)
16242       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16243     return ret;
16244 }
16245
16246 /*
16247     reformat a source message so words don't cross the width boundary.  internal
16248     newlines are not removed.  returns the wrapped size (no null character unless
16249     included in source message).  If dest is NULL, only calculate the size required
16250     for the dest buffer.  lp argument indicats line position upon entry, and it's
16251     passed back upon exit.
16252 */
16253 int wrap(char *dest, char *src, int count, int width, int *lp)
16254 {
16255     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16256
16257     cseq_len = strlen(cseq);
16258     old_line = line = *lp;
16259     ansi = len = clen = 0;
16260
16261     for (i=0; i < count; i++)
16262     {
16263         if (src[i] == '\033')
16264             ansi = 1;
16265
16266         // if we hit the width, back up
16267         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16268         {
16269             // store i & len in case the word is too long
16270             old_i = i, old_len = len;
16271
16272             // find the end of the last word
16273             while (i && src[i] != ' ' && src[i] != '\n')
16274             {
16275                 i--;
16276                 len--;
16277             }
16278
16279             // word too long?  restore i & len before splitting it
16280             if ((old_i-i+clen) >= width)
16281             {
16282                 i = old_i;
16283                 len = old_len;
16284             }
16285
16286             // extra space?
16287             if (i && src[i-1] == ' ')
16288                 len--;
16289
16290             if (src[i] != ' ' && src[i] != '\n')
16291             {
16292                 i--;
16293                 if (len)
16294                     len--;
16295             }
16296
16297             // now append the newline and continuation sequence
16298             if (dest)
16299                 dest[len] = '\n';
16300             len++;
16301             if (dest)
16302                 strncpy(dest+len, cseq, cseq_len);
16303             len += cseq_len;
16304             line = cseq_len;
16305             clen = cseq_len;
16306             continue;
16307         }
16308
16309         if (dest)
16310             dest[len] = src[i];
16311         len++;
16312         if (!ansi)
16313             line++;
16314         if (src[i] == '\n')
16315             line = 0;
16316         if (src[i] == 'm')
16317             ansi = 0;
16318     }
16319     if (dest && appData.debugMode)
16320     {
16321         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16322             count, width, line, len, *lp);
16323         show_bytes(debugFP, src, count);
16324         fprintf(debugFP, "\ndest: ");
16325         show_bytes(debugFP, dest, len);
16326         fprintf(debugFP, "\n");
16327     }
16328     *lp = dest ? line : old_line;
16329
16330     return len;
16331 }
16332
16333 // [HGM] vari: routines for shelving variations
16334
16335 void
16336 PushInner(int firstMove, int lastMove)
16337 {
16338         int i, j, nrMoves = lastMove - firstMove;
16339
16340         // push current tail of game on stack
16341         savedResult[storedGames] = gameInfo.result;
16342         savedDetails[storedGames] = gameInfo.resultDetails;
16343         gameInfo.resultDetails = NULL;
16344         savedFirst[storedGames] = firstMove;
16345         savedLast [storedGames] = lastMove;
16346         savedFramePtr[storedGames] = framePtr;
16347         framePtr -= nrMoves; // reserve space for the boards
16348         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16349             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16350             for(j=0; j<MOVE_LEN; j++)
16351                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16352             for(j=0; j<2*MOVE_LEN; j++)
16353                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16354             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16355             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16356             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16357             pvInfoList[firstMove+i-1].depth = 0;
16358             commentList[framePtr+i] = commentList[firstMove+i];
16359             commentList[firstMove+i] = NULL;
16360         }
16361
16362         storedGames++;
16363         forwardMostMove = firstMove; // truncate game so we can start variation
16364 }
16365
16366 void
16367 PushTail(int firstMove, int lastMove)
16368 {
16369         if(appData.icsActive) { // only in local mode
16370                 forwardMostMove = currentMove; // mimic old ICS behavior
16371                 return;
16372         }
16373         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16374
16375         PushInner(firstMove, lastMove);
16376         if(storedGames == 1) GreyRevert(FALSE);
16377 }
16378
16379 void
16380 PopInner(Boolean annotate)
16381 {
16382         int i, j, nrMoves;
16383         char buf[8000], moveBuf[20];
16384
16385         storedGames--;
16386         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16387         nrMoves = savedLast[storedGames] - currentMove;
16388         if(annotate) {
16389                 int cnt = 10;
16390                 if(!WhiteOnMove(currentMove))
16391                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16392                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16393                 for(i=currentMove; i<forwardMostMove; i++) {
16394                         if(WhiteOnMove(i))
16395                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16396                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16397                         strcat(buf, moveBuf);
16398                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16399                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16400                 }
16401                 strcat(buf, ")");
16402         }
16403         for(i=1; i<=nrMoves; i++) { // copy last variation back
16404             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16405             for(j=0; j<MOVE_LEN; j++)
16406                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16407             for(j=0; j<2*MOVE_LEN; j++)
16408                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16409             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16410             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16411             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16412             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16413             commentList[currentMove+i] = commentList[framePtr+i];
16414             commentList[framePtr+i] = NULL;
16415         }
16416         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16417         framePtr = savedFramePtr[storedGames];
16418         gameInfo.result = savedResult[storedGames];
16419         if(gameInfo.resultDetails != NULL) {
16420             free(gameInfo.resultDetails);
16421       }
16422         gameInfo.resultDetails = savedDetails[storedGames];
16423         forwardMostMove = currentMove + nrMoves;
16424 }
16425
16426 Boolean
16427 PopTail(Boolean annotate)
16428 {
16429         if(appData.icsActive) return FALSE; // only in local mode
16430         if(!storedGames) return FALSE; // sanity
16431         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16432
16433         PopInner(annotate);
16434
16435         if(storedGames == 0) GreyRevert(TRUE);
16436         return TRUE;
16437 }
16438
16439 void
16440 CleanupTail()
16441 {       // remove all shelved variations
16442         int i;
16443         for(i=0; i<storedGames; i++) {
16444             if(savedDetails[i])
16445                 free(savedDetails[i]);
16446             savedDetails[i] = NULL;
16447         }
16448         for(i=framePtr; i<MAX_MOVES; i++) {
16449                 if(commentList[i]) free(commentList[i]);
16450                 commentList[i] = NULL;
16451         }
16452         framePtr = MAX_MOVES-1;
16453         storedGames = 0;
16454 }
16455
16456 void
16457 LoadVariation(int index, char *text)
16458 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16459         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16460         int level = 0, move;
16461
16462         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16463         // first find outermost bracketing variation
16464         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16465             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16466                 if(*p == '{') wait = '}'; else
16467                 if(*p == '[') wait = ']'; else
16468                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16469                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16470             }
16471             if(*p == wait) wait = NULLCHAR; // closing ]} found
16472             p++;
16473         }
16474         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16475         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16476         end[1] = NULLCHAR; // clip off comment beyond variation
16477         ToNrEvent(currentMove-1);
16478         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16479         // kludge: use ParsePV() to append variation to game
16480         move = currentMove;
16481         ParsePV(start, TRUE, TRUE);
16482         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16483         ClearPremoveHighlights();
16484         CommentPopDown();
16485         ToNrEvent(currentMove+1);
16486 }
16487