Display score/depth in Eval Graph title
[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   case VariantGrand:
400     flags &= ~F_ALL_CASTLE_OK;
401     break;
402   default:
403     break;
404   }
405   return flags;
406 }
407
408 FILE *gameFileFP, *debugFP;
409
410 /*
411     [AS] Note: sometimes, the sscanf() function is used to parse the input
412     into a fixed-size buffer. Because of this, we must be prepared to
413     receive strings as long as the size of the input buffer, which is currently
414     set to 4K for Windows and 8K for the rest.
415     So, we must either allocate sufficiently large buffers here, or
416     reduce the size of the input buffer in the input reading part.
417 */
418
419 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
420 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
421 char thinkOutput1[MSG_SIZ*10];
422
423 ChessProgramState first, second, pairing;
424
425 /* premove variables */
426 int premoveToX = 0;
427 int premoveToY = 0;
428 int premoveFromX = 0;
429 int premoveFromY = 0;
430 int premovePromoChar = 0;
431 int gotPremove = 0;
432 Boolean alarmSounded;
433 /* end premove variables */
434
435 char *ics_prefix = "$";
436 int ics_type = ICS_GENERIC;
437
438 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
439 int pauseExamForwardMostMove = 0;
440 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
441 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
442 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
443 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
444 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
445 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
446 int whiteFlag = FALSE, blackFlag = FALSE;
447 int userOfferedDraw = FALSE;
448 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
449 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
450 int cmailMoveType[CMAIL_MAX_GAMES];
451 long ics_clock_paused = 0;
452 ProcRef icsPR = NoProc, cmailPR = NoProc;
453 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
454 GameMode gameMode = BeginningOfGame;
455 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
456 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
457 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
458 int hiddenThinkOutputState = 0; /* [AS] */
459 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
460 int adjudicateLossPlies = 6;
461 char white_holding[64], black_holding[64];
462 TimeMark lastNodeCountTime;
463 long lastNodeCount=0;
464 int shiftKey; // [HGM] set by mouse handler
465
466 int have_sent_ICS_logon = 0;
467 int movesPerSession;
468 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
469 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
470 long timeControl_2; /* [AS] Allow separate time controls */
471 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
472 long timeRemaining[2][MAX_MOVES];
473 int matchGame = 0, nextGame = 0, roundNr = 0;
474 Boolean waitingForGame = FALSE;
475 TimeMark programStartTime, pauseStart;
476 char ics_handle[MSG_SIZ];
477 int have_set_title = 0;
478
479 /* animateTraining preserves the state of appData.animate
480  * when Training mode is activated. This allows the
481  * response to be animated when appData.animate == TRUE and
482  * appData.animateDragging == TRUE.
483  */
484 Boolean animateTraining;
485
486 GameInfo gameInfo;
487
488 AppData appData;
489
490 Board boards[MAX_MOVES];
491 /* [HGM] Following 7 needed for accurate legality tests: */
492 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
493 signed char  initialRights[BOARD_FILES];
494 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
495 int   initialRulePlies, FENrulePlies;
496 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
497 int loadFlag = 0;
498 Boolean shuffleOpenings;
499 int mute; // mute all sounds
500
501 // [HGM] vari: next 12 to save and restore variations
502 #define MAX_VARIATIONS 10
503 int framePtr = MAX_MOVES-1; // points to free stack entry
504 int storedGames = 0;
505 int savedFirst[MAX_VARIATIONS];
506 int savedLast[MAX_VARIATIONS];
507 int savedFramePtr[MAX_VARIATIONS];
508 char *savedDetails[MAX_VARIATIONS];
509 ChessMove savedResult[MAX_VARIATIONS];
510
511 void PushTail P((int firstMove, int lastMove));
512 Boolean PopTail P((Boolean annotate));
513 void PushInner P((int firstMove, int lastMove));
514 void PopInner P((Boolean annotate));
515 void CleanupTail P((void));
516
517 ChessSquare  FIDEArray[2][BOARD_FILES] = {
518     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
519         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
521         BlackKing, BlackBishop, BlackKnight, BlackRook }
522 };
523
524 ChessSquare twoKingsArray[2][BOARD_FILES] = {
525     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
526         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
527     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
528         BlackKing, BlackKing, BlackKnight, BlackRook }
529 };
530
531 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
532     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
533         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
534     { BlackRook, BlackMan, BlackBishop, BlackQueen,
535         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
536 };
537
538 ChessSquare SpartanArray[2][BOARD_FILES] = {
539     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
540         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
541     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
542         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
543 };
544
545 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
546     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
547         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
548     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
549         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
550 };
551
552 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
553     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
554         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
555     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
556         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
557 };
558
559 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
560     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
561         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackMan, BlackFerz,
563         BlackKing, BlackMan, BlackKnight, BlackRook }
564 };
565
566
567 #if (BOARD_FILES>=10)
568 ChessSquare ShogiArray[2][BOARD_FILES] = {
569     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
570         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
571     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
572         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
573 };
574
575 ChessSquare XiangqiArray[2][BOARD_FILES] = {
576     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
577         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
579         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
580 };
581
582 ChessSquare CapablancaArray[2][BOARD_FILES] = {
583     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
584         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
585     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
586         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
587 };
588
589 ChessSquare GreatArray[2][BOARD_FILES] = {
590     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
591         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
592     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
593         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
594 };
595
596 ChessSquare JanusArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
598         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
599     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
600         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
601 };
602
603 ChessSquare GrandArray[2][BOARD_FILES] = {
604     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
605         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
606     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
607         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
608 };
609
610 #ifdef GOTHIC
611 ChessSquare GothicArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
613         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
615         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
616 };
617 #else // !GOTHIC
618 #define GothicArray CapablancaArray
619 #endif // !GOTHIC
620
621 #ifdef FALCON
622 ChessSquare FalconArray[2][BOARD_FILES] = {
623     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
624         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
625     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
626         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
627 };
628 #else // !FALCON
629 #define FalconArray CapablancaArray
630 #endif // !FALCON
631
632 #else // !(BOARD_FILES>=10)
633 #define XiangqiPosition FIDEArray
634 #define CapablancaArray FIDEArray
635 #define GothicArray FIDEArray
636 #define GreatArray FIDEArray
637 #endif // !(BOARD_FILES>=10)
638
639 #if (BOARD_FILES>=12)
640 ChessSquare CourierArray[2][BOARD_FILES] = {
641     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
642         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
643     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
644         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
645 };
646 #else // !(BOARD_FILES>=12)
647 #define CourierArray CapablancaArray
648 #endif // !(BOARD_FILES>=12)
649
650
651 Board initialPosition;
652
653
654 /* Convert str to a rating. Checks for special cases of "----",
655
656    "++++", etc. Also strips ()'s */
657 int
658 string_to_rating(str)
659   char *str;
660 {
661   while(*str && !isdigit(*str)) ++str;
662   if (!*str)
663     return 0;   /* One of the special "no rating" cases */
664   else
665     return atoi(str);
666 }
667
668 void
669 ClearProgramStats()
670 {
671     /* Init programStats */
672     programStats.movelist[0] = 0;
673     programStats.depth = 0;
674     programStats.nr_moves = 0;
675     programStats.moves_left = 0;
676     programStats.nodes = 0;
677     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
678     programStats.score = 0;
679     programStats.got_only_move = 0;
680     programStats.got_fail = 0;
681     programStats.line_is_book = 0;
682 }
683
684 void
685 CommonEngineInit()
686 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
687     if (appData.firstPlaysBlack) {
688         first.twoMachinesColor = "black\n";
689         second.twoMachinesColor = "white\n";
690     } else {
691         first.twoMachinesColor = "white\n";
692         second.twoMachinesColor = "black\n";
693     }
694
695     first.other = &second;
696     second.other = &first;
697
698     { float norm = 1;
699         if(appData.timeOddsMode) {
700             norm = appData.timeOdds[0];
701             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
702         }
703         first.timeOdds  = appData.timeOdds[0]/norm;
704         second.timeOdds = appData.timeOdds[1]/norm;
705     }
706
707     if(programVersion) free(programVersion);
708     if (appData.noChessProgram) {
709         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
710         sprintf(programVersion, "%s", PACKAGE_STRING);
711     } else {
712       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
713       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
714       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
715     }
716 }
717
718 void
719 UnloadEngine(ChessProgramState *cps)
720 {
721         /* Kill off first chess program */
722         if (cps->isr != NULL)
723           RemoveInputSource(cps->isr);
724         cps->isr = NULL;
725
726         if (cps->pr != NoProc) {
727             ExitAnalyzeMode();
728             DoSleep( appData.delayBeforeQuit );
729             SendToProgram("quit\n", cps);
730             DoSleep( appData.delayAfterQuit );
731             DestroyChildProcess(cps->pr, cps->useSigterm);
732         }
733         cps->pr = NoProc;
734         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
735 }
736
737 void
738 ClearOptions(ChessProgramState *cps)
739 {
740     int i;
741     cps->nrOptions = cps->comboCnt = 0;
742     for(i=0; i<MAX_OPTIONS; i++) {
743         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
744         cps->option[i].textValue = 0;
745     }
746 }
747
748 char *engineNames[] = {
749 "first",
750 "second"
751 };
752
753 void
754 InitEngine(ChessProgramState *cps, int n)
755 {   // [HGM] all engine initialiation put in a function that does one engine
756
757     ClearOptions(cps);
758
759     cps->which = engineNames[n];
760     cps->maybeThinking = FALSE;
761     cps->pr = NoProc;
762     cps->isr = NULL;
763     cps->sendTime = 2;
764     cps->sendDrawOffers = 1;
765
766     cps->program = appData.chessProgram[n];
767     cps->host = appData.host[n];
768     cps->dir = appData.directory[n];
769     cps->initString = appData.engInitString[n];
770     cps->computerString = appData.computerString[n];
771     cps->useSigint  = TRUE;
772     cps->useSigterm = TRUE;
773     cps->reuse = appData.reuse[n];
774     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
775     cps->useSetboard = FALSE;
776     cps->useSAN = FALSE;
777     cps->usePing = FALSE;
778     cps->lastPing = 0;
779     cps->lastPong = 0;
780     cps->usePlayother = FALSE;
781     cps->useColors = TRUE;
782     cps->useUsermove = FALSE;
783     cps->sendICS = FALSE;
784     cps->sendName = appData.icsActive;
785     cps->sdKludge = FALSE;
786     cps->stKludge = FALSE;
787     TidyProgramName(cps->program, cps->host, cps->tidy);
788     cps->matchWins = 0;
789     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
790     cps->analysisSupport = 2; /* detect */
791     cps->analyzing = FALSE;
792     cps->initDone = FALSE;
793
794     /* New features added by Tord: */
795     cps->useFEN960 = FALSE;
796     cps->useOOCastle = TRUE;
797     /* End of new features added by Tord. */
798     cps->fenOverride  = appData.fenOverride[n];
799
800     /* [HGM] time odds: set factor for each machine */
801     cps->timeOdds  = appData.timeOdds[n];
802
803     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
804     cps->accumulateTC = appData.accumulateTC[n];
805     cps->maxNrOfSessions = 1;
806
807     /* [HGM] debug */
808     cps->debug = FALSE;
809
810     cps->supportsNPS = UNKNOWN;
811     cps->memSize = FALSE;
812     cps->maxCores = FALSE;
813     cps->egtFormats[0] = NULLCHAR;
814
815     /* [HGM] options */
816     cps->optionSettings  = appData.engOptions[n];
817
818     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
819     cps->isUCI = appData.isUCI[n]; /* [AS] */
820     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
821
822     if (appData.protocolVersion[n] > PROTOVER
823         || appData.protocolVersion[n] < 1)
824       {
825         char buf[MSG_SIZ];
826         int len;
827
828         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
829                        appData.protocolVersion[n]);
830         if( (len > MSG_SIZ) && appData.debugMode )
831           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
832
833         DisplayFatalError(buf, 0, 2);
834       }
835     else
836       {
837         cps->protocolVersion = appData.protocolVersion[n];
838       }
839
840     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
841 }
842
843 ChessProgramState *savCps;
844
845 void
846 LoadEngine()
847 {
848     int i;
849     if(WaitForEngine(savCps, LoadEngine)) return;
850     CommonEngineInit(); // recalculate time odds
851     if(gameInfo.variant != StringToVariant(appData.variant)) {
852         // we changed variant when loading the engine; this forces us to reset
853         Reset(TRUE, savCps != &first);
854         EditGameEvent(); // for consistency with other path, as Reset changes mode
855     }
856     InitChessProgram(savCps, FALSE);
857     SendToProgram("force\n", savCps);
858     DisplayMessage("", "");
859     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
860     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
861     ThawUI();
862     SetGNUMode();
863 }
864
865 void
866 ReplaceEngine(ChessProgramState *cps, int n)
867 {
868     EditGameEvent();
869     UnloadEngine(cps);
870     appData.noChessProgram = FALSE;
871     appData.clockMode = TRUE;
872     InitEngine(cps, n);
873     UpdateLogos(TRUE);
874     if(n) return; // only startup first engine immediately; second can wait
875     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
876     LoadEngine();
877 }
878
879 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
880 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
881
882 static char resetOptions[] = 
883         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
884         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
885
886 void
887 Load(ChessProgramState *cps, int i)
888 {
889     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
890     if(engineLine[0]) { // an engine was selected from the combo box
891         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
892         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
893         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
894         ParseArgsFromString(buf);
895         SwapEngines(i);
896         ReplaceEngine(cps, i);
897         return;
898     }
899     p = engineName;
900     while(q = strchr(p, SLASH)) p = q+1;
901     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
902     if(engineDir[0] != NULLCHAR)
903         appData.directory[i] = engineDir;
904     else if(p != engineName) { // derive directory from engine path, when not given
905         p[-1] = 0;
906         appData.directory[i] = strdup(engineName);
907         p[-1] = SLASH;
908     } else appData.directory[i] = ".";
909     if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
910     if(params[0]) {
911         snprintf(command, MSG_SIZ, "%s %s", p, params);
912         p = command;
913     }
914     appData.chessProgram[i] = strdup(p);
915     appData.isUCI[i] = isUCI;
916     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
917     appData.hasOwnBookUCI[i] = hasBook;
918     if(!nickName[0]) useNick = FALSE;
919     if(useNick) ASSIGN(appData.pgnName[i], nickName);
920     if(addToList) {
921         int len;
922         char quote;
923         q = firstChessProgramNames;
924         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
925         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
926         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
927                         quote, p, quote, appData.directory[i], 
928                         useNick ? " -fn \"" : "",
929                         useNick ? nickName : "",
930                         useNick ? "\"" : "",
931                         v1 ? " -firstProtocolVersion 1" : "",
932                         hasBook ? "" : " -fNoOwnBookUCI",
933                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
934                         storeVariant ? " -variant " : "",
935                         storeVariant ? VariantName(gameInfo.variant) : "");
936         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
937         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
938         if(q)   free(q);
939     }
940     ReplaceEngine(cps, i);
941 }
942
943 void
944 InitTimeControls()
945 {
946     int matched, min, sec;
947     /*
948      * Parse timeControl resource
949      */
950     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
951                           appData.movesPerSession)) {
952         char buf[MSG_SIZ];
953         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
954         DisplayFatalError(buf, 0, 2);
955     }
956
957     /*
958      * Parse searchTime resource
959      */
960     if (*appData.searchTime != NULLCHAR) {
961         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
962         if (matched == 1) {
963             searchTime = min * 60;
964         } else if (matched == 2) {
965             searchTime = min * 60 + sec;
966         } else {
967             char buf[MSG_SIZ];
968             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
969             DisplayFatalError(buf, 0, 2);
970         }
971     }
972 }
973
974 void
975 InitBackEnd1()
976 {
977
978     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
979     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
980
981     GetTimeMark(&programStartTime);
982     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
983     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
984
985     ClearProgramStats();
986     programStats.ok_to_send = 1;
987     programStats.seen_stat = 0;
988
989     /*
990      * Initialize game list
991      */
992     ListNew(&gameList);
993
994
995     /*
996      * Internet chess server status
997      */
998     if (appData.icsActive) {
999         appData.matchMode = FALSE;
1000         appData.matchGames = 0;
1001 #if ZIPPY
1002         appData.noChessProgram = !appData.zippyPlay;
1003 #else
1004         appData.zippyPlay = FALSE;
1005         appData.zippyTalk = FALSE;
1006         appData.noChessProgram = TRUE;
1007 #endif
1008         if (*appData.icsHelper != NULLCHAR) {
1009             appData.useTelnet = TRUE;
1010             appData.telnetProgram = appData.icsHelper;
1011         }
1012     } else {
1013         appData.zippyTalk = appData.zippyPlay = FALSE;
1014     }
1015
1016     /* [AS] Initialize pv info list [HGM] and game state */
1017     {
1018         int i, j;
1019
1020         for( i=0; i<=framePtr; i++ ) {
1021             pvInfoList[i].depth = -1;
1022             boards[i][EP_STATUS] = EP_NONE;
1023             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1024         }
1025     }
1026
1027     InitTimeControls();
1028
1029     /* [AS] Adjudication threshold */
1030     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1031
1032     InitEngine(&first, 0);
1033     InitEngine(&second, 1);
1034     CommonEngineInit();
1035
1036     pairing.which = "pairing"; // pairing engine
1037     pairing.pr = NoProc;
1038     pairing.isr = NULL;
1039     pairing.program = appData.pairingEngine;
1040     pairing.host = "localhost";
1041     pairing.dir = ".";
1042
1043     if (appData.icsActive) {
1044         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1045     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1046         appData.clockMode = FALSE;
1047         first.sendTime = second.sendTime = 0;
1048     }
1049
1050 #if ZIPPY
1051     /* Override some settings from environment variables, for backward
1052        compatibility.  Unfortunately it's not feasible to have the env
1053        vars just set defaults, at least in xboard.  Ugh.
1054     */
1055     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1056       ZippyInit();
1057     }
1058 #endif
1059
1060     if (!appData.icsActive) {
1061       char buf[MSG_SIZ];
1062       int len;
1063
1064       /* Check for variants that are supported only in ICS mode,
1065          or not at all.  Some that are accepted here nevertheless
1066          have bugs; see comments below.
1067       */
1068       VariantClass variant = StringToVariant(appData.variant);
1069       switch (variant) {
1070       case VariantBughouse:     /* need four players and two boards */
1071       case VariantKriegspiel:   /* need to hide pieces and move details */
1072         /* case VariantFischeRandom: (Fabien: moved below) */
1073         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1074         if( (len > MSG_SIZ) && appData.debugMode )
1075           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1076
1077         DisplayFatalError(buf, 0, 2);
1078         return;
1079
1080       case VariantUnknown:
1081       case VariantLoadable:
1082       case Variant29:
1083       case Variant30:
1084       case Variant31:
1085       case Variant32:
1086       case Variant33:
1087       case Variant34:
1088       case Variant35:
1089       case Variant36:
1090       default:
1091         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1092         if( (len > MSG_SIZ) && appData.debugMode )
1093           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1094
1095         DisplayFatalError(buf, 0, 2);
1096         return;
1097
1098       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1099       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1100       case VariantGothic:     /* [HGM] should work */
1101       case VariantCapablanca: /* [HGM] should work */
1102       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1103       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1104       case VariantKnightmate: /* [HGM] should work */
1105       case VariantCylinder:   /* [HGM] untested */
1106       case VariantFalcon:     /* [HGM] untested */
1107       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1108                                  offboard interposition not understood */
1109       case VariantNormal:     /* definitely works! */
1110       case VariantWildCastle: /* pieces not automatically shuffled */
1111       case VariantNoCastle:   /* pieces not automatically shuffled */
1112       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1113       case VariantLosers:     /* should work except for win condition,
1114                                  and doesn't know captures are mandatory */
1115       case VariantSuicide:    /* should work except for win condition,
1116                                  and doesn't know captures are mandatory */
1117       case VariantGiveaway:   /* should work except for win condition,
1118                                  and doesn't know captures are mandatory */
1119       case VariantTwoKings:   /* should work */
1120       case VariantAtomic:     /* should work except for win condition */
1121       case Variant3Check:     /* should work except for win condition */
1122       case VariantShatranj:   /* should work except for all win conditions */
1123       case VariantMakruk:     /* should work except for draw countdown */
1124       case VariantBerolina:   /* might work if TestLegality is off */
1125       case VariantCapaRandom: /* should work */
1126       case VariantJanus:      /* should work */
1127       case VariantSuper:      /* experimental */
1128       case VariantGreat:      /* experimental, requires legality testing to be off */
1129       case VariantSChess:     /* S-Chess, should work */
1130       case VariantGrand:      /* should work */
1131       case VariantSpartan:    /* should work */
1132         break;
1133       }
1134     }
1135
1136 }
1137
1138 int NextIntegerFromString( char ** str, long * value )
1139 {
1140     int result = -1;
1141     char * s = *str;
1142
1143     while( *s == ' ' || *s == '\t' ) {
1144         s++;
1145     }
1146
1147     *value = 0;
1148
1149     if( *s >= '0' && *s <= '9' ) {
1150         while( *s >= '0' && *s <= '9' ) {
1151             *value = *value * 10 + (*s - '0');
1152             s++;
1153         }
1154
1155         result = 0;
1156     }
1157
1158     *str = s;
1159
1160     return result;
1161 }
1162
1163 int NextTimeControlFromString( char ** str, long * value )
1164 {
1165     long temp;
1166     int result = NextIntegerFromString( str, &temp );
1167
1168     if( result == 0 ) {
1169         *value = temp * 60; /* Minutes */
1170         if( **str == ':' ) {
1171             (*str)++;
1172             result = NextIntegerFromString( str, &temp );
1173             *value += temp; /* Seconds */
1174         }
1175     }
1176
1177     return result;
1178 }
1179
1180 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1181 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1182     int result = -1, type = 0; long temp, temp2;
1183
1184     if(**str != ':') return -1; // old params remain in force!
1185     (*str)++;
1186     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1187     if( NextIntegerFromString( str, &temp ) ) return -1;
1188     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1189
1190     if(**str != '/') {
1191         /* time only: incremental or sudden-death time control */
1192         if(**str == '+') { /* increment follows; read it */
1193             (*str)++;
1194             if(**str == '!') type = *(*str)++; // Bronstein TC
1195             if(result = NextIntegerFromString( str, &temp2)) return -1;
1196             *inc = temp2 * 1000;
1197             if(**str == '.') { // read fraction of increment
1198                 char *start = ++(*str);
1199                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1200                 temp2 *= 1000;
1201                 while(start++ < *str) temp2 /= 10;
1202                 *inc += temp2;
1203             }
1204         } else *inc = 0;
1205         *moves = 0; *tc = temp * 1000; *incType = type;
1206         return 0;
1207     }
1208
1209     (*str)++; /* classical time control */
1210     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1211
1212     if(result == 0) {
1213         *moves = temp;
1214         *tc    = temp2 * 1000;
1215         *inc   = 0;
1216         *incType = type;
1217     }
1218     return result;
1219 }
1220
1221 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1222 {   /* [HGM] get time to add from the multi-session time-control string */
1223     int incType, moves=1; /* kludge to force reading of first session */
1224     long time, increment;
1225     char *s = tcString;
1226
1227     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1228     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1229     do {
1230         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1231         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1232         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1233         if(movenr == -1) return time;    /* last move before new session     */
1234         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1235         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1236         if(!moves) return increment;     /* current session is incremental   */
1237         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1238     } while(movenr >= -1);               /* try again for next session       */
1239
1240     return 0; // no new time quota on this move
1241 }
1242
1243 int
1244 ParseTimeControl(tc, ti, mps)
1245      char *tc;
1246      float ti;
1247      int mps;
1248 {
1249   long tc1;
1250   long tc2;
1251   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1252   int min, sec=0;
1253
1254   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1255   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1256       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1257   if(ti > 0) {
1258
1259     if(mps)
1260       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1261     else 
1262       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1263   } else {
1264     if(mps)
1265       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1266     else 
1267       snprintf(buf, MSG_SIZ, ":%s", mytc);
1268   }
1269   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1270   
1271   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1272     return FALSE;
1273   }
1274
1275   if( *tc == '/' ) {
1276     /* Parse second time control */
1277     tc++;
1278
1279     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1280       return FALSE;
1281     }
1282
1283     if( tc2 == 0 ) {
1284       return FALSE;
1285     }
1286
1287     timeControl_2 = tc2 * 1000;
1288   }
1289   else {
1290     timeControl_2 = 0;
1291   }
1292
1293   if( tc1 == 0 ) {
1294     return FALSE;
1295   }
1296
1297   timeControl = tc1 * 1000;
1298
1299   if (ti >= 0) {
1300     timeIncrement = ti * 1000;  /* convert to ms */
1301     movesPerSession = 0;
1302   } else {
1303     timeIncrement = 0;
1304     movesPerSession = mps;
1305   }
1306   return TRUE;
1307 }
1308
1309 void
1310 InitBackEnd2()
1311 {
1312     if (appData.debugMode) {
1313         fprintf(debugFP, "%s\n", programVersion);
1314     }
1315
1316     set_cont_sequence(appData.wrapContSeq);
1317     if (appData.matchGames > 0) {
1318         appData.matchMode = TRUE;
1319     } else if (appData.matchMode) {
1320         appData.matchGames = 1;
1321     }
1322     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1323         appData.matchGames = appData.sameColorGames;
1324     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1325         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1326         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1327     }
1328     Reset(TRUE, FALSE);
1329     if (appData.noChessProgram || first.protocolVersion == 1) {
1330       InitBackEnd3();
1331     } else {
1332       /* kludge: allow timeout for initial "feature" commands */
1333       FreezeUI();
1334       DisplayMessage("", _("Starting chess program"));
1335       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1336     }
1337 }
1338
1339 int
1340 CalculateIndex(int index, int gameNr)
1341 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1342     int res;
1343     if(index > 0) return index; // fixed nmber
1344     if(index == 0) return 1;
1345     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1346     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1347     return res;
1348 }
1349
1350 int
1351 LoadGameOrPosition(int gameNr)
1352 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1353     if (*appData.loadGameFile != NULLCHAR) {
1354         if (!LoadGameFromFile(appData.loadGameFile,
1355                 CalculateIndex(appData.loadGameIndex, gameNr),
1356                               appData.loadGameFile, FALSE)) {
1357             DisplayFatalError(_("Bad game file"), 0, 1);
1358             return 0;
1359         }
1360     } else if (*appData.loadPositionFile != NULLCHAR) {
1361         if (!LoadPositionFromFile(appData.loadPositionFile,
1362                 CalculateIndex(appData.loadPositionIndex, gameNr),
1363                                   appData.loadPositionFile)) {
1364             DisplayFatalError(_("Bad position file"), 0, 1);
1365             return 0;
1366         }
1367     }
1368     return 1;
1369 }
1370
1371 void
1372 ReserveGame(int gameNr, char resChar)
1373 {
1374     FILE *tf = fopen(appData.tourneyFile, "r+");
1375     char *p, *q, c, buf[MSG_SIZ];
1376     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1377     safeStrCpy(buf, lastMsg, MSG_SIZ);
1378     DisplayMessage(_("Pick new game"), "");
1379     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1380     ParseArgsFromFile(tf);
1381     p = q = appData.results;
1382     if(appData.debugMode) {
1383       char *r = appData.participants;
1384       fprintf(debugFP, "results = '%s'\n", p);
1385       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1386       fprintf(debugFP, "\n");
1387     }
1388     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1389     nextGame = q - p;
1390     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1391     safeStrCpy(q, p, strlen(p) + 2);
1392     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1393     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1394     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1395         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1396         q[nextGame] = '*';
1397     }
1398     fseek(tf, -(strlen(p)+4), SEEK_END);
1399     c = fgetc(tf);
1400     if(c != '"') // depending on DOS or Unix line endings we can be one off
1401          fseek(tf, -(strlen(p)+2), SEEK_END);
1402     else fseek(tf, -(strlen(p)+3), SEEK_END);
1403     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1404     DisplayMessage(buf, "");
1405     free(p); appData.results = q;
1406     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1407        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1408         UnloadEngine(&first);  // next game belongs to other pairing;
1409         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1410     }
1411 }
1412
1413 void
1414 MatchEvent(int mode)
1415 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1416         int dummy;
1417         if(matchMode) { // already in match mode: switch it off
1418             abortMatch = TRUE;
1419             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1420             return;
1421         }
1422 //      if(gameMode != BeginningOfGame) {
1423 //          DisplayError(_("You can only start a match from the initial position."), 0);
1424 //          return;
1425 //      }
1426         abortMatch = FALSE;
1427         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1428         /* Set up machine vs. machine match */
1429         nextGame = 0;
1430         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1431         if(appData.tourneyFile[0]) {
1432             ReserveGame(-1, 0);
1433             if(nextGame > appData.matchGames) {
1434                 char buf[MSG_SIZ];
1435                 if(strchr(appData.results, '*') == NULL) {
1436                     FILE *f;
1437                     appData.tourneyCycles++;
1438                     if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1439                         fclose(f);
1440                         NextTourneyGame(-1, &dummy);
1441                         ReserveGame(-1, 0);
1442                         if(nextGame <= appData.matchGames) {
1443                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1444                             matchMode = mode;
1445                             ScheduleDelayedEvent(NextMatchGame, 10000);
1446                             return;
1447                         }
1448                     }
1449                 }
1450                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1451                 DisplayError(buf, 0);
1452                 appData.tourneyFile[0] = 0;
1453                 return;
1454             }
1455         } else
1456         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1457             DisplayFatalError(_("Can't have a match with no chess programs"),
1458                               0, 2);
1459             return;
1460         }
1461         matchMode = mode;
1462         matchGame = roundNr = 1;
1463         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1464         NextMatchGame();
1465 }
1466
1467 void
1468 InitBackEnd3 P((void))
1469 {
1470     GameMode initialMode;
1471     char buf[MSG_SIZ];
1472     int err, len;
1473
1474     InitChessProgram(&first, startedFromSetupPosition);
1475
1476     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1477         free(programVersion);
1478         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1479         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1480     }
1481
1482     if (appData.icsActive) {
1483 #ifdef WIN32
1484         /* [DM] Make a console window if needed [HGM] merged ifs */
1485         ConsoleCreate();
1486 #endif
1487         err = establish();
1488         if (err != 0)
1489           {
1490             if (*appData.icsCommPort != NULLCHAR)
1491               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1492                              appData.icsCommPort);
1493             else
1494               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1495                         appData.icsHost, appData.icsPort);
1496
1497             if( (len > MSG_SIZ) && appData.debugMode )
1498               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1499
1500             DisplayFatalError(buf, err, 1);
1501             return;
1502         }
1503         SetICSMode();
1504         telnetISR =
1505           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1506         fromUserISR =
1507           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1508         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1509             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1510     } else if (appData.noChessProgram) {
1511         SetNCPMode();
1512     } else {
1513         SetGNUMode();
1514     }
1515
1516     if (*appData.cmailGameName != NULLCHAR) {
1517         SetCmailMode();
1518         OpenLoopback(&cmailPR);
1519         cmailISR =
1520           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1521     }
1522
1523     ThawUI();
1524     DisplayMessage("", "");
1525     if (StrCaseCmp(appData.initialMode, "") == 0) {
1526       initialMode = BeginningOfGame;
1527       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1528         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1529         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1530         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1531         ModeHighlight();
1532       }
1533     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1534       initialMode = TwoMachinesPlay;
1535     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1536       initialMode = AnalyzeFile;
1537     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1538       initialMode = AnalyzeMode;
1539     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1540       initialMode = MachinePlaysWhite;
1541     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1542       initialMode = MachinePlaysBlack;
1543     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1544       initialMode = EditGame;
1545     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1546       initialMode = EditPosition;
1547     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1548       initialMode = Training;
1549     } else {
1550       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1551       if( (len > MSG_SIZ) && appData.debugMode )
1552         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1553
1554       DisplayFatalError(buf, 0, 2);
1555       return;
1556     }
1557
1558     if (appData.matchMode) {
1559         if(appData.tourneyFile[0]) { // start tourney from command line
1560             FILE *f;
1561             if(f = fopen(appData.tourneyFile, "r")) {
1562                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1563                 fclose(f);
1564                 appData.clockMode = TRUE;
1565                 SetGNUMode();
1566             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1567         }
1568         MatchEvent(TRUE);
1569     } else if (*appData.cmailGameName != NULLCHAR) {
1570         /* Set up cmail mode */
1571         ReloadCmailMsgEvent(TRUE);
1572     } else {
1573         /* Set up other modes */
1574         if (initialMode == AnalyzeFile) {
1575           if (*appData.loadGameFile == NULLCHAR) {
1576             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1577             return;
1578           }
1579         }
1580         if (*appData.loadGameFile != NULLCHAR) {
1581             (void) LoadGameFromFile(appData.loadGameFile,
1582                                     appData.loadGameIndex,
1583                                     appData.loadGameFile, TRUE);
1584         } else if (*appData.loadPositionFile != NULLCHAR) {
1585             (void) LoadPositionFromFile(appData.loadPositionFile,
1586                                         appData.loadPositionIndex,
1587                                         appData.loadPositionFile);
1588             /* [HGM] try to make self-starting even after FEN load */
1589             /* to allow automatic setup of fairy variants with wtm */
1590             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1591                 gameMode = BeginningOfGame;
1592                 setboardSpoiledMachineBlack = 1;
1593             }
1594             /* [HGM] loadPos: make that every new game uses the setup */
1595             /* from file as long as we do not switch variant          */
1596             if(!blackPlaysFirst) {
1597                 startedFromPositionFile = TRUE;
1598                 CopyBoard(filePosition, boards[0]);
1599             }
1600         }
1601         if (initialMode == AnalyzeMode) {
1602           if (appData.noChessProgram) {
1603             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1604             return;
1605           }
1606           if (appData.icsActive) {
1607             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1608             return;
1609           }
1610           AnalyzeModeEvent();
1611         } else if (initialMode == AnalyzeFile) {
1612           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1613           ShowThinkingEvent();
1614           AnalyzeFileEvent();
1615           AnalysisPeriodicEvent(1);
1616         } else if (initialMode == MachinePlaysWhite) {
1617           if (appData.noChessProgram) {
1618             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1619                               0, 2);
1620             return;
1621           }
1622           if (appData.icsActive) {
1623             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1624                               0, 2);
1625             return;
1626           }
1627           MachineWhiteEvent();
1628         } else if (initialMode == MachinePlaysBlack) {
1629           if (appData.noChessProgram) {
1630             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1631                               0, 2);
1632             return;
1633           }
1634           if (appData.icsActive) {
1635             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1636                               0, 2);
1637             return;
1638           }
1639           MachineBlackEvent();
1640         } else if (initialMode == TwoMachinesPlay) {
1641           if (appData.noChessProgram) {
1642             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1643                               0, 2);
1644             return;
1645           }
1646           if (appData.icsActive) {
1647             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1648                               0, 2);
1649             return;
1650           }
1651           TwoMachinesEvent();
1652         } else if (initialMode == EditGame) {
1653           EditGameEvent();
1654         } else if (initialMode == EditPosition) {
1655           EditPositionEvent();
1656         } else if (initialMode == Training) {
1657           if (*appData.loadGameFile == NULLCHAR) {
1658             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1659             return;
1660           }
1661           TrainingEvent();
1662         }
1663     }
1664 }
1665
1666 /*
1667  * Establish will establish a contact to a remote host.port.
1668  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1669  *  used to talk to the host.
1670  * Returns 0 if okay, error code if not.
1671  */
1672 int
1673 establish()
1674 {
1675     char buf[MSG_SIZ];
1676
1677     if (*appData.icsCommPort != NULLCHAR) {
1678         /* Talk to the host through a serial comm port */
1679         return OpenCommPort(appData.icsCommPort, &icsPR);
1680
1681     } else if (*appData.gateway != NULLCHAR) {
1682         if (*appData.remoteShell == NULLCHAR) {
1683             /* Use the rcmd protocol to run telnet program on a gateway host */
1684             snprintf(buf, sizeof(buf), "%s %s %s",
1685                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1686             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1687
1688         } else {
1689             /* Use the rsh program to run telnet program on a gateway host */
1690             if (*appData.remoteUser == NULLCHAR) {
1691                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1692                         appData.gateway, appData.telnetProgram,
1693                         appData.icsHost, appData.icsPort);
1694             } else {
1695                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1696                         appData.remoteShell, appData.gateway,
1697                         appData.remoteUser, appData.telnetProgram,
1698                         appData.icsHost, appData.icsPort);
1699             }
1700             return StartChildProcess(buf, "", &icsPR);
1701
1702         }
1703     } else if (appData.useTelnet) {
1704         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1705
1706     } else {
1707         /* TCP socket interface differs somewhat between
1708            Unix and NT; handle details in the front end.
1709            */
1710         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1711     }
1712 }
1713
1714 void EscapeExpand(char *p, char *q)
1715 {       // [HGM] initstring: routine to shape up string arguments
1716         while(*p++ = *q++) if(p[-1] == '\\')
1717             switch(*q++) {
1718                 case 'n': p[-1] = '\n'; break;
1719                 case 'r': p[-1] = '\r'; break;
1720                 case 't': p[-1] = '\t'; break;
1721                 case '\\': p[-1] = '\\'; break;
1722                 case 0: *p = 0; return;
1723                 default: p[-1] = q[-1]; break;
1724             }
1725 }
1726
1727 void
1728 show_bytes(fp, buf, count)
1729      FILE *fp;
1730      char *buf;
1731      int count;
1732 {
1733     while (count--) {
1734         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1735             fprintf(fp, "\\%03o", *buf & 0xff);
1736         } else {
1737             putc(*buf, fp);
1738         }
1739         buf++;
1740     }
1741     fflush(fp);
1742 }
1743
1744 /* Returns an errno value */
1745 int
1746 OutputMaybeTelnet(pr, message, count, outError)
1747      ProcRef pr;
1748      char *message;
1749      int count;
1750      int *outError;
1751 {
1752     char buf[8192], *p, *q, *buflim;
1753     int left, newcount, outcount;
1754
1755     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1756         *appData.gateway != NULLCHAR) {
1757         if (appData.debugMode) {
1758             fprintf(debugFP, ">ICS: ");
1759             show_bytes(debugFP, message, count);
1760             fprintf(debugFP, "\n");
1761         }
1762         return OutputToProcess(pr, message, count, outError);
1763     }
1764
1765     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1766     p = message;
1767     q = buf;
1768     left = count;
1769     newcount = 0;
1770     while (left) {
1771         if (q >= buflim) {
1772             if (appData.debugMode) {
1773                 fprintf(debugFP, ">ICS: ");
1774                 show_bytes(debugFP, buf, newcount);
1775                 fprintf(debugFP, "\n");
1776             }
1777             outcount = OutputToProcess(pr, buf, newcount, outError);
1778             if (outcount < newcount) return -1; /* to be sure */
1779             q = buf;
1780             newcount = 0;
1781         }
1782         if (*p == '\n') {
1783             *q++ = '\r';
1784             newcount++;
1785         } else if (((unsigned char) *p) == TN_IAC) {
1786             *q++ = (char) TN_IAC;
1787             newcount ++;
1788         }
1789         *q++ = *p++;
1790         newcount++;
1791         left--;
1792     }
1793     if (appData.debugMode) {
1794         fprintf(debugFP, ">ICS: ");
1795         show_bytes(debugFP, buf, newcount);
1796         fprintf(debugFP, "\n");
1797     }
1798     outcount = OutputToProcess(pr, buf, newcount, outError);
1799     if (outcount < newcount) return -1; /* to be sure */
1800     return count;
1801 }
1802
1803 void
1804 read_from_player(isr, closure, message, count, error)
1805      InputSourceRef isr;
1806      VOIDSTAR closure;
1807      char *message;
1808      int count;
1809      int error;
1810 {
1811     int outError, outCount;
1812     static int gotEof = 0;
1813
1814     /* Pass data read from player on to ICS */
1815     if (count > 0) {
1816         gotEof = 0;
1817         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1818         if (outCount < count) {
1819             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1820         }
1821     } else if (count < 0) {
1822         RemoveInputSource(isr);
1823         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1824     } else if (gotEof++ > 0) {
1825         RemoveInputSource(isr);
1826         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1827     }
1828 }
1829
1830 void
1831 KeepAlive()
1832 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1833     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1834     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1835     SendToICS("date\n");
1836     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1837 }
1838
1839 /* added routine for printf style output to ics */
1840 void ics_printf(char *format, ...)
1841 {
1842     char buffer[MSG_SIZ];
1843     va_list args;
1844
1845     va_start(args, format);
1846     vsnprintf(buffer, sizeof(buffer), format, args);
1847     buffer[sizeof(buffer)-1] = '\0';
1848     SendToICS(buffer);
1849     va_end(args);
1850 }
1851
1852 void
1853 SendToICS(s)
1854      char *s;
1855 {
1856     int count, outCount, outError;
1857
1858     if (icsPR == NULL) return;
1859
1860     count = strlen(s);
1861     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1862     if (outCount < count) {
1863         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1864     }
1865 }
1866
1867 /* This is used for sending logon scripts to the ICS. Sending
1868    without a delay causes problems when using timestamp on ICC
1869    (at least on my machine). */
1870 void
1871 SendToICSDelayed(s,msdelay)
1872      char *s;
1873      long msdelay;
1874 {
1875     int count, outCount, outError;
1876
1877     if (icsPR == NULL) return;
1878
1879     count = strlen(s);
1880     if (appData.debugMode) {
1881         fprintf(debugFP, ">ICS: ");
1882         show_bytes(debugFP, s, count);
1883         fprintf(debugFP, "\n");
1884     }
1885     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1886                                       msdelay);
1887     if (outCount < count) {
1888         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1889     }
1890 }
1891
1892
1893 /* Remove all highlighting escape sequences in s
1894    Also deletes any suffix starting with '('
1895    */
1896 char *
1897 StripHighlightAndTitle(s)
1898      char *s;
1899 {
1900     static char retbuf[MSG_SIZ];
1901     char *p = retbuf;
1902
1903     while (*s != NULLCHAR) {
1904         while (*s == '\033') {
1905             while (*s != NULLCHAR && !isalpha(*s)) s++;
1906             if (*s != NULLCHAR) s++;
1907         }
1908         while (*s != NULLCHAR && *s != '\033') {
1909             if (*s == '(' || *s == '[') {
1910                 *p = NULLCHAR;
1911                 return retbuf;
1912             }
1913             *p++ = *s++;
1914         }
1915     }
1916     *p = NULLCHAR;
1917     return retbuf;
1918 }
1919
1920 /* Remove all highlighting escape sequences in s */
1921 char *
1922 StripHighlight(s)
1923      char *s;
1924 {
1925     static char retbuf[MSG_SIZ];
1926     char *p = retbuf;
1927
1928     while (*s != NULLCHAR) {
1929         while (*s == '\033') {
1930             while (*s != NULLCHAR && !isalpha(*s)) s++;
1931             if (*s != NULLCHAR) s++;
1932         }
1933         while (*s != NULLCHAR && *s != '\033') {
1934             *p++ = *s++;
1935         }
1936     }
1937     *p = NULLCHAR;
1938     return retbuf;
1939 }
1940
1941 char *variantNames[] = VARIANT_NAMES;
1942 char *
1943 VariantName(v)
1944      VariantClass v;
1945 {
1946     return variantNames[v];
1947 }
1948
1949
1950 /* Identify a variant from the strings the chess servers use or the
1951    PGN Variant tag names we use. */
1952 VariantClass
1953 StringToVariant(e)
1954      char *e;
1955 {
1956     char *p;
1957     int wnum = -1;
1958     VariantClass v = VariantNormal;
1959     int i, found = FALSE;
1960     char buf[MSG_SIZ];
1961     int len;
1962
1963     if (!e) return v;
1964
1965     /* [HGM] skip over optional board-size prefixes */
1966     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1967         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1968         while( *e++ != '_');
1969     }
1970
1971     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1972         v = VariantNormal;
1973         found = TRUE;
1974     } else
1975     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1976       if (StrCaseStr(e, variantNames[i])) {
1977         v = (VariantClass) i;
1978         found = TRUE;
1979         break;
1980       }
1981     }
1982
1983     if (!found) {
1984       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1985           || StrCaseStr(e, "wild/fr")
1986           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1987         v = VariantFischeRandom;
1988       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1989                  (i = 1, p = StrCaseStr(e, "w"))) {
1990         p += i;
1991         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1992         if (isdigit(*p)) {
1993           wnum = atoi(p);
1994         } else {
1995           wnum = -1;
1996         }
1997         switch (wnum) {
1998         case 0: /* FICS only, actually */
1999         case 1:
2000           /* Castling legal even if K starts on d-file */
2001           v = VariantWildCastle;
2002           break;
2003         case 2:
2004         case 3:
2005         case 4:
2006           /* Castling illegal even if K & R happen to start in
2007              normal positions. */
2008           v = VariantNoCastle;
2009           break;
2010         case 5:
2011         case 7:
2012         case 8:
2013         case 10:
2014         case 11:
2015         case 12:
2016         case 13:
2017         case 14:
2018         case 15:
2019         case 18:
2020         case 19:
2021           /* Castling legal iff K & R start in normal positions */
2022           v = VariantNormal;
2023           break;
2024         case 6:
2025         case 20:
2026         case 21:
2027           /* Special wilds for position setup; unclear what to do here */
2028           v = VariantLoadable;
2029           break;
2030         case 9:
2031           /* Bizarre ICC game */
2032           v = VariantTwoKings;
2033           break;
2034         case 16:
2035           v = VariantKriegspiel;
2036           break;
2037         case 17:
2038           v = VariantLosers;
2039           break;
2040         case 22:
2041           v = VariantFischeRandom;
2042           break;
2043         case 23:
2044           v = VariantCrazyhouse;
2045           break;
2046         case 24:
2047           v = VariantBughouse;
2048           break;
2049         case 25:
2050           v = Variant3Check;
2051           break;
2052         case 26:
2053           /* Not quite the same as FICS suicide! */
2054           v = VariantGiveaway;
2055           break;
2056         case 27:
2057           v = VariantAtomic;
2058           break;
2059         case 28:
2060           v = VariantShatranj;
2061           break;
2062
2063         /* Temporary names for future ICC types.  The name *will* change in
2064            the next xboard/WinBoard release after ICC defines it. */
2065         case 29:
2066           v = Variant29;
2067           break;
2068         case 30:
2069           v = Variant30;
2070           break;
2071         case 31:
2072           v = Variant31;
2073           break;
2074         case 32:
2075           v = Variant32;
2076           break;
2077         case 33:
2078           v = Variant33;
2079           break;
2080         case 34:
2081           v = Variant34;
2082           break;
2083         case 35:
2084           v = Variant35;
2085           break;
2086         case 36:
2087           v = Variant36;
2088           break;
2089         case 37:
2090           v = VariantShogi;
2091           break;
2092         case 38:
2093           v = VariantXiangqi;
2094           break;
2095         case 39:
2096           v = VariantCourier;
2097           break;
2098         case 40:
2099           v = VariantGothic;
2100           break;
2101         case 41:
2102           v = VariantCapablanca;
2103           break;
2104         case 42:
2105           v = VariantKnightmate;
2106           break;
2107         case 43:
2108           v = VariantFairy;
2109           break;
2110         case 44:
2111           v = VariantCylinder;
2112           break;
2113         case 45:
2114           v = VariantFalcon;
2115           break;
2116         case 46:
2117           v = VariantCapaRandom;
2118           break;
2119         case 47:
2120           v = VariantBerolina;
2121           break;
2122         case 48:
2123           v = VariantJanus;
2124           break;
2125         case 49:
2126           v = VariantSuper;
2127           break;
2128         case 50:
2129           v = VariantGreat;
2130           break;
2131         case -1:
2132           /* Found "wild" or "w" in the string but no number;
2133              must assume it's normal chess. */
2134           v = VariantNormal;
2135           break;
2136         default:
2137           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2138           if( (len > MSG_SIZ) && appData.debugMode )
2139             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2140
2141           DisplayError(buf, 0);
2142           v = VariantUnknown;
2143           break;
2144         }
2145       }
2146     }
2147     if (appData.debugMode) {
2148       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2149               e, wnum, VariantName(v));
2150     }
2151     return v;
2152 }
2153
2154 static int leftover_start = 0, leftover_len = 0;
2155 char star_match[STAR_MATCH_N][MSG_SIZ];
2156
2157 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2158    advance *index beyond it, and set leftover_start to the new value of
2159    *index; else return FALSE.  If pattern contains the character '*', it
2160    matches any sequence of characters not containing '\r', '\n', or the
2161    character following the '*' (if any), and the matched sequence(s) are
2162    copied into star_match.
2163    */
2164 int
2165 looking_at(buf, index, pattern)
2166      char *buf;
2167      int *index;
2168      char *pattern;
2169 {
2170     char *bufp = &buf[*index], *patternp = pattern;
2171     int star_count = 0;
2172     char *matchp = star_match[0];
2173
2174     for (;;) {
2175         if (*patternp == NULLCHAR) {
2176             *index = leftover_start = bufp - buf;
2177             *matchp = NULLCHAR;
2178             return TRUE;
2179         }
2180         if (*bufp == NULLCHAR) return FALSE;
2181         if (*patternp == '*') {
2182             if (*bufp == *(patternp + 1)) {
2183                 *matchp = NULLCHAR;
2184                 matchp = star_match[++star_count];
2185                 patternp += 2;
2186                 bufp++;
2187                 continue;
2188             } else if (*bufp == '\n' || *bufp == '\r') {
2189                 patternp++;
2190                 if (*patternp == NULLCHAR)
2191                   continue;
2192                 else
2193                   return FALSE;
2194             } else {
2195                 *matchp++ = *bufp++;
2196                 continue;
2197             }
2198         }
2199         if (*patternp != *bufp) return FALSE;
2200         patternp++;
2201         bufp++;
2202     }
2203 }
2204
2205 void
2206 SendToPlayer(data, length)
2207      char *data;
2208      int length;
2209 {
2210     int error, outCount;
2211     outCount = OutputToProcess(NoProc, data, length, &error);
2212     if (outCount < length) {
2213         DisplayFatalError(_("Error writing to display"), error, 1);
2214     }
2215 }
2216
2217 void
2218 PackHolding(packed, holding)
2219      char packed[];
2220      char *holding;
2221 {
2222     char *p = holding;
2223     char *q = packed;
2224     int runlength = 0;
2225     int curr = 9999;
2226     do {
2227         if (*p == curr) {
2228             runlength++;
2229         } else {
2230             switch (runlength) {
2231               case 0:
2232                 break;
2233               case 1:
2234                 *q++ = curr;
2235                 break;
2236               case 2:
2237                 *q++ = curr;
2238                 *q++ = curr;
2239                 break;
2240               default:
2241                 sprintf(q, "%d", runlength);
2242                 while (*q) q++;
2243                 *q++ = curr;
2244                 break;
2245             }
2246             runlength = 1;
2247             curr = *p;
2248         }
2249     } while (*p++);
2250     *q = NULLCHAR;
2251 }
2252
2253 /* Telnet protocol requests from the front end */
2254 void
2255 TelnetRequest(ddww, option)
2256      unsigned char ddww, option;
2257 {
2258     unsigned char msg[3];
2259     int outCount, outError;
2260
2261     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2262
2263     if (appData.debugMode) {
2264         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2265         switch (ddww) {
2266           case TN_DO:
2267             ddwwStr = "DO";
2268             break;
2269           case TN_DONT:
2270             ddwwStr = "DONT";
2271             break;
2272           case TN_WILL:
2273             ddwwStr = "WILL";
2274             break;
2275           case TN_WONT:
2276             ddwwStr = "WONT";
2277             break;
2278           default:
2279             ddwwStr = buf1;
2280             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2281             break;
2282         }
2283         switch (option) {
2284           case TN_ECHO:
2285             optionStr = "ECHO";
2286             break;
2287           default:
2288             optionStr = buf2;
2289             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2290             break;
2291         }
2292         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2293     }
2294     msg[0] = TN_IAC;
2295     msg[1] = ddww;
2296     msg[2] = option;
2297     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2298     if (outCount < 3) {
2299         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2300     }
2301 }
2302
2303 void
2304 DoEcho()
2305 {
2306     if (!appData.icsActive) return;
2307     TelnetRequest(TN_DO, TN_ECHO);
2308 }
2309
2310 void
2311 DontEcho()
2312 {
2313     if (!appData.icsActive) return;
2314     TelnetRequest(TN_DONT, TN_ECHO);
2315 }
2316
2317 void
2318 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2319 {
2320     /* put the holdings sent to us by the server on the board holdings area */
2321     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2322     char p;
2323     ChessSquare piece;
2324
2325     if(gameInfo.holdingsWidth < 2)  return;
2326     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2327         return; // prevent overwriting by pre-board holdings
2328
2329     if( (int)lowestPiece >= BlackPawn ) {
2330         holdingsColumn = 0;
2331         countsColumn = 1;
2332         holdingsStartRow = BOARD_HEIGHT-1;
2333         direction = -1;
2334     } else {
2335         holdingsColumn = BOARD_WIDTH-1;
2336         countsColumn = BOARD_WIDTH-2;
2337         holdingsStartRow = 0;
2338         direction = 1;
2339     }
2340
2341     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2342         board[i][holdingsColumn] = EmptySquare;
2343         board[i][countsColumn]   = (ChessSquare) 0;
2344     }
2345     while( (p=*holdings++) != NULLCHAR ) {
2346         piece = CharToPiece( ToUpper(p) );
2347         if(piece == EmptySquare) continue;
2348         /*j = (int) piece - (int) WhitePawn;*/
2349         j = PieceToNumber(piece);
2350         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2351         if(j < 0) continue;               /* should not happen */
2352         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2353         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2354         board[holdingsStartRow+j*direction][countsColumn]++;
2355     }
2356 }
2357
2358
2359 void
2360 VariantSwitch(Board board, VariantClass newVariant)
2361 {
2362    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2363    static Board oldBoard;
2364
2365    startedFromPositionFile = FALSE;
2366    if(gameInfo.variant == newVariant) return;
2367
2368    /* [HGM] This routine is called each time an assignment is made to
2369     * gameInfo.variant during a game, to make sure the board sizes
2370     * are set to match the new variant. If that means adding or deleting
2371     * holdings, we shift the playing board accordingly
2372     * This kludge is needed because in ICS observe mode, we get boards
2373     * of an ongoing game without knowing the variant, and learn about the
2374     * latter only later. This can be because of the move list we requested,
2375     * in which case the game history is refilled from the beginning anyway,
2376     * but also when receiving holdings of a crazyhouse game. In the latter
2377     * case we want to add those holdings to the already received position.
2378     */
2379
2380
2381    if (appData.debugMode) {
2382      fprintf(debugFP, "Switch board from %s to %s\n",
2383              VariantName(gameInfo.variant), VariantName(newVariant));
2384      setbuf(debugFP, NULL);
2385    }
2386    shuffleOpenings = 0;       /* [HGM] shuffle */
2387    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2388    switch(newVariant)
2389      {
2390      case VariantShogi:
2391        newWidth = 9;  newHeight = 9;
2392        gameInfo.holdingsSize = 7;
2393      case VariantBughouse:
2394      case VariantCrazyhouse:
2395        newHoldingsWidth = 2; break;
2396      case VariantGreat:
2397        newWidth = 10;
2398      case VariantSuper:
2399        newHoldingsWidth = 2;
2400        gameInfo.holdingsSize = 8;
2401        break;
2402      case VariantGothic:
2403      case VariantCapablanca:
2404      case VariantCapaRandom:
2405        newWidth = 10;
2406      default:
2407        newHoldingsWidth = gameInfo.holdingsSize = 0;
2408      };
2409
2410    if(newWidth  != gameInfo.boardWidth  ||
2411       newHeight != gameInfo.boardHeight ||
2412       newHoldingsWidth != gameInfo.holdingsWidth ) {
2413
2414      /* shift position to new playing area, if needed */
2415      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2416        for(i=0; i<BOARD_HEIGHT; i++)
2417          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2418            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2419              board[i][j];
2420        for(i=0; i<newHeight; i++) {
2421          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2422          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2423        }
2424      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2425        for(i=0; i<BOARD_HEIGHT; i++)
2426          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2427            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2428              board[i][j];
2429      }
2430      gameInfo.boardWidth  = newWidth;
2431      gameInfo.boardHeight = newHeight;
2432      gameInfo.holdingsWidth = newHoldingsWidth;
2433      gameInfo.variant = newVariant;
2434      InitDrawingSizes(-2, 0);
2435    } else gameInfo.variant = newVariant;
2436    CopyBoard(oldBoard, board);   // remember correctly formatted board
2437      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2438    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2439 }
2440
2441 static int loggedOn = FALSE;
2442
2443 /*-- Game start info cache: --*/
2444 int gs_gamenum;
2445 char gs_kind[MSG_SIZ];
2446 static char player1Name[128] = "";
2447 static char player2Name[128] = "";
2448 static char cont_seq[] = "\n\\   ";
2449 static int player1Rating = -1;
2450 static int player2Rating = -1;
2451 /*----------------------------*/
2452
2453 ColorClass curColor = ColorNormal;
2454 int suppressKibitz = 0;
2455
2456 // [HGM] seekgraph
2457 Boolean soughtPending = FALSE;
2458 Boolean seekGraphUp;
2459 #define MAX_SEEK_ADS 200
2460 #define SQUARE 0x80
2461 char *seekAdList[MAX_SEEK_ADS];
2462 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2463 float tcList[MAX_SEEK_ADS];
2464 char colorList[MAX_SEEK_ADS];
2465 int nrOfSeekAds = 0;
2466 int minRating = 1010, maxRating = 2800;
2467 int hMargin = 10, vMargin = 20, h, w;
2468 extern int squareSize, lineGap;
2469
2470 void
2471 PlotSeekAd(int i)
2472 {
2473         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2474         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2475         if(r < minRating+100 && r >=0 ) r = minRating+100;
2476         if(r > maxRating) r = maxRating;
2477         if(tc < 1.) tc = 1.;
2478         if(tc > 95.) tc = 95.;
2479         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2480         y = ((double)r - minRating)/(maxRating - minRating)
2481             * (h-vMargin-squareSize/8-1) + vMargin;
2482         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2483         if(strstr(seekAdList[i], " u ")) color = 1;
2484         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2485            !strstr(seekAdList[i], "bullet") &&
2486            !strstr(seekAdList[i], "blitz") &&
2487            !strstr(seekAdList[i], "standard") ) color = 2;
2488         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2489         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2490 }
2491
2492 void
2493 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2494 {
2495         char buf[MSG_SIZ], *ext = "";
2496         VariantClass v = StringToVariant(type);
2497         if(strstr(type, "wild")) {
2498             ext = type + 4; // append wild number
2499             if(v == VariantFischeRandom) type = "chess960"; else
2500             if(v == VariantLoadable) type = "setup"; else
2501             type = VariantName(v);
2502         }
2503         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2504         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2505             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2506             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2507             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2508             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2509             seekNrList[nrOfSeekAds] = nr;
2510             zList[nrOfSeekAds] = 0;
2511             seekAdList[nrOfSeekAds++] = StrSave(buf);
2512             if(plot) PlotSeekAd(nrOfSeekAds-1);
2513         }
2514 }
2515
2516 void
2517 EraseSeekDot(int i)
2518 {
2519     int x = xList[i], y = yList[i], d=squareSize/4, k;
2520     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2521     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2522     // now replot every dot that overlapped
2523     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2524         int xx = xList[k], yy = yList[k];
2525         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2526             DrawSeekDot(xx, yy, colorList[k]);
2527     }
2528 }
2529
2530 void
2531 RemoveSeekAd(int nr)
2532 {
2533         int i;
2534         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2535             EraseSeekDot(i);
2536             if(seekAdList[i]) free(seekAdList[i]);
2537             seekAdList[i] = seekAdList[--nrOfSeekAds];
2538             seekNrList[i] = seekNrList[nrOfSeekAds];
2539             ratingList[i] = ratingList[nrOfSeekAds];
2540             colorList[i]  = colorList[nrOfSeekAds];
2541             tcList[i] = tcList[nrOfSeekAds];
2542             xList[i]  = xList[nrOfSeekAds];
2543             yList[i]  = yList[nrOfSeekAds];
2544             zList[i]  = zList[nrOfSeekAds];
2545             seekAdList[nrOfSeekAds] = NULL;
2546             break;
2547         }
2548 }
2549
2550 Boolean
2551 MatchSoughtLine(char *line)
2552 {
2553     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2554     int nr, base, inc, u=0; char dummy;
2555
2556     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2557        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2558        (u=1) &&
2559        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2560         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2561         // match: compact and save the line
2562         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2563         return TRUE;
2564     }
2565     return FALSE;
2566 }
2567
2568 int
2569 DrawSeekGraph()
2570 {
2571     int i;
2572     if(!seekGraphUp) return FALSE;
2573     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2574     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2575
2576     DrawSeekBackground(0, 0, w, h);
2577     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2578     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2579     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2580         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2581         yy = h-1-yy;
2582         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2583         if(i%500 == 0) {
2584             char buf[MSG_SIZ];
2585             snprintf(buf, MSG_SIZ, "%d", i);
2586             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2587         }
2588     }
2589     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2590     for(i=1; i<100; i+=(i<10?1:5)) {
2591         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2592         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2593         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2594             char buf[MSG_SIZ];
2595             snprintf(buf, MSG_SIZ, "%d", i);
2596             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2597         }
2598     }
2599     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2600     return TRUE;
2601 }
2602
2603 int SeekGraphClick(ClickType click, int x, int y, int moving)
2604 {
2605     static int lastDown = 0, displayed = 0, lastSecond;
2606     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2607         if(click == Release || moving) return FALSE;
2608         nrOfSeekAds = 0;
2609         soughtPending = TRUE;
2610         SendToICS(ics_prefix);
2611         SendToICS("sought\n"); // should this be "sought all"?
2612     } else { // issue challenge based on clicked ad
2613         int dist = 10000; int i, closest = 0, second = 0;
2614         for(i=0; i<nrOfSeekAds; i++) {
2615             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2616             if(d < dist) { dist = d; closest = i; }
2617             second += (d - zList[i] < 120); // count in-range ads
2618             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2619         }
2620         if(dist < 120) {
2621             char buf[MSG_SIZ];
2622             second = (second > 1);
2623             if(displayed != closest || second != lastSecond) {
2624                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2625                 lastSecond = second; displayed = closest;
2626             }
2627             if(click == Press) {
2628                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2629                 lastDown = closest;
2630                 return TRUE;
2631             } // on press 'hit', only show info
2632             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2633             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2634             SendToICS(ics_prefix);
2635             SendToICS(buf);
2636             return TRUE; // let incoming board of started game pop down the graph
2637         } else if(click == Release) { // release 'miss' is ignored
2638             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2639             if(moving == 2) { // right up-click
2640                 nrOfSeekAds = 0; // refresh graph
2641                 soughtPending = TRUE;
2642                 SendToICS(ics_prefix);
2643                 SendToICS("sought\n"); // should this be "sought all"?
2644             }
2645             return TRUE;
2646         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2647         // press miss or release hit 'pop down' seek graph
2648         seekGraphUp = FALSE;
2649         DrawPosition(TRUE, NULL);
2650     }
2651     return TRUE;
2652 }
2653
2654 void
2655 read_from_ics(isr, closure, data, count, error)
2656      InputSourceRef isr;
2657      VOIDSTAR closure;
2658      char *data;
2659      int count;
2660      int error;
2661 {
2662 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2663 #define STARTED_NONE 0
2664 #define STARTED_MOVES 1
2665 #define STARTED_BOARD 2
2666 #define STARTED_OBSERVE 3
2667 #define STARTED_HOLDINGS 4
2668 #define STARTED_CHATTER 5
2669 #define STARTED_COMMENT 6
2670 #define STARTED_MOVES_NOHIDE 7
2671
2672     static int started = STARTED_NONE;
2673     static char parse[20000];
2674     static int parse_pos = 0;
2675     static char buf[BUF_SIZE + 1];
2676     static int firstTime = TRUE, intfSet = FALSE;
2677     static ColorClass prevColor = ColorNormal;
2678     static int savingComment = FALSE;
2679     static int cmatch = 0; // continuation sequence match
2680     char *bp;
2681     char str[MSG_SIZ];
2682     int i, oldi;
2683     int buf_len;
2684     int next_out;
2685     int tkind;
2686     int backup;    /* [DM] For zippy color lines */
2687     char *p;
2688     char talker[MSG_SIZ]; // [HGM] chat
2689     int channel;
2690
2691     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2692
2693     if (appData.debugMode) {
2694       if (!error) {
2695         fprintf(debugFP, "<ICS: ");
2696         show_bytes(debugFP, data, count);
2697         fprintf(debugFP, "\n");
2698       }
2699     }
2700
2701     if (appData.debugMode) { int f = forwardMostMove;
2702         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2703                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2704                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2705     }
2706     if (count > 0) {
2707         /* If last read ended with a partial line that we couldn't parse,
2708            prepend it to the new read and try again. */
2709         if (leftover_len > 0) {
2710             for (i=0; i<leftover_len; i++)
2711               buf[i] = buf[leftover_start + i];
2712         }
2713
2714     /* copy new characters into the buffer */
2715     bp = buf + leftover_len;
2716     buf_len=leftover_len;
2717     for (i=0; i<count; i++)
2718     {
2719         // ignore these
2720         if (data[i] == '\r')
2721             continue;
2722
2723         // join lines split by ICS?
2724         if (!appData.noJoin)
2725         {
2726             /*
2727                 Joining just consists of finding matches against the
2728                 continuation sequence, and discarding that sequence
2729                 if found instead of copying it.  So, until a match
2730                 fails, there's nothing to do since it might be the
2731                 complete sequence, and thus, something we don't want
2732                 copied.
2733             */
2734             if (data[i] == cont_seq[cmatch])
2735             {
2736                 cmatch++;
2737                 if (cmatch == strlen(cont_seq))
2738                 {
2739                     cmatch = 0; // complete match.  just reset the counter
2740
2741                     /*
2742                         it's possible for the ICS to not include the space
2743                         at the end of the last word, making our [correct]
2744                         join operation fuse two separate words.  the server
2745                         does this when the space occurs at the width setting.
2746                     */
2747                     if (!buf_len || buf[buf_len-1] != ' ')
2748                     {
2749                         *bp++ = ' ';
2750                         buf_len++;
2751                     }
2752                 }
2753                 continue;
2754             }
2755             else if (cmatch)
2756             {
2757                 /*
2758                     match failed, so we have to copy what matched before
2759                     falling through and copying this character.  In reality,
2760                     this will only ever be just the newline character, but
2761                     it doesn't hurt to be precise.
2762                 */
2763                 strncpy(bp, cont_seq, cmatch);
2764                 bp += cmatch;
2765                 buf_len += cmatch;
2766                 cmatch = 0;
2767             }
2768         }
2769
2770         // copy this char
2771         *bp++ = data[i];
2772         buf_len++;
2773     }
2774
2775         buf[buf_len] = NULLCHAR;
2776 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2777         next_out = 0;
2778         leftover_start = 0;
2779
2780         i = 0;
2781         while (i < buf_len) {
2782             /* Deal with part of the TELNET option negotiation
2783                protocol.  We refuse to do anything beyond the
2784                defaults, except that we allow the WILL ECHO option,
2785                which ICS uses to turn off password echoing when we are
2786                directly connected to it.  We reject this option
2787                if localLineEditing mode is on (always on in xboard)
2788                and we are talking to port 23, which might be a real
2789                telnet server that will try to keep WILL ECHO on permanently.
2790              */
2791             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2792                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2793                 unsigned char option;
2794                 oldi = i;
2795                 switch ((unsigned char) buf[++i]) {
2796                   case TN_WILL:
2797                     if (appData.debugMode)
2798                       fprintf(debugFP, "\n<WILL ");
2799                     switch (option = (unsigned char) buf[++i]) {
2800                       case TN_ECHO:
2801                         if (appData.debugMode)
2802                           fprintf(debugFP, "ECHO ");
2803                         /* Reply only if this is a change, according
2804                            to the protocol rules. */
2805                         if (remoteEchoOption) break;
2806                         if (appData.localLineEditing &&
2807                             atoi(appData.icsPort) == TN_PORT) {
2808                             TelnetRequest(TN_DONT, TN_ECHO);
2809                         } else {
2810                             EchoOff();
2811                             TelnetRequest(TN_DO, TN_ECHO);
2812                             remoteEchoOption = TRUE;
2813                         }
2814                         break;
2815                       default:
2816                         if (appData.debugMode)
2817                           fprintf(debugFP, "%d ", option);
2818                         /* Whatever this is, we don't want it. */
2819                         TelnetRequest(TN_DONT, option);
2820                         break;
2821                     }
2822                     break;
2823                   case TN_WONT:
2824                     if (appData.debugMode)
2825                       fprintf(debugFP, "\n<WONT ");
2826                     switch (option = (unsigned char) buf[++i]) {
2827                       case TN_ECHO:
2828                         if (appData.debugMode)
2829                           fprintf(debugFP, "ECHO ");
2830                         /* Reply only if this is a change, according
2831                            to the protocol rules. */
2832                         if (!remoteEchoOption) break;
2833                         EchoOn();
2834                         TelnetRequest(TN_DONT, TN_ECHO);
2835                         remoteEchoOption = FALSE;
2836                         break;
2837                       default:
2838                         if (appData.debugMode)
2839                           fprintf(debugFP, "%d ", (unsigned char) option);
2840                         /* Whatever this is, it must already be turned
2841                            off, because we never agree to turn on
2842                            anything non-default, so according to the
2843                            protocol rules, we don't reply. */
2844                         break;
2845                     }
2846                     break;
2847                   case TN_DO:
2848                     if (appData.debugMode)
2849                       fprintf(debugFP, "\n<DO ");
2850                     switch (option = (unsigned char) buf[++i]) {
2851                       default:
2852                         /* Whatever this is, we refuse to do it. */
2853                         if (appData.debugMode)
2854                           fprintf(debugFP, "%d ", option);
2855                         TelnetRequest(TN_WONT, option);
2856                         break;
2857                     }
2858                     break;
2859                   case TN_DONT:
2860                     if (appData.debugMode)
2861                       fprintf(debugFP, "\n<DONT ");
2862                     switch (option = (unsigned char) buf[++i]) {
2863                       default:
2864                         if (appData.debugMode)
2865                           fprintf(debugFP, "%d ", option);
2866                         /* Whatever this is, we are already not doing
2867                            it, because we never agree to do anything
2868                            non-default, so according to the protocol
2869                            rules, we don't reply. */
2870                         break;
2871                     }
2872                     break;
2873                   case TN_IAC:
2874                     if (appData.debugMode)
2875                       fprintf(debugFP, "\n<IAC ");
2876                     /* Doubled IAC; pass it through */
2877                     i--;
2878                     break;
2879                   default:
2880                     if (appData.debugMode)
2881                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2882                     /* Drop all other telnet commands on the floor */
2883                     break;
2884                 }
2885                 if (oldi > next_out)
2886                   SendToPlayer(&buf[next_out], oldi - next_out);
2887                 if (++i > next_out)
2888                   next_out = i;
2889                 continue;
2890             }
2891
2892             /* OK, this at least will *usually* work */
2893             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2894                 loggedOn = TRUE;
2895             }
2896
2897             if (loggedOn && !intfSet) {
2898                 if (ics_type == ICS_ICC) {
2899                   snprintf(str, MSG_SIZ,
2900                           "/set-quietly interface %s\n/set-quietly style 12\n",
2901                           programVersion);
2902                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2903                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2904                 } else if (ics_type == ICS_CHESSNET) {
2905                   snprintf(str, MSG_SIZ, "/style 12\n");
2906                 } else {
2907                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2908                   strcat(str, programVersion);
2909                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2910                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2911                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2912 #ifdef WIN32
2913                   strcat(str, "$iset nohighlight 1\n");
2914 #endif
2915                   strcat(str, "$iset lock 1\n$style 12\n");
2916                 }
2917                 SendToICS(str);
2918                 NotifyFrontendLogin();
2919                 intfSet = TRUE;
2920             }
2921
2922             if (started == STARTED_COMMENT) {
2923                 /* Accumulate characters in comment */
2924                 parse[parse_pos++] = buf[i];
2925                 if (buf[i] == '\n') {
2926                     parse[parse_pos] = NULLCHAR;
2927                     if(chattingPartner>=0) {
2928                         char mess[MSG_SIZ];
2929                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2930                         OutputChatMessage(chattingPartner, mess);
2931                         chattingPartner = -1;
2932                         next_out = i+1; // [HGM] suppress printing in ICS window
2933                     } else
2934                     if(!suppressKibitz) // [HGM] kibitz
2935                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2936                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2937                         int nrDigit = 0, nrAlph = 0, j;
2938                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2939                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2940                         parse[parse_pos] = NULLCHAR;
2941                         // try to be smart: if it does not look like search info, it should go to
2942                         // ICS interaction window after all, not to engine-output window.
2943                         for(j=0; j<parse_pos; j++) { // count letters and digits
2944                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2945                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2946                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2947                         }
2948                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2949                             int depth=0; float score;
2950                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2951                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2952                                 pvInfoList[forwardMostMove-1].depth = depth;
2953                                 pvInfoList[forwardMostMove-1].score = 100*score;
2954                             }
2955                             OutputKibitz(suppressKibitz, parse);
2956                         } else {
2957                             char tmp[MSG_SIZ];
2958                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2959                             SendToPlayer(tmp, strlen(tmp));
2960                         }
2961                         next_out = i+1; // [HGM] suppress printing in ICS window
2962                     }
2963                     started = STARTED_NONE;
2964                 } else {
2965                     /* Don't match patterns against characters in comment */
2966                     i++;
2967                     continue;
2968                 }
2969             }
2970             if (started == STARTED_CHATTER) {
2971                 if (buf[i] != '\n') {
2972                     /* Don't match patterns against characters in chatter */
2973                     i++;
2974                     continue;
2975                 }
2976                 started = STARTED_NONE;
2977                 if(suppressKibitz) next_out = i+1;
2978             }
2979
2980             /* Kludge to deal with rcmd protocol */
2981             if (firstTime && looking_at(buf, &i, "\001*")) {
2982                 DisplayFatalError(&buf[1], 0, 1);
2983                 continue;
2984             } else {
2985                 firstTime = FALSE;
2986             }
2987
2988             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2989                 ics_type = ICS_ICC;
2990                 ics_prefix = "/";
2991                 if (appData.debugMode)
2992                   fprintf(debugFP, "ics_type %d\n", ics_type);
2993                 continue;
2994             }
2995             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2996                 ics_type = ICS_FICS;
2997                 ics_prefix = "$";
2998                 if (appData.debugMode)
2999                   fprintf(debugFP, "ics_type %d\n", ics_type);
3000                 continue;
3001             }
3002             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3003                 ics_type = ICS_CHESSNET;
3004                 ics_prefix = "/";
3005                 if (appData.debugMode)
3006                   fprintf(debugFP, "ics_type %d\n", ics_type);
3007                 continue;
3008             }
3009
3010             if (!loggedOn &&
3011                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3012                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3013                  looking_at(buf, &i, "will be \"*\""))) {
3014               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3015               continue;
3016             }
3017
3018             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3019               char buf[MSG_SIZ];
3020               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3021               DisplayIcsInteractionTitle(buf);
3022               have_set_title = TRUE;
3023             }
3024
3025             /* skip finger notes */
3026             if (started == STARTED_NONE &&
3027                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3028                  (buf[i] == '1' && buf[i+1] == '0')) &&
3029                 buf[i+2] == ':' && buf[i+3] == ' ') {
3030               started = STARTED_CHATTER;
3031               i += 3;
3032               continue;
3033             }
3034
3035             oldi = i;
3036             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3037             if(appData.seekGraph) {
3038                 if(soughtPending && MatchSoughtLine(buf+i)) {
3039                     i = strstr(buf+i, "rated") - buf;
3040                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3041                     next_out = leftover_start = i;
3042                     started = STARTED_CHATTER;
3043                     suppressKibitz = TRUE;
3044                     continue;
3045                 }
3046                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3047                         && looking_at(buf, &i, "* ads displayed")) {
3048                     soughtPending = FALSE;
3049                     seekGraphUp = TRUE;
3050                     DrawSeekGraph();
3051                     continue;
3052                 }
3053                 if(appData.autoRefresh) {
3054                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3055                         int s = (ics_type == ICS_ICC); // ICC format differs
3056                         if(seekGraphUp)
3057                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3058                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3059                         looking_at(buf, &i, "*% "); // eat prompt
3060                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3061                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3062                         next_out = i; // suppress
3063                         continue;
3064                     }
3065                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3066                         char *p = star_match[0];
3067                         while(*p) {
3068                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3069                             while(*p && *p++ != ' '); // next
3070                         }
3071                         looking_at(buf, &i, "*% "); // eat prompt
3072                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3073                         next_out = i;
3074                         continue;
3075                     }
3076                 }
3077             }
3078
3079             /* skip formula vars */
3080             if (started == STARTED_NONE &&
3081                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3082               started = STARTED_CHATTER;
3083               i += 3;
3084               continue;
3085             }
3086
3087             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3088             if (appData.autoKibitz && started == STARTED_NONE &&
3089                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3090                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3091                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3092                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3093                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3094                         suppressKibitz = TRUE;
3095                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3096                         next_out = i;
3097                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3098                                 && (gameMode == IcsPlayingWhite)) ||
3099                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3100                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3101                             started = STARTED_CHATTER; // own kibitz we simply discard
3102                         else {
3103                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3104                             parse_pos = 0; parse[0] = NULLCHAR;
3105                             savingComment = TRUE;
3106                             suppressKibitz = gameMode != IcsObserving ? 2 :
3107                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3108                         }
3109                         continue;
3110                 } else
3111                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3112                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3113                          && atoi(star_match[0])) {
3114                     // suppress the acknowledgements of our own autoKibitz
3115                     char *p;
3116                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3117                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3118                     SendToPlayer(star_match[0], strlen(star_match[0]));
3119                     if(looking_at(buf, &i, "*% ")) // eat prompt
3120                         suppressKibitz = FALSE;
3121                     next_out = i;
3122                     continue;
3123                 }
3124             } // [HGM] kibitz: end of patch
3125
3126             // [HGM] chat: intercept tells by users for which we have an open chat window
3127             channel = -1;
3128             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3129                                            looking_at(buf, &i, "* whispers:") ||
3130                                            looking_at(buf, &i, "* kibitzes:") ||
3131                                            looking_at(buf, &i, "* shouts:") ||
3132                                            looking_at(buf, &i, "* c-shouts:") ||
3133                                            looking_at(buf, &i, "--> * ") ||
3134                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3135                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3136                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3137                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3138                 int p;
3139                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3140                 chattingPartner = -1;
3141
3142                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3143                 for(p=0; p<MAX_CHAT; p++) {
3144                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3145                     talker[0] = '['; strcat(talker, "] ");
3146                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3147                     chattingPartner = p; break;
3148                     }
3149                 } else
3150                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3151                 for(p=0; p<MAX_CHAT; p++) {
3152                     if(!strcmp("kibitzes", chatPartner[p])) {
3153                         talker[0] = '['; strcat(talker, "] ");
3154                         chattingPartner = p; break;
3155                     }
3156                 } else
3157                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3158                 for(p=0; p<MAX_CHAT; p++) {
3159                     if(!strcmp("whispers", chatPartner[p])) {
3160                         talker[0] = '['; strcat(talker, "] ");
3161                         chattingPartner = p; break;
3162                     }
3163                 } else
3164                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3165                   if(buf[i-8] == '-' && buf[i-3] == 't')
3166                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3167                     if(!strcmp("c-shouts", chatPartner[p])) {
3168                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3169                         chattingPartner = p; break;
3170                     }
3171                   }
3172                   if(chattingPartner < 0)
3173                   for(p=0; p<MAX_CHAT; p++) {
3174                     if(!strcmp("shouts", chatPartner[p])) {
3175                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3176                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3177                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3178                         chattingPartner = p; break;
3179                     }
3180                   }
3181                 }
3182                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3183                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3184                     talker[0] = 0; Colorize(ColorTell, FALSE);
3185                     chattingPartner = p; break;
3186                 }
3187                 if(chattingPartner<0) i = oldi; else {
3188                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3189                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3190                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3191                     started = STARTED_COMMENT;
3192                     parse_pos = 0; parse[0] = NULLCHAR;
3193                     savingComment = 3 + chattingPartner; // counts as TRUE
3194                     suppressKibitz = TRUE;
3195                     continue;
3196                 }
3197             } // [HGM] chat: end of patch
3198
3199           backup = i;
3200             if (appData.zippyTalk || appData.zippyPlay) {
3201                 /* [DM] Backup address for color zippy lines */
3202 #if ZIPPY
3203                if (loggedOn == TRUE)
3204                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3205                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3206 #endif
3207             } // [DM] 'else { ' deleted
3208                 if (
3209                     /* Regular tells and says */
3210                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3211                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3212                     looking_at(buf, &i, "* says: ") ||
3213                     /* Don't color "message" or "messages" output */
3214                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3215                     looking_at(buf, &i, "*. * at *:*: ") ||
3216                     looking_at(buf, &i, "--* (*:*): ") ||
3217                     /* Message notifications (same color as tells) */
3218                     looking_at(buf, &i, "* has left a message ") ||
3219                     looking_at(buf, &i, "* just sent you a message:\n") ||
3220                     /* Whispers and kibitzes */
3221                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3222                     looking_at(buf, &i, "* kibitzes: ") ||
3223                     /* Channel tells */
3224                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3225
3226                   if (tkind == 1 && strchr(star_match[0], ':')) {
3227                       /* Avoid "tells you:" spoofs in channels */
3228                      tkind = 3;
3229                   }
3230                   if (star_match[0][0] == NULLCHAR ||
3231                       strchr(star_match[0], ' ') ||
3232                       (tkind == 3 && strchr(star_match[1], ' '))) {
3233                     /* Reject bogus matches */
3234                     i = oldi;
3235                   } else {
3236                     if (appData.colorize) {
3237                       if (oldi > next_out) {
3238                         SendToPlayer(&buf[next_out], oldi - next_out);
3239                         next_out = oldi;
3240                       }
3241                       switch (tkind) {
3242                       case 1:
3243                         Colorize(ColorTell, FALSE);
3244                         curColor = ColorTell;
3245                         break;
3246                       case 2:
3247                         Colorize(ColorKibitz, FALSE);
3248                         curColor = ColorKibitz;
3249                         break;
3250                       case 3:
3251                         p = strrchr(star_match[1], '(');
3252                         if (p == NULL) {
3253                           p = star_match[1];
3254                         } else {
3255                           p++;
3256                         }
3257                         if (atoi(p) == 1) {
3258                           Colorize(ColorChannel1, FALSE);
3259                           curColor = ColorChannel1;
3260                         } else {
3261                           Colorize(ColorChannel, FALSE);
3262                           curColor = ColorChannel;
3263                         }
3264                         break;
3265                       case 5:
3266                         curColor = ColorNormal;
3267                         break;
3268                       }
3269                     }
3270                     if (started == STARTED_NONE && appData.autoComment &&
3271                         (gameMode == IcsObserving ||
3272                          gameMode == IcsPlayingWhite ||
3273                          gameMode == IcsPlayingBlack)) {
3274                       parse_pos = i - oldi;
3275                       memcpy(parse, &buf[oldi], parse_pos);
3276                       parse[parse_pos] = NULLCHAR;
3277                       started = STARTED_COMMENT;
3278                       savingComment = TRUE;
3279                     } else {
3280                       started = STARTED_CHATTER;
3281                       savingComment = FALSE;
3282                     }
3283                     loggedOn = TRUE;
3284                     continue;
3285                   }
3286                 }
3287
3288                 if (looking_at(buf, &i, "* s-shouts: ") ||
3289                     looking_at(buf, &i, "* c-shouts: ")) {
3290                     if (appData.colorize) {
3291                         if (oldi > next_out) {
3292                             SendToPlayer(&buf[next_out], oldi - next_out);
3293                             next_out = oldi;
3294                         }
3295                         Colorize(ColorSShout, FALSE);
3296                         curColor = ColorSShout;
3297                     }
3298                     loggedOn = TRUE;
3299                     started = STARTED_CHATTER;
3300                     continue;
3301                 }
3302
3303                 if (looking_at(buf, &i, "--->")) {
3304                     loggedOn = TRUE;
3305                     continue;
3306                 }
3307
3308                 if (looking_at(buf, &i, "* shouts: ") ||
3309                     looking_at(buf, &i, "--> ")) {
3310                     if (appData.colorize) {
3311                         if (oldi > next_out) {
3312                             SendToPlayer(&buf[next_out], oldi - next_out);
3313                             next_out = oldi;
3314                         }
3315                         Colorize(ColorShout, FALSE);
3316                         curColor = ColorShout;
3317                     }
3318                     loggedOn = TRUE;
3319                     started = STARTED_CHATTER;
3320                     continue;
3321                 }
3322
3323                 if (looking_at( buf, &i, "Challenge:")) {
3324                     if (appData.colorize) {
3325                         if (oldi > next_out) {
3326                             SendToPlayer(&buf[next_out], oldi - next_out);
3327                             next_out = oldi;
3328                         }
3329                         Colorize(ColorChallenge, FALSE);
3330                         curColor = ColorChallenge;
3331                     }
3332                     loggedOn = TRUE;
3333                     continue;
3334                 }
3335
3336                 if (looking_at(buf, &i, "* offers you") ||
3337                     looking_at(buf, &i, "* offers to be") ||
3338                     looking_at(buf, &i, "* would like to") ||
3339                     looking_at(buf, &i, "* requests to") ||
3340                     looking_at(buf, &i, "Your opponent offers") ||
3341                     looking_at(buf, &i, "Your opponent requests")) {
3342
3343                     if (appData.colorize) {
3344                         if (oldi > next_out) {
3345                             SendToPlayer(&buf[next_out], oldi - next_out);
3346                             next_out = oldi;
3347                         }
3348                         Colorize(ColorRequest, FALSE);
3349                         curColor = ColorRequest;
3350                     }
3351                     continue;
3352                 }
3353
3354                 if (looking_at(buf, &i, "* (*) seeking")) {
3355                     if (appData.colorize) {
3356                         if (oldi > next_out) {
3357                             SendToPlayer(&buf[next_out], oldi - next_out);
3358                             next_out = oldi;
3359                         }
3360                         Colorize(ColorSeek, FALSE);
3361                         curColor = ColorSeek;
3362                     }
3363                     continue;
3364             }
3365
3366           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3367
3368             if (looking_at(buf, &i, "\\   ")) {
3369                 if (prevColor != ColorNormal) {
3370                     if (oldi > next_out) {
3371                         SendToPlayer(&buf[next_out], oldi - next_out);
3372                         next_out = oldi;
3373                     }
3374                     Colorize(prevColor, TRUE);
3375                     curColor = prevColor;
3376                 }
3377                 if (savingComment) {
3378                     parse_pos = i - oldi;
3379                     memcpy(parse, &buf[oldi], parse_pos);
3380                     parse[parse_pos] = NULLCHAR;
3381                     started = STARTED_COMMENT;
3382                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3383                         chattingPartner = savingComment - 3; // kludge to remember the box
3384                 } else {
3385                     started = STARTED_CHATTER;
3386                 }
3387                 continue;
3388             }
3389
3390             if (looking_at(buf, &i, "Black Strength :") ||
3391                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3392                 looking_at(buf, &i, "<10>") ||
3393                 looking_at(buf, &i, "#@#")) {
3394                 /* Wrong board style */
3395                 loggedOn = TRUE;
3396                 SendToICS(ics_prefix);
3397                 SendToICS("set style 12\n");
3398                 SendToICS(ics_prefix);
3399                 SendToICS("refresh\n");
3400                 continue;
3401             }
3402
3403             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3404                 ICSInitScript();
3405                 have_sent_ICS_logon = 1;
3406                 continue;
3407             }
3408
3409             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3410                 (looking_at(buf, &i, "\n<12> ") ||
3411                  looking_at(buf, &i, "<12> "))) {
3412                 loggedOn = TRUE;
3413                 if (oldi > next_out) {
3414                     SendToPlayer(&buf[next_out], oldi - next_out);
3415                 }
3416                 next_out = i;
3417                 started = STARTED_BOARD;
3418                 parse_pos = 0;
3419                 continue;
3420             }
3421
3422             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3423                 looking_at(buf, &i, "<b1> ")) {
3424                 if (oldi > next_out) {
3425                     SendToPlayer(&buf[next_out], oldi - next_out);
3426                 }
3427                 next_out = i;
3428                 started = STARTED_HOLDINGS;
3429                 parse_pos = 0;
3430                 continue;
3431             }
3432
3433             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3434                 loggedOn = TRUE;
3435                 /* Header for a move list -- first line */
3436
3437                 switch (ics_getting_history) {
3438                   case H_FALSE:
3439                     switch (gameMode) {
3440                       case IcsIdle:
3441                       case BeginningOfGame:
3442                         /* User typed "moves" or "oldmoves" while we
3443                            were idle.  Pretend we asked for these
3444                            moves and soak them up so user can step
3445                            through them and/or save them.
3446                            */
3447                         Reset(FALSE, TRUE);
3448                         gameMode = IcsObserving;
3449                         ModeHighlight();
3450                         ics_gamenum = -1;
3451                         ics_getting_history = H_GOT_UNREQ_HEADER;
3452                         break;
3453                       case EditGame: /*?*/
3454                       case EditPosition: /*?*/
3455                         /* Should above feature work in these modes too? */
3456                         /* For now it doesn't */
3457                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3458                         break;
3459                       default:
3460                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3461                         break;
3462                     }
3463                     break;
3464                   case H_REQUESTED:
3465                     /* Is this the right one? */
3466                     if (gameInfo.white && gameInfo.black &&
3467                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3468                         strcmp(gameInfo.black, star_match[2]) == 0) {
3469                         /* All is well */
3470                         ics_getting_history = H_GOT_REQ_HEADER;
3471                     }
3472                     break;
3473                   case H_GOT_REQ_HEADER:
3474                   case H_GOT_UNREQ_HEADER:
3475                   case H_GOT_UNWANTED_HEADER:
3476                   case H_GETTING_MOVES:
3477                     /* Should not happen */
3478                     DisplayError(_("Error gathering move list: two headers"), 0);
3479                     ics_getting_history = H_FALSE;
3480                     break;
3481                 }
3482
3483                 /* Save player ratings into gameInfo if needed */
3484                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3485                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3486                     (gameInfo.whiteRating == -1 ||
3487                      gameInfo.blackRating == -1)) {
3488
3489                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3490                     gameInfo.blackRating = string_to_rating(star_match[3]);
3491                     if (appData.debugMode)
3492                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3493                               gameInfo.whiteRating, gameInfo.blackRating);
3494                 }
3495                 continue;
3496             }
3497
3498             if (looking_at(buf, &i,
3499               "* * match, initial time: * minute*, increment: * second")) {
3500                 /* Header for a move list -- second line */
3501                 /* Initial board will follow if this is a wild game */
3502                 if (gameInfo.event != NULL) free(gameInfo.event);
3503                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3504                 gameInfo.event = StrSave(str);
3505                 /* [HGM] we switched variant. Translate boards if needed. */
3506                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3507                 continue;
3508             }
3509
3510             if (looking_at(buf, &i, "Move  ")) {
3511                 /* Beginning of a move list */
3512                 switch (ics_getting_history) {
3513                   case H_FALSE:
3514                     /* Normally should not happen */
3515                     /* Maybe user hit reset while we were parsing */
3516                     break;
3517                   case H_REQUESTED:
3518                     /* Happens if we are ignoring a move list that is not
3519                      * the one we just requested.  Common if the user
3520                      * tries to observe two games without turning off
3521                      * getMoveList */
3522                     break;
3523                   case H_GETTING_MOVES:
3524                     /* Should not happen */
3525                     DisplayError(_("Error gathering move list: nested"), 0);
3526                     ics_getting_history = H_FALSE;
3527                     break;
3528                   case H_GOT_REQ_HEADER:
3529                     ics_getting_history = H_GETTING_MOVES;
3530                     started = STARTED_MOVES;
3531                     parse_pos = 0;
3532                     if (oldi > next_out) {
3533                         SendToPlayer(&buf[next_out], oldi - next_out);
3534                     }
3535                     break;
3536                   case H_GOT_UNREQ_HEADER:
3537                     ics_getting_history = H_GETTING_MOVES;
3538                     started = STARTED_MOVES_NOHIDE;
3539                     parse_pos = 0;
3540                     break;
3541                   case H_GOT_UNWANTED_HEADER:
3542                     ics_getting_history = H_FALSE;
3543                     break;
3544                 }
3545                 continue;
3546             }
3547
3548             if (looking_at(buf, &i, "% ") ||
3549                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3550                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3551                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3552                     soughtPending = FALSE;
3553                     seekGraphUp = TRUE;
3554                     DrawSeekGraph();
3555                 }
3556                 if(suppressKibitz) next_out = i;
3557                 savingComment = FALSE;
3558                 suppressKibitz = 0;
3559                 switch (started) {
3560                   case STARTED_MOVES:
3561                   case STARTED_MOVES_NOHIDE:
3562                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3563                     parse[parse_pos + i - oldi] = NULLCHAR;
3564                     ParseGameHistory(parse);
3565 #if ZIPPY
3566                     if (appData.zippyPlay && first.initDone) {
3567                         FeedMovesToProgram(&first, forwardMostMove);
3568                         if (gameMode == IcsPlayingWhite) {
3569                             if (WhiteOnMove(forwardMostMove)) {
3570                                 if (first.sendTime) {
3571                                   if (first.useColors) {
3572                                     SendToProgram("black\n", &first);
3573                                   }
3574                                   SendTimeRemaining(&first, TRUE);
3575                                 }
3576                                 if (first.useColors) {
3577                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3578                                 }
3579                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3580                                 first.maybeThinking = TRUE;
3581                             } else {
3582                                 if (first.usePlayother) {
3583                                   if (first.sendTime) {
3584                                     SendTimeRemaining(&first, TRUE);
3585                                   }
3586                                   SendToProgram("playother\n", &first);
3587                                   firstMove = FALSE;
3588                                 } else {
3589                                   firstMove = TRUE;
3590                                 }
3591                             }
3592                         } else if (gameMode == IcsPlayingBlack) {
3593                             if (!WhiteOnMove(forwardMostMove)) {
3594                                 if (first.sendTime) {
3595                                   if (first.useColors) {
3596                                     SendToProgram("white\n", &first);
3597                                   }
3598                                   SendTimeRemaining(&first, FALSE);
3599                                 }
3600                                 if (first.useColors) {
3601                                   SendToProgram("black\n", &first);
3602                                 }
3603                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3604                                 first.maybeThinking = TRUE;
3605                             } else {
3606                                 if (first.usePlayother) {
3607                                   if (first.sendTime) {
3608                                     SendTimeRemaining(&first, FALSE);
3609                                   }
3610                                   SendToProgram("playother\n", &first);
3611                                   firstMove = FALSE;
3612                                 } else {
3613                                   firstMove = TRUE;
3614                                 }
3615                             }
3616                         }
3617                     }
3618 #endif
3619                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3620                         /* Moves came from oldmoves or moves command
3621                            while we weren't doing anything else.
3622                            */
3623                         currentMove = forwardMostMove;
3624                         ClearHighlights();/*!!could figure this out*/
3625                         flipView = appData.flipView;
3626                         DrawPosition(TRUE, boards[currentMove]);
3627                         DisplayBothClocks();
3628                         snprintf(str, MSG_SIZ, "%s vs. %s",
3629                                 gameInfo.white, gameInfo.black);
3630                         DisplayTitle(str);
3631                         gameMode = IcsIdle;
3632                     } else {
3633                         /* Moves were history of an active game */
3634                         if (gameInfo.resultDetails != NULL) {
3635                             free(gameInfo.resultDetails);
3636                             gameInfo.resultDetails = NULL;
3637                         }
3638                     }
3639                     HistorySet(parseList, backwardMostMove,
3640                                forwardMostMove, currentMove-1);
3641                     DisplayMove(currentMove - 1);
3642                     if (started == STARTED_MOVES) next_out = i;
3643                     started = STARTED_NONE;
3644                     ics_getting_history = H_FALSE;
3645                     break;
3646
3647                   case STARTED_OBSERVE:
3648                     started = STARTED_NONE;
3649                     SendToICS(ics_prefix);
3650                     SendToICS("refresh\n");
3651                     break;
3652
3653                   default:
3654                     break;
3655                 }
3656                 if(bookHit) { // [HGM] book: simulate book reply
3657                     static char bookMove[MSG_SIZ]; // a bit generous?
3658
3659                     programStats.nodes = programStats.depth = programStats.time =
3660                     programStats.score = programStats.got_only_move = 0;
3661                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3662
3663                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3664                     strcat(bookMove, bookHit);
3665                     HandleMachineMove(bookMove, &first);
3666                 }
3667                 continue;
3668             }
3669
3670             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3671                  started == STARTED_HOLDINGS ||
3672                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3673                 /* Accumulate characters in move list or board */
3674                 parse[parse_pos++] = buf[i];
3675             }
3676
3677             /* Start of game messages.  Mostly we detect start of game
3678                when the first board image arrives.  On some versions
3679                of the ICS, though, we need to do a "refresh" after starting
3680                to observe in order to get the current board right away. */
3681             if (looking_at(buf, &i, "Adding game * to observation list")) {
3682                 started = STARTED_OBSERVE;
3683                 continue;
3684             }
3685
3686             /* Handle auto-observe */
3687             if (appData.autoObserve &&
3688                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3689                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3690                 char *player;
3691                 /* Choose the player that was highlighted, if any. */
3692                 if (star_match[0][0] == '\033' ||
3693                     star_match[1][0] != '\033') {
3694                     player = star_match[0];
3695                 } else {
3696                     player = star_match[2];
3697                 }
3698                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3699                         ics_prefix, StripHighlightAndTitle(player));
3700                 SendToICS(str);
3701
3702                 /* Save ratings from notify string */
3703                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3704                 player1Rating = string_to_rating(star_match[1]);
3705                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3706                 player2Rating = string_to_rating(star_match[3]);
3707
3708                 if (appData.debugMode)
3709                   fprintf(debugFP,
3710                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3711                           player1Name, player1Rating,
3712                           player2Name, player2Rating);
3713
3714                 continue;
3715             }
3716
3717             /* Deal with automatic examine mode after a game,
3718                and with IcsObserving -> IcsExamining transition */
3719             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3720                 looking_at(buf, &i, "has made you an examiner of game *")) {
3721
3722                 int gamenum = atoi(star_match[0]);
3723                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3724                     gamenum == ics_gamenum) {
3725                     /* We were already playing or observing this game;
3726                        no need to refetch history */
3727                     gameMode = IcsExamining;
3728                     if (pausing) {
3729                         pauseExamForwardMostMove = forwardMostMove;
3730                     } else if (currentMove < forwardMostMove) {
3731                         ForwardInner(forwardMostMove);
3732                     }
3733                 } else {
3734                     /* I don't think this case really can happen */
3735                     SendToICS(ics_prefix);
3736                     SendToICS("refresh\n");
3737                 }
3738                 continue;
3739             }
3740
3741             /* Error messages */
3742 //          if (ics_user_moved) {
3743             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3744                 if (looking_at(buf, &i, "Illegal move") ||
3745                     looking_at(buf, &i, "Not a legal move") ||
3746                     looking_at(buf, &i, "Your king is in check") ||
3747                     looking_at(buf, &i, "It isn't your turn") ||
3748                     looking_at(buf, &i, "It is not your move")) {
3749                     /* Illegal move */
3750                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3751                         currentMove = forwardMostMove-1;
3752                         DisplayMove(currentMove - 1); /* before DMError */
3753                         DrawPosition(FALSE, boards[currentMove]);
3754                         SwitchClocks(forwardMostMove-1); // [HGM] race
3755                         DisplayBothClocks();
3756                     }
3757                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3758                     ics_user_moved = 0;
3759                     continue;
3760                 }
3761             }
3762
3763             if (looking_at(buf, &i, "still have time") ||
3764                 looking_at(buf, &i, "not out of time") ||
3765                 looking_at(buf, &i, "either player is out of time") ||
3766                 looking_at(buf, &i, "has timeseal; checking")) {
3767                 /* We must have called his flag a little too soon */
3768                 whiteFlag = blackFlag = FALSE;
3769                 continue;
3770             }
3771
3772             if (looking_at(buf, &i, "added * seconds to") ||
3773                 looking_at(buf, &i, "seconds were added to")) {
3774                 /* Update the clocks */
3775                 SendToICS(ics_prefix);
3776                 SendToICS("refresh\n");
3777                 continue;
3778             }
3779
3780             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3781                 ics_clock_paused = TRUE;
3782                 StopClocks();
3783                 continue;
3784             }
3785
3786             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3787                 ics_clock_paused = FALSE;
3788                 StartClocks();
3789                 continue;
3790             }
3791
3792             /* Grab player ratings from the Creating: message.
3793                Note we have to check for the special case when
3794                the ICS inserts things like [white] or [black]. */
3795             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3796                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3797                 /* star_matches:
3798                    0    player 1 name (not necessarily white)
3799                    1    player 1 rating
3800                    2    empty, white, or black (IGNORED)
3801                    3    player 2 name (not necessarily black)
3802                    4    player 2 rating
3803
3804                    The names/ratings are sorted out when the game
3805                    actually starts (below).
3806                 */
3807                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3808                 player1Rating = string_to_rating(star_match[1]);
3809                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3810                 player2Rating = string_to_rating(star_match[4]);
3811
3812                 if (appData.debugMode)
3813                   fprintf(debugFP,
3814                           "Ratings from 'Creating:' %s %d, %s %d\n",
3815                           player1Name, player1Rating,
3816                           player2Name, player2Rating);
3817
3818                 continue;
3819             }
3820
3821             /* Improved generic start/end-of-game messages */
3822             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3823                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3824                 /* If tkind == 0: */
3825                 /* star_match[0] is the game number */
3826                 /*           [1] is the white player's name */
3827                 /*           [2] is the black player's name */
3828                 /* For end-of-game: */
3829                 /*           [3] is the reason for the game end */
3830                 /*           [4] is a PGN end game-token, preceded by " " */
3831                 /* For start-of-game: */
3832                 /*           [3] begins with "Creating" or "Continuing" */
3833                 /*           [4] is " *" or empty (don't care). */
3834                 int gamenum = atoi(star_match[0]);
3835                 char *whitename, *blackname, *why, *endtoken;
3836                 ChessMove endtype = EndOfFile;
3837
3838                 if (tkind == 0) {
3839                   whitename = star_match[1];
3840                   blackname = star_match[2];
3841                   why = star_match[3];
3842                   endtoken = star_match[4];
3843                 } else {
3844                   whitename = star_match[1];
3845                   blackname = star_match[3];
3846                   why = star_match[5];
3847                   endtoken = star_match[6];
3848                 }
3849
3850                 /* Game start messages */
3851                 if (strncmp(why, "Creating ", 9) == 0 ||
3852                     strncmp(why, "Continuing ", 11) == 0) {
3853                     gs_gamenum = gamenum;
3854                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3855                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3856 #if ZIPPY
3857                     if (appData.zippyPlay) {
3858                         ZippyGameStart(whitename, blackname);
3859                     }
3860 #endif /*ZIPPY*/
3861                     partnerBoardValid = FALSE; // [HGM] bughouse
3862                     continue;
3863                 }
3864
3865                 /* Game end messages */
3866                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3867                     ics_gamenum != gamenum) {
3868                     continue;
3869                 }
3870                 while (endtoken[0] == ' ') endtoken++;
3871                 switch (endtoken[0]) {
3872                   case '*':
3873                   default:
3874                     endtype = GameUnfinished;
3875                     break;
3876                   case '0':
3877                     endtype = BlackWins;
3878                     break;
3879                   case '1':
3880                     if (endtoken[1] == '/')
3881                       endtype = GameIsDrawn;
3882                     else
3883                       endtype = WhiteWins;
3884                     break;
3885                 }
3886                 GameEnds(endtype, why, GE_ICS);
3887 #if ZIPPY
3888                 if (appData.zippyPlay && first.initDone) {
3889                     ZippyGameEnd(endtype, why);
3890                     if (first.pr == NULL) {
3891                       /* Start the next process early so that we'll
3892                          be ready for the next challenge */
3893                       StartChessProgram(&first);
3894                     }
3895                     /* Send "new" early, in case this command takes
3896                        a long time to finish, so that we'll be ready
3897                        for the next challenge. */
3898                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3899                     Reset(TRUE, TRUE);
3900                 }
3901 #endif /*ZIPPY*/
3902                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3903                 continue;
3904             }
3905
3906             if (looking_at(buf, &i, "Removing game * from observation") ||
3907                 looking_at(buf, &i, "no longer observing game *") ||
3908                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3909                 if (gameMode == IcsObserving &&
3910                     atoi(star_match[0]) == ics_gamenum)
3911                   {
3912                       /* icsEngineAnalyze */
3913                       if (appData.icsEngineAnalyze) {
3914                             ExitAnalyzeMode();
3915                             ModeHighlight();
3916                       }
3917                       StopClocks();
3918                       gameMode = IcsIdle;
3919                       ics_gamenum = -1;
3920                       ics_user_moved = FALSE;
3921                   }
3922                 continue;
3923             }
3924
3925             if (looking_at(buf, &i, "no longer examining game *")) {
3926                 if (gameMode == IcsExamining &&
3927                     atoi(star_match[0]) == ics_gamenum)
3928                   {
3929                       gameMode = IcsIdle;
3930                       ics_gamenum = -1;
3931                       ics_user_moved = FALSE;
3932                   }
3933                 continue;
3934             }
3935
3936             /* Advance leftover_start past any newlines we find,
3937                so only partial lines can get reparsed */
3938             if (looking_at(buf, &i, "\n")) {
3939                 prevColor = curColor;
3940                 if (curColor != ColorNormal) {
3941                     if (oldi > next_out) {
3942                         SendToPlayer(&buf[next_out], oldi - next_out);
3943                         next_out = oldi;
3944                     }
3945                     Colorize(ColorNormal, FALSE);
3946                     curColor = ColorNormal;
3947                 }
3948                 if (started == STARTED_BOARD) {
3949                     started = STARTED_NONE;
3950                     parse[parse_pos] = NULLCHAR;
3951                     ParseBoard12(parse);
3952                     ics_user_moved = 0;
3953
3954                     /* Send premove here */
3955                     if (appData.premove) {
3956                       char str[MSG_SIZ];
3957                       if (currentMove == 0 &&
3958                           gameMode == IcsPlayingWhite &&
3959                           appData.premoveWhite) {
3960                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3961                         if (appData.debugMode)
3962                           fprintf(debugFP, "Sending premove:\n");
3963                         SendToICS(str);
3964                       } else if (currentMove == 1 &&
3965                                  gameMode == IcsPlayingBlack &&
3966                                  appData.premoveBlack) {
3967                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3968                         if (appData.debugMode)
3969                           fprintf(debugFP, "Sending premove:\n");
3970                         SendToICS(str);
3971                       } else if (gotPremove) {
3972                         gotPremove = 0;
3973                         ClearPremoveHighlights();
3974                         if (appData.debugMode)
3975                           fprintf(debugFP, "Sending premove:\n");
3976                           UserMoveEvent(premoveFromX, premoveFromY,
3977                                         premoveToX, premoveToY,
3978                                         premovePromoChar);
3979                       }
3980                     }
3981
3982                     /* Usually suppress following prompt */
3983                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3984                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3985                         if (looking_at(buf, &i, "*% ")) {
3986                             savingComment = FALSE;
3987                             suppressKibitz = 0;
3988                         }
3989                     }
3990                     next_out = i;
3991                 } else if (started == STARTED_HOLDINGS) {
3992                     int gamenum;
3993                     char new_piece[MSG_SIZ];
3994                     started = STARTED_NONE;
3995                     parse[parse_pos] = NULLCHAR;
3996                     if (appData.debugMode)
3997                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3998                                                         parse, currentMove);
3999                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4000                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4001                         if (gameInfo.variant == VariantNormal) {
4002                           /* [HGM] We seem to switch variant during a game!
4003                            * Presumably no holdings were displayed, so we have
4004                            * to move the position two files to the right to
4005                            * create room for them!
4006                            */
4007                           VariantClass newVariant;
4008                           switch(gameInfo.boardWidth) { // base guess on board width
4009                                 case 9:  newVariant = VariantShogi; break;
4010                                 case 10: newVariant = VariantGreat; break;
4011                                 default: newVariant = VariantCrazyhouse; break;
4012                           }
4013                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4014                           /* Get a move list just to see the header, which
4015                              will tell us whether this is really bug or zh */
4016                           if (ics_getting_history == H_FALSE) {
4017                             ics_getting_history = H_REQUESTED;
4018                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4019                             SendToICS(str);
4020                           }
4021                         }
4022                         new_piece[0] = NULLCHAR;
4023                         sscanf(parse, "game %d white [%s black [%s <- %s",
4024                                &gamenum, white_holding, black_holding,
4025                                new_piece);
4026                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4027                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4028                         /* [HGM] copy holdings to board holdings area */
4029                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4030                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4031                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4032 #if ZIPPY
4033                         if (appData.zippyPlay && first.initDone) {
4034                             ZippyHoldings(white_holding, black_holding,
4035                                           new_piece);
4036                         }
4037 #endif /*ZIPPY*/
4038                         if (tinyLayout || smallLayout) {
4039                             char wh[16], bh[16];
4040                             PackHolding(wh, white_holding);
4041                             PackHolding(bh, black_holding);
4042                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4043                                     gameInfo.white, gameInfo.black);
4044                         } else {
4045                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4046                                     gameInfo.white, white_holding,
4047                                     gameInfo.black, black_holding);
4048                         }
4049                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4050                         DrawPosition(FALSE, boards[currentMove]);
4051                         DisplayTitle(str);
4052                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4053                         sscanf(parse, "game %d white [%s black [%s <- %s",
4054                                &gamenum, white_holding, black_holding,
4055                                new_piece);
4056                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4057                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4058                         /* [HGM] copy holdings to partner-board holdings area */
4059                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4060                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4061                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4062                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4063                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4064                       }
4065                     }
4066                     /* Suppress following prompt */
4067                     if (looking_at(buf, &i, "*% ")) {
4068                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4069                         savingComment = FALSE;
4070                         suppressKibitz = 0;
4071                     }
4072                     next_out = i;
4073                 }
4074                 continue;
4075             }
4076
4077             i++;                /* skip unparsed character and loop back */
4078         }
4079
4080         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4081 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4082 //          SendToPlayer(&buf[next_out], i - next_out);
4083             started != STARTED_HOLDINGS && leftover_start > next_out) {
4084             SendToPlayer(&buf[next_out], leftover_start - next_out);
4085             next_out = i;
4086         }
4087
4088         leftover_len = buf_len - leftover_start;
4089         /* if buffer ends with something we couldn't parse,
4090            reparse it after appending the next read */
4091
4092     } else if (count == 0) {
4093         RemoveInputSource(isr);
4094         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4095     } else {
4096         DisplayFatalError(_("Error reading from ICS"), error, 1);
4097     }
4098 }
4099
4100
4101 /* Board style 12 looks like this:
4102
4103    <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
4104
4105  * The "<12> " is stripped before it gets to this routine.  The two
4106  * trailing 0's (flip state and clock ticking) are later addition, and
4107  * some chess servers may not have them, or may have only the first.
4108  * Additional trailing fields may be added in the future.
4109  */
4110
4111 #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"
4112
4113 #define RELATION_OBSERVING_PLAYED    0
4114 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4115 #define RELATION_PLAYING_MYMOVE      1
4116 #define RELATION_PLAYING_NOTMYMOVE  -1
4117 #define RELATION_EXAMINING           2
4118 #define RELATION_ISOLATED_BOARD     -3
4119 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4120
4121 void
4122 ParseBoard12(string)
4123      char *string;
4124 {
4125     GameMode newGameMode;
4126     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4127     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4128     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4129     char to_play, board_chars[200];
4130     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4131     char black[32], white[32];
4132     Board board;
4133     int prevMove = currentMove;
4134     int ticking = 2;
4135     ChessMove moveType;
4136     int fromX, fromY, toX, toY;
4137     char promoChar;
4138     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4139     char *bookHit = NULL; // [HGM] book
4140     Boolean weird = FALSE, reqFlag = FALSE;
4141
4142     fromX = fromY = toX = toY = -1;
4143
4144     newGame = FALSE;
4145
4146     if (appData.debugMode)
4147       fprintf(debugFP, _("Parsing board: %s\n"), string);
4148
4149     move_str[0] = NULLCHAR;
4150     elapsed_time[0] = NULLCHAR;
4151     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4152         int  i = 0, j;
4153         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4154             if(string[i] == ' ') { ranks++; files = 0; }
4155             else files++;
4156             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4157             i++;
4158         }
4159         for(j = 0; j <i; j++) board_chars[j] = string[j];
4160         board_chars[i] = '\0';
4161         string += i + 1;
4162     }
4163     n = sscanf(string, PATTERN, &to_play, &double_push,
4164                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4165                &gamenum, white, black, &relation, &basetime, &increment,
4166                &white_stren, &black_stren, &white_time, &black_time,
4167                &moveNum, str, elapsed_time, move_str, &ics_flip,
4168                &ticking);
4169
4170     if (n < 21) {
4171         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4172         DisplayError(str, 0);
4173         return;
4174     }
4175
4176     /* Convert the move number to internal form */
4177     moveNum = (moveNum - 1) * 2;
4178     if (to_play == 'B') moveNum++;
4179     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4180       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4181                         0, 1);
4182       return;
4183     }
4184
4185     switch (relation) {
4186       case RELATION_OBSERVING_PLAYED:
4187       case RELATION_OBSERVING_STATIC:
4188         if (gamenum == -1) {
4189             /* Old ICC buglet */
4190             relation = RELATION_OBSERVING_STATIC;
4191         }
4192         newGameMode = IcsObserving;
4193         break;
4194       case RELATION_PLAYING_MYMOVE:
4195       case RELATION_PLAYING_NOTMYMOVE:
4196         newGameMode =
4197           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4198             IcsPlayingWhite : IcsPlayingBlack;
4199         break;
4200       case RELATION_EXAMINING:
4201         newGameMode = IcsExamining;
4202         break;
4203       case RELATION_ISOLATED_BOARD:
4204       default:
4205         /* Just display this board.  If user was doing something else,
4206            we will forget about it until the next board comes. */
4207         newGameMode = IcsIdle;
4208         break;
4209       case RELATION_STARTING_POSITION:
4210         newGameMode = gameMode;
4211         break;
4212     }
4213
4214     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4215          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4216       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4217       char *toSqr;
4218       for (k = 0; k < ranks; k++) {
4219         for (j = 0; j < files; j++)
4220           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4221         if(gameInfo.holdingsWidth > 1) {
4222              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4223              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4224         }
4225       }
4226       CopyBoard(partnerBoard, board);
4227       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4228         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4229         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4230       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4231       if(toSqr = strchr(str, '-')) {
4232         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4233         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4234       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4235       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4236       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4237       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4238       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4239       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4240                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4241       DisplayMessage(partnerStatus, "");
4242         partnerBoardValid = TRUE;
4243       return;
4244     }
4245
4246     /* Modify behavior for initial board display on move listing
4247        of wild games.
4248        */
4249     switch (ics_getting_history) {
4250       case H_FALSE:
4251       case H_REQUESTED:
4252         break;
4253       case H_GOT_REQ_HEADER:
4254       case H_GOT_UNREQ_HEADER:
4255         /* This is the initial position of the current game */
4256         gamenum = ics_gamenum;
4257         moveNum = 0;            /* old ICS bug workaround */
4258         if (to_play == 'B') {
4259           startedFromSetupPosition = TRUE;
4260           blackPlaysFirst = TRUE;
4261           moveNum = 1;
4262           if (forwardMostMove == 0) forwardMostMove = 1;
4263           if (backwardMostMove == 0) backwardMostMove = 1;
4264           if (currentMove == 0) currentMove = 1;
4265         }
4266         newGameMode = gameMode;
4267         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4268         break;
4269       case H_GOT_UNWANTED_HEADER:
4270         /* This is an initial board that we don't want */
4271         return;
4272       case H_GETTING_MOVES:
4273         /* Should not happen */
4274         DisplayError(_("Error gathering move list: extra board"), 0);
4275         ics_getting_history = H_FALSE;
4276         return;
4277     }
4278
4279    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4280                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4281      /* [HGM] We seem to have switched variant unexpectedly
4282       * Try to guess new variant from board size
4283       */
4284           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4285           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4286           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4287           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4288           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4289           if(!weird) newVariant = VariantNormal;
4290           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4291           /* Get a move list just to see the header, which
4292              will tell us whether this is really bug or zh */
4293           if (ics_getting_history == H_FALSE) {
4294             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4295             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4296             SendToICS(str);
4297           }
4298     }
4299
4300     /* Take action if this is the first board of a new game, or of a
4301        different game than is currently being displayed.  */
4302     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4303         relation == RELATION_ISOLATED_BOARD) {
4304
4305         /* Forget the old game and get the history (if any) of the new one */
4306         if (gameMode != BeginningOfGame) {
4307           Reset(TRUE, TRUE);
4308         }
4309         newGame = TRUE;
4310         if (appData.autoRaiseBoard) BoardToTop();
4311         prevMove = -3;
4312         if (gamenum == -1) {
4313             newGameMode = IcsIdle;
4314         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4315                    appData.getMoveList && !reqFlag) {
4316             /* Need to get game history */
4317             ics_getting_history = H_REQUESTED;
4318             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4319             SendToICS(str);
4320         }
4321
4322         /* Initially flip the board to have black on the bottom if playing
4323            black or if the ICS flip flag is set, but let the user change
4324            it with the Flip View button. */
4325         flipView = appData.autoFlipView ?
4326           (newGameMode == IcsPlayingBlack) || ics_flip :
4327           appData.flipView;
4328
4329         /* Done with values from previous mode; copy in new ones */
4330         gameMode = newGameMode;
4331         ModeHighlight();
4332         ics_gamenum = gamenum;
4333         if (gamenum == gs_gamenum) {
4334             int klen = strlen(gs_kind);
4335             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4336             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4337             gameInfo.event = StrSave(str);
4338         } else {
4339             gameInfo.event = StrSave("ICS game");
4340         }
4341         gameInfo.site = StrSave(appData.icsHost);
4342         gameInfo.date = PGNDate();
4343         gameInfo.round = StrSave("-");
4344         gameInfo.white = StrSave(white);
4345         gameInfo.black = StrSave(black);
4346         timeControl = basetime * 60 * 1000;
4347         timeControl_2 = 0;
4348         timeIncrement = increment * 1000;
4349         movesPerSession = 0;
4350         gameInfo.timeControl = TimeControlTagValue();
4351         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4352   if (appData.debugMode) {
4353     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4354     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4355     setbuf(debugFP, NULL);
4356   }
4357
4358         gameInfo.outOfBook = NULL;
4359
4360         /* Do we have the ratings? */
4361         if (strcmp(player1Name, white) == 0 &&
4362             strcmp(player2Name, black) == 0) {
4363             if (appData.debugMode)
4364               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4365                       player1Rating, player2Rating);
4366             gameInfo.whiteRating = player1Rating;
4367             gameInfo.blackRating = player2Rating;
4368         } else if (strcmp(player2Name, white) == 0 &&
4369                    strcmp(player1Name, black) == 0) {
4370             if (appData.debugMode)
4371               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4372                       player2Rating, player1Rating);
4373             gameInfo.whiteRating = player2Rating;
4374             gameInfo.blackRating = player1Rating;
4375         }
4376         player1Name[0] = player2Name[0] = NULLCHAR;
4377
4378         /* Silence shouts if requested */
4379         if (appData.quietPlay &&
4380             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4381             SendToICS(ics_prefix);
4382             SendToICS("set shout 0\n");
4383         }
4384     }
4385
4386     /* Deal with midgame name changes */
4387     if (!newGame) {
4388         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4389             if (gameInfo.white) free(gameInfo.white);
4390             gameInfo.white = StrSave(white);
4391         }
4392         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4393             if (gameInfo.black) free(gameInfo.black);
4394             gameInfo.black = StrSave(black);
4395         }
4396     }
4397
4398     /* Throw away game result if anything actually changes in examine mode */
4399     if (gameMode == IcsExamining && !newGame) {
4400         gameInfo.result = GameUnfinished;
4401         if (gameInfo.resultDetails != NULL) {
4402             free(gameInfo.resultDetails);
4403             gameInfo.resultDetails = NULL;
4404         }
4405     }
4406
4407     /* In pausing && IcsExamining mode, we ignore boards coming
4408        in if they are in a different variation than we are. */
4409     if (pauseExamInvalid) return;
4410     if (pausing && gameMode == IcsExamining) {
4411         if (moveNum <= pauseExamForwardMostMove) {
4412             pauseExamInvalid = TRUE;
4413             forwardMostMove = pauseExamForwardMostMove;
4414             return;
4415         }
4416     }
4417
4418   if (appData.debugMode) {
4419     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4420   }
4421     /* Parse the board */
4422     for (k = 0; k < ranks; k++) {
4423       for (j = 0; j < files; j++)
4424         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4425       if(gameInfo.holdingsWidth > 1) {
4426            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4427            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4428       }
4429     }
4430     CopyBoard(boards[moveNum], board);
4431     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4432     if (moveNum == 0) {
4433         startedFromSetupPosition =
4434           !CompareBoards(board, initialPosition);
4435         if(startedFromSetupPosition)
4436             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4437     }
4438
4439     /* [HGM] Set castling rights. Take the outermost Rooks,
4440        to make it also work for FRC opening positions. Note that board12
4441        is really defective for later FRC positions, as it has no way to
4442        indicate which Rook can castle if they are on the same side of King.
4443        For the initial position we grant rights to the outermost Rooks,
4444        and remember thos rights, and we then copy them on positions
4445        later in an FRC game. This means WB might not recognize castlings with
4446        Rooks that have moved back to their original position as illegal,
4447        but in ICS mode that is not its job anyway.
4448     */
4449     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4450     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4451
4452         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4453             if(board[0][i] == WhiteRook) j = i;
4454         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4455         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4456             if(board[0][i] == WhiteRook) j = i;
4457         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4458         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4459             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4460         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4461         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4462             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4463         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4464
4465         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4466         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4467             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4468         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4469             if(board[BOARD_HEIGHT-1][k] == bKing)
4470                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4471         if(gameInfo.variant == VariantTwoKings) {
4472             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4473             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4474             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4475         }
4476     } else { int r;
4477         r = boards[moveNum][CASTLING][0] = initialRights[0];
4478         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4479         r = boards[moveNum][CASTLING][1] = initialRights[1];
4480         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4481         r = boards[moveNum][CASTLING][3] = initialRights[3];
4482         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4483         r = boards[moveNum][CASTLING][4] = initialRights[4];
4484         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4485         /* wildcastle kludge: always assume King has rights */
4486         r = boards[moveNum][CASTLING][2] = initialRights[2];
4487         r = boards[moveNum][CASTLING][5] = initialRights[5];
4488     }
4489     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4490     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4491
4492
4493     if (ics_getting_history == H_GOT_REQ_HEADER ||
4494         ics_getting_history == H_GOT_UNREQ_HEADER) {
4495         /* This was an initial position from a move list, not
4496            the current position */
4497         return;
4498     }
4499
4500     /* Update currentMove and known move number limits */
4501     newMove = newGame || moveNum > forwardMostMove;
4502
4503     if (newGame) {
4504         forwardMostMove = backwardMostMove = currentMove = moveNum;
4505         if (gameMode == IcsExamining && moveNum == 0) {
4506           /* Workaround for ICS limitation: we are not told the wild
4507              type when starting to examine a game.  But if we ask for
4508              the move list, the move list header will tell us */
4509             ics_getting_history = H_REQUESTED;
4510             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4511             SendToICS(str);
4512         }
4513     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4514                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4515 #if ZIPPY
4516         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4517         /* [HGM] applied this also to an engine that is silently watching        */
4518         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4519             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4520             gameInfo.variant == currentlyInitializedVariant) {
4521           takeback = forwardMostMove - moveNum;
4522           for (i = 0; i < takeback; i++) {
4523             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4524             SendToProgram("undo\n", &first);
4525           }
4526         }
4527 #endif
4528
4529         forwardMostMove = moveNum;
4530         if (!pausing || currentMove > forwardMostMove)
4531           currentMove = forwardMostMove;
4532     } else {
4533         /* New part of history that is not contiguous with old part */
4534         if (pausing && gameMode == IcsExamining) {
4535             pauseExamInvalid = TRUE;
4536             forwardMostMove = pauseExamForwardMostMove;
4537             return;
4538         }
4539         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4540 #if ZIPPY
4541             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4542                 // [HGM] when we will receive the move list we now request, it will be
4543                 // fed to the engine from the first move on. So if the engine is not
4544                 // in the initial position now, bring it there.
4545                 InitChessProgram(&first, 0);
4546             }
4547 #endif
4548             ics_getting_history = H_REQUESTED;
4549             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4550             SendToICS(str);
4551         }
4552         forwardMostMove = backwardMostMove = currentMove = moveNum;
4553     }
4554
4555     /* Update the clocks */
4556     if (strchr(elapsed_time, '.')) {
4557       /* Time is in ms */
4558       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4559       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4560     } else {
4561       /* Time is in seconds */
4562       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4563       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4564     }
4565
4566
4567 #if ZIPPY
4568     if (appData.zippyPlay && newGame &&
4569         gameMode != IcsObserving && gameMode != IcsIdle &&
4570         gameMode != IcsExamining)
4571       ZippyFirstBoard(moveNum, basetime, increment);
4572 #endif
4573
4574     /* Put the move on the move list, first converting
4575        to canonical algebraic form. */
4576     if (moveNum > 0) {
4577   if (appData.debugMode) {
4578     if (appData.debugMode) { int f = forwardMostMove;
4579         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4580                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4581                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4582     }
4583     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4584     fprintf(debugFP, "moveNum = %d\n", moveNum);
4585     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4586     setbuf(debugFP, NULL);
4587   }
4588         if (moveNum <= backwardMostMove) {
4589             /* We don't know what the board looked like before
4590                this move.  Punt. */
4591           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4592             strcat(parseList[moveNum - 1], " ");
4593             strcat(parseList[moveNum - 1], elapsed_time);
4594             moveList[moveNum - 1][0] = NULLCHAR;
4595         } else if (strcmp(move_str, "none") == 0) {
4596             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4597             /* Again, we don't know what the board looked like;
4598                this is really the start of the game. */
4599             parseList[moveNum - 1][0] = NULLCHAR;
4600             moveList[moveNum - 1][0] = NULLCHAR;
4601             backwardMostMove = moveNum;
4602             startedFromSetupPosition = TRUE;
4603             fromX = fromY = toX = toY = -1;
4604         } else {
4605           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4606           //                 So we parse the long-algebraic move string in stead of the SAN move
4607           int valid; char buf[MSG_SIZ], *prom;
4608
4609           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4610                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4611           // str looks something like "Q/a1-a2"; kill the slash
4612           if(str[1] == '/')
4613             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4614           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4615           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4616                 strcat(buf, prom); // long move lacks promo specification!
4617           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4618                 if(appData.debugMode)
4619                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4620                 safeStrCpy(move_str, buf, MSG_SIZ);
4621           }
4622           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4623                                 &fromX, &fromY, &toX, &toY, &promoChar)
4624                || ParseOneMove(buf, moveNum - 1, &moveType,
4625                                 &fromX, &fromY, &toX, &toY, &promoChar);
4626           // end of long SAN patch
4627           if (valid) {
4628             (void) CoordsToAlgebraic(boards[moveNum - 1],
4629                                      PosFlags(moveNum - 1),
4630                                      fromY, fromX, toY, toX, promoChar,
4631                                      parseList[moveNum-1]);
4632             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4633               case MT_NONE:
4634               case MT_STALEMATE:
4635               default:
4636                 break;
4637               case MT_CHECK:
4638                 if(gameInfo.variant != VariantShogi)
4639                     strcat(parseList[moveNum - 1], "+");
4640                 break;
4641               case MT_CHECKMATE:
4642               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4643                 strcat(parseList[moveNum - 1], "#");
4644                 break;
4645             }
4646             strcat(parseList[moveNum - 1], " ");
4647             strcat(parseList[moveNum - 1], elapsed_time);
4648             /* currentMoveString is set as a side-effect of ParseOneMove */
4649             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4650             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4651             strcat(moveList[moveNum - 1], "\n");
4652
4653             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4654                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4655               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4656                 ChessSquare old, new = boards[moveNum][k][j];
4657                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4658                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4659                   if(old == new) continue;
4660                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4661                   else if(new == WhiteWazir || new == BlackWazir) {
4662                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4663                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4664                       else boards[moveNum][k][j] = old; // preserve type of Gold
4665                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4666                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4667               }
4668           } else {
4669             /* Move from ICS was illegal!?  Punt. */
4670             if (appData.debugMode) {
4671               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4672               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4673             }
4674             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4675             strcat(parseList[moveNum - 1], " ");
4676             strcat(parseList[moveNum - 1], elapsed_time);
4677             moveList[moveNum - 1][0] = NULLCHAR;
4678             fromX = fromY = toX = toY = -1;
4679           }
4680         }
4681   if (appData.debugMode) {
4682     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4683     setbuf(debugFP, NULL);
4684   }
4685
4686 #if ZIPPY
4687         /* Send move to chess program (BEFORE animating it). */
4688         if (appData.zippyPlay && !newGame && newMove &&
4689            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4690
4691             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4692                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4693                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4694                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4695                             move_str);
4696                     DisplayError(str, 0);
4697                 } else {
4698                     if (first.sendTime) {
4699                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4700                     }
4701                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4702                     if (firstMove && !bookHit) {
4703                         firstMove = FALSE;
4704                         if (first.useColors) {
4705                           SendToProgram(gameMode == IcsPlayingWhite ?
4706                                         "white\ngo\n" :
4707                                         "black\ngo\n", &first);
4708                         } else {
4709                           SendToProgram("go\n", &first);
4710                         }
4711                         first.maybeThinking = TRUE;
4712                     }
4713                 }
4714             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4715               if (moveList[moveNum - 1][0] == NULLCHAR) {
4716                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4717                 DisplayError(str, 0);
4718               } else {
4719                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4720                 SendMoveToProgram(moveNum - 1, &first);
4721               }
4722             }
4723         }
4724 #endif
4725     }
4726
4727     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4728         /* If move comes from a remote source, animate it.  If it
4729            isn't remote, it will have already been animated. */
4730         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4731             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4732         }
4733         if (!pausing && appData.highlightLastMove) {
4734             SetHighlights(fromX, fromY, toX, toY);
4735         }
4736     }
4737
4738     /* Start the clocks */
4739     whiteFlag = blackFlag = FALSE;
4740     appData.clockMode = !(basetime == 0 && increment == 0);
4741     if (ticking == 0) {
4742       ics_clock_paused = TRUE;
4743       StopClocks();
4744     } else if (ticking == 1) {
4745       ics_clock_paused = FALSE;
4746     }
4747     if (gameMode == IcsIdle ||
4748         relation == RELATION_OBSERVING_STATIC ||
4749         relation == RELATION_EXAMINING ||
4750         ics_clock_paused)
4751       DisplayBothClocks();
4752     else
4753       StartClocks();
4754
4755     /* Display opponents and material strengths */
4756     if (gameInfo.variant != VariantBughouse &&
4757         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4758         if (tinyLayout || smallLayout) {
4759             if(gameInfo.variant == VariantNormal)
4760               snprintf(str, MSG_SIZ, "%s(%d) %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) %s(%d) {%d %d w%d}",
4765                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4766                     basetime, increment, (int) gameInfo.variant);
4767         } else {
4768             if(gameInfo.variant == VariantNormal)
4769               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4770                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4771                     basetime, increment);
4772             else
4773               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4774                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4775                     basetime, increment, VariantName(gameInfo.variant));
4776         }
4777         DisplayTitle(str);
4778   if (appData.debugMode) {
4779     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4780   }
4781     }
4782
4783
4784     /* Display the board */
4785     if (!pausing && !appData.noGUI) {
4786
4787       if (appData.premove)
4788           if (!gotPremove ||
4789              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4790              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4791               ClearPremoveHighlights();
4792
4793       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4794         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4795       DrawPosition(j, boards[currentMove]);
4796
4797       DisplayMove(moveNum - 1);
4798       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4799             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4800               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4801         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4802       }
4803     }
4804
4805     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4806 #if ZIPPY
4807     if(bookHit) { // [HGM] book: simulate book reply
4808         static char bookMove[MSG_SIZ]; // a bit generous?
4809
4810         programStats.nodes = programStats.depth = programStats.time =
4811         programStats.score = programStats.got_only_move = 0;
4812         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4813
4814         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4815         strcat(bookMove, bookHit);
4816         HandleMachineMove(bookMove, &first);
4817     }
4818 #endif
4819 }
4820
4821 void
4822 GetMoveListEvent()
4823 {
4824     char buf[MSG_SIZ];
4825     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4826         ics_getting_history = H_REQUESTED;
4827         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4828         SendToICS(buf);
4829     }
4830 }
4831
4832 void
4833 AnalysisPeriodicEvent(force)
4834      int force;
4835 {
4836     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4837          && !force) || !appData.periodicUpdates)
4838       return;
4839
4840     /* Send . command to Crafty to collect stats */
4841     SendToProgram(".\n", &first);
4842
4843     /* Don't send another until we get a response (this makes
4844        us stop sending to old Crafty's which don't understand
4845        the "." command (sending illegal cmds resets node count & time,
4846        which looks bad)) */
4847     programStats.ok_to_send = 0;
4848 }
4849
4850 void ics_update_width(new_width)
4851         int new_width;
4852 {
4853         ics_printf("set width %d\n", new_width);
4854 }
4855
4856 void
4857 SendMoveToProgram(moveNum, cps)
4858      int moveNum;
4859      ChessProgramState *cps;
4860 {
4861     char buf[MSG_SIZ];
4862
4863     if (cps->useUsermove) {
4864       SendToProgram("usermove ", cps);
4865     }
4866     if (cps->useSAN) {
4867       char *space;
4868       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4869         int len = space - parseList[moveNum];
4870         memcpy(buf, parseList[moveNum], len);
4871         buf[len++] = '\n';
4872         buf[len] = NULLCHAR;
4873       } else {
4874         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4875       }
4876       SendToProgram(buf, cps);
4877     } else {
4878       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4879         AlphaRank(moveList[moveNum], 4);
4880         SendToProgram(moveList[moveNum], cps);
4881         AlphaRank(moveList[moveNum], 4); // and back
4882       } else
4883       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4884        * the engine. It would be nice to have a better way to identify castle
4885        * moves here. */
4886       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4887                                                                          && cps->useOOCastle) {
4888         int fromX = moveList[moveNum][0] - AAA;
4889         int fromY = moveList[moveNum][1] - ONE;
4890         int toX = moveList[moveNum][2] - AAA;
4891         int toY = moveList[moveNum][3] - ONE;
4892         if((boards[moveNum][fromY][fromX] == WhiteKing
4893             && boards[moveNum][toY][toX] == WhiteRook)
4894            || (boards[moveNum][fromY][fromX] == BlackKing
4895                && boards[moveNum][toY][toX] == BlackRook)) {
4896           if(toX > fromX) SendToProgram("O-O\n", cps);
4897           else SendToProgram("O-O-O\n", cps);
4898         }
4899         else SendToProgram(moveList[moveNum], cps);
4900       } else
4901       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4902         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4903           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4904                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4905         } else
4906           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4907                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4908         SendToProgram(buf, cps);
4909       }
4910       else SendToProgram(moveList[moveNum], cps);
4911       /* End of additions by Tord */
4912     }
4913
4914     /* [HGM] setting up the opening has brought engine in force mode! */
4915     /*       Send 'go' if we are in a mode where machine should play. */
4916     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4917         (gameMode == TwoMachinesPlay   ||
4918 #if ZIPPY
4919          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4920 #endif
4921          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4922         SendToProgram("go\n", cps);
4923   if (appData.debugMode) {
4924     fprintf(debugFP, "(extra)\n");
4925   }
4926     }
4927     setboardSpoiledMachineBlack = 0;
4928 }
4929
4930 void
4931 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4932      ChessMove moveType;
4933      int fromX, fromY, toX, toY;
4934      char promoChar;
4935 {
4936     char user_move[MSG_SIZ];
4937
4938     switch (moveType) {
4939       default:
4940         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4941                 (int)moveType, fromX, fromY, toX, toY);
4942         DisplayError(user_move + strlen("say "), 0);
4943         break;
4944       case WhiteKingSideCastle:
4945       case BlackKingSideCastle:
4946       case WhiteQueenSideCastleWild:
4947       case BlackQueenSideCastleWild:
4948       /* PUSH Fabien */
4949       case WhiteHSideCastleFR:
4950       case BlackHSideCastleFR:
4951       /* POP Fabien */
4952         snprintf(user_move, MSG_SIZ, "o-o\n");
4953         break;
4954       case WhiteQueenSideCastle:
4955       case BlackQueenSideCastle:
4956       case WhiteKingSideCastleWild:
4957       case BlackKingSideCastleWild:
4958       /* PUSH Fabien */
4959       case WhiteASideCastleFR:
4960       case BlackASideCastleFR:
4961       /* POP Fabien */
4962         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4963         break;
4964       case WhiteNonPromotion:
4965       case BlackNonPromotion:
4966         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4967         break;
4968       case WhitePromotion:
4969       case BlackPromotion:
4970         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4971           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4972                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4973                 PieceToChar(WhiteFerz));
4974         else if(gameInfo.variant == VariantGreat)
4975           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4976                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4977                 PieceToChar(WhiteMan));
4978         else
4979           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4980                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4981                 promoChar);
4982         break;
4983       case WhiteDrop:
4984       case BlackDrop:
4985       drop:
4986         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4987                  ToUpper(PieceToChar((ChessSquare) fromX)),
4988                  AAA + toX, ONE + toY);
4989         break;
4990       case IllegalMove:  /* could be a variant we don't quite understand */
4991         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4992       case NormalMove:
4993       case WhiteCapturesEnPassant:
4994       case BlackCapturesEnPassant:
4995         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4996                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4997         break;
4998     }
4999     SendToICS(user_move);
5000     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5001         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5002 }
5003
5004 void
5005 UploadGameEvent()
5006 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5007     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5008     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5009     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5010         DisplayError("You cannot do this while you are playing or observing", 0);
5011         return;
5012     }
5013     if(gameMode != IcsExamining) { // is this ever not the case?
5014         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5015
5016         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5017           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5018         } else { // on FICS we must first go to general examine mode
5019           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5020         }
5021         if(gameInfo.variant != VariantNormal) {
5022             // try figure out wild number, as xboard names are not always valid on ICS
5023             for(i=1; i<=36; i++) {
5024               snprintf(buf, MSG_SIZ, "wild/%d", i);
5025                 if(StringToVariant(buf) == gameInfo.variant) break;
5026             }
5027             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5028             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5029             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5030         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5031         SendToICS(ics_prefix);
5032         SendToICS(buf);
5033         if(startedFromSetupPosition || backwardMostMove != 0) {
5034           fen = PositionToFEN(backwardMostMove, NULL);
5035           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5036             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5037             SendToICS(buf);
5038           } else { // FICS: everything has to set by separate bsetup commands
5039             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5040             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5041             SendToICS(buf);
5042             if(!WhiteOnMove(backwardMostMove)) {
5043                 SendToICS("bsetup tomove black\n");
5044             }
5045             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5046             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5047             SendToICS(buf);
5048             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5049             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5050             SendToICS(buf);
5051             i = boards[backwardMostMove][EP_STATUS];
5052             if(i >= 0) { // set e.p.
5053               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5054                 SendToICS(buf);
5055             }
5056             bsetup++;
5057           }
5058         }
5059       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5060             SendToICS("bsetup done\n"); // switch to normal examining.
5061     }
5062     for(i = backwardMostMove; i<last; i++) {
5063         char buf[20];
5064         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5065         SendToICS(buf);
5066     }
5067     SendToICS(ics_prefix);
5068     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5069 }
5070
5071 void
5072 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5073      int rf, ff, rt, ft;
5074      char promoChar;
5075      char move[7];
5076 {
5077     if (rf == DROP_RANK) {
5078       sprintf(move, "%c@%c%c\n",
5079                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5080     } else {
5081         if (promoChar == 'x' || promoChar == NULLCHAR) {
5082           sprintf(move, "%c%c%c%c\n",
5083                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5084         } else {
5085             sprintf(move, "%c%c%c%c%c\n",
5086                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5087         }
5088     }
5089 }
5090
5091 void
5092 ProcessICSInitScript(f)
5093      FILE *f;
5094 {
5095     char buf[MSG_SIZ];
5096
5097     while (fgets(buf, MSG_SIZ, f)) {
5098         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5099     }
5100
5101     fclose(f);
5102 }
5103
5104
5105 static int lastX, lastY, selectFlag, dragging;
5106
5107 void
5108 Sweep(int step)
5109 {
5110     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5111     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5112     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5113     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5114     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5115     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5116     do {
5117         promoSweep -= step;
5118         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5119         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5120         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5121         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5122         if(!step) step = 1;
5123     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5124             appData.testLegality && (promoSweep == king ||
5125             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5126     ChangeDragPiece(promoSweep);
5127 }
5128
5129 int PromoScroll(int x, int y)
5130 {
5131   int step = 0;
5132
5133   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5134   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5135   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5136   if(!step) return FALSE;
5137   lastX = x; lastY = y;
5138   if((promoSweep < BlackPawn) == flipView) step = -step;
5139   if(step > 0) selectFlag = 1;
5140   if(!selectFlag) Sweep(step);
5141   return FALSE;
5142 }
5143
5144 void
5145 NextPiece(int step)
5146 {
5147     ChessSquare piece = boards[currentMove][toY][toX];
5148     do {
5149         pieceSweep -= step;
5150         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5151         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5152         if(!step) step = -1;
5153     } while(PieceToChar(pieceSweep) == '.');
5154     boards[currentMove][toY][toX] = pieceSweep;
5155     DrawPosition(FALSE, boards[currentMove]);
5156     boards[currentMove][toY][toX] = piece;
5157 }
5158 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5159 void
5160 AlphaRank(char *move, int n)
5161 {
5162 //    char *p = move, c; int x, y;
5163
5164     if (appData.debugMode) {
5165         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5166     }
5167
5168     if(move[1]=='*' &&
5169        move[2]>='0' && move[2]<='9' &&
5170        move[3]>='a' && move[3]<='x'    ) {
5171         move[1] = '@';
5172         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5173         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5174     } else
5175     if(move[0]>='0' && move[0]<='9' &&
5176        move[1]>='a' && move[1]<='x' &&
5177        move[2]>='0' && move[2]<='9' &&
5178        move[3]>='a' && move[3]<='x'    ) {
5179         /* input move, Shogi -> normal */
5180         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5181         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5182         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5183         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5184     } else
5185     if(move[1]=='@' &&
5186        move[3]>='0' && move[3]<='9' &&
5187        move[2]>='a' && move[2]<='x'    ) {
5188         move[1] = '*';
5189         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5190         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5191     } else
5192     if(
5193        move[0]>='a' && move[0]<='x' &&
5194        move[3]>='0' && move[3]<='9' &&
5195        move[2]>='a' && move[2]<='x'    ) {
5196          /* output move, normal -> Shogi */
5197         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5198         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5199         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5200         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5201         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5202     }
5203     if (appData.debugMode) {
5204         fprintf(debugFP, "   out = '%s'\n", move);
5205     }
5206 }
5207
5208 char yy_textstr[8000];
5209
5210 /* Parser for moves from gnuchess, ICS, or user typein box */
5211 Boolean
5212 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5213      char *move;
5214      int moveNum;
5215      ChessMove *moveType;
5216      int *fromX, *fromY, *toX, *toY;
5217      char *promoChar;
5218 {
5219     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5220
5221     switch (*moveType) {
5222       case WhitePromotion:
5223       case BlackPromotion:
5224       case WhiteNonPromotion:
5225       case BlackNonPromotion:
5226       case NormalMove:
5227       case WhiteCapturesEnPassant:
5228       case BlackCapturesEnPassant:
5229       case WhiteKingSideCastle:
5230       case WhiteQueenSideCastle:
5231       case BlackKingSideCastle:
5232       case BlackQueenSideCastle:
5233       case WhiteKingSideCastleWild:
5234       case WhiteQueenSideCastleWild:
5235       case BlackKingSideCastleWild:
5236       case BlackQueenSideCastleWild:
5237       /* Code added by Tord: */
5238       case WhiteHSideCastleFR:
5239       case WhiteASideCastleFR:
5240       case BlackHSideCastleFR:
5241       case BlackASideCastleFR:
5242       /* End of code added by Tord */
5243       case IllegalMove:         /* bug or odd chess variant */
5244         *fromX = currentMoveString[0] - AAA;
5245         *fromY = currentMoveString[1] - ONE;
5246         *toX = currentMoveString[2] - AAA;
5247         *toY = currentMoveString[3] - ONE;
5248         *promoChar = currentMoveString[4];
5249         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5250             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5251     if (appData.debugMode) {
5252         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5253     }
5254             *fromX = *fromY = *toX = *toY = 0;
5255             return FALSE;
5256         }
5257         if (appData.testLegality) {
5258           return (*moveType != IllegalMove);
5259         } else {
5260           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5261                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5262         }
5263
5264       case WhiteDrop:
5265       case BlackDrop:
5266         *fromX = *moveType == WhiteDrop ?
5267           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5268           (int) CharToPiece(ToLower(currentMoveString[0]));
5269         *fromY = DROP_RANK;
5270         *toX = currentMoveString[2] - AAA;
5271         *toY = currentMoveString[3] - ONE;
5272         *promoChar = NULLCHAR;
5273         return TRUE;
5274
5275       case AmbiguousMove:
5276       case ImpossibleMove:
5277       case EndOfFile:
5278       case ElapsedTime:
5279       case Comment:
5280       case PGNTag:
5281       case NAG:
5282       case WhiteWins:
5283       case BlackWins:
5284       case GameIsDrawn:
5285       default:
5286     if (appData.debugMode) {
5287         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5288     }
5289         /* bug? */
5290         *fromX = *fromY = *toX = *toY = 0;
5291         *promoChar = NULLCHAR;
5292         return FALSE;
5293     }
5294 }
5295
5296 Boolean pushed = FALSE;
5297 char *lastParseAttempt;
5298
5299 void
5300 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5301 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5302   int fromX, fromY, toX, toY; char promoChar;
5303   ChessMove moveType;
5304   Boolean valid;
5305   int nr = 0;
5306
5307   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5308     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5309     pushed = TRUE;
5310   }
5311   endPV = forwardMostMove;
5312   do {
5313     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5314     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5315     lastParseAttempt = pv;
5316     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5317 if(appData.debugMode){
5318 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);
5319 }
5320     if(!valid && nr == 0 &&
5321        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5322         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5323         // Hande case where played move is different from leading PV move
5324         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5325         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5326         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5327         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5328           endPV += 2; // if position different, keep this
5329           moveList[endPV-1][0] = fromX + AAA;
5330           moveList[endPV-1][1] = fromY + ONE;
5331           moveList[endPV-1][2] = toX + AAA;
5332           moveList[endPV-1][3] = toY + ONE;
5333           parseList[endPV-1][0] = NULLCHAR;
5334           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5335         }
5336       }
5337     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5338     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5339     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5340     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5341         valid++; // allow comments in PV
5342         continue;
5343     }
5344     nr++;
5345     if(endPV+1 > framePtr) break; // no space, truncate
5346     if(!valid) break;
5347     endPV++;
5348     CopyBoard(boards[endPV], boards[endPV-1]);
5349     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5350     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5351     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5352     CoordsToAlgebraic(boards[endPV - 1],
5353                              PosFlags(endPV - 1),
5354                              fromY, fromX, toY, toX, promoChar,
5355                              parseList[endPV - 1]);
5356   } while(valid);
5357   if(atEnd == 2) return; // used hidden, for PV conversion
5358   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5359   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5360   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5361                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5362   DrawPosition(TRUE, boards[currentMove]);
5363 }
5364
5365 int
5366 MultiPV(ChessProgramState *cps)
5367 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5368         int i;
5369         for(i=0; i<cps->nrOptions; i++)
5370             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5371                 return i;
5372         return -1;
5373 }
5374
5375 Boolean
5376 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5377 {
5378         int startPV, multi, lineStart, origIndex = index;
5379         char *p, buf2[MSG_SIZ];
5380
5381         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5382         lastX = x; lastY = y;
5383         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5384         lineStart = startPV = index;
5385         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5386         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5387         index = startPV;
5388         do{ while(buf[index] && buf[index] != '\n') index++;
5389         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5390         buf[index] = 0;
5391         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5392                 int n = first.option[multi].value;
5393                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5394                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5395                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5396                 first.option[multi].value = n;
5397                 *start = *end = 0;
5398                 return FALSE;
5399         }
5400         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5401         *start = startPV; *end = index-1;
5402         return TRUE;
5403 }
5404
5405 char *
5406 PvToSAN(char *pv)
5407 {
5408         static char buf[10*MSG_SIZ];
5409         int i, k=0, savedEnd=endPV;
5410         *buf = NULLCHAR;
5411         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5412         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5413         for(i = forwardMostMove; i<endPV; i++){
5414             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5415             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5416             k += strlen(buf+k);
5417         }
5418         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5419         if(forwardMostMove < savedEnd) PopInner(0);
5420         endPV = savedEnd;
5421         return buf;
5422 }
5423
5424 Boolean
5425 LoadPV(int x, int y)
5426 { // called on right mouse click to load PV
5427   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5428   lastX = x; lastY = y;
5429   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5430   return TRUE;
5431 }
5432
5433 void
5434 UnLoadPV()
5435 {
5436   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5437   if(endPV < 0) return;
5438   endPV = -1;
5439   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5440         Boolean saveAnimate = appData.animate;
5441         if(pushed) {
5442             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5443                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5444             } else storedGames--; // abandon shelved tail of original game
5445         }
5446         pushed = FALSE;
5447         forwardMostMove = currentMove;
5448         currentMove = oldFMM;
5449         appData.animate = FALSE;
5450         ToNrEvent(forwardMostMove);
5451         appData.animate = saveAnimate;
5452   }
5453   currentMove = forwardMostMove;
5454   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5455   ClearPremoveHighlights();
5456   DrawPosition(TRUE, boards[currentMove]);
5457 }
5458
5459 void
5460 MovePV(int x, int y, int h)
5461 { // step through PV based on mouse coordinates (called on mouse move)
5462   int margin = h>>3, step = 0;
5463
5464   // we must somehow check if right button is still down (might be released off board!)
5465   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5466   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5467   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5468   if(!step) return;
5469   lastX = x; lastY = y;
5470
5471   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5472   if(endPV < 0) return;
5473   if(y < margin) step = 1; else
5474   if(y > h - margin) step = -1;
5475   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5476   currentMove += step;
5477   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5478   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5479                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5480   DrawPosition(FALSE, boards[currentMove]);
5481 }
5482
5483
5484 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5485 // All positions will have equal probability, but the current method will not provide a unique
5486 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5487 #define DARK 1
5488 #define LITE 2
5489 #define ANY 3
5490
5491 int squaresLeft[4];
5492 int piecesLeft[(int)BlackPawn];
5493 int seed, nrOfShuffles;
5494
5495 void GetPositionNumber()
5496 {       // sets global variable seed
5497         int i;
5498
5499         seed = appData.defaultFrcPosition;
5500         if(seed < 0) { // randomize based on time for negative FRC position numbers
5501                 for(i=0; i<50; i++) seed += random();
5502                 seed = random() ^ random() >> 8 ^ random() << 8;
5503                 if(seed<0) seed = -seed;
5504         }
5505 }
5506
5507 int put(Board board, int pieceType, int rank, int n, int shade)
5508 // put the piece on the (n-1)-th empty squares of the given shade
5509 {
5510         int i;
5511
5512         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5513                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5514                         board[rank][i] = (ChessSquare) pieceType;
5515                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5516                         squaresLeft[ANY]--;
5517                         piecesLeft[pieceType]--;
5518                         return i;
5519                 }
5520         }
5521         return -1;
5522 }
5523
5524
5525 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5526 // calculate where the next piece goes, (any empty square), and put it there
5527 {
5528         int i;
5529
5530         i = seed % squaresLeft[shade];
5531         nrOfShuffles *= squaresLeft[shade];
5532         seed /= squaresLeft[shade];
5533         put(board, pieceType, rank, i, shade);
5534 }
5535
5536 void AddTwoPieces(Board board, int pieceType, int rank)
5537 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5538 {
5539         int i, n=squaresLeft[ANY], j=n-1, k;
5540
5541         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5542         i = seed % k;  // pick one
5543         nrOfShuffles *= k;
5544         seed /= k;
5545         while(i >= j) i -= j--;
5546         j = n - 1 - j; i += j;
5547         put(board, pieceType, rank, j, ANY);
5548         put(board, pieceType, rank, i, ANY);
5549 }
5550
5551 void SetUpShuffle(Board board, int number)
5552 {
5553         int i, p, first=1;
5554
5555         GetPositionNumber(); nrOfShuffles = 1;
5556
5557         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5558         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5559         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5560
5561         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5562
5563         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5564             p = (int) board[0][i];
5565             if(p < (int) BlackPawn) piecesLeft[p] ++;
5566             board[0][i] = EmptySquare;
5567         }
5568
5569         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5570             // shuffles restricted to allow normal castling put KRR first
5571             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5572                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5573             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5574                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5575             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5576                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5577             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5578                 put(board, WhiteRook, 0, 0, ANY);
5579             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5580         }
5581
5582         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5583             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5584             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5585                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5586                 while(piecesLeft[p] >= 2) {
5587                     AddOnePiece(board, p, 0, LITE);
5588                     AddOnePiece(board, p, 0, DARK);
5589                 }
5590                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5591             }
5592
5593         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5594             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5595             // but we leave King and Rooks for last, to possibly obey FRC restriction
5596             if(p == (int)WhiteRook) continue;
5597             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5598             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5599         }
5600
5601         // now everything is placed, except perhaps King (Unicorn) and Rooks
5602
5603         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5604             // Last King gets castling rights
5605             while(piecesLeft[(int)WhiteUnicorn]) {
5606                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5607                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5608             }
5609
5610             while(piecesLeft[(int)WhiteKing]) {
5611                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5612                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5613             }
5614
5615
5616         } else {
5617             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5618             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5619         }
5620
5621         // Only Rooks can be left; simply place them all
5622         while(piecesLeft[(int)WhiteRook]) {
5623                 i = put(board, WhiteRook, 0, 0, ANY);
5624                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5625                         if(first) {
5626                                 first=0;
5627                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5628                         }
5629                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5630                 }
5631         }
5632         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5633             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5634         }
5635
5636         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5637 }
5638
5639 int SetCharTable( char *table, const char * map )
5640 /* [HGM] moved here from winboard.c because of its general usefulness */
5641 /*       Basically a safe strcpy that uses the last character as King */
5642 {
5643     int result = FALSE; int NrPieces;
5644
5645     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5646                     && NrPieces >= 12 && !(NrPieces&1)) {
5647         int i; /* [HGM] Accept even length from 12 to 34 */
5648
5649         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5650         for( i=0; i<NrPieces/2-1; i++ ) {
5651             table[i] = map[i];
5652             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5653         }
5654         table[(int) WhiteKing]  = map[NrPieces/2-1];
5655         table[(int) BlackKing]  = map[NrPieces-1];
5656
5657         result = TRUE;
5658     }
5659
5660     return result;
5661 }
5662
5663 void Prelude(Board board)
5664 {       // [HGM] superchess: random selection of exo-pieces
5665         int i, j, k; ChessSquare p;
5666         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5667
5668         GetPositionNumber(); // use FRC position number
5669
5670         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5671             SetCharTable(pieceToChar, appData.pieceToCharTable);
5672             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5673                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5674         }
5675
5676         j = seed%4;                 seed /= 4;
5677         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5678         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5679         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5680         j = seed%3 + (seed%3 >= j); seed /= 3;
5681         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5682         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5683         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5684         j = seed%3;                 seed /= 3;
5685         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5686         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5687         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5688         j = seed%2 + (seed%2 >= j); seed /= 2;
5689         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5690         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5691         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5692         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5693         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5694         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5695         put(board, exoPieces[0],    0, 0, ANY);
5696         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5697 }
5698
5699 void
5700 InitPosition(redraw)
5701      int redraw;
5702 {
5703     ChessSquare (* pieces)[BOARD_FILES];
5704     int i, j, pawnRow, overrule,
5705     oldx = gameInfo.boardWidth,
5706     oldy = gameInfo.boardHeight,
5707     oldh = gameInfo.holdingsWidth;
5708     static int oldv;
5709
5710     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5711
5712     /* [AS] Initialize pv info list [HGM] and game status */
5713     {
5714         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5715             pvInfoList[i].depth = 0;
5716             boards[i][EP_STATUS] = EP_NONE;
5717             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5718         }
5719
5720         initialRulePlies = 0; /* 50-move counter start */
5721
5722         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5723         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5724     }
5725
5726
5727     /* [HGM] logic here is completely changed. In stead of full positions */
5728     /* the initialized data only consist of the two backranks. The switch */
5729     /* selects which one we will use, which is than copied to the Board   */
5730     /* initialPosition, which for the rest is initialized by Pawns and    */
5731     /* empty squares. This initial position is then copied to boards[0],  */
5732     /* possibly after shuffling, so that it remains available.            */
5733
5734     gameInfo.holdingsWidth = 0; /* default board sizes */
5735     gameInfo.boardWidth    = 8;
5736     gameInfo.boardHeight   = 8;
5737     gameInfo.holdingsSize  = 0;
5738     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5739     for(i=0; i<BOARD_FILES-2; i++)
5740       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5741     initialPosition[EP_STATUS] = EP_NONE;
5742     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5743     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5744          SetCharTable(pieceNickName, appData.pieceNickNames);
5745     else SetCharTable(pieceNickName, "............");
5746     pieces = FIDEArray;
5747
5748     switch (gameInfo.variant) {
5749     case VariantFischeRandom:
5750       shuffleOpenings = TRUE;
5751     default:
5752       break;
5753     case VariantShatranj:
5754       pieces = ShatranjArray;
5755       nrCastlingRights = 0;
5756       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5757       break;
5758     case VariantMakruk:
5759       pieces = makrukArray;
5760       nrCastlingRights = 0;
5761       startedFromSetupPosition = TRUE;
5762       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5763       break;
5764     case VariantTwoKings:
5765       pieces = twoKingsArray;
5766       break;
5767     case VariantGrand:
5768       pieces = GrandArray;
5769       nrCastlingRights = 0;
5770       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5771       gameInfo.boardWidth = 10;
5772       gameInfo.boardHeight = 10;
5773       gameInfo.holdingsSize = 7;
5774       break;
5775     case VariantCapaRandom:
5776       shuffleOpenings = TRUE;
5777     case VariantCapablanca:
5778       pieces = CapablancaArray;
5779       gameInfo.boardWidth = 10;
5780       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5781       break;
5782     case VariantGothic:
5783       pieces = GothicArray;
5784       gameInfo.boardWidth = 10;
5785       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5786       break;
5787     case VariantSChess:
5788       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5789       gameInfo.holdingsSize = 7;
5790       break;
5791     case VariantJanus:
5792       pieces = JanusArray;
5793       gameInfo.boardWidth = 10;
5794       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5795       nrCastlingRights = 6;
5796         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5797         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5798         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5799         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5800         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5801         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5802       break;
5803     case VariantFalcon:
5804       pieces = FalconArray;
5805       gameInfo.boardWidth = 10;
5806       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5807       break;
5808     case VariantXiangqi:
5809       pieces = XiangqiArray;
5810       gameInfo.boardWidth  = 9;
5811       gameInfo.boardHeight = 10;
5812       nrCastlingRights = 0;
5813       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5814       break;
5815     case VariantShogi:
5816       pieces = ShogiArray;
5817       gameInfo.boardWidth  = 9;
5818       gameInfo.boardHeight = 9;
5819       gameInfo.holdingsSize = 7;
5820       nrCastlingRights = 0;
5821       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5822       break;
5823     case VariantCourier:
5824       pieces = CourierArray;
5825       gameInfo.boardWidth  = 12;
5826       nrCastlingRights = 0;
5827       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5828       break;
5829     case VariantKnightmate:
5830       pieces = KnightmateArray;
5831       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5832       break;
5833     case VariantSpartan:
5834       pieces = SpartanArray;
5835       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5836       break;
5837     case VariantFairy:
5838       pieces = fairyArray;
5839       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5840       break;
5841     case VariantGreat:
5842       pieces = GreatArray;
5843       gameInfo.boardWidth = 10;
5844       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5845       gameInfo.holdingsSize = 8;
5846       break;
5847     case VariantSuper:
5848       pieces = FIDEArray;
5849       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5850       gameInfo.holdingsSize = 8;
5851       startedFromSetupPosition = TRUE;
5852       break;
5853     case VariantCrazyhouse:
5854     case VariantBughouse:
5855       pieces = FIDEArray;
5856       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5857       gameInfo.holdingsSize = 5;
5858       break;
5859     case VariantWildCastle:
5860       pieces = FIDEArray;
5861       /* !!?shuffle with kings guaranteed to be on d or e file */
5862       shuffleOpenings = 1;
5863       break;
5864     case VariantNoCastle:
5865       pieces = FIDEArray;
5866       nrCastlingRights = 0;
5867       /* !!?unconstrained back-rank shuffle */
5868       shuffleOpenings = 1;
5869       break;
5870     }
5871
5872     overrule = 0;
5873     if(appData.NrFiles >= 0) {
5874         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5875         gameInfo.boardWidth = appData.NrFiles;
5876     }
5877     if(appData.NrRanks >= 0) {
5878         gameInfo.boardHeight = appData.NrRanks;
5879     }
5880     if(appData.holdingsSize >= 0) {
5881         i = appData.holdingsSize;
5882         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5883         gameInfo.holdingsSize = i;
5884     }
5885     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5886     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5887         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5888
5889     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5890     if(pawnRow < 1) pawnRow = 1;
5891     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5892
5893     /* User pieceToChar list overrules defaults */
5894     if(appData.pieceToCharTable != NULL)
5895         SetCharTable(pieceToChar, appData.pieceToCharTable);
5896
5897     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5898
5899         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5900             s = (ChessSquare) 0; /* account holding counts in guard band */
5901         for( i=0; i<BOARD_HEIGHT; i++ )
5902             initialPosition[i][j] = s;
5903
5904         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5905         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5906         initialPosition[pawnRow][j] = WhitePawn;
5907         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5908         if(gameInfo.variant == VariantXiangqi) {
5909             if(j&1) {
5910                 initialPosition[pawnRow][j] =
5911                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5912                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5913                    initialPosition[2][j] = WhiteCannon;
5914                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5915                 }
5916             }
5917         }
5918         if(gameInfo.variant == VariantGrand) {
5919             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5920                initialPosition[0][j] = WhiteRook;
5921                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5922             }
5923         }
5924         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5925     }
5926     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5927
5928             j=BOARD_LEFT+1;
5929             initialPosition[1][j] = WhiteBishop;
5930             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5931             j=BOARD_RGHT-2;
5932             initialPosition[1][j] = WhiteRook;
5933             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5934     }
5935
5936     if( nrCastlingRights == -1) {
5937         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5938         /*       This sets default castling rights from none to normal corners   */
5939         /* Variants with other castling rights must set them themselves above    */
5940         nrCastlingRights = 6;
5941
5942         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5943         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5944         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5945         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5946         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5947         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5948      }
5949
5950      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5951      if(gameInfo.variant == VariantGreat) { // promotion commoners
5952         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5953         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5954         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5955         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5956      }
5957      if( gameInfo.variant == VariantSChess ) {
5958       initialPosition[1][0] = BlackMarshall;
5959       initialPosition[2][0] = BlackAngel;
5960       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5961       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5962       initialPosition[1][1] = initialPosition[2][1] = 
5963       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5964      }
5965   if (appData.debugMode) {
5966     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5967   }
5968     if(shuffleOpenings) {
5969         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5970         startedFromSetupPosition = TRUE;
5971     }
5972     if(startedFromPositionFile) {
5973       /* [HGM] loadPos: use PositionFile for every new game */
5974       CopyBoard(initialPosition, filePosition);
5975       for(i=0; i<nrCastlingRights; i++)
5976           initialRights[i] = filePosition[CASTLING][i];
5977       startedFromSetupPosition = TRUE;
5978     }
5979
5980     CopyBoard(boards[0], initialPosition);
5981
5982     if(oldx != gameInfo.boardWidth ||
5983        oldy != gameInfo.boardHeight ||
5984        oldv != gameInfo.variant ||
5985        oldh != gameInfo.holdingsWidth
5986                                          )
5987             InitDrawingSizes(-2 ,0);
5988
5989     oldv = gameInfo.variant;
5990     if (redraw)
5991       DrawPosition(TRUE, boards[currentMove]);
5992 }
5993
5994 void
5995 SendBoard(cps, moveNum)
5996      ChessProgramState *cps;
5997      int moveNum;
5998 {
5999     char message[MSG_SIZ];
6000
6001     if (cps->useSetboard) {
6002       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6003       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6004       SendToProgram(message, cps);
6005       free(fen);
6006
6007     } else {
6008       ChessSquare *bp;
6009       int i, j;
6010       /* Kludge to set black to move, avoiding the troublesome and now
6011        * deprecated "black" command.
6012        */
6013       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6014         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6015
6016       SendToProgram("edit\n", cps);
6017       SendToProgram("#\n", cps);
6018       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6019         bp = &boards[moveNum][i][BOARD_LEFT];
6020         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6021           if ((int) *bp < (int) BlackPawn) {
6022             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6023                     AAA + j, ONE + i);
6024             if(message[0] == '+' || message[0] == '~') {
6025               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6026                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6027                         AAA + j, ONE + i);
6028             }
6029             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6030                 message[1] = BOARD_RGHT   - 1 - j + '1';
6031                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6032             }
6033             SendToProgram(message, cps);
6034           }
6035         }
6036       }
6037
6038       SendToProgram("c\n", cps);
6039       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6040         bp = &boards[moveNum][i][BOARD_LEFT];
6041         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6042           if (((int) *bp != (int) EmptySquare)
6043               && ((int) *bp >= (int) BlackPawn)) {
6044             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6045                     AAA + j, ONE + i);
6046             if(message[0] == '+' || message[0] == '~') {
6047               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6048                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6049                         AAA + j, ONE + i);
6050             }
6051             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6052                 message[1] = BOARD_RGHT   - 1 - j + '1';
6053                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6054             }
6055             SendToProgram(message, cps);
6056           }
6057         }
6058       }
6059
6060       SendToProgram(".\n", cps);
6061     }
6062     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6063 }
6064
6065 ChessSquare
6066 DefaultPromoChoice(int white)
6067 {
6068     ChessSquare result;
6069     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6070         result = WhiteFerz; // no choice
6071     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6072         result= WhiteKing; // in Suicide Q is the last thing we want
6073     else if(gameInfo.variant == VariantSpartan)
6074         result = white ? WhiteQueen : WhiteAngel;
6075     else result = WhiteQueen;
6076     if(!white) result = WHITE_TO_BLACK result;
6077     return result;
6078 }
6079
6080 static int autoQueen; // [HGM] oneclick
6081
6082 int
6083 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6084 {
6085     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6086     /* [HGM] add Shogi promotions */
6087     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6088     ChessSquare piece;
6089     ChessMove moveType;
6090     Boolean premove;
6091
6092     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6093     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6094
6095     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6096       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6097         return FALSE;
6098
6099     piece = boards[currentMove][fromY][fromX];
6100     if(gameInfo.variant == VariantShogi) {
6101         promotionZoneSize = BOARD_HEIGHT/3;
6102         highestPromotingPiece = (int)WhiteFerz;
6103     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6104         promotionZoneSize = 3;
6105     }
6106
6107     // Treat Lance as Pawn when it is not representing Amazon
6108     if(gameInfo.variant != VariantSuper) {
6109         if(piece == WhiteLance) piece = WhitePawn; else
6110         if(piece == BlackLance) piece = BlackPawn;
6111     }
6112
6113     // next weed out all moves that do not touch the promotion zone at all
6114     if((int)piece >= BlackPawn) {
6115         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6116              return FALSE;
6117         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6118     } else {
6119         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6120            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6121     }
6122
6123     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6124
6125     // weed out mandatory Shogi promotions
6126     if(gameInfo.variant == VariantShogi) {
6127         if(piece >= BlackPawn) {
6128             if(toY == 0 && piece == BlackPawn ||
6129                toY == 0 && piece == BlackQueen ||
6130                toY <= 1 && piece == BlackKnight) {
6131                 *promoChoice = '+';
6132                 return FALSE;
6133             }
6134         } else {
6135             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6136                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6137                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6138                 *promoChoice = '+';
6139                 return FALSE;
6140             }
6141         }
6142     }
6143
6144     // weed out obviously illegal Pawn moves
6145     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6146         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6147         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6148         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6149         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6150         // note we are not allowed to test for valid (non-)capture, due to premove
6151     }
6152
6153     // we either have a choice what to promote to, or (in Shogi) whether to promote
6154     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6155         *promoChoice = PieceToChar(BlackFerz);  // no choice
6156         return FALSE;
6157     }
6158     // no sense asking what we must promote to if it is going to explode...
6159     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6160         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6161         return FALSE;
6162     }
6163     // give caller the default choice even if we will not make it
6164     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6165     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6166     if(        sweepSelect && gameInfo.variant != VariantGreat
6167                            && gameInfo.variant != VariantGrand
6168                            && gameInfo.variant != VariantSuper) return FALSE;
6169     if(autoQueen) return FALSE; // predetermined
6170
6171     // suppress promotion popup on illegal moves that are not premoves
6172     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6173               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6174     if(appData.testLegality && !premove) {
6175         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6176                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6177         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6178             return FALSE;
6179     }
6180
6181     return TRUE;
6182 }
6183
6184 int
6185 InPalace(row, column)
6186      int row, column;
6187 {   /* [HGM] for Xiangqi */
6188     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6189          column < (BOARD_WIDTH + 4)/2 &&
6190          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6191     return FALSE;
6192 }
6193
6194 int
6195 PieceForSquare (x, y)
6196      int x;
6197      int y;
6198 {
6199   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6200      return -1;
6201   else
6202      return boards[currentMove][y][x];
6203 }
6204
6205 int
6206 OKToStartUserMove(x, y)
6207      int x, y;
6208 {
6209     ChessSquare from_piece;
6210     int white_piece;
6211
6212     if (matchMode) return FALSE;
6213     if (gameMode == EditPosition) return TRUE;
6214
6215     if (x >= 0 && y >= 0)
6216       from_piece = boards[currentMove][y][x];
6217     else
6218       from_piece = EmptySquare;
6219
6220     if (from_piece == EmptySquare) return FALSE;
6221
6222     white_piece = (int)from_piece >= (int)WhitePawn &&
6223       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6224
6225     switch (gameMode) {
6226       case PlayFromGameFile:
6227       case AnalyzeFile:
6228       case TwoMachinesPlay:
6229       case EndOfGame:
6230         return FALSE;
6231
6232       case IcsObserving:
6233       case IcsIdle:
6234         return FALSE;
6235
6236       case MachinePlaysWhite:
6237       case IcsPlayingBlack:
6238         if (appData.zippyPlay) return FALSE;
6239         if (white_piece) {
6240             DisplayMoveError(_("You are playing Black"));
6241             return FALSE;
6242         }
6243         break;
6244
6245       case MachinePlaysBlack:
6246       case IcsPlayingWhite:
6247         if (appData.zippyPlay) return FALSE;
6248         if (!white_piece) {
6249             DisplayMoveError(_("You are playing White"));
6250             return FALSE;
6251         }
6252         break;
6253
6254       case EditGame:
6255         if (!white_piece && WhiteOnMove(currentMove)) {
6256             DisplayMoveError(_("It is White's turn"));
6257             return FALSE;
6258         }
6259         if (white_piece && !WhiteOnMove(currentMove)) {
6260             DisplayMoveError(_("It is Black's turn"));
6261             return FALSE;
6262         }
6263         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6264             /* Editing correspondence game history */
6265             /* Could disallow this or prompt for confirmation */
6266             cmailOldMove = -1;
6267         }
6268         break;
6269
6270       case BeginningOfGame:
6271         if (appData.icsActive) return FALSE;
6272         if (!appData.noChessProgram) {
6273             if (!white_piece) {
6274                 DisplayMoveError(_("You are playing White"));
6275                 return FALSE;
6276             }
6277         }
6278         break;
6279
6280       case Training:
6281         if (!white_piece && WhiteOnMove(currentMove)) {
6282             DisplayMoveError(_("It is White's turn"));
6283             return FALSE;
6284         }
6285         if (white_piece && !WhiteOnMove(currentMove)) {
6286             DisplayMoveError(_("It is Black's turn"));
6287             return FALSE;
6288         }
6289         break;
6290
6291       default:
6292       case IcsExamining:
6293         break;
6294     }
6295     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6296         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6297         && gameMode != AnalyzeFile && gameMode != Training) {
6298         DisplayMoveError(_("Displayed position is not current"));
6299         return FALSE;
6300     }
6301     return TRUE;
6302 }
6303
6304 Boolean
6305 OnlyMove(int *x, int *y, Boolean captures) {
6306     DisambiguateClosure cl;
6307     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6308     switch(gameMode) {
6309       case MachinePlaysBlack:
6310       case IcsPlayingWhite:
6311       case BeginningOfGame:
6312         if(!WhiteOnMove(currentMove)) return FALSE;
6313         break;
6314       case MachinePlaysWhite:
6315       case IcsPlayingBlack:
6316         if(WhiteOnMove(currentMove)) return FALSE;
6317         break;
6318       case EditGame:
6319         break;
6320       default:
6321         return FALSE;
6322     }
6323     cl.pieceIn = EmptySquare;
6324     cl.rfIn = *y;
6325     cl.ffIn = *x;
6326     cl.rtIn = -1;
6327     cl.ftIn = -1;
6328     cl.promoCharIn = NULLCHAR;
6329     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6330     if( cl.kind == NormalMove ||
6331         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6332         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6333         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6334       fromX = cl.ff;
6335       fromY = cl.rf;
6336       *x = cl.ft;
6337       *y = cl.rt;
6338       return TRUE;
6339     }
6340     if(cl.kind != ImpossibleMove) return FALSE;
6341     cl.pieceIn = EmptySquare;
6342     cl.rfIn = -1;
6343     cl.ffIn = -1;
6344     cl.rtIn = *y;
6345     cl.ftIn = *x;
6346     cl.promoCharIn = NULLCHAR;
6347     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6348     if( cl.kind == NormalMove ||
6349         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6350         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6351         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6352       fromX = cl.ff;
6353       fromY = cl.rf;
6354       *x = cl.ft;
6355       *y = cl.rt;
6356       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6357       return TRUE;
6358     }
6359     return FALSE;
6360 }
6361
6362 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6363 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6364 int lastLoadGameUseList = FALSE;
6365 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6366 ChessMove lastLoadGameStart = EndOfFile;
6367
6368 void
6369 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6370      int fromX, fromY, toX, toY;
6371      int promoChar;
6372 {
6373     ChessMove moveType;
6374     ChessSquare pdown, pup;
6375
6376     /* Check if the user is playing in turn.  This is complicated because we
6377        let the user "pick up" a piece before it is his turn.  So the piece he
6378        tried to pick up may have been captured by the time he puts it down!
6379        Therefore we use the color the user is supposed to be playing in this
6380        test, not the color of the piece that is currently on the starting
6381        square---except in EditGame mode, where the user is playing both
6382        sides; fortunately there the capture race can't happen.  (It can
6383        now happen in IcsExamining mode, but that's just too bad.  The user
6384        will get a somewhat confusing message in that case.)
6385        */
6386
6387     switch (gameMode) {
6388       case PlayFromGameFile:
6389       case AnalyzeFile:
6390       case TwoMachinesPlay:
6391       case EndOfGame:
6392       case IcsObserving:
6393       case IcsIdle:
6394         /* We switched into a game mode where moves are not accepted,
6395            perhaps while the mouse button was down. */
6396         return;
6397
6398       case MachinePlaysWhite:
6399         /* User is moving for Black */
6400         if (WhiteOnMove(currentMove)) {
6401             DisplayMoveError(_("It is White's turn"));
6402             return;
6403         }
6404         break;
6405
6406       case MachinePlaysBlack:
6407         /* User is moving for White */
6408         if (!WhiteOnMove(currentMove)) {
6409             DisplayMoveError(_("It is Black's turn"));
6410             return;
6411         }
6412         break;
6413
6414       case EditGame:
6415       case IcsExamining:
6416       case BeginningOfGame:
6417       case AnalyzeMode:
6418       case Training:
6419         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6420         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6421             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6422             /* User is moving for Black */
6423             if (WhiteOnMove(currentMove)) {
6424                 DisplayMoveError(_("It is White's turn"));
6425                 return;
6426             }
6427         } else {
6428             /* User is moving for White */
6429             if (!WhiteOnMove(currentMove)) {
6430                 DisplayMoveError(_("It is Black's turn"));
6431                 return;
6432             }
6433         }
6434         break;
6435
6436       case IcsPlayingBlack:
6437         /* User is moving for Black */
6438         if (WhiteOnMove(currentMove)) {
6439             if (!appData.premove) {
6440                 DisplayMoveError(_("It is White's turn"));
6441             } else if (toX >= 0 && toY >= 0) {
6442                 premoveToX = toX;
6443                 premoveToY = toY;
6444                 premoveFromX = fromX;
6445                 premoveFromY = fromY;
6446                 premovePromoChar = promoChar;
6447                 gotPremove = 1;
6448                 if (appData.debugMode)
6449                     fprintf(debugFP, "Got premove: fromX %d,"
6450                             "fromY %d, toX %d, toY %d\n",
6451                             fromX, fromY, toX, toY);
6452             }
6453             return;
6454         }
6455         break;
6456
6457       case IcsPlayingWhite:
6458         /* User is moving for White */
6459         if (!WhiteOnMove(currentMove)) {
6460             if (!appData.premove) {
6461                 DisplayMoveError(_("It is Black's turn"));
6462             } else if (toX >= 0 && toY >= 0) {
6463                 premoveToX = toX;
6464                 premoveToY = toY;
6465                 premoveFromX = fromX;
6466                 premoveFromY = fromY;
6467                 premovePromoChar = promoChar;
6468                 gotPremove = 1;
6469                 if (appData.debugMode)
6470                     fprintf(debugFP, "Got premove: fromX %d,"
6471                             "fromY %d, toX %d, toY %d\n",
6472                             fromX, fromY, toX, toY);
6473             }
6474             return;
6475         }
6476         break;
6477
6478       default:
6479         break;
6480
6481       case EditPosition:
6482         /* EditPosition, empty square, or different color piece;
6483            click-click move is possible */
6484         if (toX == -2 || toY == -2) {
6485             boards[0][fromY][fromX] = EmptySquare;
6486             DrawPosition(FALSE, boards[currentMove]);
6487             return;
6488         } else if (toX >= 0 && toY >= 0) {
6489             boards[0][toY][toX] = boards[0][fromY][fromX];
6490             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6491                 if(boards[0][fromY][0] != EmptySquare) {
6492                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6493                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6494                 }
6495             } else
6496             if(fromX == BOARD_RGHT+1) {
6497                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6498                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6499                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6500                 }
6501             } else
6502             boards[0][fromY][fromX] = EmptySquare;
6503             DrawPosition(FALSE, boards[currentMove]);
6504             return;
6505         }
6506         return;
6507     }
6508
6509     if(toX < 0 || toY < 0) return;
6510     pdown = boards[currentMove][fromY][fromX];
6511     pup = boards[currentMove][toY][toX];
6512
6513     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6514     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6515          if( pup != EmptySquare ) return;
6516          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6517            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6518                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6519            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6520            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6521            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6522            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6523          fromY = DROP_RANK;
6524     }
6525
6526     /* [HGM] always test for legality, to get promotion info */
6527     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6528                                          fromY, fromX, toY, toX, promoChar);
6529     /* [HGM] but possibly ignore an IllegalMove result */
6530     if (appData.testLegality) {
6531         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6532             DisplayMoveError(_("Illegal move"));
6533             return;
6534         }
6535     }
6536
6537     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6538 }
6539
6540 /* Common tail of UserMoveEvent and DropMenuEvent */
6541 int
6542 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6543      ChessMove moveType;
6544      int fromX, fromY, toX, toY;
6545      /*char*/int promoChar;
6546 {
6547     char *bookHit = 0;
6548
6549     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6550         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6551         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6552         if(WhiteOnMove(currentMove)) {
6553             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6554         } else {
6555             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6556         }
6557     }
6558
6559     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6560        move type in caller when we know the move is a legal promotion */
6561     if(moveType == NormalMove && promoChar)
6562         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6563
6564     /* [HGM] <popupFix> The following if has been moved here from
6565        UserMoveEvent(). Because it seemed to belong here (why not allow
6566        piece drops in training games?), and because it can only be
6567        performed after it is known to what we promote. */
6568     if (gameMode == Training) {
6569       /* compare the move played on the board to the next move in the
6570        * game. If they match, display the move and the opponent's response.
6571        * If they don't match, display an error message.
6572        */
6573       int saveAnimate;
6574       Board testBoard;
6575       CopyBoard(testBoard, boards[currentMove]);
6576       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6577
6578       if (CompareBoards(testBoard, boards[currentMove+1])) {
6579         ForwardInner(currentMove+1);
6580
6581         /* Autoplay the opponent's response.
6582          * if appData.animate was TRUE when Training mode was entered,
6583          * the response will be animated.
6584          */
6585         saveAnimate = appData.animate;
6586         appData.animate = animateTraining;
6587         ForwardInner(currentMove+1);
6588         appData.animate = saveAnimate;
6589
6590         /* check for the end of the game */
6591         if (currentMove >= forwardMostMove) {
6592           gameMode = PlayFromGameFile;
6593           ModeHighlight();
6594           SetTrainingModeOff();
6595           DisplayInformation(_("End of game"));
6596         }
6597       } else {
6598         DisplayError(_("Incorrect move"), 0);
6599       }
6600       return 1;
6601     }
6602
6603   /* Ok, now we know that the move is good, so we can kill
6604      the previous line in Analysis Mode */
6605   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6606                                 && currentMove < forwardMostMove) {
6607     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6608     else forwardMostMove = currentMove;
6609   }
6610
6611   /* If we need the chess program but it's dead, restart it */
6612   ResurrectChessProgram();
6613
6614   /* A user move restarts a paused game*/
6615   if (pausing)
6616     PauseEvent();
6617
6618   thinkOutput[0] = NULLCHAR;
6619
6620   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6621
6622   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6623     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6624     return 1;
6625   }
6626
6627   if (gameMode == BeginningOfGame) {
6628     if (appData.noChessProgram) {
6629       gameMode = EditGame;
6630       SetGameInfo();
6631     } else {
6632       char buf[MSG_SIZ];
6633       gameMode = MachinePlaysBlack;
6634       StartClocks();
6635       SetGameInfo();
6636       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6637       DisplayTitle(buf);
6638       if (first.sendName) {
6639         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6640         SendToProgram(buf, &first);
6641       }
6642       StartClocks();
6643     }
6644     ModeHighlight();
6645   }
6646
6647   /* Relay move to ICS or chess engine */
6648   if (appData.icsActive) {
6649     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6650         gameMode == IcsExamining) {
6651       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6652         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6653         SendToICS("draw ");
6654         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6655       }
6656       // also send plain move, in case ICS does not understand atomic claims
6657       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6658       ics_user_moved = 1;
6659     }
6660   } else {
6661     if (first.sendTime && (gameMode == BeginningOfGame ||
6662                            gameMode == MachinePlaysWhite ||
6663                            gameMode == MachinePlaysBlack)) {
6664       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6665     }
6666     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6667          // [HGM] book: if program might be playing, let it use book
6668         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6669         first.maybeThinking = TRUE;
6670     } else SendMoveToProgram(forwardMostMove-1, &first);
6671     if (currentMove == cmailOldMove + 1) {
6672       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6673     }
6674   }
6675
6676   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6677
6678   switch (gameMode) {
6679   case EditGame:
6680     if(appData.testLegality)
6681     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6682     case MT_NONE:
6683     case MT_CHECK:
6684       break;
6685     case MT_CHECKMATE:
6686     case MT_STAINMATE:
6687       if (WhiteOnMove(currentMove)) {
6688         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6689       } else {
6690         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6691       }
6692       break;
6693     case MT_STALEMATE:
6694       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6695       break;
6696     }
6697     break;
6698
6699   case MachinePlaysBlack:
6700   case MachinePlaysWhite:
6701     /* disable certain menu options while machine is thinking */
6702     SetMachineThinkingEnables();
6703     break;
6704
6705   default:
6706     break;
6707   }
6708
6709   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6710   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6711
6712   if(bookHit) { // [HGM] book: simulate book reply
6713         static char bookMove[MSG_SIZ]; // a bit generous?
6714
6715         programStats.nodes = programStats.depth = programStats.time =
6716         programStats.score = programStats.got_only_move = 0;
6717         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6718
6719         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6720         strcat(bookMove, bookHit);
6721         HandleMachineMove(bookMove, &first);
6722   }
6723   return 1;
6724 }
6725
6726 void
6727 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6728      Board board;
6729      int flags;
6730      ChessMove kind;
6731      int rf, ff, rt, ft;
6732      VOIDSTAR closure;
6733 {
6734     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6735     Markers *m = (Markers *) closure;
6736     if(rf == fromY && ff == fromX)
6737         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6738                          || kind == WhiteCapturesEnPassant
6739                          || kind == BlackCapturesEnPassant);
6740     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6741 }
6742
6743 void
6744 MarkTargetSquares(int clear)
6745 {
6746   int x, y;
6747   if(!appData.markers || !appData.highlightDragging ||
6748      !appData.testLegality || gameMode == EditPosition) return;
6749   if(clear) {
6750     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6751   } else {
6752     int capt = 0;
6753     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6754     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6755       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6756       if(capt)
6757       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6758     }
6759   }
6760   DrawPosition(TRUE, NULL);
6761 }
6762
6763 int
6764 Explode(Board board, int fromX, int fromY, int toX, int toY)
6765 {
6766     if(gameInfo.variant == VariantAtomic &&
6767        (board[toY][toX] != EmptySquare ||                     // capture?
6768         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6769                          board[fromY][fromX] == BlackPawn   )
6770       )) {
6771         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6772         return TRUE;
6773     }
6774     return FALSE;
6775 }
6776
6777 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6778
6779 int CanPromote(ChessSquare piece, int y)
6780 {
6781         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6782         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6783         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6784            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6785            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6786                                                   gameInfo.variant == VariantMakruk) return FALSE;
6787         return (piece == BlackPawn && y == 1 ||
6788                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6789                 piece == BlackLance && y == 1 ||
6790                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6791 }
6792
6793 void LeftClick(ClickType clickType, int xPix, int yPix)
6794 {
6795     int x, y;
6796     Boolean saveAnimate;
6797     static int second = 0, promotionChoice = 0, clearFlag = 0;
6798     char promoChoice = NULLCHAR;
6799     ChessSquare piece;
6800
6801     if(appData.seekGraph && appData.icsActive && loggedOn &&
6802         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6803         SeekGraphClick(clickType, xPix, yPix, 0);
6804         return;
6805     }
6806
6807     if (clickType == Press) ErrorPopDown();
6808
6809     x = EventToSquare(xPix, BOARD_WIDTH);
6810     y = EventToSquare(yPix, BOARD_HEIGHT);
6811     if (!flipView && y >= 0) {
6812         y = BOARD_HEIGHT - 1 - y;
6813     }
6814     if (flipView && x >= 0) {
6815         x = BOARD_WIDTH - 1 - x;
6816     }
6817
6818     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6819         defaultPromoChoice = promoSweep;
6820         promoSweep = EmptySquare;   // terminate sweep
6821         promoDefaultAltered = TRUE;
6822         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6823     }
6824
6825     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6826         if(clickType == Release) return; // ignore upclick of click-click destination
6827         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6828         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6829         if(gameInfo.holdingsWidth &&
6830                 (WhiteOnMove(currentMove)
6831                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6832                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6833             // click in right holdings, for determining promotion piece
6834             ChessSquare p = boards[currentMove][y][x];
6835             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6836             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6837             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6838                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6839                 fromX = fromY = -1;
6840                 return;
6841             }
6842         }
6843         DrawPosition(FALSE, boards[currentMove]);
6844         return;
6845     }
6846
6847     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6848     if(clickType == Press
6849             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6850               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6851               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6852         return;
6853
6854     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6855         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6856
6857     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6858         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6859                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6860         defaultPromoChoice = DefaultPromoChoice(side);
6861     }
6862
6863     autoQueen = appData.alwaysPromoteToQueen;
6864
6865     if (fromX == -1) {
6866       int originalY = y;
6867       gatingPiece = EmptySquare;
6868       if (clickType != Press) {
6869         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6870             DragPieceEnd(xPix, yPix); dragging = 0;
6871             DrawPosition(FALSE, NULL);
6872         }
6873         return;
6874       }
6875       fromX = x; fromY = y; toX = toY = -1;
6876       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6877          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6878          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6879             /* First square */
6880             if (OKToStartUserMove(fromX, fromY)) {
6881                 second = 0;
6882                 MarkTargetSquares(0);
6883                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6884                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6885                     promoSweep = defaultPromoChoice;
6886                     selectFlag = 0; lastX = xPix; lastY = yPix;
6887                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6888                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6889                 }
6890                 if (appData.highlightDragging) {
6891                     SetHighlights(fromX, fromY, -1, -1);
6892                 }
6893             } else fromX = fromY = -1;
6894             return;
6895         }
6896     }
6897
6898     /* fromX != -1 */
6899     if (clickType == Press && gameMode != EditPosition) {
6900         ChessSquare fromP;
6901         ChessSquare toP;
6902         int frc;
6903
6904         // ignore off-board to clicks
6905         if(y < 0 || x < 0) return;
6906
6907         /* Check if clicking again on the same color piece */
6908         fromP = boards[currentMove][fromY][fromX];
6909         toP = boards[currentMove][y][x];
6910         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6911         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6912              WhitePawn <= toP && toP <= WhiteKing &&
6913              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6914              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6915             (BlackPawn <= fromP && fromP <= BlackKing &&
6916              BlackPawn <= toP && toP <= BlackKing &&
6917              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6918              !(fromP == BlackKing && toP == BlackRook && frc))) {
6919             /* Clicked again on same color piece -- changed his mind */
6920             second = (x == fromX && y == fromY);
6921             promoDefaultAltered = FALSE;
6922             MarkTargetSquares(1);
6923            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6924             if (appData.highlightDragging) {
6925                 SetHighlights(x, y, -1, -1);
6926             } else {
6927                 ClearHighlights();
6928             }
6929             if (OKToStartUserMove(x, y)) {
6930                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6931                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6932                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6933                  gatingPiece = boards[currentMove][fromY][fromX];
6934                 else gatingPiece = EmptySquare;
6935                 fromX = x;
6936                 fromY = y; dragging = 1;
6937                 MarkTargetSquares(0);
6938                 DragPieceBegin(xPix, yPix, FALSE);
6939                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6940                     promoSweep = defaultPromoChoice;
6941                     selectFlag = 0; lastX = xPix; lastY = yPix;
6942                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6943                 }
6944             }
6945            }
6946            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6947            second = FALSE; 
6948         }
6949         // ignore clicks on holdings
6950         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6951     }
6952
6953     if (clickType == Release && x == fromX && y == fromY) {
6954         DragPieceEnd(xPix, yPix); dragging = 0;
6955         if(clearFlag) {
6956             // a deferred attempt to click-click move an empty square on top of a piece
6957             boards[currentMove][y][x] = EmptySquare;
6958             ClearHighlights();
6959             DrawPosition(FALSE, boards[currentMove]);
6960             fromX = fromY = -1; clearFlag = 0;
6961             return;
6962         }
6963         if (appData.animateDragging) {
6964             /* Undo animation damage if any */
6965             DrawPosition(FALSE, NULL);
6966         }
6967         if (second) {
6968             /* Second up/down in same square; just abort move */
6969             second = 0;
6970             fromX = fromY = -1;
6971             gatingPiece = EmptySquare;
6972             ClearHighlights();
6973             gotPremove = 0;
6974             ClearPremoveHighlights();
6975         } else {
6976             /* First upclick in same square; start click-click mode */
6977             SetHighlights(x, y, -1, -1);
6978         }
6979         return;
6980     }
6981
6982     clearFlag = 0;
6983
6984     /* we now have a different from- and (possibly off-board) to-square */
6985     /* Completed move */
6986     toX = x;
6987     toY = y;
6988     saveAnimate = appData.animate;
6989     MarkTargetSquares(1);
6990     if (clickType == Press) {
6991         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6992             // must be Edit Position mode with empty-square selected
6993             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
6994             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6995             return;
6996         }
6997         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
6998             ChessSquare piece = boards[currentMove][fromY][fromX];
6999             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7000             promoSweep = defaultPromoChoice;
7001             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7002             selectFlag = 0; lastX = xPix; lastY = yPix;
7003             Sweep(0); // Pawn that is going to promote: preview promotion piece
7004             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7005             DrawPosition(FALSE, boards[currentMove]);
7006             return;
7007         }
7008         /* Finish clickclick move */
7009         if (appData.animate || appData.highlightLastMove) {
7010             SetHighlights(fromX, fromY, toX, toY);
7011         } else {
7012             ClearHighlights();
7013         }
7014     } else {
7015         /* Finish drag move */
7016         if (appData.highlightLastMove) {
7017             SetHighlights(fromX, fromY, toX, toY);
7018         } else {
7019             ClearHighlights();
7020         }
7021         DragPieceEnd(xPix, yPix); dragging = 0;
7022         /* Don't animate move and drag both */
7023         appData.animate = FALSE;
7024     }
7025
7026     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7027     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7028         ChessSquare piece = boards[currentMove][fromY][fromX];
7029         if(gameMode == EditPosition && piece != EmptySquare &&
7030            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7031             int n;
7032
7033             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7034                 n = PieceToNumber(piece - (int)BlackPawn);
7035                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7036                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7037                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7038             } else
7039             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7040                 n = PieceToNumber(piece);
7041                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7042                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7043                 boards[currentMove][n][BOARD_WIDTH-2]++;
7044             }
7045             boards[currentMove][fromY][fromX] = EmptySquare;
7046         }
7047         ClearHighlights();
7048         fromX = fromY = -1;
7049         DrawPosition(TRUE, boards[currentMove]);
7050         return;
7051     }
7052
7053     // off-board moves should not be highlighted
7054     if(x < 0 || y < 0) ClearHighlights();
7055
7056     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7057
7058     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7059         SetHighlights(fromX, fromY, toX, toY);
7060         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7061             // [HGM] super: promotion to captured piece selected from holdings
7062             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7063             promotionChoice = TRUE;
7064             // kludge follows to temporarily execute move on display, without promoting yet
7065             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7066             boards[currentMove][toY][toX] = p;
7067             DrawPosition(FALSE, boards[currentMove]);
7068             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7069             boards[currentMove][toY][toX] = q;
7070             DisplayMessage("Click in holdings to choose piece", "");
7071             return;
7072         }
7073         PromotionPopUp();
7074     } else {
7075         int oldMove = currentMove;
7076         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7077         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7078         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7079         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7080            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7081             DrawPosition(TRUE, boards[currentMove]);
7082         fromX = fromY = -1;
7083     }
7084     appData.animate = saveAnimate;
7085     if (appData.animate || appData.animateDragging) {
7086         /* Undo animation damage if needed */
7087         DrawPosition(FALSE, NULL);
7088     }
7089 }
7090
7091 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7092 {   // front-end-free part taken out of PieceMenuPopup
7093     int whichMenu; int xSqr, ySqr;
7094
7095     if(seekGraphUp) { // [HGM] seekgraph
7096         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7097         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7098         return -2;
7099     }
7100
7101     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7102          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7103         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7104         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7105         if(action == Press)   {
7106             originalFlip = flipView;
7107             flipView = !flipView; // temporarily flip board to see game from partners perspective
7108             DrawPosition(TRUE, partnerBoard);
7109             DisplayMessage(partnerStatus, "");
7110             partnerUp = TRUE;
7111         } else if(action == Release) {
7112             flipView = originalFlip;
7113             DrawPosition(TRUE, boards[currentMove]);
7114             partnerUp = FALSE;
7115         }
7116         return -2;
7117     }
7118
7119     xSqr = EventToSquare(x, BOARD_WIDTH);
7120     ySqr = EventToSquare(y, BOARD_HEIGHT);
7121     if (action == Release) {
7122         if(pieceSweep != EmptySquare) {
7123             EditPositionMenuEvent(pieceSweep, toX, toY);
7124             pieceSweep = EmptySquare;
7125         } else UnLoadPV(); // [HGM] pv
7126     }
7127     if (action != Press) return -2; // return code to be ignored
7128     switch (gameMode) {
7129       case IcsExamining:
7130         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7131       case EditPosition:
7132         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7133         if (xSqr < 0 || ySqr < 0) return -1;
7134         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7135         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7136         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7137         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7138         NextPiece(0);
7139         return -2;
7140       case IcsObserving:
7141         if(!appData.icsEngineAnalyze) return -1;
7142       case IcsPlayingWhite:
7143       case IcsPlayingBlack:
7144         if(!appData.zippyPlay) goto noZip;
7145       case AnalyzeMode:
7146       case AnalyzeFile:
7147       case MachinePlaysWhite:
7148       case MachinePlaysBlack:
7149       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7150         if (!appData.dropMenu) {
7151           LoadPV(x, y);
7152           return 2; // flag front-end to grab mouse events
7153         }
7154         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7155            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7156       case EditGame:
7157       noZip:
7158         if (xSqr < 0 || ySqr < 0) return -1;
7159         if (!appData.dropMenu || appData.testLegality &&
7160             gameInfo.variant != VariantBughouse &&
7161             gameInfo.variant != VariantCrazyhouse) return -1;
7162         whichMenu = 1; // drop menu
7163         break;
7164       default:
7165         return -1;
7166     }
7167
7168     if (((*fromX = xSqr) < 0) ||
7169         ((*fromY = ySqr) < 0)) {
7170         *fromX = *fromY = -1;
7171         return -1;
7172     }
7173     if (flipView)
7174       *fromX = BOARD_WIDTH - 1 - *fromX;
7175     else
7176       *fromY = BOARD_HEIGHT - 1 - *fromY;
7177
7178     return whichMenu;
7179 }
7180
7181 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7182 {
7183 //    char * hint = lastHint;
7184     FrontEndProgramStats stats;
7185
7186     stats.which = cps == &first ? 0 : 1;
7187     stats.depth = cpstats->depth;
7188     stats.nodes = cpstats->nodes;
7189     stats.score = cpstats->score;
7190     stats.time = cpstats->time;
7191     stats.pv = cpstats->movelist;
7192     stats.hint = lastHint;
7193     stats.an_move_index = 0;
7194     stats.an_move_count = 0;
7195
7196     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7197         stats.hint = cpstats->move_name;
7198         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7199         stats.an_move_count = cpstats->nr_moves;
7200     }
7201
7202     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
7203
7204     SetProgramStats( &stats );
7205 }
7206
7207 void
7208 ClearEngineOutputPane(int which)
7209 {
7210     static FrontEndProgramStats dummyStats;
7211     dummyStats.which = which;
7212     dummyStats.pv = "#";
7213     SetProgramStats( &dummyStats );
7214 }
7215
7216 #define MAXPLAYERS 500
7217
7218 char *
7219 TourneyStandings(int display)
7220 {
7221     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7222     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7223     char result, *p, *names[MAXPLAYERS];
7224
7225     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7226         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7227     names[0] = p = strdup(appData.participants);
7228     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7229
7230     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7231
7232     while(result = appData.results[nr]) {
7233         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7234         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7235         wScore = bScore = 0;
7236         switch(result) {
7237           case '+': wScore = 2; break;
7238           case '-': bScore = 2; break;
7239           case '=': wScore = bScore = 1; break;
7240           case ' ':
7241           case '*': return strdup("busy"); // tourney not finished
7242         }
7243         score[w] += wScore;
7244         score[b] += bScore;
7245         games[w]++;
7246         games[b]++;
7247         nr++;
7248     }
7249     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7250     for(w=0; w<nPlayers; w++) {
7251         bScore = -1;
7252         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7253         ranking[w] = b; points[w] = bScore; score[b] = -2;
7254     }
7255     p = malloc(nPlayers*34+1);
7256     for(w=0; w<nPlayers && w<display; w++)
7257         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7258     free(names[0]);
7259     return p;
7260 }
7261
7262 void
7263 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7264 {       // count all piece types
7265         int p, f, r;
7266         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7267         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7268         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7269                 p = board[r][f];
7270                 pCnt[p]++;
7271                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7272                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7273                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7274                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7275                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7276                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7277         }
7278 }
7279
7280 int
7281 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7282 {
7283         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7284         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7285
7286         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7287         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7288         if(myPawns == 2 && nMine == 3) // KPP
7289             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7290         if(myPawns == 1 && nMine == 2) // KP
7291             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7292         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7293             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7294         if(myPawns) return FALSE;
7295         if(pCnt[WhiteRook+side])
7296             return pCnt[BlackRook-side] ||
7297                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7298                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7299                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7300         if(pCnt[WhiteCannon+side]) {
7301             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7302             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7303         }
7304         if(pCnt[WhiteKnight+side])
7305             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7306         return FALSE;
7307 }
7308
7309 int
7310 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7311 {
7312         VariantClass v = gameInfo.variant;
7313
7314         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7315         if(v == VariantShatranj) return TRUE; // always winnable through baring
7316         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7317         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7318
7319         if(v == VariantXiangqi) {
7320                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7321
7322                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7323                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7324                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7325                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7326                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7327                 if(stale) // we have at least one last-rank P plus perhaps C
7328                     return majors // KPKX
7329                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7330                 else // KCA*E*
7331                     return pCnt[WhiteFerz+side] // KCAK
7332                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7333                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7334                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7335
7336         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7337                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7338
7339                 if(nMine == 1) return FALSE; // bare King
7340                 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
7341                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7342                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7343                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7344                 if(pCnt[WhiteKnight+side])
7345                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7346                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7347                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7348                 if(nBishops)
7349                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7350                 if(pCnt[WhiteAlfil+side])
7351                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7352                 if(pCnt[WhiteWazir+side])
7353                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7354         }
7355
7356         return TRUE;
7357 }
7358
7359 int
7360 Adjudicate(ChessProgramState *cps)
7361 {       // [HGM] some adjudications useful with buggy engines
7362         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7363         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7364         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7365         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7366         int k, count = 0; static int bare = 1;
7367         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7368         Boolean canAdjudicate = !appData.icsActive;
7369
7370         // most tests only when we understand the game, i.e. legality-checking on
7371             if( appData.testLegality )
7372             {   /* [HGM] Some more adjudications for obstinate engines */
7373                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7374                 static int moveCount = 6;
7375                 ChessMove result;
7376                 char *reason = NULL;
7377
7378                 /* Count what is on board. */
7379                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7380
7381                 /* Some material-based adjudications that have to be made before stalemate test */
7382                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7383                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7384                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7385                      if(canAdjudicate && appData.checkMates) {
7386                          if(engineOpponent)
7387                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7388                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7389                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7390                          return 1;
7391                      }
7392                 }
7393
7394                 /* Bare King in Shatranj (loses) or Losers (wins) */
7395                 if( nrW == 1 || nrB == 1) {
7396                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7397                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7398                      if(canAdjudicate && appData.checkMates) {
7399                          if(engineOpponent)
7400                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7401                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7402                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7403                          return 1;
7404                      }
7405                   } else
7406                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7407                   {    /* bare King */
7408                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7409                         if(canAdjudicate && appData.checkMates) {
7410                             /* but only adjudicate if adjudication enabled */
7411                             if(engineOpponent)
7412                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7413                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7414                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7415                             return 1;
7416                         }
7417                   }
7418                 } else bare = 1;
7419
7420
7421             // don't wait for engine to announce game end if we can judge ourselves
7422             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7423               case MT_CHECK:
7424                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7425                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7426                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7427                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7428                             checkCnt++;
7429                         if(checkCnt >= 2) {
7430                             reason = "Xboard adjudication: 3rd check";
7431                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7432                             break;
7433                         }
7434                     }
7435                 }
7436               case MT_NONE:
7437               default:
7438                 break;
7439               case MT_STALEMATE:
7440               case MT_STAINMATE:
7441                 reason = "Xboard adjudication: Stalemate";
7442                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7443                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7444                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7445                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7446                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7447                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7448                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7449                                                                         EP_CHECKMATE : EP_WINS);
7450                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7451                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7452                 }
7453                 break;
7454               case MT_CHECKMATE:
7455                 reason = "Xboard adjudication: Checkmate";
7456                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7457                 break;
7458             }
7459
7460                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7461                     case EP_STALEMATE:
7462                         result = GameIsDrawn; break;
7463                     case EP_CHECKMATE:
7464                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7465                     case EP_WINS:
7466                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7467                     default:
7468                         result = EndOfFile;
7469                 }
7470                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7471                     if(engineOpponent)
7472                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7473                     GameEnds( result, reason, GE_XBOARD );
7474                     return 1;
7475                 }
7476
7477                 /* Next absolutely insufficient mating material. */
7478                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7479                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7480                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7481
7482                      /* always flag draws, for judging claims */
7483                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7484
7485                      if(canAdjudicate && appData.materialDraws) {
7486                          /* but only adjudicate them if adjudication enabled */
7487                          if(engineOpponent) {
7488                            SendToProgram("force\n", engineOpponent); // suppress reply
7489                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7490                          }
7491                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7492                          return 1;
7493                      }
7494                 }
7495
7496                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7497                 if(gameInfo.variant == VariantXiangqi ?
7498                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7499                  : nrW + nrB == 4 &&
7500                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7501                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7502                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7503                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7504                    ) ) {
7505                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7506                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7507                           if(engineOpponent) {
7508                             SendToProgram("force\n", engineOpponent); // suppress reply
7509                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7510                           }
7511                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7512                           return 1;
7513                      }
7514                 } else moveCount = 6;
7515             }
7516         if (appData.debugMode) { int i;
7517             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7518                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7519                     appData.drawRepeats);
7520             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7521               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7522
7523         }
7524
7525         // Repetition draws and 50-move rule can be applied independently of legality testing
7526
7527                 /* Check for rep-draws */
7528                 count = 0;
7529                 for(k = forwardMostMove-2;
7530                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7531                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7532                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7533                     k-=2)
7534                 {   int rights=0;
7535                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7536                         /* compare castling rights */
7537                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7538                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7539                                 rights++; /* King lost rights, while rook still had them */
7540                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7541                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7542                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7543                                    rights++; /* but at least one rook lost them */
7544                         }
7545                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7546                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7547                                 rights++;
7548                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7549                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7550                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7551                                    rights++;
7552                         }
7553                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7554                             && appData.drawRepeats > 1) {
7555                              /* adjudicate after user-specified nr of repeats */
7556                              int result = GameIsDrawn;
7557                              char *details = "XBoard adjudication: repetition draw";
7558                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7559                                 // [HGM] xiangqi: check for forbidden perpetuals
7560                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7561                                 for(m=forwardMostMove; m>k; m-=2) {
7562                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7563                                         ourPerpetual = 0; // the current mover did not always check
7564                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7565                                         hisPerpetual = 0; // the opponent did not always check
7566                                 }
7567                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7568                                                                         ourPerpetual, hisPerpetual);
7569                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7570                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7571                                     details = "Xboard adjudication: perpetual checking";
7572                                 } else
7573                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7574                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7575                                 } else
7576                                 // Now check for perpetual chases
7577                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7578                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7579                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7580                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7581                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7582                                         details = "Xboard adjudication: perpetual chasing";
7583                                     } else
7584                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7585                                         break; // Abort repetition-checking loop.
7586                                 }
7587                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7588                              }
7589                              if(engineOpponent) {
7590                                SendToProgram("force\n", engineOpponent); // suppress reply
7591                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7592                              }
7593                              GameEnds( result, details, GE_XBOARD );
7594                              return 1;
7595                         }
7596                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7597                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7598                     }
7599                 }
7600
7601                 /* Now we test for 50-move draws. Determine ply count */
7602                 count = forwardMostMove;
7603                 /* look for last irreversble move */
7604                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7605                     count--;
7606                 /* if we hit starting position, add initial plies */
7607                 if( count == backwardMostMove )
7608                     count -= initialRulePlies;
7609                 count = forwardMostMove - count;
7610                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7611                         // adjust reversible move counter for checks in Xiangqi
7612                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7613                         if(i < backwardMostMove) i = backwardMostMove;
7614                         while(i <= forwardMostMove) {
7615                                 lastCheck = inCheck; // check evasion does not count
7616                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7617                                 if(inCheck || lastCheck) count--; // check does not count
7618                                 i++;
7619                         }
7620                 }
7621                 if( count >= 100)
7622                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7623                          /* this is used to judge if draw claims are legal */
7624                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7625                          if(engineOpponent) {
7626                            SendToProgram("force\n", engineOpponent); // suppress reply
7627                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7628                          }
7629                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7630                          return 1;
7631                 }
7632
7633                 /* if draw offer is pending, treat it as a draw claim
7634                  * when draw condition present, to allow engines a way to
7635                  * claim draws before making their move to avoid a race
7636                  * condition occurring after their move
7637                  */
7638                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7639                          char *p = NULL;
7640                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7641                              p = "Draw claim: 50-move rule";
7642                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7643                              p = "Draw claim: 3-fold repetition";
7644                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7645                              p = "Draw claim: insufficient mating material";
7646                          if( p != NULL && canAdjudicate) {
7647                              if(engineOpponent) {
7648                                SendToProgram("force\n", engineOpponent); // suppress reply
7649                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7650                              }
7651                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7652                              return 1;
7653                          }
7654                 }
7655
7656                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7657                     if(engineOpponent) {
7658                       SendToProgram("force\n", engineOpponent); // suppress reply
7659                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7660                     }
7661                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7662                     return 1;
7663                 }
7664         return 0;
7665 }
7666
7667 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7668 {   // [HGM] book: this routine intercepts moves to simulate book replies
7669     char *bookHit = NULL;
7670
7671     //first determine if the incoming move brings opponent into his book
7672     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7673         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7674     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7675     if(bookHit != NULL && !cps->bookSuspend) {
7676         // make sure opponent is not going to reply after receiving move to book position
7677         SendToProgram("force\n", cps);
7678         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7679     }
7680     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7681     // now arrange restart after book miss
7682     if(bookHit) {
7683         // after a book hit we never send 'go', and the code after the call to this routine
7684         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7685         char buf[MSG_SIZ], *move = bookHit;
7686         if(cps->useSAN) {
7687             int fromX, fromY, toX, toY;
7688             char promoChar;
7689             ChessMove moveType;
7690             move = buf + 30;
7691             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7692                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7693                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7694                                     PosFlags(forwardMostMove),
7695                                     fromY, fromX, toY, toX, promoChar, move);
7696             } else {
7697                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7698                 bookHit = NULL;
7699             }
7700         }
7701         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7702         SendToProgram(buf, cps);
7703         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7704     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7705         SendToProgram("go\n", cps);
7706         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7707     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7708         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7709             SendToProgram("go\n", cps);
7710         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7711     }
7712     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7713 }
7714
7715 char *savedMessage;
7716 ChessProgramState *savedState;
7717 void DeferredBookMove(void)
7718 {
7719         if(savedState->lastPing != savedState->lastPong)
7720                     ScheduleDelayedEvent(DeferredBookMove, 10);
7721         else
7722         HandleMachineMove(savedMessage, savedState);
7723 }
7724
7725 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7726
7727 void
7728 HandleMachineMove(message, cps)
7729      char *message;
7730      ChessProgramState *cps;
7731 {
7732     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7733     char realname[MSG_SIZ];
7734     int fromX, fromY, toX, toY;
7735     ChessMove moveType;
7736     char promoChar;
7737     char *p, *pv=buf1;
7738     int machineWhite;
7739     char *bookHit;
7740
7741     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7742         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7743         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7744             DisplayError(_("Invalid pairing from pairing engine"), 0);
7745             return;
7746         }
7747         pairingReceived = 1;
7748         NextMatchGame();
7749         return; // Skim the pairing messages here.
7750     }
7751
7752     cps->userError = 0;
7753
7754 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7755     /*
7756      * Kludge to ignore BEL characters
7757      */
7758     while (*message == '\007') message++;
7759
7760     /*
7761      * [HGM] engine debug message: ignore lines starting with '#' character
7762      */
7763     if(cps->debug && *message == '#') return;
7764
7765     /*
7766      * Look for book output
7767      */
7768     if (cps == &first && bookRequested) {
7769         if (message[0] == '\t' || message[0] == ' ') {
7770             /* Part of the book output is here; append it */
7771             strcat(bookOutput, message);
7772             strcat(bookOutput, "  \n");
7773             return;
7774         } else if (bookOutput[0] != NULLCHAR) {
7775             /* All of book output has arrived; display it */
7776             char *p = bookOutput;
7777             while (*p != NULLCHAR) {
7778                 if (*p == '\t') *p = ' ';
7779                 p++;
7780             }
7781             DisplayInformation(bookOutput);
7782             bookRequested = FALSE;
7783             /* Fall through to parse the current output */
7784         }
7785     }
7786
7787     /*
7788      * Look for machine move.
7789      */
7790     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7791         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7792     {
7793         /* This method is only useful on engines that support ping */
7794         if (cps->lastPing != cps->lastPong) {
7795           if (gameMode == BeginningOfGame) {
7796             /* Extra move from before last new; ignore */
7797             if (appData.debugMode) {
7798                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7799             }
7800           } else {
7801             if (appData.debugMode) {
7802                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7803                         cps->which, gameMode);
7804             }
7805
7806             SendToProgram("undo\n", cps);
7807           }
7808           return;
7809         }
7810
7811         switch (gameMode) {
7812           case BeginningOfGame:
7813             /* Extra move from before last reset; ignore */
7814             if (appData.debugMode) {
7815                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7816             }
7817             return;
7818
7819           case EndOfGame:
7820           case IcsIdle:
7821           default:
7822             /* Extra move after we tried to stop.  The mode test is
7823                not a reliable way of detecting this problem, but it's
7824                the best we can do on engines that don't support ping.
7825             */
7826             if (appData.debugMode) {
7827                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7828                         cps->which, gameMode);
7829             }
7830             SendToProgram("undo\n", cps);
7831             return;
7832
7833           case MachinePlaysWhite:
7834           case IcsPlayingWhite:
7835             machineWhite = TRUE;
7836             break;
7837
7838           case MachinePlaysBlack:
7839           case IcsPlayingBlack:
7840             machineWhite = FALSE;
7841             break;
7842
7843           case TwoMachinesPlay:
7844             machineWhite = (cps->twoMachinesColor[0] == 'w');
7845             break;
7846         }
7847         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7848             if (appData.debugMode) {
7849                 fprintf(debugFP,
7850                         "Ignoring move out of turn by %s, gameMode %d"
7851                         ", forwardMost %d\n",
7852                         cps->which, gameMode, forwardMostMove);
7853             }
7854             return;
7855         }
7856
7857     if (appData.debugMode) { int f = forwardMostMove;
7858         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7859                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7860                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7861     }
7862         if(cps->alphaRank) AlphaRank(machineMove, 4);
7863         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7864                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7865             /* Machine move could not be parsed; ignore it. */
7866           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7867                     machineMove, _(cps->which));
7868             DisplayError(buf1, 0);
7869             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7870                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7871             if (gameMode == TwoMachinesPlay) {
7872               GameEnds(machineWhite ? BlackWins : WhiteWins,
7873                        buf1, GE_XBOARD);
7874             }
7875             return;
7876         }
7877
7878         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7879         /* So we have to redo legality test with true e.p. status here,  */
7880         /* to make sure an illegal e.p. capture does not slip through,   */
7881         /* to cause a forfeit on a justified illegal-move complaint      */
7882         /* of the opponent.                                              */
7883         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7884            ChessMove moveType;
7885            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7886                              fromY, fromX, toY, toX, promoChar);
7887             if (appData.debugMode) {
7888                 int i;
7889                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7890                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7891                 fprintf(debugFP, "castling rights\n");
7892             }
7893             if(moveType == IllegalMove) {
7894               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7895                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7896                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7897                            buf1, GE_XBOARD);
7898                 return;
7899            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7900            /* [HGM] Kludge to handle engines that send FRC-style castling
7901               when they shouldn't (like TSCP-Gothic) */
7902            switch(moveType) {
7903              case WhiteASideCastleFR:
7904              case BlackASideCastleFR:
7905                toX+=2;
7906                currentMoveString[2]++;
7907                break;
7908              case WhiteHSideCastleFR:
7909              case BlackHSideCastleFR:
7910                toX--;
7911                currentMoveString[2]--;
7912                break;
7913              default: ; // nothing to do, but suppresses warning of pedantic compilers
7914            }
7915         }
7916         hintRequested = FALSE;
7917         lastHint[0] = NULLCHAR;
7918         bookRequested = FALSE;
7919         /* Program may be pondering now */
7920         cps->maybeThinking = TRUE;
7921         if (cps->sendTime == 2) cps->sendTime = 1;
7922         if (cps->offeredDraw) cps->offeredDraw--;
7923
7924         /* [AS] Save move info*/
7925         pvInfoList[ forwardMostMove ].score = programStats.score;
7926         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7927         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7928
7929         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7930
7931         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7932         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7933             int count = 0;
7934
7935             while( count < adjudicateLossPlies ) {
7936                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7937
7938                 if( count & 1 ) {
7939                     score = -score; /* Flip score for winning side */
7940                 }
7941
7942                 if( score > adjudicateLossThreshold ) {
7943                     break;
7944                 }
7945
7946                 count++;
7947             }
7948
7949             if( count >= adjudicateLossPlies ) {
7950                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7951
7952                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7953                     "Xboard adjudication",
7954                     GE_XBOARD );
7955
7956                 return;
7957             }
7958         }
7959
7960         if(Adjudicate(cps)) {
7961             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7962             return; // [HGM] adjudicate: for all automatic game ends
7963         }
7964
7965 #if ZIPPY
7966         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7967             first.initDone) {
7968           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7969                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7970                 SendToICS("draw ");
7971                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7972           }
7973           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7974           ics_user_moved = 1;
7975           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7976                 char buf[3*MSG_SIZ];
7977
7978                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7979                         programStats.score / 100.,
7980                         programStats.depth,
7981                         programStats.time / 100.,
7982                         (unsigned int)programStats.nodes,
7983                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7984                         programStats.movelist);
7985                 SendToICS(buf);
7986 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7987           }
7988         }
7989 #endif
7990
7991         /* [AS] Clear stats for next move */
7992         ClearProgramStats();
7993         thinkOutput[0] = NULLCHAR;
7994         hiddenThinkOutputState = 0;
7995
7996         bookHit = NULL;
7997         if (gameMode == TwoMachinesPlay) {
7998             /* [HGM] relaying draw offers moved to after reception of move */
7999             /* and interpreting offer as claim if it brings draw condition */
8000             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8001                 SendToProgram("draw\n", cps->other);
8002             }
8003             if (cps->other->sendTime) {
8004                 SendTimeRemaining(cps->other,
8005                                   cps->other->twoMachinesColor[0] == 'w');
8006             }
8007             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8008             if (firstMove && !bookHit) {
8009                 firstMove = FALSE;
8010                 if (cps->other->useColors) {
8011                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8012                 }
8013                 SendToProgram("go\n", cps->other);
8014             }
8015             cps->other->maybeThinking = TRUE;
8016         }
8017
8018         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8019
8020         if (!pausing && appData.ringBellAfterMoves) {
8021             RingBell();
8022         }
8023
8024         /*
8025          * Reenable menu items that were disabled while
8026          * machine was thinking
8027          */
8028         if (gameMode != TwoMachinesPlay)
8029             SetUserThinkingEnables();
8030
8031         // [HGM] book: after book hit opponent has received move and is now in force mode
8032         // force the book reply into it, and then fake that it outputted this move by jumping
8033         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8034         if(bookHit) {
8035                 static char bookMove[MSG_SIZ]; // a bit generous?
8036
8037                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8038                 strcat(bookMove, bookHit);
8039                 message = bookMove;
8040                 cps = cps->other;
8041                 programStats.nodes = programStats.depth = programStats.time =
8042                 programStats.score = programStats.got_only_move = 0;
8043                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8044
8045                 if(cps->lastPing != cps->lastPong) {
8046                     savedMessage = message; // args for deferred call
8047                     savedState = cps;
8048                     ScheduleDelayedEvent(DeferredBookMove, 10);
8049                     return;
8050                 }
8051                 goto FakeBookMove;
8052         }
8053
8054         return;
8055     }
8056
8057     /* Set special modes for chess engines.  Later something general
8058      *  could be added here; for now there is just one kludge feature,
8059      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8060      *  when "xboard" is given as an interactive command.
8061      */
8062     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8063         cps->useSigint = FALSE;
8064         cps->useSigterm = FALSE;
8065     }
8066     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8067       ParseFeatures(message+8, cps);
8068       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8069     }
8070
8071     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8072       int dummy, s=6; char buf[MSG_SIZ];
8073       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8074       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8075       ParseFEN(boards[0], &dummy, message+s);
8076       DrawPosition(TRUE, boards[0]);
8077       startedFromSetupPosition = TRUE;
8078       return;
8079     }
8080     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8081      * want this, I was asked to put it in, and obliged.
8082      */
8083     if (!strncmp(message, "setboard ", 9)) {
8084         Board initial_position;
8085
8086         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8087
8088         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8089             DisplayError(_("Bad FEN received from engine"), 0);
8090             return ;
8091         } else {
8092            Reset(TRUE, FALSE);
8093            CopyBoard(boards[0], initial_position);
8094            initialRulePlies = FENrulePlies;
8095            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8096            else gameMode = MachinePlaysBlack;
8097            DrawPosition(FALSE, boards[currentMove]);
8098         }
8099         return;
8100     }
8101
8102     /*
8103      * Look for communication commands
8104      */
8105     if (!strncmp(message, "telluser ", 9)) {
8106         if(message[9] == '\\' && message[10] == '\\')
8107             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8108         PlayTellSound();
8109         DisplayNote(message + 9);
8110         return;
8111     }
8112     if (!strncmp(message, "tellusererror ", 14)) {
8113         cps->userError = 1;
8114         if(message[14] == '\\' && message[15] == '\\')
8115             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8116         PlayTellSound();
8117         DisplayError(message + 14, 0);
8118         return;
8119     }
8120     if (!strncmp(message, "tellopponent ", 13)) {
8121       if (appData.icsActive) {
8122         if (loggedOn) {
8123           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8124           SendToICS(buf1);
8125         }
8126       } else {
8127         DisplayNote(message + 13);
8128       }
8129       return;
8130     }
8131     if (!strncmp(message, "tellothers ", 11)) {
8132       if (appData.icsActive) {
8133         if (loggedOn) {
8134           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8135           SendToICS(buf1);
8136         }
8137       }
8138       return;
8139     }
8140     if (!strncmp(message, "tellall ", 8)) {
8141       if (appData.icsActive) {
8142         if (loggedOn) {
8143           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8144           SendToICS(buf1);
8145         }
8146       } else {
8147         DisplayNote(message + 8);
8148       }
8149       return;
8150     }
8151     if (strncmp(message, "warning", 7) == 0) {
8152         /* Undocumented feature, use tellusererror in new code */
8153         DisplayError(message, 0);
8154         return;
8155     }
8156     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8157         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8158         strcat(realname, " query");
8159         AskQuestion(realname, buf2, buf1, cps->pr);
8160         return;
8161     }
8162     /* Commands from the engine directly to ICS.  We don't allow these to be
8163      *  sent until we are logged on. Crafty kibitzes have been known to
8164      *  interfere with the login process.
8165      */
8166     if (loggedOn) {
8167         if (!strncmp(message, "tellics ", 8)) {
8168             SendToICS(message + 8);
8169             SendToICS("\n");
8170             return;
8171         }
8172         if (!strncmp(message, "tellicsnoalias ", 15)) {
8173             SendToICS(ics_prefix);
8174             SendToICS(message + 15);
8175             SendToICS("\n");
8176             return;
8177         }
8178         /* The following are for backward compatibility only */
8179         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8180             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8181             SendToICS(ics_prefix);
8182             SendToICS(message);
8183             SendToICS("\n");
8184             return;
8185         }
8186     }
8187     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8188         return;
8189     }
8190     /*
8191      * If the move is illegal, cancel it and redraw the board.
8192      * Also deal with other error cases.  Matching is rather loose
8193      * here to accommodate engines written before the spec.
8194      */
8195     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8196         strncmp(message, "Error", 5) == 0) {
8197         if (StrStr(message, "name") ||
8198             StrStr(message, "rating") || StrStr(message, "?") ||
8199             StrStr(message, "result") || StrStr(message, "board") ||
8200             StrStr(message, "bk") || StrStr(message, "computer") ||
8201             StrStr(message, "variant") || StrStr(message, "hint") ||
8202             StrStr(message, "random") || StrStr(message, "depth") ||
8203             StrStr(message, "accepted")) {
8204             return;
8205         }
8206         if (StrStr(message, "protover")) {
8207           /* Program is responding to input, so it's apparently done
8208              initializing, and this error message indicates it is
8209              protocol version 1.  So we don't need to wait any longer
8210              for it to initialize and send feature commands. */
8211           FeatureDone(cps, 1);
8212           cps->protocolVersion = 1;
8213           return;
8214         }
8215         cps->maybeThinking = FALSE;
8216
8217         if (StrStr(message, "draw")) {
8218             /* Program doesn't have "draw" command */
8219             cps->sendDrawOffers = 0;
8220             return;
8221         }
8222         if (cps->sendTime != 1 &&
8223             (StrStr(message, "time") || StrStr(message, "otim"))) {
8224           /* Program apparently doesn't have "time" or "otim" command */
8225           cps->sendTime = 0;
8226           return;
8227         }
8228         if (StrStr(message, "analyze")) {
8229             cps->analysisSupport = FALSE;
8230             cps->analyzing = FALSE;
8231             Reset(FALSE, TRUE);
8232             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8233             DisplayError(buf2, 0);
8234             return;
8235         }
8236         if (StrStr(message, "(no matching move)st")) {
8237           /* Special kludge for GNU Chess 4 only */
8238           cps->stKludge = TRUE;
8239           SendTimeControl(cps, movesPerSession, timeControl,
8240                           timeIncrement, appData.searchDepth,
8241                           searchTime);
8242           return;
8243         }
8244         if (StrStr(message, "(no matching move)sd")) {
8245           /* Special kludge for GNU Chess 4 only */
8246           cps->sdKludge = TRUE;
8247           SendTimeControl(cps, movesPerSession, timeControl,
8248                           timeIncrement, appData.searchDepth,
8249                           searchTime);
8250           return;
8251         }
8252         if (!StrStr(message, "llegal")) {
8253             return;
8254         }
8255         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8256             gameMode == IcsIdle) return;
8257         if (forwardMostMove <= backwardMostMove) return;
8258         if (pausing) PauseEvent();
8259       if(appData.forceIllegal) {
8260             // [HGM] illegal: machine refused move; force position after move into it
8261           SendToProgram("force\n", cps);
8262           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8263                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8264                 // when black is to move, while there might be nothing on a2 or black
8265                 // might already have the move. So send the board as if white has the move.
8266                 // But first we must change the stm of the engine, as it refused the last move
8267                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8268                 if(WhiteOnMove(forwardMostMove)) {
8269                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8270                     SendBoard(cps, forwardMostMove); // kludgeless board
8271                 } else {
8272                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8273                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8274                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8275                 }
8276           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8277             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8278                  gameMode == TwoMachinesPlay)
8279               SendToProgram("go\n", cps);
8280             return;
8281       } else
8282         if (gameMode == PlayFromGameFile) {
8283             /* Stop reading this game file */
8284             gameMode = EditGame;
8285             ModeHighlight();
8286         }
8287         /* [HGM] illegal-move claim should forfeit game when Xboard */
8288         /* only passes fully legal moves                            */
8289         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8290             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8291                                 "False illegal-move claim", GE_XBOARD );
8292             return; // do not take back move we tested as valid
8293         }
8294         currentMove = forwardMostMove-1;
8295         DisplayMove(currentMove-1); /* before DisplayMoveError */
8296         SwitchClocks(forwardMostMove-1); // [HGM] race
8297         DisplayBothClocks();
8298         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8299                 parseList[currentMove], _(cps->which));
8300         DisplayMoveError(buf1);
8301         DrawPosition(FALSE, boards[currentMove]);
8302         return;
8303     }
8304     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8305         /* Program has a broken "time" command that
8306            outputs a string not ending in newline.
8307            Don't use it. */
8308         cps->sendTime = 0;
8309     }
8310
8311     /*
8312      * If chess program startup fails, exit with an error message.
8313      * Attempts to recover here are futile.
8314      */
8315     if ((StrStr(message, "unknown host") != NULL)
8316         || (StrStr(message, "No remote directory") != NULL)
8317         || (StrStr(message, "not found") != NULL)
8318         || (StrStr(message, "No such file") != NULL)
8319         || (StrStr(message, "can't alloc") != NULL)
8320         || (StrStr(message, "Permission denied") != NULL)) {
8321
8322         cps->maybeThinking = FALSE;
8323         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8324                 _(cps->which), cps->program, cps->host, message);
8325         RemoveInputSource(cps->isr);
8326         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8327             if(cps == &first) appData.noChessProgram = TRUE;
8328             DisplayError(buf1, 0);
8329         }
8330         return;
8331     }
8332
8333     /*
8334      * Look for hint output
8335      */
8336     if (sscanf(message, "Hint: %s", buf1) == 1) {
8337         if (cps == &first && hintRequested) {
8338             hintRequested = FALSE;
8339             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8340                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8341                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8342                                     PosFlags(forwardMostMove),
8343                                     fromY, fromX, toY, toX, promoChar, buf1);
8344                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8345                 DisplayInformation(buf2);
8346             } else {
8347                 /* Hint move could not be parsed!? */
8348               snprintf(buf2, sizeof(buf2),
8349                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8350                         buf1, _(cps->which));
8351                 DisplayError(buf2, 0);
8352             }
8353         } else {
8354           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8355         }
8356         return;
8357     }
8358
8359     /*
8360      * Ignore other messages if game is not in progress
8361      */
8362     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8363         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8364
8365     /*
8366      * look for win, lose, draw, or draw offer
8367      */
8368     if (strncmp(message, "1-0", 3) == 0) {
8369         char *p, *q, *r = "";
8370         p = strchr(message, '{');
8371         if (p) {
8372             q = strchr(p, '}');
8373             if (q) {
8374                 *q = NULLCHAR;
8375                 r = p + 1;
8376             }
8377         }
8378         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8379         return;
8380     } else if (strncmp(message, "0-1", 3) == 0) {
8381         char *p, *q, *r = "";
8382         p = strchr(message, '{');
8383         if (p) {
8384             q = strchr(p, '}');
8385             if (q) {
8386                 *q = NULLCHAR;
8387                 r = p + 1;
8388             }
8389         }
8390         /* Kludge for Arasan 4.1 bug */
8391         if (strcmp(r, "Black resigns") == 0) {
8392             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8393             return;
8394         }
8395         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8396         return;
8397     } else if (strncmp(message, "1/2", 3) == 0) {
8398         char *p, *q, *r = "";
8399         p = strchr(message, '{');
8400         if (p) {
8401             q = strchr(p, '}');
8402             if (q) {
8403                 *q = NULLCHAR;
8404                 r = p + 1;
8405             }
8406         }
8407
8408         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8409         return;
8410
8411     } else if (strncmp(message, "White resign", 12) == 0) {
8412         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8413         return;
8414     } else if (strncmp(message, "Black resign", 12) == 0) {
8415         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8416         return;
8417     } else if (strncmp(message, "White matches", 13) == 0 ||
8418                strncmp(message, "Black matches", 13) == 0   ) {
8419         /* [HGM] ignore GNUShogi noises */
8420         return;
8421     } else if (strncmp(message, "White", 5) == 0 &&
8422                message[5] != '(' &&
8423                StrStr(message, "Black") == NULL) {
8424         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8425         return;
8426     } else if (strncmp(message, "Black", 5) == 0 &&
8427                message[5] != '(') {
8428         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8429         return;
8430     } else if (strcmp(message, "resign") == 0 ||
8431                strcmp(message, "computer resigns") == 0) {
8432         switch (gameMode) {
8433           case MachinePlaysBlack:
8434           case IcsPlayingBlack:
8435             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8436             break;
8437           case MachinePlaysWhite:
8438           case IcsPlayingWhite:
8439             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8440             break;
8441           case TwoMachinesPlay:
8442             if (cps->twoMachinesColor[0] == 'w')
8443               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8444             else
8445               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8446             break;
8447           default:
8448             /* can't happen */
8449             break;
8450         }
8451         return;
8452     } else if (strncmp(message, "opponent mates", 14) == 0) {
8453         switch (gameMode) {
8454           case MachinePlaysBlack:
8455           case IcsPlayingBlack:
8456             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8457             break;
8458           case MachinePlaysWhite:
8459           case IcsPlayingWhite:
8460             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8461             break;
8462           case TwoMachinesPlay:
8463             if (cps->twoMachinesColor[0] == 'w')
8464               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8465             else
8466               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8467             break;
8468           default:
8469             /* can't happen */
8470             break;
8471         }
8472         return;
8473     } else if (strncmp(message, "computer mates", 14) == 0) {
8474         switch (gameMode) {
8475           case MachinePlaysBlack:
8476           case IcsPlayingBlack:
8477             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8478             break;
8479           case MachinePlaysWhite:
8480           case IcsPlayingWhite:
8481             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8482             break;
8483           case TwoMachinesPlay:
8484             if (cps->twoMachinesColor[0] == 'w')
8485               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8486             else
8487               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8488             break;
8489           default:
8490             /* can't happen */
8491             break;
8492         }
8493         return;
8494     } else if (strncmp(message, "checkmate", 9) == 0) {
8495         if (WhiteOnMove(forwardMostMove)) {
8496             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8497         } else {
8498             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8499         }
8500         return;
8501     } else if (strstr(message, "Draw") != NULL ||
8502                strstr(message, "game is a draw") != NULL) {
8503         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8504         return;
8505     } else if (strstr(message, "offer") != NULL &&
8506                strstr(message, "draw") != NULL) {
8507 #if ZIPPY
8508         if (appData.zippyPlay && first.initDone) {
8509             /* Relay offer to ICS */
8510             SendToICS(ics_prefix);
8511             SendToICS("draw\n");
8512         }
8513 #endif
8514         cps->offeredDraw = 2; /* valid until this engine moves twice */
8515         if (gameMode == TwoMachinesPlay) {
8516             if (cps->other->offeredDraw) {
8517                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8518             /* [HGM] in two-machine mode we delay relaying draw offer      */
8519             /* until after we also have move, to see if it is really claim */
8520             }
8521         } else if (gameMode == MachinePlaysWhite ||
8522                    gameMode == MachinePlaysBlack) {
8523           if (userOfferedDraw) {
8524             DisplayInformation(_("Machine accepts your draw offer"));
8525             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8526           } else {
8527             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8528           }
8529         }
8530     }
8531
8532
8533     /*
8534      * Look for thinking output
8535      */
8536     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8537           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8538                                 ) {
8539         int plylev, mvleft, mvtot, curscore, time;
8540         char mvname[MOVE_LEN];
8541         u64 nodes; // [DM]
8542         char plyext;
8543         int ignore = FALSE;
8544         int prefixHint = FALSE;
8545         mvname[0] = NULLCHAR;
8546
8547         switch (gameMode) {
8548           case MachinePlaysBlack:
8549           case IcsPlayingBlack:
8550             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8551             break;
8552           case MachinePlaysWhite:
8553           case IcsPlayingWhite:
8554             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8555             break;
8556           case AnalyzeMode:
8557           case AnalyzeFile:
8558             break;
8559           case IcsObserving: /* [DM] icsEngineAnalyze */
8560             if (!appData.icsEngineAnalyze) ignore = TRUE;
8561             break;
8562           case TwoMachinesPlay:
8563             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8564                 ignore = TRUE;
8565             }
8566             break;
8567           default:
8568             ignore = TRUE;
8569             break;
8570         }
8571
8572         if (!ignore) {
8573             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8574             buf1[0] = NULLCHAR;
8575             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8576                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8577
8578                 if (plyext != ' ' && plyext != '\t') {
8579                     time *= 100;
8580                 }
8581
8582                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8583                 if( cps->scoreIsAbsolute &&
8584                     ( gameMode == MachinePlaysBlack ||
8585                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8586                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8587                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8588                      !WhiteOnMove(currentMove)
8589                     ) )
8590                 {
8591                     curscore = -curscore;
8592                 }
8593
8594                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8595
8596                 tempStats.depth = plylev;
8597                 tempStats.nodes = nodes;
8598                 tempStats.time = time;
8599                 tempStats.score = curscore;
8600                 tempStats.got_only_move = 0;
8601
8602                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8603                         int ticklen;
8604
8605                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8606                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8607                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8608                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8609                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8610                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8611                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8612                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8613                 }
8614
8615                 /* Buffer overflow protection */
8616                 if (pv[0] != NULLCHAR) {
8617                     if (strlen(pv) >= sizeof(tempStats.movelist)
8618                         && appData.debugMode) {
8619                         fprintf(debugFP,
8620                                 "PV is too long; using the first %u bytes.\n",
8621                                 (unsigned) sizeof(tempStats.movelist) - 1);
8622                     }
8623
8624                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8625                 } else {
8626                     sprintf(tempStats.movelist, " no PV\n");
8627                 }
8628
8629                 if (tempStats.seen_stat) {
8630                     tempStats.ok_to_send = 1;
8631                 }
8632
8633                 if (strchr(tempStats.movelist, '(') != NULL) {
8634                     tempStats.line_is_book = 1;
8635                     tempStats.nr_moves = 0;
8636                     tempStats.moves_left = 0;
8637                 } else {
8638                     tempStats.line_is_book = 0;
8639                 }
8640
8641                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8642                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8643
8644                 SendProgramStatsToFrontend( cps, &tempStats );
8645
8646                 /*
8647                     [AS] Protect the thinkOutput buffer from overflow... this
8648                     is only useful if buf1 hasn't overflowed first!
8649                 */
8650                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8651                          plylev,
8652                          (gameMode == TwoMachinesPlay ?
8653                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8654                          ((double) curscore) / 100.0,
8655                          prefixHint ? lastHint : "",
8656                          prefixHint ? " " : "" );
8657
8658                 if( buf1[0] != NULLCHAR ) {
8659                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8660
8661                     if( strlen(pv) > max_len ) {
8662                         if( appData.debugMode) {
8663                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8664                         }
8665                         pv[max_len+1] = '\0';
8666                     }
8667
8668                     strcat( thinkOutput, pv);
8669                 }
8670
8671                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8672                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8673                     DisplayMove(currentMove - 1);
8674                 }
8675                 return;
8676
8677             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8678                 /* crafty (9.25+) says "(only move) <move>"
8679                  * if there is only 1 legal move
8680                  */
8681                 sscanf(p, "(only move) %s", buf1);
8682                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8683                 sprintf(programStats.movelist, "%s (only move)", buf1);
8684                 programStats.depth = 1;
8685                 programStats.nr_moves = 1;
8686                 programStats.moves_left = 1;
8687                 programStats.nodes = 1;
8688                 programStats.time = 1;
8689                 programStats.got_only_move = 1;
8690
8691                 /* Not really, but we also use this member to
8692                    mean "line isn't going to change" (Crafty
8693                    isn't searching, so stats won't change) */
8694                 programStats.line_is_book = 1;
8695
8696                 SendProgramStatsToFrontend( cps, &programStats );
8697
8698                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8699                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8700                     DisplayMove(currentMove - 1);
8701                 }
8702                 return;
8703             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8704                               &time, &nodes, &plylev, &mvleft,
8705                               &mvtot, mvname) >= 5) {
8706                 /* The stat01: line is from Crafty (9.29+) in response
8707                    to the "." command */
8708                 programStats.seen_stat = 1;
8709                 cps->maybeThinking = TRUE;
8710
8711                 if (programStats.got_only_move || !appData.periodicUpdates)
8712                   return;
8713
8714                 programStats.depth = plylev;
8715                 programStats.time = time;
8716                 programStats.nodes = nodes;
8717                 programStats.moves_left = mvleft;
8718                 programStats.nr_moves = mvtot;
8719                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8720                 programStats.ok_to_send = 1;
8721                 programStats.movelist[0] = '\0';
8722
8723                 SendProgramStatsToFrontend( cps, &programStats );
8724
8725                 return;
8726
8727             } else if (strncmp(message,"++",2) == 0) {
8728                 /* Crafty 9.29+ outputs this */
8729                 programStats.got_fail = 2;
8730                 return;
8731
8732             } else if (strncmp(message,"--",2) == 0) {
8733                 /* Crafty 9.29+ outputs this */
8734                 programStats.got_fail = 1;
8735                 return;
8736
8737             } else if (thinkOutput[0] != NULLCHAR &&
8738                        strncmp(message, "    ", 4) == 0) {
8739                 unsigned message_len;
8740
8741                 p = message;
8742                 while (*p && *p == ' ') p++;
8743
8744                 message_len = strlen( p );
8745
8746                 /* [AS] Avoid buffer overflow */
8747                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8748                     strcat(thinkOutput, " ");
8749                     strcat(thinkOutput, p);
8750                 }
8751
8752                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8753                     strcat(programStats.movelist, " ");
8754                     strcat(programStats.movelist, p);
8755                 }
8756
8757                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8758                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8759                     DisplayMove(currentMove - 1);
8760                 }
8761                 return;
8762             }
8763         }
8764         else {
8765             buf1[0] = NULLCHAR;
8766
8767             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8768                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8769             {
8770                 ChessProgramStats cpstats;
8771
8772                 if (plyext != ' ' && plyext != '\t') {
8773                     time *= 100;
8774                 }
8775
8776                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8777                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8778                     curscore = -curscore;
8779                 }
8780
8781                 cpstats.depth = plylev;
8782                 cpstats.nodes = nodes;
8783                 cpstats.time = time;
8784                 cpstats.score = curscore;
8785                 cpstats.got_only_move = 0;
8786                 cpstats.movelist[0] = '\0';
8787
8788                 if (buf1[0] != NULLCHAR) {
8789                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8790                 }
8791
8792                 cpstats.ok_to_send = 0;
8793                 cpstats.line_is_book = 0;
8794                 cpstats.nr_moves = 0;
8795                 cpstats.moves_left = 0;
8796
8797                 SendProgramStatsToFrontend( cps, &cpstats );
8798             }
8799         }
8800     }
8801 }
8802
8803
8804 /* Parse a game score from the character string "game", and
8805    record it as the history of the current game.  The game
8806    score is NOT assumed to start from the standard position.
8807    The display is not updated in any way.
8808    */
8809 void
8810 ParseGameHistory(game)
8811      char *game;
8812 {
8813     ChessMove moveType;
8814     int fromX, fromY, toX, toY, boardIndex;
8815     char promoChar;
8816     char *p, *q;
8817     char buf[MSG_SIZ];
8818
8819     if (appData.debugMode)
8820       fprintf(debugFP, "Parsing game history: %s\n", game);
8821
8822     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8823     gameInfo.site = StrSave(appData.icsHost);
8824     gameInfo.date = PGNDate();
8825     gameInfo.round = StrSave("-");
8826
8827     /* Parse out names of players */
8828     while (*game == ' ') game++;
8829     p = buf;
8830     while (*game != ' ') *p++ = *game++;
8831     *p = NULLCHAR;
8832     gameInfo.white = StrSave(buf);
8833     while (*game == ' ') game++;
8834     p = buf;
8835     while (*game != ' ' && *game != '\n') *p++ = *game++;
8836     *p = NULLCHAR;
8837     gameInfo.black = StrSave(buf);
8838
8839     /* Parse moves */
8840     boardIndex = blackPlaysFirst ? 1 : 0;
8841     yynewstr(game);
8842     for (;;) {
8843         yyboardindex = boardIndex;
8844         moveType = (ChessMove) Myylex();
8845         switch (moveType) {
8846           case IllegalMove:             /* maybe suicide chess, etc. */
8847   if (appData.debugMode) {
8848     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8849     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8850     setbuf(debugFP, NULL);
8851   }
8852           case WhitePromotion:
8853           case BlackPromotion:
8854           case WhiteNonPromotion:
8855           case BlackNonPromotion:
8856           case NormalMove:
8857           case WhiteCapturesEnPassant:
8858           case BlackCapturesEnPassant:
8859           case WhiteKingSideCastle:
8860           case WhiteQueenSideCastle:
8861           case BlackKingSideCastle:
8862           case BlackQueenSideCastle:
8863           case WhiteKingSideCastleWild:
8864           case WhiteQueenSideCastleWild:
8865           case BlackKingSideCastleWild:
8866           case BlackQueenSideCastleWild:
8867           /* PUSH Fabien */
8868           case WhiteHSideCastleFR:
8869           case WhiteASideCastleFR:
8870           case BlackHSideCastleFR:
8871           case BlackASideCastleFR:
8872           /* POP Fabien */
8873             fromX = currentMoveString[0] - AAA;
8874             fromY = currentMoveString[1] - ONE;
8875             toX = currentMoveString[2] - AAA;
8876             toY = currentMoveString[3] - ONE;
8877             promoChar = currentMoveString[4];
8878             break;
8879           case WhiteDrop:
8880           case BlackDrop:
8881             fromX = moveType == WhiteDrop ?
8882               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8883             (int) CharToPiece(ToLower(currentMoveString[0]));
8884             fromY = DROP_RANK;
8885             toX = currentMoveString[2] - AAA;
8886             toY = currentMoveString[3] - ONE;
8887             promoChar = NULLCHAR;
8888             break;
8889           case AmbiguousMove:
8890             /* bug? */
8891             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8892   if (appData.debugMode) {
8893     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8894     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8895     setbuf(debugFP, NULL);
8896   }
8897             DisplayError(buf, 0);
8898             return;
8899           case ImpossibleMove:
8900             /* bug? */
8901             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8902   if (appData.debugMode) {
8903     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8904     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8905     setbuf(debugFP, NULL);
8906   }
8907             DisplayError(buf, 0);
8908             return;
8909           case EndOfFile:
8910             if (boardIndex < backwardMostMove) {
8911                 /* Oops, gap.  How did that happen? */
8912                 DisplayError(_("Gap in move list"), 0);
8913                 return;
8914             }
8915             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8916             if (boardIndex > forwardMostMove) {
8917                 forwardMostMove = boardIndex;
8918             }
8919             return;
8920           case ElapsedTime:
8921             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8922                 strcat(parseList[boardIndex-1], " ");
8923                 strcat(parseList[boardIndex-1], yy_text);
8924             }
8925             continue;
8926           case Comment:
8927           case PGNTag:
8928           case NAG:
8929           default:
8930             /* ignore */
8931             continue;
8932           case WhiteWins:
8933           case BlackWins:
8934           case GameIsDrawn:
8935           case GameUnfinished:
8936             if (gameMode == IcsExamining) {
8937                 if (boardIndex < backwardMostMove) {
8938                     /* Oops, gap.  How did that happen? */
8939                     return;
8940                 }
8941                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8942                 return;
8943             }
8944             gameInfo.result = moveType;
8945             p = strchr(yy_text, '{');
8946             if (p == NULL) p = strchr(yy_text, '(');
8947             if (p == NULL) {
8948                 p = yy_text;
8949                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8950             } else {
8951                 q = strchr(p, *p == '{' ? '}' : ')');
8952                 if (q != NULL) *q = NULLCHAR;
8953                 p++;
8954             }
8955             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8956             gameInfo.resultDetails = StrSave(p);
8957             continue;
8958         }
8959         if (boardIndex >= forwardMostMove &&
8960             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8961             backwardMostMove = blackPlaysFirst ? 1 : 0;
8962             return;
8963         }
8964         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8965                                  fromY, fromX, toY, toX, promoChar,
8966                                  parseList[boardIndex]);
8967         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8968         /* currentMoveString is set as a side-effect of yylex */
8969         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8970         strcat(moveList[boardIndex], "\n");
8971         boardIndex++;
8972         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8973         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8974           case MT_NONE:
8975           case MT_STALEMATE:
8976           default:
8977             break;
8978           case MT_CHECK:
8979             if(gameInfo.variant != VariantShogi)
8980                 strcat(parseList[boardIndex - 1], "+");
8981             break;
8982           case MT_CHECKMATE:
8983           case MT_STAINMATE:
8984             strcat(parseList[boardIndex - 1], "#");
8985             break;
8986         }
8987     }
8988 }
8989
8990
8991 /* Apply a move to the given board  */
8992 void
8993 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8994      int fromX, fromY, toX, toY;
8995      int promoChar;
8996      Board board;
8997 {
8998   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8999   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9000
9001     /* [HGM] compute & store e.p. status and castling rights for new position */
9002     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9003
9004       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9005       oldEP = (signed char)board[EP_STATUS];
9006       board[EP_STATUS] = EP_NONE;
9007
9008       if( board[toY][toX] != EmptySquare )
9009            board[EP_STATUS] = EP_CAPTURE;
9010
9011   if (fromY == DROP_RANK) {
9012         /* must be first */
9013         piece = board[toY][toX] = (ChessSquare) fromX;
9014   } else {
9015       int i;
9016
9017       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9018            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9019                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9020       } else
9021       if( board[fromY][fromX] == WhitePawn ) {
9022            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9023                board[EP_STATUS] = EP_PAWN_MOVE;
9024            if( toY-fromY==2) {
9025                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9026                         gameInfo.variant != VariantBerolina || toX < fromX)
9027                       board[EP_STATUS] = toX | berolina;
9028                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9029                         gameInfo.variant != VariantBerolina || toX > fromX)
9030                       board[EP_STATUS] = toX;
9031            }
9032       } else
9033       if( board[fromY][fromX] == BlackPawn ) {
9034            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9035                board[EP_STATUS] = EP_PAWN_MOVE;
9036            if( toY-fromY== -2) {
9037                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9038                         gameInfo.variant != VariantBerolina || toX < fromX)
9039                       board[EP_STATUS] = toX | berolina;
9040                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9041                         gameInfo.variant != VariantBerolina || toX > fromX)
9042                       board[EP_STATUS] = toX;
9043            }
9044        }
9045
9046        for(i=0; i<nrCastlingRights; i++) {
9047            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9048               board[CASTLING][i] == toX   && castlingRank[i] == toY
9049              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9050        }
9051
9052      if (fromX == toX && fromY == toY) return;
9053
9054      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9055      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9056      if(gameInfo.variant == VariantKnightmate)
9057          king += (int) WhiteUnicorn - (int) WhiteKing;
9058
9059     /* Code added by Tord: */
9060     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9061     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9062         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9063       board[fromY][fromX] = EmptySquare;
9064       board[toY][toX] = EmptySquare;
9065       if((toX > fromX) != (piece == WhiteRook)) {
9066         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9067       } else {
9068         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9069       }
9070     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9071                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9072       board[fromY][fromX] = EmptySquare;
9073       board[toY][toX] = EmptySquare;
9074       if((toX > fromX) != (piece == BlackRook)) {
9075         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9076       } else {
9077         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9078       }
9079     /* End of code added by Tord */
9080
9081     } else if (board[fromY][fromX] == king
9082         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9083         && toY == fromY && toX > fromX+1) {
9084         board[fromY][fromX] = EmptySquare;
9085         board[toY][toX] = king;
9086         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9087         board[fromY][BOARD_RGHT-1] = EmptySquare;
9088     } else if (board[fromY][fromX] == king
9089         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9090                && toY == fromY && toX < fromX-1) {
9091         board[fromY][fromX] = EmptySquare;
9092         board[toY][toX] = king;
9093         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9094         board[fromY][BOARD_LEFT] = EmptySquare;
9095     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9096                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9097                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9098                ) {
9099         /* white pawn promotion */
9100         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9101         if(gameInfo.variant==VariantBughouse ||
9102            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9103             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9104         board[fromY][fromX] = EmptySquare;
9105     } else if ((fromY >= BOARD_HEIGHT>>1)
9106                && (toX != fromX)
9107                && gameInfo.variant != VariantXiangqi
9108                && gameInfo.variant != VariantBerolina
9109                && (board[fromY][fromX] == WhitePawn)
9110                && (board[toY][toX] == EmptySquare)) {
9111         board[fromY][fromX] = EmptySquare;
9112         board[toY][toX] = WhitePawn;
9113         captured = board[toY - 1][toX];
9114         board[toY - 1][toX] = EmptySquare;
9115     } else if ((fromY == BOARD_HEIGHT-4)
9116                && (toX == fromX)
9117                && gameInfo.variant == VariantBerolina
9118                && (board[fromY][fromX] == WhitePawn)
9119                && (board[toY][toX] == EmptySquare)) {
9120         board[fromY][fromX] = EmptySquare;
9121         board[toY][toX] = WhitePawn;
9122         if(oldEP & EP_BEROLIN_A) {
9123                 captured = board[fromY][fromX-1];
9124                 board[fromY][fromX-1] = EmptySquare;
9125         }else{  captured = board[fromY][fromX+1];
9126                 board[fromY][fromX+1] = EmptySquare;
9127         }
9128     } else if (board[fromY][fromX] == king
9129         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9130                && toY == fromY && toX > fromX+1) {
9131         board[fromY][fromX] = EmptySquare;
9132         board[toY][toX] = king;
9133         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9134         board[fromY][BOARD_RGHT-1] = EmptySquare;
9135     } else if (board[fromY][fromX] == king
9136         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9137                && toY == fromY && toX < fromX-1) {
9138         board[fromY][fromX] = EmptySquare;
9139         board[toY][toX] = king;
9140         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9141         board[fromY][BOARD_LEFT] = EmptySquare;
9142     } else if (fromY == 7 && fromX == 3
9143                && board[fromY][fromX] == BlackKing
9144                && toY == 7 && toX == 5) {
9145         board[fromY][fromX] = EmptySquare;
9146         board[toY][toX] = BlackKing;
9147         board[fromY][7] = EmptySquare;
9148         board[toY][4] = BlackRook;
9149     } else if (fromY == 7 && fromX == 3
9150                && board[fromY][fromX] == BlackKing
9151                && toY == 7 && toX == 1) {
9152         board[fromY][fromX] = EmptySquare;
9153         board[toY][toX] = BlackKing;
9154         board[fromY][0] = EmptySquare;
9155         board[toY][2] = BlackRook;
9156     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9157                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9158                && toY < promoRank && promoChar
9159                ) {
9160         /* black pawn promotion */
9161         board[toY][toX] = CharToPiece(ToLower(promoChar));
9162         if(gameInfo.variant==VariantBughouse ||
9163            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9164             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9165         board[fromY][fromX] = EmptySquare;
9166     } else if ((fromY < BOARD_HEIGHT>>1)
9167                && (toX != fromX)
9168                && gameInfo.variant != VariantXiangqi
9169                && gameInfo.variant != VariantBerolina
9170                && (board[fromY][fromX] == BlackPawn)
9171                && (board[toY][toX] == EmptySquare)) {
9172         board[fromY][fromX] = EmptySquare;
9173         board[toY][toX] = BlackPawn;
9174         captured = board[toY + 1][toX];
9175         board[toY + 1][toX] = EmptySquare;
9176     } else if ((fromY == 3)
9177                && (toX == fromX)
9178                && gameInfo.variant == VariantBerolina
9179                && (board[fromY][fromX] == BlackPawn)
9180                && (board[toY][toX] == EmptySquare)) {
9181         board[fromY][fromX] = EmptySquare;
9182         board[toY][toX] = BlackPawn;
9183         if(oldEP & EP_BEROLIN_A) {
9184                 captured = board[fromY][fromX-1];
9185                 board[fromY][fromX-1] = EmptySquare;
9186         }else{  captured = board[fromY][fromX+1];
9187                 board[fromY][fromX+1] = EmptySquare;
9188         }
9189     } else {
9190         board[toY][toX] = board[fromY][fromX];
9191         board[fromY][fromX] = EmptySquare;
9192     }
9193   }
9194
9195     if (gameInfo.holdingsWidth != 0) {
9196
9197       /* !!A lot more code needs to be written to support holdings  */
9198       /* [HGM] OK, so I have written it. Holdings are stored in the */
9199       /* penultimate board files, so they are automaticlly stored   */
9200       /* in the game history.                                       */
9201       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9202                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9203         /* Delete from holdings, by decreasing count */
9204         /* and erasing image if necessary            */
9205         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9206         if(p < (int) BlackPawn) { /* white drop */
9207              p -= (int)WhitePawn;
9208                  p = PieceToNumber((ChessSquare)p);
9209              if(p >= gameInfo.holdingsSize) p = 0;
9210              if(--board[p][BOARD_WIDTH-2] <= 0)
9211                   board[p][BOARD_WIDTH-1] = EmptySquare;
9212              if((int)board[p][BOARD_WIDTH-2] < 0)
9213                         board[p][BOARD_WIDTH-2] = 0;
9214         } else {                  /* black drop */
9215              p -= (int)BlackPawn;
9216                  p = PieceToNumber((ChessSquare)p);
9217              if(p >= gameInfo.holdingsSize) p = 0;
9218              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9219                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9220              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9221                         board[BOARD_HEIGHT-1-p][1] = 0;
9222         }
9223       }
9224       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9225           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9226         /* [HGM] holdings: Add to holdings, if holdings exist */
9227         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9228                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9229                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9230         }
9231         p = (int) captured;
9232         if (p >= (int) BlackPawn) {
9233           p -= (int)BlackPawn;
9234           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9235                   /* in Shogi restore piece to its original  first */
9236                   captured = (ChessSquare) (DEMOTED captured);
9237                   p = DEMOTED p;
9238           }
9239           p = PieceToNumber((ChessSquare)p);
9240           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9241           board[p][BOARD_WIDTH-2]++;
9242           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9243         } else {
9244           p -= (int)WhitePawn;
9245           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9246                   captured = (ChessSquare) (DEMOTED captured);
9247                   p = DEMOTED p;
9248           }
9249           p = PieceToNumber((ChessSquare)p);
9250           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9251           board[BOARD_HEIGHT-1-p][1]++;
9252           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9253         }
9254       }
9255     } else if (gameInfo.variant == VariantAtomic) {
9256       if (captured != EmptySquare) {
9257         int y, x;
9258         for (y = toY-1; y <= toY+1; y++) {
9259           for (x = toX-1; x <= toX+1; x++) {
9260             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9261                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9262               board[y][x] = EmptySquare;
9263             }
9264           }
9265         }
9266         board[toY][toX] = EmptySquare;
9267       }
9268     }
9269     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9270         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9271     } else
9272     if(promoChar == '+') {
9273         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9274         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9275     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9276         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9277     }
9278     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9279                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9280         // [HGM] superchess: take promotion piece out of holdings
9281         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9282         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9283             if(!--board[k][BOARD_WIDTH-2])
9284                 board[k][BOARD_WIDTH-1] = EmptySquare;
9285         } else {
9286             if(!--board[BOARD_HEIGHT-1-k][1])
9287                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9288         }
9289     }
9290
9291 }
9292
9293 /* Updates forwardMostMove */
9294 void
9295 MakeMove(fromX, fromY, toX, toY, promoChar)
9296      int fromX, fromY, toX, toY;
9297      int promoChar;
9298 {
9299 //    forwardMostMove++; // [HGM] bare: moved downstream
9300
9301     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9302         int timeLeft; static int lastLoadFlag=0; int king, piece;
9303         piece = boards[forwardMostMove][fromY][fromX];
9304         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9305         if(gameInfo.variant == VariantKnightmate)
9306             king += (int) WhiteUnicorn - (int) WhiteKing;
9307         if(forwardMostMove == 0) {
9308             if(blackPlaysFirst)
9309                 fprintf(serverMoves, "%s;", second.tidy);
9310             fprintf(serverMoves, "%s;", first.tidy);
9311             if(!blackPlaysFirst)
9312                 fprintf(serverMoves, "%s;", second.tidy);
9313         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9314         lastLoadFlag = loadFlag;
9315         // print base move
9316         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9317         // print castling suffix
9318         if( toY == fromY && piece == king ) {
9319             if(toX-fromX > 1)
9320                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9321             if(fromX-toX >1)
9322                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9323         }
9324         // e.p. suffix
9325         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9326              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9327              boards[forwardMostMove][toY][toX] == EmptySquare
9328              && fromX != toX && fromY != toY)
9329                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9330         // promotion suffix
9331         if(promoChar != NULLCHAR)
9332                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9333         if(!loadFlag) {
9334             fprintf(serverMoves, "/%d/%d",
9335                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9336             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9337             else                      timeLeft = blackTimeRemaining/1000;
9338             fprintf(serverMoves, "/%d", timeLeft);
9339         }
9340         fflush(serverMoves);
9341     }
9342
9343     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9344       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9345                         0, 1);
9346       return;
9347     }
9348     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9349     if (commentList[forwardMostMove+1] != NULL) {
9350         free(commentList[forwardMostMove+1]);
9351         commentList[forwardMostMove+1] = NULL;
9352     }
9353     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9354     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9355     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9356     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9357     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9358     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9359     gameInfo.result = GameUnfinished;
9360     if (gameInfo.resultDetails != NULL) {
9361         free(gameInfo.resultDetails);
9362         gameInfo.resultDetails = NULL;
9363     }
9364     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9365                               moveList[forwardMostMove - 1]);
9366     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9367                              PosFlags(forwardMostMove - 1),
9368                              fromY, fromX, toY, toX, promoChar,
9369                              parseList[forwardMostMove - 1]);
9370     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9371       case MT_NONE:
9372       case MT_STALEMATE:
9373       default:
9374         break;
9375       case MT_CHECK:
9376         if(gameInfo.variant != VariantShogi)
9377             strcat(parseList[forwardMostMove - 1], "+");
9378         break;
9379       case MT_CHECKMATE:
9380       case MT_STAINMATE:
9381         strcat(parseList[forwardMostMove - 1], "#");
9382         break;
9383     }
9384     if (appData.debugMode) {
9385         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9386     }
9387
9388 }
9389
9390 /* Updates currentMove if not pausing */
9391 void
9392 ShowMove(fromX, fromY, toX, toY)
9393 {
9394     int instant = (gameMode == PlayFromGameFile) ?
9395         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9396     if(appData.noGUI) return;
9397     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9398         if (!instant) {
9399             if (forwardMostMove == currentMove + 1) {
9400                 AnimateMove(boards[forwardMostMove - 1],
9401                             fromX, fromY, toX, toY);
9402             }
9403             if (appData.highlightLastMove) {
9404                 SetHighlights(fromX, fromY, toX, toY);
9405             }
9406         }
9407         currentMove = forwardMostMove;
9408     }
9409
9410     if (instant) return;
9411
9412     DisplayMove(currentMove - 1);
9413     DrawPosition(FALSE, boards[currentMove]);
9414     DisplayBothClocks();
9415     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9416     DisplayBook(currentMove);
9417 }
9418
9419 void SendEgtPath(ChessProgramState *cps)
9420 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9421         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9422
9423         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9424
9425         while(*p) {
9426             char c, *q = name+1, *r, *s;
9427
9428             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9429             while(*p && *p != ',') *q++ = *p++;
9430             *q++ = ':'; *q = 0;
9431             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9432                 strcmp(name, ",nalimov:") == 0 ) {
9433                 // take nalimov path from the menu-changeable option first, if it is defined
9434               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9435                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9436             } else
9437             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9438                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9439                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9440                 s = r = StrStr(s, ":") + 1; // beginning of path info
9441                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9442                 c = *r; *r = 0;             // temporarily null-terminate path info
9443                     *--q = 0;               // strip of trailig ':' from name
9444                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9445                 *r = c;
9446                 SendToProgram(buf,cps);     // send egtbpath command for this format
9447             }
9448             if(*p == ',') p++; // read away comma to position for next format name
9449         }
9450 }
9451
9452 void
9453 InitChessProgram(cps, setup)
9454      ChessProgramState *cps;
9455      int setup; /* [HGM] needed to setup FRC opening position */
9456 {
9457     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9458     if (appData.noChessProgram) return;
9459     hintRequested = FALSE;
9460     bookRequested = FALSE;
9461
9462     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9463     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9464     if(cps->memSize) { /* [HGM] memory */
9465       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9466         SendToProgram(buf, cps);
9467     }
9468     SendEgtPath(cps); /* [HGM] EGT */
9469     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9470       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9471         SendToProgram(buf, cps);
9472     }
9473
9474     SendToProgram(cps->initString, cps);
9475     if (gameInfo.variant != VariantNormal &&
9476         gameInfo.variant != VariantLoadable
9477         /* [HGM] also send variant if board size non-standard */
9478         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9479                                             ) {
9480       char *v = VariantName(gameInfo.variant);
9481       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9482         /* [HGM] in protocol 1 we have to assume all variants valid */
9483         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9484         DisplayFatalError(buf, 0, 1);
9485         return;
9486       }
9487
9488       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9489       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9490       if( gameInfo.variant == VariantXiangqi )
9491            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9492       if( gameInfo.variant == VariantShogi )
9493            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9494       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9495            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9496       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9497           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9498            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9499       if( gameInfo.variant == VariantCourier )
9500            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9501       if( gameInfo.variant == VariantSuper )
9502            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9503       if( gameInfo.variant == VariantGreat )
9504            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9505       if( gameInfo.variant == VariantSChess )
9506            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9507       if( gameInfo.variant == VariantGrand )
9508            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9509
9510       if(overruled) {
9511         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9512                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9513            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9514            if(StrStr(cps->variants, b) == NULL) {
9515                // specific sized variant not known, check if general sizing allowed
9516                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9517                    if(StrStr(cps->variants, "boardsize") == NULL) {
9518                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9519                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9520                        DisplayFatalError(buf, 0, 1);
9521                        return;
9522                    }
9523                    /* [HGM] here we really should compare with the maximum supported board size */
9524                }
9525            }
9526       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9527       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9528       SendToProgram(buf, cps);
9529     }
9530     currentlyInitializedVariant = gameInfo.variant;
9531
9532     /* [HGM] send opening position in FRC to first engine */
9533     if(setup) {
9534           SendToProgram("force\n", cps);
9535           SendBoard(cps, 0);
9536           /* engine is now in force mode! Set flag to wake it up after first move. */
9537           setboardSpoiledMachineBlack = 1;
9538     }
9539
9540     if (cps->sendICS) {
9541       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9542       SendToProgram(buf, cps);
9543     }
9544     cps->maybeThinking = FALSE;
9545     cps->offeredDraw = 0;
9546     if (!appData.icsActive) {
9547         SendTimeControl(cps, movesPerSession, timeControl,
9548                         timeIncrement, appData.searchDepth,
9549                         searchTime);
9550     }
9551     if (appData.showThinking
9552         // [HGM] thinking: four options require thinking output to be sent
9553         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9554                                 ) {
9555         SendToProgram("post\n", cps);
9556     }
9557     SendToProgram("hard\n", cps);
9558     if (!appData.ponderNextMove) {
9559         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9560            it without being sure what state we are in first.  "hard"
9561            is not a toggle, so that one is OK.
9562          */
9563         SendToProgram("easy\n", cps);
9564     }
9565     if (cps->usePing) {
9566       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9567       SendToProgram(buf, cps);
9568     }
9569     cps->initDone = TRUE;
9570     ClearEngineOutputPane(cps == &second);
9571 }
9572
9573
9574 void
9575 StartChessProgram(cps)
9576      ChessProgramState *cps;
9577 {
9578     char buf[MSG_SIZ];
9579     int err;
9580
9581     if (appData.noChessProgram) return;
9582     cps->initDone = FALSE;
9583
9584     if (strcmp(cps->host, "localhost") == 0) {
9585         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9586     } else if (*appData.remoteShell == NULLCHAR) {
9587         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9588     } else {
9589         if (*appData.remoteUser == NULLCHAR) {
9590           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9591                     cps->program);
9592         } else {
9593           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9594                     cps->host, appData.remoteUser, cps->program);
9595         }
9596         err = StartChildProcess(buf, "", &cps->pr);
9597     }
9598
9599     if (err != 0) {
9600       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9601         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9602         if(cps != &first) return;
9603         appData.noChessProgram = TRUE;
9604         ThawUI();
9605         SetNCPMode();
9606 //      DisplayFatalError(buf, err, 1);
9607 //      cps->pr = NoProc;
9608 //      cps->isr = NULL;
9609         return;
9610     }
9611
9612     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9613     if (cps->protocolVersion > 1) {
9614       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9615       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9616       cps->comboCnt = 0;  //                and values of combo boxes
9617       SendToProgram(buf, cps);
9618     } else {
9619       SendToProgram("xboard\n", cps);
9620     }
9621 }
9622
9623 void
9624 TwoMachinesEventIfReady P((void))
9625 {
9626   static int curMess = 0;
9627   if (first.lastPing != first.lastPong) {
9628     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9629     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9630     return;
9631   }
9632   if (second.lastPing != second.lastPong) {
9633     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9634     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9635     return;
9636   }
9637   DisplayMessage("", ""); curMess = 0;
9638   ThawUI();
9639   TwoMachinesEvent();
9640 }
9641
9642 char *
9643 MakeName(char *template)
9644 {
9645     time_t clock;
9646     struct tm *tm;
9647     static char buf[MSG_SIZ];
9648     char *p = buf;
9649     int i;
9650
9651     clock = time((time_t *)NULL);
9652     tm = localtime(&clock);
9653
9654     while(*p++ = *template++) if(p[-1] == '%') {
9655         switch(*template++) {
9656           case 0:   *p = 0; return buf;
9657           case 'Y': i = tm->tm_year+1900; break;
9658           case 'y': i = tm->tm_year-100; break;
9659           case 'M': i = tm->tm_mon+1; break;
9660           case 'd': i = tm->tm_mday; break;
9661           case 'h': i = tm->tm_hour; break;
9662           case 'm': i = tm->tm_min; break;
9663           case 's': i = tm->tm_sec; break;
9664           default:  i = 0;
9665         }
9666         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9667     }
9668     return buf;
9669 }
9670
9671 int
9672 CountPlayers(char *p)
9673 {
9674     int n = 0;
9675     while(p = strchr(p, '\n')) p++, n++; // count participants
9676     return n;
9677 }
9678
9679 FILE *
9680 WriteTourneyFile(char *results)
9681 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9682     FILE *f = fopen(appData.tourneyFile, "w");
9683     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9684         // create a file with tournament description
9685         fprintf(f, "-participants {%s}\n", appData.participants);
9686         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9687         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9688         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9689         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9690         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9691         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9692         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9693         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9694         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9695         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9696         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9697         if(searchTime > 0)
9698                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9699         else {
9700                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9701                 fprintf(f, "-tc %s\n", appData.timeControl);
9702                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9703         }
9704         fprintf(f, "-results \"%s\"\n", results);
9705     }
9706     return f;
9707 }
9708
9709 int
9710 CreateTourney(char *name)
9711 {
9712         FILE *f;
9713         if(name[0] == NULLCHAR) {
9714             if(appData.participants[0])
9715                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9716             return 0;
9717         }
9718         f = fopen(name, "r");
9719         if(f) { // file exists
9720             ASSIGN(appData.tourneyFile, name);
9721             ParseArgsFromFile(f); // parse it
9722         } else {
9723             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9724             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9725                 DisplayError(_("Not enough participants"), 0);
9726                 return 0;
9727             }
9728             ASSIGN(appData.tourneyFile, name);
9729             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9730             if((f = WriteTourneyFile("")) == NULL) return 0;
9731         }
9732         fclose(f);
9733         appData.noChessProgram = FALSE;
9734         appData.clockMode = TRUE;
9735         SetGNUMode();
9736         return 1;
9737 }
9738
9739 #define MAXENGINES 1000
9740 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9741
9742 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9743 {
9744     char buf[MSG_SIZ], *p, *q;
9745     int i=1;
9746     while(*names) {
9747         p = names; q = buf;
9748         while(*p && *p != '\n') *q++ = *p++;
9749         *q = 0;
9750         if(engineList[i]) free(engineList[i]);
9751         engineList[i] = strdup(buf);
9752         if(*p == '\n') p++;
9753         TidyProgramName(engineList[i], "localhost", buf);
9754         if(engineMnemonic[i]) free(engineMnemonic[i]);
9755         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9756             strcat(buf, " (");
9757             sscanf(q + 8, "%s", buf + strlen(buf));
9758             strcat(buf, ")");
9759         }
9760         engineMnemonic[i] = strdup(buf);
9761         names = p; i++;
9762       if(i > MAXENGINES - 2) break;
9763     }
9764     engineList[i] = NULL;
9765 }
9766
9767 // following implemented as macro to avoid type limitations
9768 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9769
9770 void SwapEngines(int n)
9771 {   // swap settings for first engine and other engine (so far only some selected options)
9772     int h;
9773     char *p;
9774     if(n == 0) return;
9775     SWAP(directory, p)
9776     SWAP(chessProgram, p)
9777     SWAP(isUCI, h)
9778     SWAP(hasOwnBookUCI, h)
9779     SWAP(protocolVersion, h)
9780     SWAP(reuse, h)
9781     SWAP(scoreIsAbsolute, h)
9782     SWAP(timeOdds, h)
9783     SWAP(logo, p)
9784     SWAP(pgnName, p)
9785     SWAP(pvSAN, h)
9786 }
9787
9788 void
9789 SetPlayer(int player)
9790 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9791     int i;
9792     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9793     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9794     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9795     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9796     if(mnemonic[i]) {
9797         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9798         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9799         ParseArgsFromString(buf);
9800     }
9801     free(engineName);
9802 }
9803
9804 int
9805 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9806 {   // determine players from game number
9807     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9808
9809     if(appData.tourneyType == 0) {
9810         roundsPerCycle = (nPlayers - 1) | 1;
9811         pairingsPerRound = nPlayers / 2;
9812     } else if(appData.tourneyType > 0) {
9813         roundsPerCycle = nPlayers - appData.tourneyType;
9814         pairingsPerRound = appData.tourneyType;
9815     }
9816     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9817     gamesPerCycle = gamesPerRound * roundsPerCycle;
9818     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9819     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9820     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9821     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9822     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9823     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9824
9825     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9826     if(appData.roundSync) *syncInterval = gamesPerRound;
9827
9828     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9829
9830     if(appData.tourneyType == 0) {
9831         if(curPairing == (nPlayers-1)/2 ) {
9832             *whitePlayer = curRound;
9833             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9834         } else {
9835             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9836             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9837             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9838             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9839         }
9840     } else if(appData.tourneyType > 0) {
9841         *whitePlayer = curPairing;
9842         *blackPlayer = curRound + appData.tourneyType;
9843     }
9844
9845     // take care of white/black alternation per round. 
9846     // For cycles and games this is already taken care of by default, derived from matchGame!
9847     return curRound & 1;
9848 }
9849
9850 int
9851 NextTourneyGame(int nr, int *swapColors)
9852 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9853     char *p, *q;
9854     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9855     FILE *tf;
9856     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9857     tf = fopen(appData.tourneyFile, "r");
9858     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9859     ParseArgsFromFile(tf); fclose(tf);
9860     InitTimeControls(); // TC might be altered from tourney file
9861
9862     nPlayers = CountPlayers(appData.participants); // count participants
9863     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9864     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9865
9866     if(syncInterval) {
9867         p = q = appData.results;
9868         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9869         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9870             DisplayMessage(_("Waiting for other game(s)"),"");
9871             waitingForGame = TRUE;
9872             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9873             return 0;
9874         }
9875         waitingForGame = FALSE;
9876     }
9877
9878     if(appData.tourneyType < 0) {
9879         if(nr>=0 && !pairingReceived) {
9880             char buf[1<<16];
9881             if(pairing.pr == NoProc) {
9882                 if(!appData.pairingEngine[0]) {
9883                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9884                     return 0;
9885                 }
9886                 StartChessProgram(&pairing); // starts the pairing engine
9887             }
9888             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9889             SendToProgram(buf, &pairing);
9890             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9891             SendToProgram(buf, &pairing);
9892             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9893         }
9894         pairingReceived = 0;                              // ... so we continue here 
9895         *swapColors = 0;
9896         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9897         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9898         matchGame = 1; roundNr = nr / syncInterval + 1;
9899     }
9900
9901     if(first.pr != NoProc) return 1; // engines already loaded
9902
9903     // redefine engines, engine dir, etc.
9904     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9905     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9906     SwapEngines(1);
9907     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9908     SwapEngines(1);         // and make that valid for second engine by swapping
9909     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9910     InitEngine(&second, 1);
9911     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9912     UpdateLogos(FALSE);     // leave display to ModeHiglight()
9913     return 1;
9914 }
9915
9916 void
9917 NextMatchGame()
9918 {   // performs game initialization that does not invoke engines, and then tries to start the game
9919     int firstWhite, swapColors = 0;
9920     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9921     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9922     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9923     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9924     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9925     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9926     Reset(FALSE, first.pr != NoProc);
9927     appData.noChessProgram = FALSE;
9928     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9929     TwoMachinesEvent();
9930 }
9931
9932 void UserAdjudicationEvent( int result )
9933 {
9934     ChessMove gameResult = GameIsDrawn;
9935
9936     if( result > 0 ) {
9937         gameResult = WhiteWins;
9938     }
9939     else if( result < 0 ) {
9940         gameResult = BlackWins;
9941     }
9942
9943     if( gameMode == TwoMachinesPlay ) {
9944         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9945     }
9946 }
9947
9948
9949 // [HGM] save: calculate checksum of game to make games easily identifiable
9950 int StringCheckSum(char *s)
9951 {
9952         int i = 0;
9953         if(s==NULL) return 0;
9954         while(*s) i = i*259 + *s++;
9955         return i;
9956 }
9957
9958 int GameCheckSum()
9959 {
9960         int i, sum=0;
9961         for(i=backwardMostMove; i<forwardMostMove; i++) {
9962                 sum += pvInfoList[i].depth;
9963                 sum += StringCheckSum(parseList[i]);
9964                 sum += StringCheckSum(commentList[i]);
9965                 sum *= 261;
9966         }
9967         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9968         return sum + StringCheckSum(commentList[i]);
9969 } // end of save patch
9970
9971 void
9972 GameEnds(result, resultDetails, whosays)
9973      ChessMove result;
9974      char *resultDetails;
9975      int whosays;
9976 {
9977     GameMode nextGameMode;
9978     int isIcsGame;
9979     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9980
9981     if(endingGame) return; /* [HGM] crash: forbid recursion */
9982     endingGame = 1;
9983     if(twoBoards) { // [HGM] dual: switch back to one board
9984         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9985         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9986     }
9987     if (appData.debugMode) {
9988       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9989               result, resultDetails ? resultDetails : "(null)", whosays);
9990     }
9991
9992     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9993
9994     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9995         /* If we are playing on ICS, the server decides when the
9996            game is over, but the engine can offer to draw, claim
9997            a draw, or resign.
9998          */
9999 #if ZIPPY
10000         if (appData.zippyPlay && first.initDone) {
10001             if (result == GameIsDrawn) {
10002                 /* In case draw still needs to be claimed */
10003                 SendToICS(ics_prefix);
10004                 SendToICS("draw\n");
10005             } else if (StrCaseStr(resultDetails, "resign")) {
10006                 SendToICS(ics_prefix);
10007                 SendToICS("resign\n");
10008             }
10009         }
10010 #endif
10011         endingGame = 0; /* [HGM] crash */
10012         return;
10013     }
10014
10015     /* If we're loading the game from a file, stop */
10016     if (whosays == GE_FILE) {
10017       (void) StopLoadGameTimer();
10018       gameFileFP = NULL;
10019     }
10020
10021     /* Cancel draw offers */
10022     first.offeredDraw = second.offeredDraw = 0;
10023
10024     /* If this is an ICS game, only ICS can really say it's done;
10025        if not, anyone can. */
10026     isIcsGame = (gameMode == IcsPlayingWhite ||
10027                  gameMode == IcsPlayingBlack ||
10028                  gameMode == IcsObserving    ||
10029                  gameMode == IcsExamining);
10030
10031     if (!isIcsGame || whosays == GE_ICS) {
10032         /* OK -- not an ICS game, or ICS said it was done */
10033         StopClocks();
10034         if (!isIcsGame && !appData.noChessProgram)
10035           SetUserThinkingEnables();
10036
10037         /* [HGM] if a machine claims the game end we verify this claim */
10038         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10039             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10040                 char claimer;
10041                 ChessMove trueResult = (ChessMove) -1;
10042
10043                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10044                                             first.twoMachinesColor[0] :
10045                                             second.twoMachinesColor[0] ;
10046
10047                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10048                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10049                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10050                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10051                 } else
10052                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10053                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10054                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10055                 } else
10056                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10057                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10058                 }
10059
10060                 // now verify win claims, but not in drop games, as we don't understand those yet
10061                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10062                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10063                     (result == WhiteWins && claimer == 'w' ||
10064                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10065                       if (appData.debugMode) {
10066                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10067                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10068                       }
10069                       if(result != trueResult) {
10070                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10071                               result = claimer == 'w' ? BlackWins : WhiteWins;
10072                               resultDetails = buf;
10073                       }
10074                 } else
10075                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10076                     && (forwardMostMove <= backwardMostMove ||
10077                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10078                         (claimer=='b')==(forwardMostMove&1))
10079                                                                                   ) {
10080                       /* [HGM] verify: draws that were not flagged are false claims */
10081                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10082                       result = claimer == 'w' ? BlackWins : WhiteWins;
10083                       resultDetails = buf;
10084                 }
10085                 /* (Claiming a loss is accepted no questions asked!) */
10086             }
10087             /* [HGM] bare: don't allow bare King to win */
10088             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10089                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10090                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10091                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10092                && result != GameIsDrawn)
10093             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10094                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10095                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10096                         if(p >= 0 && p <= (int)WhiteKing) k++;
10097                 }
10098                 if (appData.debugMode) {
10099                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10100                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10101                 }
10102                 if(k <= 1) {
10103                         result = GameIsDrawn;
10104                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10105                         resultDetails = buf;
10106                 }
10107             }
10108         }
10109
10110
10111         if(serverMoves != NULL && !loadFlag) { char c = '=';
10112             if(result==WhiteWins) c = '+';
10113             if(result==BlackWins) c = '-';
10114             if(resultDetails != NULL)
10115                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10116         }
10117         if (resultDetails != NULL) {
10118             gameInfo.result = result;
10119             gameInfo.resultDetails = StrSave(resultDetails);
10120
10121             /* display last move only if game was not loaded from file */
10122             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10123                 DisplayMove(currentMove - 1);
10124
10125             if (forwardMostMove != 0) {
10126                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10127                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10128                                                                 ) {
10129                     if (*appData.saveGameFile != NULLCHAR) {
10130                         SaveGameToFile(appData.saveGameFile, TRUE);
10131                     } else if (appData.autoSaveGames) {
10132                         AutoSaveGame();
10133                     }
10134                     if (*appData.savePositionFile != NULLCHAR) {
10135                         SavePositionToFile(appData.savePositionFile);
10136                     }
10137                 }
10138             }
10139
10140             /* Tell program how game ended in case it is learning */
10141             /* [HGM] Moved this to after saving the PGN, just in case */
10142             /* engine died and we got here through time loss. In that */
10143             /* case we will get a fatal error writing the pipe, which */
10144             /* would otherwise lose us the PGN.                       */
10145             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10146             /* output during GameEnds should never be fatal anymore   */
10147             if (gameMode == MachinePlaysWhite ||
10148                 gameMode == MachinePlaysBlack ||
10149                 gameMode == TwoMachinesPlay ||
10150                 gameMode == IcsPlayingWhite ||
10151                 gameMode == IcsPlayingBlack ||
10152                 gameMode == BeginningOfGame) {
10153                 char buf[MSG_SIZ];
10154                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10155                         resultDetails);
10156                 if (first.pr != NoProc) {
10157                     SendToProgram(buf, &first);
10158                 }
10159                 if (second.pr != NoProc &&
10160                     gameMode == TwoMachinesPlay) {
10161                     SendToProgram(buf, &second);
10162                 }
10163             }
10164         }
10165
10166         if (appData.icsActive) {
10167             if (appData.quietPlay &&
10168                 (gameMode == IcsPlayingWhite ||
10169                  gameMode == IcsPlayingBlack)) {
10170                 SendToICS(ics_prefix);
10171                 SendToICS("set shout 1\n");
10172             }
10173             nextGameMode = IcsIdle;
10174             ics_user_moved = FALSE;
10175             /* clean up premove.  It's ugly when the game has ended and the
10176              * premove highlights are still on the board.
10177              */
10178             if (gotPremove) {
10179               gotPremove = FALSE;
10180               ClearPremoveHighlights();
10181               DrawPosition(FALSE, boards[currentMove]);
10182             }
10183             if (whosays == GE_ICS) {
10184                 switch (result) {
10185                 case WhiteWins:
10186                     if (gameMode == IcsPlayingWhite)
10187                         PlayIcsWinSound();
10188                     else if(gameMode == IcsPlayingBlack)
10189                         PlayIcsLossSound();
10190                     break;
10191                 case BlackWins:
10192                     if (gameMode == IcsPlayingBlack)
10193                         PlayIcsWinSound();
10194                     else if(gameMode == IcsPlayingWhite)
10195                         PlayIcsLossSound();
10196                     break;
10197                 case GameIsDrawn:
10198                     PlayIcsDrawSound();
10199                     break;
10200                 default:
10201                     PlayIcsUnfinishedSound();
10202                 }
10203             }
10204         } else if (gameMode == EditGame ||
10205                    gameMode == PlayFromGameFile ||
10206                    gameMode == AnalyzeMode ||
10207                    gameMode == AnalyzeFile) {
10208             nextGameMode = gameMode;
10209         } else {
10210             nextGameMode = EndOfGame;
10211         }
10212         pausing = FALSE;
10213         ModeHighlight();
10214     } else {
10215         nextGameMode = gameMode;
10216     }
10217
10218     if (appData.noChessProgram) {
10219         gameMode = nextGameMode;
10220         ModeHighlight();
10221         endingGame = 0; /* [HGM] crash */
10222         return;
10223     }
10224
10225     if (first.reuse) {
10226         /* Put first chess program into idle state */
10227         if (first.pr != NoProc &&
10228             (gameMode == MachinePlaysWhite ||
10229              gameMode == MachinePlaysBlack ||
10230              gameMode == TwoMachinesPlay ||
10231              gameMode == IcsPlayingWhite ||
10232              gameMode == IcsPlayingBlack ||
10233              gameMode == BeginningOfGame)) {
10234             SendToProgram("force\n", &first);
10235             if (first.usePing) {
10236               char buf[MSG_SIZ];
10237               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10238               SendToProgram(buf, &first);
10239             }
10240         }
10241     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10242         /* Kill off first chess program */
10243         if (first.isr != NULL)
10244           RemoveInputSource(first.isr);
10245         first.isr = NULL;
10246
10247         if (first.pr != NoProc) {
10248             ExitAnalyzeMode();
10249             DoSleep( appData.delayBeforeQuit );
10250             SendToProgram("quit\n", &first);
10251             DoSleep( appData.delayAfterQuit );
10252             DestroyChildProcess(first.pr, first.useSigterm);
10253         }
10254         first.pr = NoProc;
10255     }
10256     if (second.reuse) {
10257         /* Put second chess program into idle state */
10258         if (second.pr != NoProc &&
10259             gameMode == TwoMachinesPlay) {
10260             SendToProgram("force\n", &second);
10261             if (second.usePing) {
10262               char buf[MSG_SIZ];
10263               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10264               SendToProgram(buf, &second);
10265             }
10266         }
10267     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10268         /* Kill off second chess program */
10269         if (second.isr != NULL)
10270           RemoveInputSource(second.isr);
10271         second.isr = NULL;
10272
10273         if (second.pr != NoProc) {
10274             DoSleep( appData.delayBeforeQuit );
10275             SendToProgram("quit\n", &second);
10276             DoSleep( appData.delayAfterQuit );
10277             DestroyChildProcess(second.pr, second.useSigterm);
10278         }
10279         second.pr = NoProc;
10280     }
10281
10282     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10283         char resChar = '=';
10284         switch (result) {
10285         case WhiteWins:
10286           resChar = '+';
10287           if (first.twoMachinesColor[0] == 'w') {
10288             first.matchWins++;
10289           } else {
10290             second.matchWins++;
10291           }
10292           break;
10293         case BlackWins:
10294           resChar = '-';
10295           if (first.twoMachinesColor[0] == 'b') {
10296             first.matchWins++;
10297           } else {
10298             second.matchWins++;
10299           }
10300           break;
10301         case GameUnfinished:
10302           resChar = ' ';
10303         default:
10304           break;
10305         }
10306
10307         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10308         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10309             ReserveGame(nextGame, resChar); // sets nextGame
10310             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10311             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10312         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10313
10314         if (nextGame <= appData.matchGames && !abortMatch) {
10315             gameMode = nextGameMode;
10316             matchGame = nextGame; // this will be overruled in tourney mode!
10317             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10318             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10319             endingGame = 0; /* [HGM] crash */
10320             return;
10321         } else {
10322             gameMode = nextGameMode;
10323             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10324                      first.tidy, second.tidy,
10325                      first.matchWins, second.matchWins,
10326                      appData.matchGames - (first.matchWins + second.matchWins));
10327             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10328             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10329             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10330                 first.twoMachinesColor = "black\n";
10331                 second.twoMachinesColor = "white\n";
10332             } else {
10333                 first.twoMachinesColor = "white\n";
10334                 second.twoMachinesColor = "black\n";
10335             }
10336         }
10337     }
10338     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10339         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10340       ExitAnalyzeMode();
10341     gameMode = nextGameMode;
10342     ModeHighlight();
10343     endingGame = 0;  /* [HGM] crash */
10344     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10345         if(matchMode == TRUE) { // match through command line: exit with or without popup
10346             if(ranking) {
10347                 ToNrEvent(forwardMostMove);
10348                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10349                 else ExitEvent(0);
10350             } else DisplayFatalError(buf, 0, 0);
10351         } else { // match through menu; just stop, with or without popup
10352             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10353             ModeHighlight();
10354             if(ranking){
10355                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10356             } else DisplayNote(buf);
10357       }
10358       if(ranking) free(ranking);
10359     }
10360 }
10361
10362 /* Assumes program was just initialized (initString sent).
10363    Leaves program in force mode. */
10364 void
10365 FeedMovesToProgram(cps, upto)
10366      ChessProgramState *cps;
10367      int upto;
10368 {
10369     int i;
10370
10371     if (appData.debugMode)
10372       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10373               startedFromSetupPosition ? "position and " : "",
10374               backwardMostMove, upto, cps->which);
10375     if(currentlyInitializedVariant != gameInfo.variant) {
10376       char buf[MSG_SIZ];
10377         // [HGM] variantswitch: make engine aware of new variant
10378         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10379                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10380         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10381         SendToProgram(buf, cps);
10382         currentlyInitializedVariant = gameInfo.variant;
10383     }
10384     SendToProgram("force\n", cps);
10385     if (startedFromSetupPosition) {
10386         SendBoard(cps, backwardMostMove);
10387     if (appData.debugMode) {
10388         fprintf(debugFP, "feedMoves\n");
10389     }
10390     }
10391     for (i = backwardMostMove; i < upto; i++) {
10392         SendMoveToProgram(i, cps);
10393     }
10394 }
10395
10396
10397 int
10398 ResurrectChessProgram()
10399 {
10400      /* The chess program may have exited.
10401         If so, restart it and feed it all the moves made so far. */
10402     static int doInit = 0;
10403
10404     if (appData.noChessProgram) return 1;
10405
10406     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10407         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10408         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10409         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10410     } else {
10411         if (first.pr != NoProc) return 1;
10412         StartChessProgram(&first);
10413     }
10414     InitChessProgram(&first, FALSE);
10415     FeedMovesToProgram(&first, currentMove);
10416
10417     if (!first.sendTime) {
10418         /* can't tell gnuchess what its clock should read,
10419            so we bow to its notion. */
10420         ResetClocks();
10421         timeRemaining[0][currentMove] = whiteTimeRemaining;
10422         timeRemaining[1][currentMove] = blackTimeRemaining;
10423     }
10424
10425     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10426                 appData.icsEngineAnalyze) && first.analysisSupport) {
10427       SendToProgram("analyze\n", &first);
10428       first.analyzing = TRUE;
10429     }
10430     return 1;
10431 }
10432
10433 /*
10434  * Button procedures
10435  */
10436 void
10437 Reset(redraw, init)
10438      int redraw, init;
10439 {
10440     int i;
10441
10442     if (appData.debugMode) {
10443         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10444                 redraw, init, gameMode);
10445     }
10446     CleanupTail(); // [HGM] vari: delete any stored variations
10447     pausing = pauseExamInvalid = FALSE;
10448     startedFromSetupPosition = blackPlaysFirst = FALSE;
10449     firstMove = TRUE;
10450     whiteFlag = blackFlag = FALSE;
10451     userOfferedDraw = FALSE;
10452     hintRequested = bookRequested = FALSE;
10453     first.maybeThinking = FALSE;
10454     second.maybeThinking = FALSE;
10455     first.bookSuspend = FALSE; // [HGM] book
10456     second.bookSuspend = FALSE;
10457     thinkOutput[0] = NULLCHAR;
10458     lastHint[0] = NULLCHAR;
10459     ClearGameInfo(&gameInfo);
10460     gameInfo.variant = StringToVariant(appData.variant);
10461     ics_user_moved = ics_clock_paused = FALSE;
10462     ics_getting_history = H_FALSE;
10463     ics_gamenum = -1;
10464     white_holding[0] = black_holding[0] = NULLCHAR;
10465     ClearProgramStats();
10466     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10467
10468     ResetFrontEnd();
10469     ClearHighlights();
10470     flipView = appData.flipView;
10471     ClearPremoveHighlights();
10472     gotPremove = FALSE;
10473     alarmSounded = FALSE;
10474
10475     GameEnds(EndOfFile, NULL, GE_PLAYER);
10476     if(appData.serverMovesName != NULL) {
10477         /* [HGM] prepare to make moves file for broadcasting */
10478         clock_t t = clock();
10479         if(serverMoves != NULL) fclose(serverMoves);
10480         serverMoves = fopen(appData.serverMovesName, "r");
10481         if(serverMoves != NULL) {
10482             fclose(serverMoves);
10483             /* delay 15 sec before overwriting, so all clients can see end */
10484             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10485         }
10486         serverMoves = fopen(appData.serverMovesName, "w");
10487     }
10488
10489     ExitAnalyzeMode();
10490     gameMode = BeginningOfGame;
10491     ModeHighlight();
10492     if(appData.icsActive) gameInfo.variant = VariantNormal;
10493     currentMove = forwardMostMove = backwardMostMove = 0;
10494     InitPosition(redraw);
10495     for (i = 0; i < MAX_MOVES; i++) {
10496         if (commentList[i] != NULL) {
10497             free(commentList[i]);
10498             commentList[i] = NULL;
10499         }
10500     }
10501     ResetClocks();
10502     timeRemaining[0][0] = whiteTimeRemaining;
10503     timeRemaining[1][0] = blackTimeRemaining;
10504
10505     if (first.pr == NULL) {
10506         StartChessProgram(&first);
10507     }
10508     if (init) {
10509             InitChessProgram(&first, startedFromSetupPosition);
10510     }
10511     DisplayTitle("");
10512     DisplayMessage("", "");
10513     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10514     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10515 }
10516
10517 void
10518 AutoPlayGameLoop()
10519 {
10520     for (;;) {
10521         if (!AutoPlayOneMove())
10522           return;
10523         if (matchMode || appData.timeDelay == 0)
10524           continue;
10525         if (appData.timeDelay < 0)
10526           return;
10527         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10528         break;
10529     }
10530 }
10531
10532
10533 int
10534 AutoPlayOneMove()
10535 {
10536     int fromX, fromY, toX, toY;
10537
10538     if (appData.debugMode) {
10539       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10540     }
10541
10542     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10543       return FALSE;
10544
10545     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10546       pvInfoList[currentMove].depth = programStats.depth;
10547       pvInfoList[currentMove].score = programStats.score;
10548       pvInfoList[currentMove].time  = 0;
10549       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10550     }
10551
10552     if (currentMove >= forwardMostMove) {
10553       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10554       gameMode = EditGame;
10555       ModeHighlight();
10556
10557       /* [AS] Clear current move marker at the end of a game */
10558       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10559
10560       return FALSE;
10561     }
10562
10563     toX = moveList[currentMove][2] - AAA;
10564     toY = moveList[currentMove][3] - ONE;
10565
10566     if (moveList[currentMove][1] == '@') {
10567         if (appData.highlightLastMove) {
10568             SetHighlights(-1, -1, toX, toY);
10569         }
10570     } else {
10571         fromX = moveList[currentMove][0] - AAA;
10572         fromY = moveList[currentMove][1] - ONE;
10573
10574         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10575
10576         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10577
10578         if (appData.highlightLastMove) {
10579             SetHighlights(fromX, fromY, toX, toY);
10580         }
10581     }
10582     DisplayMove(currentMove);
10583     SendMoveToProgram(currentMove++, &first);
10584     DisplayBothClocks();
10585     DrawPosition(FALSE, boards[currentMove]);
10586     // [HGM] PV info: always display, routine tests if empty
10587     DisplayComment(currentMove - 1, commentList[currentMove]);
10588     return TRUE;
10589 }
10590
10591
10592 int
10593 LoadGameOneMove(readAhead)
10594      ChessMove readAhead;
10595 {
10596     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10597     char promoChar = NULLCHAR;
10598     ChessMove moveType;
10599     char move[MSG_SIZ];
10600     char *p, *q;
10601
10602     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10603         gameMode != AnalyzeMode && gameMode != Training) {
10604         gameFileFP = NULL;
10605         return FALSE;
10606     }
10607
10608     yyboardindex = forwardMostMove;
10609     if (readAhead != EndOfFile) {
10610       moveType = readAhead;
10611     } else {
10612       if (gameFileFP == NULL)
10613           return FALSE;
10614       moveType = (ChessMove) Myylex();
10615     }
10616
10617     done = FALSE;
10618     switch (moveType) {
10619       case Comment:
10620         if (appData.debugMode)
10621           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10622         p = yy_text;
10623
10624         /* append the comment but don't display it */
10625         AppendComment(currentMove, p, FALSE);
10626         return TRUE;
10627
10628       case WhiteCapturesEnPassant:
10629       case BlackCapturesEnPassant:
10630       case WhitePromotion:
10631       case BlackPromotion:
10632       case WhiteNonPromotion:
10633       case BlackNonPromotion:
10634       case NormalMove:
10635       case WhiteKingSideCastle:
10636       case WhiteQueenSideCastle:
10637       case BlackKingSideCastle:
10638       case BlackQueenSideCastle:
10639       case WhiteKingSideCastleWild:
10640       case WhiteQueenSideCastleWild:
10641       case BlackKingSideCastleWild:
10642       case BlackQueenSideCastleWild:
10643       /* PUSH Fabien */
10644       case WhiteHSideCastleFR:
10645       case WhiteASideCastleFR:
10646       case BlackHSideCastleFR:
10647       case BlackASideCastleFR:
10648       /* POP Fabien */
10649         if (appData.debugMode)
10650           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10651         fromX = currentMoveString[0] - AAA;
10652         fromY = currentMoveString[1] - ONE;
10653         toX = currentMoveString[2] - AAA;
10654         toY = currentMoveString[3] - ONE;
10655         promoChar = currentMoveString[4];
10656         break;
10657
10658       case WhiteDrop:
10659       case BlackDrop:
10660         if (appData.debugMode)
10661           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10662         fromX = moveType == WhiteDrop ?
10663           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10664         (int) CharToPiece(ToLower(currentMoveString[0]));
10665         fromY = DROP_RANK;
10666         toX = currentMoveString[2] - AAA;
10667         toY = currentMoveString[3] - ONE;
10668         break;
10669
10670       case WhiteWins:
10671       case BlackWins:
10672       case GameIsDrawn:
10673       case GameUnfinished:
10674         if (appData.debugMode)
10675           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10676         p = strchr(yy_text, '{');
10677         if (p == NULL) p = strchr(yy_text, '(');
10678         if (p == NULL) {
10679             p = yy_text;
10680             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10681         } else {
10682             q = strchr(p, *p == '{' ? '}' : ')');
10683             if (q != NULL) *q = NULLCHAR;
10684             p++;
10685         }
10686         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10687         GameEnds(moveType, p, GE_FILE);
10688         done = TRUE;
10689         if (cmailMsgLoaded) {
10690             ClearHighlights();
10691             flipView = WhiteOnMove(currentMove);
10692             if (moveType == GameUnfinished) flipView = !flipView;
10693             if (appData.debugMode)
10694               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10695         }
10696         break;
10697
10698       case EndOfFile:
10699         if (appData.debugMode)
10700           fprintf(debugFP, "Parser hit end of file\n");
10701         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10702           case MT_NONE:
10703           case MT_CHECK:
10704             break;
10705           case MT_CHECKMATE:
10706           case MT_STAINMATE:
10707             if (WhiteOnMove(currentMove)) {
10708                 GameEnds(BlackWins, "Black mates", GE_FILE);
10709             } else {
10710                 GameEnds(WhiteWins, "White mates", GE_FILE);
10711             }
10712             break;
10713           case MT_STALEMATE:
10714             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10715             break;
10716         }
10717         done = TRUE;
10718         break;
10719
10720       case MoveNumberOne:
10721         if (lastLoadGameStart == GNUChessGame) {
10722             /* GNUChessGames have numbers, but they aren't move numbers */
10723             if (appData.debugMode)
10724               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10725                       yy_text, (int) moveType);
10726             return LoadGameOneMove(EndOfFile); /* tail recursion */
10727         }
10728         /* else fall thru */
10729
10730       case XBoardGame:
10731       case GNUChessGame:
10732       case PGNTag:
10733         /* Reached start of next game in file */
10734         if (appData.debugMode)
10735           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10736         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10737           case MT_NONE:
10738           case MT_CHECK:
10739             break;
10740           case MT_CHECKMATE:
10741           case MT_STAINMATE:
10742             if (WhiteOnMove(currentMove)) {
10743                 GameEnds(BlackWins, "Black mates", GE_FILE);
10744             } else {
10745                 GameEnds(WhiteWins, "White mates", GE_FILE);
10746             }
10747             break;
10748           case MT_STALEMATE:
10749             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10750             break;
10751         }
10752         done = TRUE;
10753         break;
10754
10755       case PositionDiagram:     /* should not happen; ignore */
10756       case ElapsedTime:         /* ignore */
10757       case NAG:                 /* ignore */
10758         if (appData.debugMode)
10759           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10760                   yy_text, (int) moveType);
10761         return LoadGameOneMove(EndOfFile); /* tail recursion */
10762
10763       case IllegalMove:
10764         if (appData.testLegality) {
10765             if (appData.debugMode)
10766               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10767             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10768                     (forwardMostMove / 2) + 1,
10769                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10770             DisplayError(move, 0);
10771             done = TRUE;
10772         } else {
10773             if (appData.debugMode)
10774               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10775                       yy_text, currentMoveString);
10776             fromX = currentMoveString[0] - AAA;
10777             fromY = currentMoveString[1] - ONE;
10778             toX = currentMoveString[2] - AAA;
10779             toY = currentMoveString[3] - ONE;
10780             promoChar = currentMoveString[4];
10781         }
10782         break;
10783
10784       case AmbiguousMove:
10785         if (appData.debugMode)
10786           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10787         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10788                 (forwardMostMove / 2) + 1,
10789                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10790         DisplayError(move, 0);
10791         done = TRUE;
10792         break;
10793
10794       default:
10795       case ImpossibleMove:
10796         if (appData.debugMode)
10797           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10798         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10799                 (forwardMostMove / 2) + 1,
10800                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10801         DisplayError(move, 0);
10802         done = TRUE;
10803         break;
10804     }
10805
10806     if (done) {
10807         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10808             DrawPosition(FALSE, boards[currentMove]);
10809             DisplayBothClocks();
10810             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10811               DisplayComment(currentMove - 1, commentList[currentMove]);
10812         }
10813         (void) StopLoadGameTimer();
10814         gameFileFP = NULL;
10815         cmailOldMove = forwardMostMove;
10816         return FALSE;
10817     } else {
10818         /* currentMoveString is set as a side-effect of yylex */
10819
10820         thinkOutput[0] = NULLCHAR;
10821         MakeMove(fromX, fromY, toX, toY, promoChar);
10822         currentMove = forwardMostMove;
10823         return TRUE;
10824     }
10825 }
10826
10827 /* Load the nth game from the given file */
10828 int
10829 LoadGameFromFile(filename, n, title, useList)
10830      char *filename;
10831      int n;
10832      char *title;
10833      /*Boolean*/ int useList;
10834 {
10835     FILE *f;
10836     char buf[MSG_SIZ];
10837
10838     if (strcmp(filename, "-") == 0) {
10839         f = stdin;
10840         title = "stdin";
10841     } else {
10842         f = fopen(filename, "rb");
10843         if (f == NULL) {
10844           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10845             DisplayError(buf, errno);
10846             return FALSE;
10847         }
10848     }
10849     if (fseek(f, 0, 0) == -1) {
10850         /* f is not seekable; probably a pipe */
10851         useList = FALSE;
10852     }
10853     if (useList && n == 0) {
10854         int error = GameListBuild(f);
10855         if (error) {
10856             DisplayError(_("Cannot build game list"), error);
10857         } else if (!ListEmpty(&gameList) &&
10858                    ((ListGame *) gameList.tailPred)->number > 1) {
10859             GameListPopUp(f, title);
10860             return TRUE;
10861         }
10862         GameListDestroy();
10863         n = 1;
10864     }
10865     if (n == 0) n = 1;
10866     return LoadGame(f, n, title, FALSE);
10867 }
10868
10869
10870 void
10871 MakeRegisteredMove()
10872 {
10873     int fromX, fromY, toX, toY;
10874     char promoChar;
10875     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10876         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10877           case CMAIL_MOVE:
10878           case CMAIL_DRAW:
10879             if (appData.debugMode)
10880               fprintf(debugFP, "Restoring %s for game %d\n",
10881                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10882
10883             thinkOutput[0] = NULLCHAR;
10884             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10885             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10886             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10887             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10888             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10889             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10890             MakeMove(fromX, fromY, toX, toY, promoChar);
10891             ShowMove(fromX, fromY, toX, toY);
10892
10893             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10894               case MT_NONE:
10895               case MT_CHECK:
10896                 break;
10897
10898               case MT_CHECKMATE:
10899               case MT_STAINMATE:
10900                 if (WhiteOnMove(currentMove)) {
10901                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10902                 } else {
10903                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10904                 }
10905                 break;
10906
10907               case MT_STALEMATE:
10908                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10909                 break;
10910             }
10911
10912             break;
10913
10914           case CMAIL_RESIGN:
10915             if (WhiteOnMove(currentMove)) {
10916                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10917             } else {
10918                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10919             }
10920             break;
10921
10922           case CMAIL_ACCEPT:
10923             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10924             break;
10925
10926           default:
10927             break;
10928         }
10929     }
10930
10931     return;
10932 }
10933
10934 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10935 int
10936 CmailLoadGame(f, gameNumber, title, useList)
10937      FILE *f;
10938      int gameNumber;
10939      char *title;
10940      int useList;
10941 {
10942     int retVal;
10943
10944     if (gameNumber > nCmailGames) {
10945         DisplayError(_("No more games in this message"), 0);
10946         return FALSE;
10947     }
10948     if (f == lastLoadGameFP) {
10949         int offset = gameNumber - lastLoadGameNumber;
10950         if (offset == 0) {
10951             cmailMsg[0] = NULLCHAR;
10952             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10953                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10954                 nCmailMovesRegistered--;
10955             }
10956             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10957             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10958                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10959             }
10960         } else {
10961             if (! RegisterMove()) return FALSE;
10962         }
10963     }
10964
10965     retVal = LoadGame(f, gameNumber, title, useList);
10966
10967     /* Make move registered during previous look at this game, if any */
10968     MakeRegisteredMove();
10969
10970     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10971         commentList[currentMove]
10972           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10973         DisplayComment(currentMove - 1, commentList[currentMove]);
10974     }
10975
10976     return retVal;
10977 }
10978
10979 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10980 int
10981 ReloadGame(offset)
10982      int offset;
10983 {
10984     int gameNumber = lastLoadGameNumber + offset;
10985     if (lastLoadGameFP == NULL) {
10986         DisplayError(_("No game has been loaded yet"), 0);
10987         return FALSE;
10988     }
10989     if (gameNumber <= 0) {
10990         DisplayError(_("Can't back up any further"), 0);
10991         return FALSE;
10992     }
10993     if (cmailMsgLoaded) {
10994         return CmailLoadGame(lastLoadGameFP, gameNumber,
10995                              lastLoadGameTitle, lastLoadGameUseList);
10996     } else {
10997         return LoadGame(lastLoadGameFP, gameNumber,
10998                         lastLoadGameTitle, lastLoadGameUseList);
10999     }
11000 }
11001
11002
11003
11004 /* Load the nth game from open file f */
11005 int
11006 LoadGame(f, gameNumber, title, useList)
11007      FILE *f;
11008      int gameNumber;
11009      char *title;
11010      int useList;
11011 {
11012     ChessMove cm;
11013     char buf[MSG_SIZ];
11014     int gn = gameNumber;
11015     ListGame *lg = NULL;
11016     int numPGNTags = 0;
11017     int err;
11018     GameMode oldGameMode;
11019     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11020
11021     if (appData.debugMode)
11022         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11023
11024     if (gameMode == Training )
11025         SetTrainingModeOff();
11026
11027     oldGameMode = gameMode;
11028     if (gameMode != BeginningOfGame) {
11029       Reset(FALSE, TRUE);
11030     }
11031
11032     gameFileFP = f;
11033     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11034         fclose(lastLoadGameFP);
11035     }
11036
11037     if (useList) {
11038         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11039
11040         if (lg) {
11041             fseek(f, lg->offset, 0);
11042             GameListHighlight(gameNumber);
11043             gn = 1;
11044         }
11045         else {
11046             DisplayError(_("Game number out of range"), 0);
11047             return FALSE;
11048         }
11049     } else {
11050         GameListDestroy();
11051         if (fseek(f, 0, 0) == -1) {
11052             if (f == lastLoadGameFP ?
11053                 gameNumber == lastLoadGameNumber + 1 :
11054                 gameNumber == 1) {
11055                 gn = 1;
11056             } else {
11057                 DisplayError(_("Can't seek on game file"), 0);
11058                 return FALSE;
11059             }
11060         }
11061     }
11062     lastLoadGameFP = f;
11063     lastLoadGameNumber = gameNumber;
11064     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11065     lastLoadGameUseList = useList;
11066
11067     yynewfile(f);
11068
11069     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11070       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11071                 lg->gameInfo.black);
11072             DisplayTitle(buf);
11073     } else if (*title != NULLCHAR) {
11074         if (gameNumber > 1) {
11075           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11076             DisplayTitle(buf);
11077         } else {
11078             DisplayTitle(title);
11079         }
11080     }
11081
11082     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11083         gameMode = PlayFromGameFile;
11084         ModeHighlight();
11085     }
11086
11087     currentMove = forwardMostMove = backwardMostMove = 0;
11088     CopyBoard(boards[0], initialPosition);
11089     StopClocks();
11090
11091     /*
11092      * Skip the first gn-1 games in the file.
11093      * Also skip over anything that precedes an identifiable
11094      * start of game marker, to avoid being confused by
11095      * garbage at the start of the file.  Currently
11096      * recognized start of game markers are the move number "1",
11097      * the pattern "gnuchess .* game", the pattern
11098      * "^[#;%] [^ ]* game file", and a PGN tag block.
11099      * A game that starts with one of the latter two patterns
11100      * will also have a move number 1, possibly
11101      * following a position diagram.
11102      * 5-4-02: Let's try being more lenient and allowing a game to
11103      * start with an unnumbered move.  Does that break anything?
11104      */
11105     cm = lastLoadGameStart = EndOfFile;
11106     while (gn > 0) {
11107         yyboardindex = forwardMostMove;
11108         cm = (ChessMove) Myylex();
11109         switch (cm) {
11110           case EndOfFile:
11111             if (cmailMsgLoaded) {
11112                 nCmailGames = CMAIL_MAX_GAMES - gn;
11113             } else {
11114                 Reset(TRUE, TRUE);
11115                 DisplayError(_("Game not found in file"), 0);
11116             }
11117             return FALSE;
11118
11119           case GNUChessGame:
11120           case XBoardGame:
11121             gn--;
11122             lastLoadGameStart = cm;
11123             break;
11124
11125           case MoveNumberOne:
11126             switch (lastLoadGameStart) {
11127               case GNUChessGame:
11128               case XBoardGame:
11129               case PGNTag:
11130                 break;
11131               case MoveNumberOne:
11132               case EndOfFile:
11133                 gn--;           /* count this game */
11134                 lastLoadGameStart = cm;
11135                 break;
11136               default:
11137                 /* impossible */
11138                 break;
11139             }
11140             break;
11141
11142           case PGNTag:
11143             switch (lastLoadGameStart) {
11144               case GNUChessGame:
11145               case PGNTag:
11146               case MoveNumberOne:
11147               case EndOfFile:
11148                 gn--;           /* count this game */
11149                 lastLoadGameStart = cm;
11150                 break;
11151               case XBoardGame:
11152                 lastLoadGameStart = cm; /* game counted already */
11153                 break;
11154               default:
11155                 /* impossible */
11156                 break;
11157             }
11158             if (gn > 0) {
11159                 do {
11160                     yyboardindex = forwardMostMove;
11161                     cm = (ChessMove) Myylex();
11162                 } while (cm == PGNTag || cm == Comment);
11163             }
11164             break;
11165
11166           case WhiteWins:
11167           case BlackWins:
11168           case GameIsDrawn:
11169             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11170                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11171                     != CMAIL_OLD_RESULT) {
11172                     nCmailResults ++ ;
11173                     cmailResult[  CMAIL_MAX_GAMES
11174                                 - gn - 1] = CMAIL_OLD_RESULT;
11175                 }
11176             }
11177             break;
11178
11179           case NormalMove:
11180             /* Only a NormalMove can be at the start of a game
11181              * without a position diagram. */
11182             if (lastLoadGameStart == EndOfFile ) {
11183               gn--;
11184               lastLoadGameStart = MoveNumberOne;
11185             }
11186             break;
11187
11188           default:
11189             break;
11190         }
11191     }
11192
11193     if (appData.debugMode)
11194       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11195
11196     if (cm == XBoardGame) {
11197         /* Skip any header junk before position diagram and/or move 1 */
11198         for (;;) {
11199             yyboardindex = forwardMostMove;
11200             cm = (ChessMove) Myylex();
11201
11202             if (cm == EndOfFile ||
11203                 cm == GNUChessGame || cm == XBoardGame) {
11204                 /* Empty game; pretend end-of-file and handle later */
11205                 cm = EndOfFile;
11206                 break;
11207             }
11208
11209             if (cm == MoveNumberOne || cm == PositionDiagram ||
11210                 cm == PGNTag || cm == Comment)
11211               break;
11212         }
11213     } else if (cm == GNUChessGame) {
11214         if (gameInfo.event != NULL) {
11215             free(gameInfo.event);
11216         }
11217         gameInfo.event = StrSave(yy_text);
11218     }
11219
11220     startedFromSetupPosition = FALSE;
11221     while (cm == PGNTag) {
11222         if (appData.debugMode)
11223           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11224         err = ParsePGNTag(yy_text, &gameInfo);
11225         if (!err) numPGNTags++;
11226
11227         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11228         if(gameInfo.variant != oldVariant) {
11229             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11230             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11231             InitPosition(TRUE);
11232             oldVariant = gameInfo.variant;
11233             if (appData.debugMode)
11234               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11235         }
11236
11237
11238         if (gameInfo.fen != NULL) {
11239           Board initial_position;
11240           startedFromSetupPosition = TRUE;
11241           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11242             Reset(TRUE, TRUE);
11243             DisplayError(_("Bad FEN position in file"), 0);
11244             return FALSE;
11245           }
11246           CopyBoard(boards[0], initial_position);
11247           if (blackPlaysFirst) {
11248             currentMove = forwardMostMove = backwardMostMove = 1;
11249             CopyBoard(boards[1], initial_position);
11250             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11251             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11252             timeRemaining[0][1] = whiteTimeRemaining;
11253             timeRemaining[1][1] = blackTimeRemaining;
11254             if (commentList[0] != NULL) {
11255               commentList[1] = commentList[0];
11256               commentList[0] = NULL;
11257             }
11258           } else {
11259             currentMove = forwardMostMove = backwardMostMove = 0;
11260           }
11261           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11262           {   int i;
11263               initialRulePlies = FENrulePlies;
11264               for( i=0; i< nrCastlingRights; i++ )
11265                   initialRights[i] = initial_position[CASTLING][i];
11266           }
11267           yyboardindex = forwardMostMove;
11268           free(gameInfo.fen);
11269           gameInfo.fen = NULL;
11270         }
11271
11272         yyboardindex = forwardMostMove;
11273         cm = (ChessMove) Myylex();
11274
11275         /* Handle comments interspersed among the tags */
11276         while (cm == Comment) {
11277             char *p;
11278             if (appData.debugMode)
11279               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11280             p = yy_text;
11281             AppendComment(currentMove, p, FALSE);
11282             yyboardindex = forwardMostMove;
11283             cm = (ChessMove) Myylex();
11284         }
11285     }
11286
11287     /* don't rely on existence of Event tag since if game was
11288      * pasted from clipboard the Event tag may not exist
11289      */
11290     if (numPGNTags > 0){
11291         char *tags;
11292         if (gameInfo.variant == VariantNormal) {
11293           VariantClass v = StringToVariant(gameInfo.event);
11294           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11295           if(v < VariantShogi) gameInfo.variant = v;
11296         }
11297         if (!matchMode) {
11298           if( appData.autoDisplayTags ) {
11299             tags = PGNTags(&gameInfo);
11300             TagsPopUp(tags, CmailMsg());
11301             free(tags);
11302           }
11303         }
11304     } else {
11305         /* Make something up, but don't display it now */
11306         SetGameInfo();
11307         TagsPopDown();
11308     }
11309
11310     if (cm == PositionDiagram) {
11311         int i, j;
11312         char *p;
11313         Board initial_position;
11314
11315         if (appData.debugMode)
11316           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11317
11318         if (!startedFromSetupPosition) {
11319             p = yy_text;
11320             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11321               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11322                 switch (*p) {
11323                   case '{':
11324                   case '[':
11325                   case '-':
11326                   case ' ':
11327                   case '\t':
11328                   case '\n':
11329                   case '\r':
11330                     break;
11331                   default:
11332                     initial_position[i][j++] = CharToPiece(*p);
11333                     break;
11334                 }
11335             while (*p == ' ' || *p == '\t' ||
11336                    *p == '\n' || *p == '\r') p++;
11337
11338             if (strncmp(p, "black", strlen("black"))==0)
11339               blackPlaysFirst = TRUE;
11340             else
11341               blackPlaysFirst = FALSE;
11342             startedFromSetupPosition = TRUE;
11343
11344             CopyBoard(boards[0], initial_position);
11345             if (blackPlaysFirst) {
11346                 currentMove = forwardMostMove = backwardMostMove = 1;
11347                 CopyBoard(boards[1], initial_position);
11348                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11349                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11350                 timeRemaining[0][1] = whiteTimeRemaining;
11351                 timeRemaining[1][1] = blackTimeRemaining;
11352                 if (commentList[0] != NULL) {
11353                     commentList[1] = commentList[0];
11354                     commentList[0] = NULL;
11355                 }
11356             } else {
11357                 currentMove = forwardMostMove = backwardMostMove = 0;
11358             }
11359         }
11360         yyboardindex = forwardMostMove;
11361         cm = (ChessMove) Myylex();
11362     }
11363
11364     if (first.pr == NoProc) {
11365         StartChessProgram(&first);
11366     }
11367     InitChessProgram(&first, FALSE);
11368     SendToProgram("force\n", &first);
11369     if (startedFromSetupPosition) {
11370         SendBoard(&first, forwardMostMove);
11371     if (appData.debugMode) {
11372         fprintf(debugFP, "Load Game\n");
11373     }
11374         DisplayBothClocks();
11375     }
11376
11377     /* [HGM] server: flag to write setup moves in broadcast file as one */
11378     loadFlag = appData.suppressLoadMoves;
11379
11380     while (cm == Comment) {
11381         char *p;
11382         if (appData.debugMode)
11383           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11384         p = yy_text;
11385         AppendComment(currentMove, p, FALSE);
11386         yyboardindex = forwardMostMove;
11387         cm = (ChessMove) Myylex();
11388     }
11389
11390     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11391         cm == WhiteWins || cm == BlackWins ||
11392         cm == GameIsDrawn || cm == GameUnfinished) {
11393         DisplayMessage("", _("No moves in game"));
11394         if (cmailMsgLoaded) {
11395             if (appData.debugMode)
11396               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11397             ClearHighlights();
11398             flipView = FALSE;
11399         }
11400         DrawPosition(FALSE, boards[currentMove]);
11401         DisplayBothClocks();
11402         gameMode = EditGame;
11403         ModeHighlight();
11404         gameFileFP = NULL;
11405         cmailOldMove = 0;
11406         return TRUE;
11407     }
11408
11409     // [HGM] PV info: routine tests if comment empty
11410     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11411         DisplayComment(currentMove - 1, commentList[currentMove]);
11412     }
11413     if (!matchMode && appData.timeDelay != 0)
11414       DrawPosition(FALSE, boards[currentMove]);
11415
11416     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11417       programStats.ok_to_send = 1;
11418     }
11419
11420     /* if the first token after the PGN tags is a move
11421      * and not move number 1, retrieve it from the parser
11422      */
11423     if (cm != MoveNumberOne)
11424         LoadGameOneMove(cm);
11425
11426     /* load the remaining moves from the file */
11427     while (LoadGameOneMove(EndOfFile)) {
11428       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11429       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11430     }
11431
11432     /* rewind to the start of the game */
11433     currentMove = backwardMostMove;
11434
11435     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11436
11437     if (oldGameMode == AnalyzeFile ||
11438         oldGameMode == AnalyzeMode) {
11439       AnalyzeFileEvent();
11440     }
11441
11442     if (matchMode || appData.timeDelay == 0) {
11443       ToEndEvent();
11444       gameMode = EditGame;
11445       ModeHighlight();
11446     } else if (appData.timeDelay > 0) {
11447       AutoPlayGameLoop();
11448     }
11449
11450     if (appData.debugMode)
11451         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11452
11453     loadFlag = 0; /* [HGM] true game starts */
11454     return TRUE;
11455 }
11456
11457 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11458 int
11459 ReloadPosition(offset)
11460      int offset;
11461 {
11462     int positionNumber = lastLoadPositionNumber + offset;
11463     if (lastLoadPositionFP == NULL) {
11464         DisplayError(_("No position has been loaded yet"), 0);
11465         return FALSE;
11466     }
11467     if (positionNumber <= 0) {
11468         DisplayError(_("Can't back up any further"), 0);
11469         return FALSE;
11470     }
11471     return LoadPosition(lastLoadPositionFP, positionNumber,
11472                         lastLoadPositionTitle);
11473 }
11474
11475 /* Load the nth position from the given file */
11476 int
11477 LoadPositionFromFile(filename, n, title)
11478      char *filename;
11479      int n;
11480      char *title;
11481 {
11482     FILE *f;
11483     char buf[MSG_SIZ];
11484
11485     if (strcmp(filename, "-") == 0) {
11486         return LoadPosition(stdin, n, "stdin");
11487     } else {
11488         f = fopen(filename, "rb");
11489         if (f == NULL) {
11490             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11491             DisplayError(buf, errno);
11492             return FALSE;
11493         } else {
11494             return LoadPosition(f, n, title);
11495         }
11496     }
11497 }
11498
11499 /* Load the nth position from the given open file, and close it */
11500 int
11501 LoadPosition(f, positionNumber, title)
11502      FILE *f;
11503      int positionNumber;
11504      char *title;
11505 {
11506     char *p, line[MSG_SIZ];
11507     Board initial_position;
11508     int i, j, fenMode, pn;
11509
11510     if (gameMode == Training )
11511         SetTrainingModeOff();
11512
11513     if (gameMode != BeginningOfGame) {
11514         Reset(FALSE, TRUE);
11515     }
11516     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11517         fclose(lastLoadPositionFP);
11518     }
11519     if (positionNumber == 0) positionNumber = 1;
11520     lastLoadPositionFP = f;
11521     lastLoadPositionNumber = positionNumber;
11522     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11523     if (first.pr == NoProc) {
11524       StartChessProgram(&first);
11525       InitChessProgram(&first, FALSE);
11526     }
11527     pn = positionNumber;
11528     if (positionNumber < 0) {
11529         /* Negative position number means to seek to that byte offset */
11530         if (fseek(f, -positionNumber, 0) == -1) {
11531             DisplayError(_("Can't seek on position file"), 0);
11532             return FALSE;
11533         };
11534         pn = 1;
11535     } else {
11536         if (fseek(f, 0, 0) == -1) {
11537             if (f == lastLoadPositionFP ?
11538                 positionNumber == lastLoadPositionNumber + 1 :
11539                 positionNumber == 1) {
11540                 pn = 1;
11541             } else {
11542                 DisplayError(_("Can't seek on position file"), 0);
11543                 return FALSE;
11544             }
11545         }
11546     }
11547     /* See if this file is FEN or old-style xboard */
11548     if (fgets(line, MSG_SIZ, f) == NULL) {
11549         DisplayError(_("Position not found in file"), 0);
11550         return FALSE;
11551     }
11552     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11553     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11554
11555     if (pn >= 2) {
11556         if (fenMode || line[0] == '#') pn--;
11557         while (pn > 0) {
11558             /* skip positions before number pn */
11559             if (fgets(line, MSG_SIZ, f) == NULL) {
11560                 Reset(TRUE, TRUE);
11561                 DisplayError(_("Position not found in file"), 0);
11562                 return FALSE;
11563             }
11564             if (fenMode || line[0] == '#') pn--;
11565         }
11566     }
11567
11568     if (fenMode) {
11569         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11570             DisplayError(_("Bad FEN position in file"), 0);
11571             return FALSE;
11572         }
11573     } else {
11574         (void) fgets(line, MSG_SIZ, f);
11575         (void) fgets(line, MSG_SIZ, f);
11576
11577         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11578             (void) fgets(line, MSG_SIZ, f);
11579             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11580                 if (*p == ' ')
11581                   continue;
11582                 initial_position[i][j++] = CharToPiece(*p);
11583             }
11584         }
11585
11586         blackPlaysFirst = FALSE;
11587         if (!feof(f)) {
11588             (void) fgets(line, MSG_SIZ, f);
11589             if (strncmp(line, "black", strlen("black"))==0)
11590               blackPlaysFirst = TRUE;
11591         }
11592     }
11593     startedFromSetupPosition = TRUE;
11594
11595     SendToProgram("force\n", &first);
11596     CopyBoard(boards[0], initial_position);
11597     if (blackPlaysFirst) {
11598         currentMove = forwardMostMove = backwardMostMove = 1;
11599         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11600         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11601         CopyBoard(boards[1], initial_position);
11602         DisplayMessage("", _("Black to play"));
11603     } else {
11604         currentMove = forwardMostMove = backwardMostMove = 0;
11605         DisplayMessage("", _("White to play"));
11606     }
11607     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11608     SendBoard(&first, forwardMostMove);
11609     if (appData.debugMode) {
11610 int i, j;
11611   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11612   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11613         fprintf(debugFP, "Load Position\n");
11614     }
11615
11616     if (positionNumber > 1) {
11617       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11618         DisplayTitle(line);
11619     } else {
11620         DisplayTitle(title);
11621     }
11622     gameMode = EditGame;
11623     ModeHighlight();
11624     ResetClocks();
11625     timeRemaining[0][1] = whiteTimeRemaining;
11626     timeRemaining[1][1] = blackTimeRemaining;
11627     DrawPosition(FALSE, boards[currentMove]);
11628
11629     return TRUE;
11630 }
11631
11632
11633 void
11634 CopyPlayerNameIntoFileName(dest, src)
11635      char **dest, *src;
11636 {
11637     while (*src != NULLCHAR && *src != ',') {
11638         if (*src == ' ') {
11639             *(*dest)++ = '_';
11640             src++;
11641         } else {
11642             *(*dest)++ = *src++;
11643         }
11644     }
11645 }
11646
11647 char *DefaultFileName(ext)
11648      char *ext;
11649 {
11650     static char def[MSG_SIZ];
11651     char *p;
11652
11653     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11654         p = def;
11655         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11656         *p++ = '-';
11657         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11658         *p++ = '.';
11659         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11660     } else {
11661         def[0] = NULLCHAR;
11662     }
11663     return def;
11664 }
11665
11666 /* Save the current game to the given file */
11667 int
11668 SaveGameToFile(filename, append)
11669      char *filename;
11670      int append;
11671 {
11672     FILE *f;
11673     char buf[MSG_SIZ];
11674     int result;
11675
11676     if (strcmp(filename, "-") == 0) {
11677         return SaveGame(stdout, 0, NULL);
11678     } else {
11679         f = fopen(filename, append ? "a" : "w");
11680         if (f == NULL) {
11681             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11682             DisplayError(buf, errno);
11683             return FALSE;
11684         } else {
11685             safeStrCpy(buf, lastMsg, MSG_SIZ);
11686             DisplayMessage(_("Waiting for access to save file"), "");
11687             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11688             DisplayMessage(_("Saving game"), "");
11689             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11690             result = SaveGame(f, 0, NULL);
11691             DisplayMessage(buf, "");
11692             return result;
11693         }
11694     }
11695 }
11696
11697 char *
11698 SavePart(str)
11699      char *str;
11700 {
11701     static char buf[MSG_SIZ];
11702     char *p;
11703
11704     p = strchr(str, ' ');
11705     if (p == NULL) return str;
11706     strncpy(buf, str, p - str);
11707     buf[p - str] = NULLCHAR;
11708     return buf;
11709 }
11710
11711 #define PGN_MAX_LINE 75
11712
11713 #define PGN_SIDE_WHITE  0
11714 #define PGN_SIDE_BLACK  1
11715
11716 /* [AS] */
11717 static int FindFirstMoveOutOfBook( int side )
11718 {
11719     int result = -1;
11720
11721     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11722         int index = backwardMostMove;
11723         int has_book_hit = 0;
11724
11725         if( (index % 2) != side ) {
11726             index++;
11727         }
11728
11729         while( index < forwardMostMove ) {
11730             /* Check to see if engine is in book */
11731             int depth = pvInfoList[index].depth;
11732             int score = pvInfoList[index].score;
11733             int in_book = 0;
11734
11735             if( depth <= 2 ) {
11736                 in_book = 1;
11737             }
11738             else if( score == 0 && depth == 63 ) {
11739                 in_book = 1; /* Zappa */
11740             }
11741             else if( score == 2 && depth == 99 ) {
11742                 in_book = 1; /* Abrok */
11743             }
11744
11745             has_book_hit += in_book;
11746
11747             if( ! in_book ) {
11748                 result = index;
11749
11750                 break;
11751             }
11752
11753             index += 2;
11754         }
11755     }
11756
11757     return result;
11758 }
11759
11760 /* [AS] */
11761 void GetOutOfBookInfo( char * buf )
11762 {
11763     int oob[2];
11764     int i;
11765     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11766
11767     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11768     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11769
11770     *buf = '\0';
11771
11772     if( oob[0] >= 0 || oob[1] >= 0 ) {
11773         for( i=0; i<2; i++ ) {
11774             int idx = oob[i];
11775
11776             if( idx >= 0 ) {
11777                 if( i > 0 && oob[0] >= 0 ) {
11778                     strcat( buf, "   " );
11779                 }
11780
11781                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11782                 sprintf( buf+strlen(buf), "%s%.2f",
11783                     pvInfoList[idx].score >= 0 ? "+" : "",
11784                     pvInfoList[idx].score / 100.0 );
11785             }
11786         }
11787     }
11788 }
11789
11790 /* Save game in PGN style and close the file */
11791 int
11792 SaveGamePGN(f)
11793      FILE *f;
11794 {
11795     int i, offset, linelen, newblock;
11796     time_t tm;
11797 //    char *movetext;
11798     char numtext[32];
11799     int movelen, numlen, blank;
11800     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11801
11802     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11803
11804     tm = time((time_t *) NULL);
11805
11806     PrintPGNTags(f, &gameInfo);
11807
11808     if (backwardMostMove > 0 || startedFromSetupPosition) {
11809         char *fen = PositionToFEN(backwardMostMove, NULL);
11810         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11811         fprintf(f, "\n{--------------\n");
11812         PrintPosition(f, backwardMostMove);
11813         fprintf(f, "--------------}\n");
11814         free(fen);
11815     }
11816     else {
11817         /* [AS] Out of book annotation */
11818         if( appData.saveOutOfBookInfo ) {
11819             char buf[64];
11820
11821             GetOutOfBookInfo( buf );
11822
11823             if( buf[0] != '\0' ) {
11824                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11825             }
11826         }
11827
11828         fprintf(f, "\n");
11829     }
11830
11831     i = backwardMostMove;
11832     linelen = 0;
11833     newblock = TRUE;
11834
11835     while (i < forwardMostMove) {
11836         /* Print comments preceding this move */
11837         if (commentList[i] != NULL) {
11838             if (linelen > 0) fprintf(f, "\n");
11839             fprintf(f, "%s", commentList[i]);
11840             linelen = 0;
11841             newblock = TRUE;
11842         }
11843
11844         /* Format move number */
11845         if ((i % 2) == 0)
11846           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11847         else
11848           if (newblock)
11849             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11850           else
11851             numtext[0] = NULLCHAR;
11852
11853         numlen = strlen(numtext);
11854         newblock = FALSE;
11855
11856         /* Print move number */
11857         blank = linelen > 0 && numlen > 0;
11858         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11859             fprintf(f, "\n");
11860             linelen = 0;
11861             blank = 0;
11862         }
11863         if (blank) {
11864             fprintf(f, " ");
11865             linelen++;
11866         }
11867         fprintf(f, "%s", numtext);
11868         linelen += numlen;
11869
11870         /* Get move */
11871         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11872         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11873
11874         /* Print move */
11875         blank = linelen > 0 && movelen > 0;
11876         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11877             fprintf(f, "\n");
11878             linelen = 0;
11879             blank = 0;
11880         }
11881         if (blank) {
11882             fprintf(f, " ");
11883             linelen++;
11884         }
11885         fprintf(f, "%s", move_buffer);
11886         linelen += movelen;
11887
11888         /* [AS] Add PV info if present */
11889         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11890             /* [HGM] add time */
11891             char buf[MSG_SIZ]; int seconds;
11892
11893             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11894
11895             if( seconds <= 0)
11896               buf[0] = 0;
11897             else
11898               if( seconds < 30 )
11899                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11900               else
11901                 {
11902                   seconds = (seconds + 4)/10; // round to full seconds
11903                   if( seconds < 60 )
11904                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11905                   else
11906                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11907                 }
11908
11909             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11910                       pvInfoList[i].score >= 0 ? "+" : "",
11911                       pvInfoList[i].score / 100.0,
11912                       pvInfoList[i].depth,
11913                       buf );
11914
11915             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11916
11917             /* Print score/depth */
11918             blank = linelen > 0 && movelen > 0;
11919             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11920                 fprintf(f, "\n");
11921                 linelen = 0;
11922                 blank = 0;
11923             }
11924             if (blank) {
11925                 fprintf(f, " ");
11926                 linelen++;
11927             }
11928             fprintf(f, "%s", move_buffer);
11929             linelen += movelen;
11930         }
11931
11932         i++;
11933     }
11934
11935     /* Start a new line */
11936     if (linelen > 0) fprintf(f, "\n");
11937
11938     /* Print comments after last move */
11939     if (commentList[i] != NULL) {
11940         fprintf(f, "%s\n", commentList[i]);
11941     }
11942
11943     /* Print result */
11944     if (gameInfo.resultDetails != NULL &&
11945         gameInfo.resultDetails[0] != NULLCHAR) {
11946         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11947                 PGNResult(gameInfo.result));
11948     } else {
11949         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11950     }
11951
11952     fclose(f);
11953     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11954     return TRUE;
11955 }
11956
11957 /* Save game in old style and close the file */
11958 int
11959 SaveGameOldStyle(f)
11960      FILE *f;
11961 {
11962     int i, offset;
11963     time_t tm;
11964
11965     tm = time((time_t *) NULL);
11966
11967     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11968     PrintOpponents(f);
11969
11970     if (backwardMostMove > 0 || startedFromSetupPosition) {
11971         fprintf(f, "\n[--------------\n");
11972         PrintPosition(f, backwardMostMove);
11973         fprintf(f, "--------------]\n");
11974     } else {
11975         fprintf(f, "\n");
11976     }
11977
11978     i = backwardMostMove;
11979     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11980
11981     while (i < forwardMostMove) {
11982         if (commentList[i] != NULL) {
11983             fprintf(f, "[%s]\n", commentList[i]);
11984         }
11985
11986         if ((i % 2) == 1) {
11987             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11988             i++;
11989         } else {
11990             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11991             i++;
11992             if (commentList[i] != NULL) {
11993                 fprintf(f, "\n");
11994                 continue;
11995             }
11996             if (i >= forwardMostMove) {
11997                 fprintf(f, "\n");
11998                 break;
11999             }
12000             fprintf(f, "%s\n", parseList[i]);
12001             i++;
12002         }
12003     }
12004
12005     if (commentList[i] != NULL) {
12006         fprintf(f, "[%s]\n", commentList[i]);
12007     }
12008
12009     /* This isn't really the old style, but it's close enough */
12010     if (gameInfo.resultDetails != NULL &&
12011         gameInfo.resultDetails[0] != NULLCHAR) {
12012         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12013                 gameInfo.resultDetails);
12014     } else {
12015         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12016     }
12017
12018     fclose(f);
12019     return TRUE;
12020 }
12021
12022 /* Save the current game to open file f and close the file */
12023 int
12024 SaveGame(f, dummy, dummy2)
12025      FILE *f;
12026      int dummy;
12027      char *dummy2;
12028 {
12029     if (gameMode == EditPosition) EditPositionDone(TRUE);
12030     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12031     if (appData.oldSaveStyle)
12032       return SaveGameOldStyle(f);
12033     else
12034       return SaveGamePGN(f);
12035 }
12036
12037 /* Save the current position to the given file */
12038 int
12039 SavePositionToFile(filename)
12040      char *filename;
12041 {
12042     FILE *f;
12043     char buf[MSG_SIZ];
12044
12045     if (strcmp(filename, "-") == 0) {
12046         return SavePosition(stdout, 0, NULL);
12047     } else {
12048         f = fopen(filename, "a");
12049         if (f == NULL) {
12050             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12051             DisplayError(buf, errno);
12052             return FALSE;
12053         } else {
12054             safeStrCpy(buf, lastMsg, MSG_SIZ);
12055             DisplayMessage(_("Waiting for access to save file"), "");
12056             flock(fileno(f), LOCK_EX); // [HGM] lock
12057             DisplayMessage(_("Saving position"), "");
12058             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12059             SavePosition(f, 0, NULL);
12060             DisplayMessage(buf, "");
12061             return TRUE;
12062         }
12063     }
12064 }
12065
12066 /* Save the current position to the given open file and close the file */
12067 int
12068 SavePosition(f, dummy, dummy2)
12069      FILE *f;
12070      int dummy;
12071      char *dummy2;
12072 {
12073     time_t tm;
12074     char *fen;
12075
12076     if (gameMode == EditPosition) EditPositionDone(TRUE);
12077     if (appData.oldSaveStyle) {
12078         tm = time((time_t *) NULL);
12079
12080         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12081         PrintOpponents(f);
12082         fprintf(f, "[--------------\n");
12083         PrintPosition(f, currentMove);
12084         fprintf(f, "--------------]\n");
12085     } else {
12086         fen = PositionToFEN(currentMove, NULL);
12087         fprintf(f, "%s\n", fen);
12088         free(fen);
12089     }
12090     fclose(f);
12091     return TRUE;
12092 }
12093
12094 void
12095 ReloadCmailMsgEvent(unregister)
12096      int unregister;
12097 {
12098 #if !WIN32
12099     static char *inFilename = NULL;
12100     static char *outFilename;
12101     int i;
12102     struct stat inbuf, outbuf;
12103     int status;
12104
12105     /* Any registered moves are unregistered if unregister is set, */
12106     /* i.e. invoked by the signal handler */
12107     if (unregister) {
12108         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12109             cmailMoveRegistered[i] = FALSE;
12110             if (cmailCommentList[i] != NULL) {
12111                 free(cmailCommentList[i]);
12112                 cmailCommentList[i] = NULL;
12113             }
12114         }
12115         nCmailMovesRegistered = 0;
12116     }
12117
12118     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12119         cmailResult[i] = CMAIL_NOT_RESULT;
12120     }
12121     nCmailResults = 0;
12122
12123     if (inFilename == NULL) {
12124         /* Because the filenames are static they only get malloced once  */
12125         /* and they never get freed                                      */
12126         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12127         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12128
12129         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12130         sprintf(outFilename, "%s.out", appData.cmailGameName);
12131     }
12132
12133     status = stat(outFilename, &outbuf);
12134     if (status < 0) {
12135         cmailMailedMove = FALSE;
12136     } else {
12137         status = stat(inFilename, &inbuf);
12138         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12139     }
12140
12141     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12142        counts the games, notes how each one terminated, etc.
12143
12144        It would be nice to remove this kludge and instead gather all
12145        the information while building the game list.  (And to keep it
12146        in the game list nodes instead of having a bunch of fixed-size
12147        parallel arrays.)  Note this will require getting each game's
12148        termination from the PGN tags, as the game list builder does
12149        not process the game moves.  --mann
12150        */
12151     cmailMsgLoaded = TRUE;
12152     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12153
12154     /* Load first game in the file or popup game menu */
12155     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12156
12157 #endif /* !WIN32 */
12158     return;
12159 }
12160
12161 int
12162 RegisterMove()
12163 {
12164     FILE *f;
12165     char string[MSG_SIZ];
12166
12167     if (   cmailMailedMove
12168         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12169         return TRUE;            /* Allow free viewing  */
12170     }
12171
12172     /* Unregister move to ensure that we don't leave RegisterMove        */
12173     /* with the move registered when the conditions for registering no   */
12174     /* longer hold                                                       */
12175     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12176         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12177         nCmailMovesRegistered --;
12178
12179         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12180           {
12181               free(cmailCommentList[lastLoadGameNumber - 1]);
12182               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12183           }
12184     }
12185
12186     if (cmailOldMove == -1) {
12187         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12188         return FALSE;
12189     }
12190
12191     if (currentMove > cmailOldMove + 1) {
12192         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12193         return FALSE;
12194     }
12195
12196     if (currentMove < cmailOldMove) {
12197         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12198         return FALSE;
12199     }
12200
12201     if (forwardMostMove > currentMove) {
12202         /* Silently truncate extra moves */
12203         TruncateGame();
12204     }
12205
12206     if (   (currentMove == cmailOldMove + 1)
12207         || (   (currentMove == cmailOldMove)
12208             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12209                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12210         if (gameInfo.result != GameUnfinished) {
12211             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12212         }
12213
12214         if (commentList[currentMove] != NULL) {
12215             cmailCommentList[lastLoadGameNumber - 1]
12216               = StrSave(commentList[currentMove]);
12217         }
12218         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12219
12220         if (appData.debugMode)
12221           fprintf(debugFP, "Saving %s for game %d\n",
12222                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12223
12224         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12225
12226         f = fopen(string, "w");
12227         if (appData.oldSaveStyle) {
12228             SaveGameOldStyle(f); /* also closes the file */
12229
12230             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12231             f = fopen(string, "w");
12232             SavePosition(f, 0, NULL); /* also closes the file */
12233         } else {
12234             fprintf(f, "{--------------\n");
12235             PrintPosition(f, currentMove);
12236             fprintf(f, "--------------}\n\n");
12237
12238             SaveGame(f, 0, NULL); /* also closes the file*/
12239         }
12240
12241         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12242         nCmailMovesRegistered ++;
12243     } else if (nCmailGames == 1) {
12244         DisplayError(_("You have not made a move yet"), 0);
12245         return FALSE;
12246     }
12247
12248     return TRUE;
12249 }
12250
12251 void
12252 MailMoveEvent()
12253 {
12254 #if !WIN32
12255     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12256     FILE *commandOutput;
12257     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12258     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12259     int nBuffers;
12260     int i;
12261     int archived;
12262     char *arcDir;
12263
12264     if (! cmailMsgLoaded) {
12265         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12266         return;
12267     }
12268
12269     if (nCmailGames == nCmailResults) {
12270         DisplayError(_("No unfinished games"), 0);
12271         return;
12272     }
12273
12274 #if CMAIL_PROHIBIT_REMAIL
12275     if (cmailMailedMove) {
12276       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);
12277         DisplayError(msg, 0);
12278         return;
12279     }
12280 #endif
12281
12282     if (! (cmailMailedMove || RegisterMove())) return;
12283
12284     if (   cmailMailedMove
12285         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12286       snprintf(string, MSG_SIZ, partCommandString,
12287                appData.debugMode ? " -v" : "", appData.cmailGameName);
12288         commandOutput = popen(string, "r");
12289
12290         if (commandOutput == NULL) {
12291             DisplayError(_("Failed to invoke cmail"), 0);
12292         } else {
12293             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12294                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12295             }
12296             if (nBuffers > 1) {
12297                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12298                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12299                 nBytes = MSG_SIZ - 1;
12300             } else {
12301                 (void) memcpy(msg, buffer, nBytes);
12302             }
12303             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12304
12305             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12306                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12307
12308                 archived = TRUE;
12309                 for (i = 0; i < nCmailGames; i ++) {
12310                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12311                         archived = FALSE;
12312                     }
12313                 }
12314                 if (   archived
12315                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12316                         != NULL)) {
12317                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12318                            arcDir,
12319                            appData.cmailGameName,
12320                            gameInfo.date);
12321                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12322                     cmailMsgLoaded = FALSE;
12323                 }
12324             }
12325
12326             DisplayInformation(msg);
12327             pclose(commandOutput);
12328         }
12329     } else {
12330         if ((*cmailMsg) != '\0') {
12331             DisplayInformation(cmailMsg);
12332         }
12333     }
12334
12335     return;
12336 #endif /* !WIN32 */
12337 }
12338
12339 char *
12340 CmailMsg()
12341 {
12342 #if WIN32
12343     return NULL;
12344 #else
12345     int  prependComma = 0;
12346     char number[5];
12347     char string[MSG_SIZ];       /* Space for game-list */
12348     int  i;
12349
12350     if (!cmailMsgLoaded) return "";
12351
12352     if (cmailMailedMove) {
12353       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12354     } else {
12355         /* Create a list of games left */
12356       snprintf(string, MSG_SIZ, "[");
12357         for (i = 0; i < nCmailGames; i ++) {
12358             if (! (   cmailMoveRegistered[i]
12359                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12360                 if (prependComma) {
12361                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12362                 } else {
12363                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12364                     prependComma = 1;
12365                 }
12366
12367                 strcat(string, number);
12368             }
12369         }
12370         strcat(string, "]");
12371
12372         if (nCmailMovesRegistered + nCmailResults == 0) {
12373             switch (nCmailGames) {
12374               case 1:
12375                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12376                 break;
12377
12378               case 2:
12379                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12380                 break;
12381
12382               default:
12383                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12384                          nCmailGames);
12385                 break;
12386             }
12387         } else {
12388             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12389               case 1:
12390                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12391                          string);
12392                 break;
12393
12394               case 0:
12395                 if (nCmailResults == nCmailGames) {
12396                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12397                 } else {
12398                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12399                 }
12400                 break;
12401
12402               default:
12403                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12404                          string);
12405             }
12406         }
12407     }
12408     return cmailMsg;
12409 #endif /* WIN32 */
12410 }
12411
12412 void
12413 ResetGameEvent()
12414 {
12415     if (gameMode == Training)
12416       SetTrainingModeOff();
12417
12418     Reset(TRUE, TRUE);
12419     cmailMsgLoaded = FALSE;
12420     if (appData.icsActive) {
12421       SendToICS(ics_prefix);
12422       SendToICS("refresh\n");
12423     }
12424 }
12425
12426 void
12427 ExitEvent(status)
12428      int status;
12429 {
12430     exiting++;
12431     if (exiting > 2) {
12432       /* Give up on clean exit */
12433       exit(status);
12434     }
12435     if (exiting > 1) {
12436       /* Keep trying for clean exit */
12437       return;
12438     }
12439
12440     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12441
12442     if (telnetISR != NULL) {
12443       RemoveInputSource(telnetISR);
12444     }
12445     if (icsPR != NoProc) {
12446       DestroyChildProcess(icsPR, TRUE);
12447     }
12448
12449     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12450     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12451
12452     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12453     /* make sure this other one finishes before killing it!                  */
12454     if(endingGame) { int count = 0;
12455         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12456         while(endingGame && count++ < 10) DoSleep(1);
12457         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12458     }
12459
12460     /* Kill off chess programs */
12461     if (first.pr != NoProc) {
12462         ExitAnalyzeMode();
12463
12464         DoSleep( appData.delayBeforeQuit );
12465         SendToProgram("quit\n", &first);
12466         DoSleep( appData.delayAfterQuit );
12467         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12468     }
12469     if (second.pr != NoProc) {
12470         DoSleep( appData.delayBeforeQuit );
12471         SendToProgram("quit\n", &second);
12472         DoSleep( appData.delayAfterQuit );
12473         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12474     }
12475     if (first.isr != NULL) {
12476         RemoveInputSource(first.isr);
12477     }
12478     if (second.isr != NULL) {
12479         RemoveInputSource(second.isr);
12480     }
12481
12482     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12483     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12484
12485     ShutDownFrontEnd();
12486     exit(status);
12487 }
12488
12489 void
12490 PauseEvent()
12491 {
12492     if (appData.debugMode)
12493         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12494     if (pausing) {
12495         pausing = FALSE;
12496         ModeHighlight();
12497         if (gameMode == MachinePlaysWhite ||
12498             gameMode == MachinePlaysBlack) {
12499             StartClocks();
12500         } else {
12501             DisplayBothClocks();
12502         }
12503         if (gameMode == PlayFromGameFile) {
12504             if (appData.timeDelay >= 0)
12505                 AutoPlayGameLoop();
12506         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12507             Reset(FALSE, TRUE);
12508             SendToICS(ics_prefix);
12509             SendToICS("refresh\n");
12510         } else if (currentMove < forwardMostMove) {
12511             ForwardInner(forwardMostMove);
12512         }
12513         pauseExamInvalid = FALSE;
12514     } else {
12515         switch (gameMode) {
12516           default:
12517             return;
12518           case IcsExamining:
12519             pauseExamForwardMostMove = forwardMostMove;
12520             pauseExamInvalid = FALSE;
12521             /* fall through */
12522           case IcsObserving:
12523           case IcsPlayingWhite:
12524           case IcsPlayingBlack:
12525             pausing = TRUE;
12526             ModeHighlight();
12527             return;
12528           case PlayFromGameFile:
12529             (void) StopLoadGameTimer();
12530             pausing = TRUE;
12531             ModeHighlight();
12532             break;
12533           case BeginningOfGame:
12534             if (appData.icsActive) return;
12535             /* else fall through */
12536           case MachinePlaysWhite:
12537           case MachinePlaysBlack:
12538           case TwoMachinesPlay:
12539             if (forwardMostMove == 0)
12540               return;           /* don't pause if no one has moved */
12541             if ((gameMode == MachinePlaysWhite &&
12542                  !WhiteOnMove(forwardMostMove)) ||
12543                 (gameMode == MachinePlaysBlack &&
12544                  WhiteOnMove(forwardMostMove))) {
12545                 StopClocks();
12546             }
12547             pausing = TRUE;
12548             ModeHighlight();
12549             break;
12550         }
12551     }
12552 }
12553
12554 void
12555 EditCommentEvent()
12556 {
12557     char title[MSG_SIZ];
12558
12559     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12560       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12561     } else {
12562       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12563                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12564                parseList[currentMove - 1]);
12565     }
12566
12567     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12568 }
12569
12570
12571 void
12572 EditTagsEvent()
12573 {
12574     char *tags = PGNTags(&gameInfo);
12575     bookUp = FALSE;
12576     EditTagsPopUp(tags, NULL);
12577     free(tags);
12578 }
12579
12580 void
12581 AnalyzeModeEvent()
12582 {
12583     if (appData.noChessProgram || gameMode == AnalyzeMode)
12584       return;
12585
12586     if (gameMode != AnalyzeFile) {
12587         if (!appData.icsEngineAnalyze) {
12588                EditGameEvent();
12589                if (gameMode != EditGame) return;
12590         }
12591         ResurrectChessProgram();
12592         SendToProgram("analyze\n", &first);
12593         first.analyzing = TRUE;
12594         /*first.maybeThinking = TRUE;*/
12595         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12596         EngineOutputPopUp();
12597     }
12598     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12599     pausing = FALSE;
12600     ModeHighlight();
12601     SetGameInfo();
12602
12603     StartAnalysisClock();
12604     GetTimeMark(&lastNodeCountTime);
12605     lastNodeCount = 0;
12606 }
12607
12608 void
12609 AnalyzeFileEvent()
12610 {
12611     if (appData.noChessProgram || gameMode == AnalyzeFile)
12612       return;
12613
12614     if (gameMode != AnalyzeMode) {
12615         EditGameEvent();
12616         if (gameMode != EditGame) return;
12617         ResurrectChessProgram();
12618         SendToProgram("analyze\n", &first);
12619         first.analyzing = TRUE;
12620         /*first.maybeThinking = TRUE;*/
12621         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12622         EngineOutputPopUp();
12623     }
12624     gameMode = AnalyzeFile;
12625     pausing = FALSE;
12626     ModeHighlight();
12627     SetGameInfo();
12628
12629     StartAnalysisClock();
12630     GetTimeMark(&lastNodeCountTime);
12631     lastNodeCount = 0;
12632 }
12633
12634 void
12635 MachineWhiteEvent()
12636 {
12637     char buf[MSG_SIZ];
12638     char *bookHit = NULL;
12639
12640     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12641       return;
12642
12643
12644     if (gameMode == PlayFromGameFile ||
12645         gameMode == TwoMachinesPlay  ||
12646         gameMode == Training         ||
12647         gameMode == AnalyzeMode      ||
12648         gameMode == EndOfGame)
12649         EditGameEvent();
12650
12651     if (gameMode == EditPosition)
12652         EditPositionDone(TRUE);
12653
12654     if (!WhiteOnMove(currentMove)) {
12655         DisplayError(_("It is not White's turn"), 0);
12656         return;
12657     }
12658
12659     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12660       ExitAnalyzeMode();
12661
12662     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12663         gameMode == AnalyzeFile)
12664         TruncateGame();
12665
12666     ResurrectChessProgram();    /* in case it isn't running */
12667     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12668         gameMode = MachinePlaysWhite;
12669         ResetClocks();
12670     } else
12671     gameMode = MachinePlaysWhite;
12672     pausing = FALSE;
12673     ModeHighlight();
12674     SetGameInfo();
12675     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12676     DisplayTitle(buf);
12677     if (first.sendName) {
12678       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12679       SendToProgram(buf, &first);
12680     }
12681     if (first.sendTime) {
12682       if (first.useColors) {
12683         SendToProgram("black\n", &first); /*gnu kludge*/
12684       }
12685       SendTimeRemaining(&first, TRUE);
12686     }
12687     if (first.useColors) {
12688       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12689     }
12690     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12691     SetMachineThinkingEnables();
12692     first.maybeThinking = TRUE;
12693     StartClocks();
12694     firstMove = FALSE;
12695
12696     if (appData.autoFlipView && !flipView) {
12697       flipView = !flipView;
12698       DrawPosition(FALSE, NULL);
12699       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12700     }
12701
12702     if(bookHit) { // [HGM] book: simulate book reply
12703         static char bookMove[MSG_SIZ]; // a bit generous?
12704
12705         programStats.nodes = programStats.depth = programStats.time =
12706         programStats.score = programStats.got_only_move = 0;
12707         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12708
12709         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12710         strcat(bookMove, bookHit);
12711         HandleMachineMove(bookMove, &first);
12712     }
12713 }
12714
12715 void
12716 MachineBlackEvent()
12717 {
12718   char buf[MSG_SIZ];
12719   char *bookHit = NULL;
12720
12721     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12722         return;
12723
12724
12725     if (gameMode == PlayFromGameFile ||
12726         gameMode == TwoMachinesPlay  ||
12727         gameMode == Training         ||
12728         gameMode == AnalyzeMode      ||
12729         gameMode == EndOfGame)
12730         EditGameEvent();
12731
12732     if (gameMode == EditPosition)
12733         EditPositionDone(TRUE);
12734
12735     if (WhiteOnMove(currentMove)) {
12736         DisplayError(_("It is not Black's turn"), 0);
12737         return;
12738     }
12739
12740     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12741       ExitAnalyzeMode();
12742
12743     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12744         gameMode == AnalyzeFile)
12745         TruncateGame();
12746
12747     ResurrectChessProgram();    /* in case it isn't running */
12748     gameMode = MachinePlaysBlack;
12749     pausing = FALSE;
12750     ModeHighlight();
12751     SetGameInfo();
12752     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12753     DisplayTitle(buf);
12754     if (first.sendName) {
12755       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12756       SendToProgram(buf, &first);
12757     }
12758     if (first.sendTime) {
12759       if (first.useColors) {
12760         SendToProgram("white\n", &first); /*gnu kludge*/
12761       }
12762       SendTimeRemaining(&first, FALSE);
12763     }
12764     if (first.useColors) {
12765       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12766     }
12767     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12768     SetMachineThinkingEnables();
12769     first.maybeThinking = TRUE;
12770     StartClocks();
12771
12772     if (appData.autoFlipView && flipView) {
12773       flipView = !flipView;
12774       DrawPosition(FALSE, NULL);
12775       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12776     }
12777     if(bookHit) { // [HGM] book: simulate book reply
12778         static char bookMove[MSG_SIZ]; // a bit generous?
12779
12780         programStats.nodes = programStats.depth = programStats.time =
12781         programStats.score = programStats.got_only_move = 0;
12782         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12783
12784         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12785         strcat(bookMove, bookHit);
12786         HandleMachineMove(bookMove, &first);
12787     }
12788 }
12789
12790
12791 void
12792 DisplayTwoMachinesTitle()
12793 {
12794     char buf[MSG_SIZ];
12795     if (appData.matchGames > 0) {
12796         if(appData.tourneyFile[0]) {
12797           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12798                    gameInfo.white, gameInfo.black,
12799                    nextGame+1, appData.matchGames+1,
12800                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12801         } else 
12802         if (first.twoMachinesColor[0] == 'w') {
12803           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12804                    gameInfo.white, gameInfo.black,
12805                    first.matchWins, second.matchWins,
12806                    matchGame - 1 - (first.matchWins + second.matchWins));
12807         } else {
12808           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12809                    gameInfo.white, gameInfo.black,
12810                    second.matchWins, first.matchWins,
12811                    matchGame - 1 - (first.matchWins + second.matchWins));
12812         }
12813     } else {
12814       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12815     }
12816     DisplayTitle(buf);
12817 }
12818
12819 void
12820 SettingsMenuIfReady()
12821 {
12822   if (second.lastPing != second.lastPong) {
12823     DisplayMessage("", _("Waiting for second chess program"));
12824     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12825     return;
12826   }
12827   ThawUI();
12828   DisplayMessage("", "");
12829   SettingsPopUp(&second);
12830 }
12831
12832 int
12833 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12834 {
12835     char buf[MSG_SIZ];
12836     if (cps->pr == NULL) {
12837         StartChessProgram(cps);
12838         if (cps->protocolVersion == 1) {
12839           retry();
12840         } else {
12841           /* kludge: allow timeout for initial "feature" command */
12842           FreezeUI();
12843           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12844           DisplayMessage("", buf);
12845           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12846         }
12847         return 1;
12848     }
12849     return 0;
12850 }
12851
12852 void
12853 TwoMachinesEvent P((void))
12854 {
12855     int i;
12856     char buf[MSG_SIZ];
12857     ChessProgramState *onmove;
12858     char *bookHit = NULL;
12859     static int stalling = 0;
12860     TimeMark now;
12861     long wait;
12862
12863     if (appData.noChessProgram) return;
12864
12865     switch (gameMode) {
12866       case TwoMachinesPlay:
12867         return;
12868       case MachinePlaysWhite:
12869       case MachinePlaysBlack:
12870         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12871             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12872             return;
12873         }
12874         /* fall through */
12875       case BeginningOfGame:
12876       case PlayFromGameFile:
12877       case EndOfGame:
12878         EditGameEvent();
12879         if (gameMode != EditGame) return;
12880         break;
12881       case EditPosition:
12882         EditPositionDone(TRUE);
12883         break;
12884       case AnalyzeMode:
12885       case AnalyzeFile:
12886         ExitAnalyzeMode();
12887         break;
12888       case EditGame:
12889       default:
12890         break;
12891     }
12892
12893 //    forwardMostMove = currentMove;
12894     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12895
12896     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12897
12898     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12899     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12900       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12901       return;
12902     }
12903     if(!stalling) {
12904       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12905       SendToProgram("force\n", &second);
12906       stalling = 1;
12907       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12908       return;
12909     }
12910     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12911     if(appData.matchPause>10000 || appData.matchPause<10)
12912                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12913     wait = SubtractTimeMarks(&now, &pauseStart);
12914     if(wait < appData.matchPause) {
12915         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12916         return;
12917     }
12918     stalling = 0;
12919     DisplayMessage("", "");
12920     if (startedFromSetupPosition) {
12921         SendBoard(&second, backwardMostMove);
12922     if (appData.debugMode) {
12923         fprintf(debugFP, "Two Machines\n");
12924     }
12925     }
12926     for (i = backwardMostMove; i < forwardMostMove; i++) {
12927         SendMoveToProgram(i, &second);
12928     }
12929
12930     gameMode = TwoMachinesPlay;
12931     pausing = FALSE;
12932     ModeHighlight(); // [HGM] logo: this triggers display update of logos
12933     SetGameInfo();
12934     DisplayTwoMachinesTitle();
12935     firstMove = TRUE;
12936     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12937         onmove = &first;
12938     } else {
12939         onmove = &second;
12940     }
12941     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12942     SendToProgram(first.computerString, &first);
12943     if (first.sendName) {
12944       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12945       SendToProgram(buf, &first);
12946     }
12947     SendToProgram(second.computerString, &second);
12948     if (second.sendName) {
12949       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12950       SendToProgram(buf, &second);
12951     }
12952
12953     ResetClocks();
12954     if (!first.sendTime || !second.sendTime) {
12955         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12956         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12957     }
12958     if (onmove->sendTime) {
12959       if (onmove->useColors) {
12960         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12961       }
12962       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12963     }
12964     if (onmove->useColors) {
12965       SendToProgram(onmove->twoMachinesColor, onmove);
12966     }
12967     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12968 //    SendToProgram("go\n", onmove);
12969     onmove->maybeThinking = TRUE;
12970     SetMachineThinkingEnables();
12971
12972     StartClocks();
12973
12974     if(bookHit) { // [HGM] book: simulate book reply
12975         static char bookMove[MSG_SIZ]; // a bit generous?
12976
12977         programStats.nodes = programStats.depth = programStats.time =
12978         programStats.score = programStats.got_only_move = 0;
12979         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12980
12981         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12982         strcat(bookMove, bookHit);
12983         savedMessage = bookMove; // args for deferred call
12984         savedState = onmove;
12985         ScheduleDelayedEvent(DeferredBookMove, 1);
12986     }
12987 }
12988
12989 void
12990 TrainingEvent()
12991 {
12992     if (gameMode == Training) {
12993       SetTrainingModeOff();
12994       gameMode = PlayFromGameFile;
12995       DisplayMessage("", _("Training mode off"));
12996     } else {
12997       gameMode = Training;
12998       animateTraining = appData.animate;
12999
13000       /* make sure we are not already at the end of the game */
13001       if (currentMove < forwardMostMove) {
13002         SetTrainingModeOn();
13003         DisplayMessage("", _("Training mode on"));
13004       } else {
13005         gameMode = PlayFromGameFile;
13006         DisplayError(_("Already at end of game"), 0);
13007       }
13008     }
13009     ModeHighlight();
13010 }
13011
13012 void
13013 IcsClientEvent()
13014 {
13015     if (!appData.icsActive) return;
13016     switch (gameMode) {
13017       case IcsPlayingWhite:
13018       case IcsPlayingBlack:
13019       case IcsObserving:
13020       case IcsIdle:
13021       case BeginningOfGame:
13022       case IcsExamining:
13023         return;
13024
13025       case EditGame:
13026         break;
13027
13028       case EditPosition:
13029         EditPositionDone(TRUE);
13030         break;
13031
13032       case AnalyzeMode:
13033       case AnalyzeFile:
13034         ExitAnalyzeMode();
13035         break;
13036
13037       default:
13038         EditGameEvent();
13039         break;
13040     }
13041
13042     gameMode = IcsIdle;
13043     ModeHighlight();
13044     return;
13045 }
13046
13047
13048 void
13049 EditGameEvent()
13050 {
13051     int i;
13052
13053     switch (gameMode) {
13054       case Training:
13055         SetTrainingModeOff();
13056         break;
13057       case MachinePlaysWhite:
13058       case MachinePlaysBlack:
13059       case BeginningOfGame:
13060         SendToProgram("force\n", &first);
13061         SetUserThinkingEnables();
13062         break;
13063       case PlayFromGameFile:
13064         (void) StopLoadGameTimer();
13065         if (gameFileFP != NULL) {
13066             gameFileFP = NULL;
13067         }
13068         break;
13069       case EditPosition:
13070         EditPositionDone(TRUE);
13071         break;
13072       case AnalyzeMode:
13073       case AnalyzeFile:
13074         ExitAnalyzeMode();
13075         SendToProgram("force\n", &first);
13076         break;
13077       case TwoMachinesPlay:
13078         GameEnds(EndOfFile, NULL, GE_PLAYER);
13079         ResurrectChessProgram();
13080         SetUserThinkingEnables();
13081         break;
13082       case EndOfGame:
13083         ResurrectChessProgram();
13084         break;
13085       case IcsPlayingBlack:
13086       case IcsPlayingWhite:
13087         DisplayError(_("Warning: You are still playing a game"), 0);
13088         break;
13089       case IcsObserving:
13090         DisplayError(_("Warning: You are still observing a game"), 0);
13091         break;
13092       case IcsExamining:
13093         DisplayError(_("Warning: You are still examining a game"), 0);
13094         break;
13095       case IcsIdle:
13096         break;
13097       case EditGame:
13098       default:
13099         return;
13100     }
13101
13102     pausing = FALSE;
13103     StopClocks();
13104     first.offeredDraw = second.offeredDraw = 0;
13105
13106     if (gameMode == PlayFromGameFile) {
13107         whiteTimeRemaining = timeRemaining[0][currentMove];
13108         blackTimeRemaining = timeRemaining[1][currentMove];
13109         DisplayTitle("");
13110     }
13111
13112     if (gameMode == MachinePlaysWhite ||
13113         gameMode == MachinePlaysBlack ||
13114         gameMode == TwoMachinesPlay ||
13115         gameMode == EndOfGame) {
13116         i = forwardMostMove;
13117         while (i > currentMove) {
13118             SendToProgram("undo\n", &first);
13119             i--;
13120         }
13121         whiteTimeRemaining = timeRemaining[0][currentMove];
13122         blackTimeRemaining = timeRemaining[1][currentMove];
13123         DisplayBothClocks();
13124         if (whiteFlag || blackFlag) {
13125             whiteFlag = blackFlag = 0;
13126         }
13127         DisplayTitle("");
13128     }
13129
13130     gameMode = EditGame;
13131     ModeHighlight();
13132     SetGameInfo();
13133 }
13134
13135
13136 void
13137 EditPositionEvent()
13138 {
13139     if (gameMode == EditPosition) {
13140         EditGameEvent();
13141         return;
13142     }
13143
13144     EditGameEvent();
13145     if (gameMode != EditGame) return;
13146
13147     gameMode = EditPosition;
13148     ModeHighlight();
13149     SetGameInfo();
13150     if (currentMove > 0)
13151       CopyBoard(boards[0], boards[currentMove]);
13152
13153     blackPlaysFirst = !WhiteOnMove(currentMove);
13154     ResetClocks();
13155     currentMove = forwardMostMove = backwardMostMove = 0;
13156     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13157     DisplayMove(-1);
13158 }
13159
13160 void
13161 ExitAnalyzeMode()
13162 {
13163     /* [DM] icsEngineAnalyze - possible call from other functions */
13164     if (appData.icsEngineAnalyze) {
13165         appData.icsEngineAnalyze = FALSE;
13166
13167         DisplayMessage("",_("Close ICS engine analyze..."));
13168     }
13169     if (first.analysisSupport && first.analyzing) {
13170       SendToProgram("exit\n", &first);
13171       first.analyzing = FALSE;
13172     }
13173     thinkOutput[0] = NULLCHAR;
13174 }
13175
13176 void
13177 EditPositionDone(Boolean fakeRights)
13178 {
13179     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13180
13181     startedFromSetupPosition = TRUE;
13182     InitChessProgram(&first, FALSE);
13183     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13184       boards[0][EP_STATUS] = EP_NONE;
13185       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13186     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13187         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13188         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13189       } else boards[0][CASTLING][2] = NoRights;
13190     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13191         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13192         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13193       } else boards[0][CASTLING][5] = NoRights;
13194     }
13195     SendToProgram("force\n", &first);
13196     if (blackPlaysFirst) {
13197         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13198         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13199         currentMove = forwardMostMove = backwardMostMove = 1;
13200         CopyBoard(boards[1], boards[0]);
13201     } else {
13202         currentMove = forwardMostMove = backwardMostMove = 0;
13203     }
13204     SendBoard(&first, forwardMostMove);
13205     if (appData.debugMode) {
13206         fprintf(debugFP, "EditPosDone\n");
13207     }
13208     DisplayTitle("");
13209     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13210     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13211     gameMode = EditGame;
13212     ModeHighlight();
13213     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13214     ClearHighlights(); /* [AS] */
13215 }
13216
13217 /* Pause for `ms' milliseconds */
13218 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13219 void
13220 TimeDelay(ms)
13221      long ms;
13222 {
13223     TimeMark m1, m2;
13224
13225     GetTimeMark(&m1);
13226     do {
13227         GetTimeMark(&m2);
13228     } while (SubtractTimeMarks(&m2, &m1) < ms);
13229 }
13230
13231 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13232 void
13233 SendMultiLineToICS(buf)
13234      char *buf;
13235 {
13236     char temp[MSG_SIZ+1], *p;
13237     int len;
13238
13239     len = strlen(buf);
13240     if (len > MSG_SIZ)
13241       len = MSG_SIZ;
13242
13243     strncpy(temp, buf, len);
13244     temp[len] = 0;
13245
13246     p = temp;
13247     while (*p) {
13248         if (*p == '\n' || *p == '\r')
13249           *p = ' ';
13250         ++p;
13251     }
13252
13253     strcat(temp, "\n");
13254     SendToICS(temp);
13255     SendToPlayer(temp, strlen(temp));
13256 }
13257
13258 void
13259 SetWhiteToPlayEvent()
13260 {
13261     if (gameMode == EditPosition) {
13262         blackPlaysFirst = FALSE;
13263         DisplayBothClocks();    /* works because currentMove is 0 */
13264     } else if (gameMode == IcsExamining) {
13265         SendToICS(ics_prefix);
13266         SendToICS("tomove white\n");
13267     }
13268 }
13269
13270 void
13271 SetBlackToPlayEvent()
13272 {
13273     if (gameMode == EditPosition) {
13274         blackPlaysFirst = TRUE;
13275         currentMove = 1;        /* kludge */
13276         DisplayBothClocks();
13277         currentMove = 0;
13278     } else if (gameMode == IcsExamining) {
13279         SendToICS(ics_prefix);
13280         SendToICS("tomove black\n");
13281     }
13282 }
13283
13284 void
13285 EditPositionMenuEvent(selection, x, y)
13286      ChessSquare selection;
13287      int x, y;
13288 {
13289     char buf[MSG_SIZ];
13290     ChessSquare piece = boards[0][y][x];
13291
13292     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13293
13294     switch (selection) {
13295       case ClearBoard:
13296         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13297             SendToICS(ics_prefix);
13298             SendToICS("bsetup clear\n");
13299         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13300             SendToICS(ics_prefix);
13301             SendToICS("clearboard\n");
13302         } else {
13303             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13304                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13305                 for (y = 0; y < BOARD_HEIGHT; y++) {
13306                     if (gameMode == IcsExamining) {
13307                         if (boards[currentMove][y][x] != EmptySquare) {
13308                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13309                                     AAA + x, ONE + y);
13310                             SendToICS(buf);
13311                         }
13312                     } else {
13313                         boards[0][y][x] = p;
13314                     }
13315                 }
13316             }
13317         }
13318         if (gameMode == EditPosition) {
13319             DrawPosition(FALSE, boards[0]);
13320         }
13321         break;
13322
13323       case WhitePlay:
13324         SetWhiteToPlayEvent();
13325         break;
13326
13327       case BlackPlay:
13328         SetBlackToPlayEvent();
13329         break;
13330
13331       case EmptySquare:
13332         if (gameMode == IcsExamining) {
13333             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13334             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13335             SendToICS(buf);
13336         } else {
13337             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13338                 if(x == BOARD_LEFT-2) {
13339                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13340                     boards[0][y][1] = 0;
13341                 } else
13342                 if(x == BOARD_RGHT+1) {
13343                     if(y >= gameInfo.holdingsSize) break;
13344                     boards[0][y][BOARD_WIDTH-2] = 0;
13345                 } else break;
13346             }
13347             boards[0][y][x] = EmptySquare;
13348             DrawPosition(FALSE, boards[0]);
13349         }
13350         break;
13351
13352       case PromotePiece:
13353         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13354            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13355             selection = (ChessSquare) (PROMOTED piece);
13356         } else if(piece == EmptySquare) selection = WhiteSilver;
13357         else selection = (ChessSquare)((int)piece - 1);
13358         goto defaultlabel;
13359
13360       case DemotePiece:
13361         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13362            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13363             selection = (ChessSquare) (DEMOTED piece);
13364         } else if(piece == EmptySquare) selection = BlackSilver;
13365         else selection = (ChessSquare)((int)piece + 1);
13366         goto defaultlabel;
13367
13368       case WhiteQueen:
13369       case BlackQueen:
13370         if(gameInfo.variant == VariantShatranj ||
13371            gameInfo.variant == VariantXiangqi  ||
13372            gameInfo.variant == VariantCourier  ||
13373            gameInfo.variant == VariantMakruk     )
13374             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13375         goto defaultlabel;
13376
13377       case WhiteKing:
13378       case BlackKing:
13379         if(gameInfo.variant == VariantXiangqi)
13380             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13381         if(gameInfo.variant == VariantKnightmate)
13382             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13383       default:
13384         defaultlabel:
13385         if (gameMode == IcsExamining) {
13386             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13387             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13388                      PieceToChar(selection), AAA + x, ONE + y);
13389             SendToICS(buf);
13390         } else {
13391             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13392                 int n;
13393                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13394                     n = PieceToNumber(selection - BlackPawn);
13395                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13396                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13397                     boards[0][BOARD_HEIGHT-1-n][1]++;
13398                 } else
13399                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13400                     n = PieceToNumber(selection);
13401                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13402                     boards[0][n][BOARD_WIDTH-1] = selection;
13403                     boards[0][n][BOARD_WIDTH-2]++;
13404                 }
13405             } else
13406             boards[0][y][x] = selection;
13407             DrawPosition(TRUE, boards[0]);
13408         }
13409         break;
13410     }
13411 }
13412
13413
13414 void
13415 DropMenuEvent(selection, x, y)
13416      ChessSquare selection;
13417      int x, y;
13418 {
13419     ChessMove moveType;
13420
13421     switch (gameMode) {
13422       case IcsPlayingWhite:
13423       case MachinePlaysBlack:
13424         if (!WhiteOnMove(currentMove)) {
13425             DisplayMoveError(_("It is Black's turn"));
13426             return;
13427         }
13428         moveType = WhiteDrop;
13429         break;
13430       case IcsPlayingBlack:
13431       case MachinePlaysWhite:
13432         if (WhiteOnMove(currentMove)) {
13433             DisplayMoveError(_("It is White's turn"));
13434             return;
13435         }
13436         moveType = BlackDrop;
13437         break;
13438       case EditGame:
13439         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13440         break;
13441       default:
13442         return;
13443     }
13444
13445     if (moveType == BlackDrop && selection < BlackPawn) {
13446       selection = (ChessSquare) ((int) selection
13447                                  + (int) BlackPawn - (int) WhitePawn);
13448     }
13449     if (boards[currentMove][y][x] != EmptySquare) {
13450         DisplayMoveError(_("That square is occupied"));
13451         return;
13452     }
13453
13454     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13455 }
13456
13457 void
13458 AcceptEvent()
13459 {
13460     /* Accept a pending offer of any kind from opponent */
13461
13462     if (appData.icsActive) {
13463         SendToICS(ics_prefix);
13464         SendToICS("accept\n");
13465     } else if (cmailMsgLoaded) {
13466         if (currentMove == cmailOldMove &&
13467             commentList[cmailOldMove] != NULL &&
13468             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13469                    "Black offers a draw" : "White offers a draw")) {
13470             TruncateGame();
13471             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13472             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13473         } else {
13474             DisplayError(_("There is no pending offer on this move"), 0);
13475             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13476         }
13477     } else {
13478         /* Not used for offers from chess program */
13479     }
13480 }
13481
13482 void
13483 DeclineEvent()
13484 {
13485     /* Decline a pending offer of any kind from opponent */
13486
13487     if (appData.icsActive) {
13488         SendToICS(ics_prefix);
13489         SendToICS("decline\n");
13490     } else if (cmailMsgLoaded) {
13491         if (currentMove == cmailOldMove &&
13492             commentList[cmailOldMove] != NULL &&
13493             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13494                    "Black offers a draw" : "White offers a draw")) {
13495 #ifdef NOTDEF
13496             AppendComment(cmailOldMove, "Draw declined", TRUE);
13497             DisplayComment(cmailOldMove - 1, "Draw declined");
13498 #endif /*NOTDEF*/
13499         } else {
13500             DisplayError(_("There is no pending offer on this move"), 0);
13501         }
13502     } else {
13503         /* Not used for offers from chess program */
13504     }
13505 }
13506
13507 void
13508 RematchEvent()
13509 {
13510     /* Issue ICS rematch command */
13511     if (appData.icsActive) {
13512         SendToICS(ics_prefix);
13513         SendToICS("rematch\n");
13514     }
13515 }
13516
13517 void
13518 CallFlagEvent()
13519 {
13520     /* Call your opponent's flag (claim a win on time) */
13521     if (appData.icsActive) {
13522         SendToICS(ics_prefix);
13523         SendToICS("flag\n");
13524     } else {
13525         switch (gameMode) {
13526           default:
13527             return;
13528           case MachinePlaysWhite:
13529             if (whiteFlag) {
13530                 if (blackFlag)
13531                   GameEnds(GameIsDrawn, "Both players ran out of time",
13532                            GE_PLAYER);
13533                 else
13534                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13535             } else {
13536                 DisplayError(_("Your opponent is not out of time"), 0);
13537             }
13538             break;
13539           case MachinePlaysBlack:
13540             if (blackFlag) {
13541                 if (whiteFlag)
13542                   GameEnds(GameIsDrawn, "Both players ran out of time",
13543                            GE_PLAYER);
13544                 else
13545                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13546             } else {
13547                 DisplayError(_("Your opponent is not out of time"), 0);
13548             }
13549             break;
13550         }
13551     }
13552 }
13553
13554 void
13555 ClockClick(int which)
13556 {       // [HGM] code moved to back-end from winboard.c
13557         if(which) { // black clock
13558           if (gameMode == EditPosition || gameMode == IcsExamining) {
13559             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13560             SetBlackToPlayEvent();
13561           } else if (gameMode == EditGame || shiftKey) {
13562             AdjustClock(which, -1);
13563           } else if (gameMode == IcsPlayingWhite ||
13564                      gameMode == MachinePlaysBlack) {
13565             CallFlagEvent();
13566           }
13567         } else { // white clock
13568           if (gameMode == EditPosition || gameMode == IcsExamining) {
13569             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13570             SetWhiteToPlayEvent();
13571           } else if (gameMode == EditGame || shiftKey) {
13572             AdjustClock(which, -1);
13573           } else if (gameMode == IcsPlayingBlack ||
13574                    gameMode == MachinePlaysWhite) {
13575             CallFlagEvent();
13576           }
13577         }
13578 }
13579
13580 void
13581 DrawEvent()
13582 {
13583     /* Offer draw or accept pending draw offer from opponent */
13584
13585     if (appData.icsActive) {
13586         /* Note: tournament rules require draw offers to be
13587            made after you make your move but before you punch
13588            your clock.  Currently ICS doesn't let you do that;
13589            instead, you immediately punch your clock after making
13590            a move, but you can offer a draw at any time. */
13591
13592         SendToICS(ics_prefix);
13593         SendToICS("draw\n");
13594         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13595     } else if (cmailMsgLoaded) {
13596         if (currentMove == cmailOldMove &&
13597             commentList[cmailOldMove] != NULL &&
13598             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13599                    "Black offers a draw" : "White offers a draw")) {
13600             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13601             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13602         } else if (currentMove == cmailOldMove + 1) {
13603             char *offer = WhiteOnMove(cmailOldMove) ?
13604               "White offers a draw" : "Black offers a draw";
13605             AppendComment(currentMove, offer, TRUE);
13606             DisplayComment(currentMove - 1, offer);
13607             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13608         } else {
13609             DisplayError(_("You must make your move before offering a draw"), 0);
13610             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13611         }
13612     } else if (first.offeredDraw) {
13613         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13614     } else {
13615         if (first.sendDrawOffers) {
13616             SendToProgram("draw\n", &first);
13617             userOfferedDraw = TRUE;
13618         }
13619     }
13620 }
13621
13622 void
13623 AdjournEvent()
13624 {
13625     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13626
13627     if (appData.icsActive) {
13628         SendToICS(ics_prefix);
13629         SendToICS("adjourn\n");
13630     } else {
13631         /* Currently GNU Chess doesn't offer or accept Adjourns */
13632     }
13633 }
13634
13635
13636 void
13637 AbortEvent()
13638 {
13639     /* Offer Abort or accept pending Abort offer from opponent */
13640
13641     if (appData.icsActive) {
13642         SendToICS(ics_prefix);
13643         SendToICS("abort\n");
13644     } else {
13645         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13646     }
13647 }
13648
13649 void
13650 ResignEvent()
13651 {
13652     /* Resign.  You can do this even if it's not your turn. */
13653
13654     if (appData.icsActive) {
13655         SendToICS(ics_prefix);
13656         SendToICS("resign\n");
13657     } else {
13658         switch (gameMode) {
13659           case MachinePlaysWhite:
13660             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13661             break;
13662           case MachinePlaysBlack:
13663             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13664             break;
13665           case EditGame:
13666             if (cmailMsgLoaded) {
13667                 TruncateGame();
13668                 if (WhiteOnMove(cmailOldMove)) {
13669                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13670                 } else {
13671                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13672                 }
13673                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13674             }
13675             break;
13676           default:
13677             break;
13678         }
13679     }
13680 }
13681
13682
13683 void
13684 StopObservingEvent()
13685 {
13686     /* Stop observing current games */
13687     SendToICS(ics_prefix);
13688     SendToICS("unobserve\n");
13689 }
13690
13691 void
13692 StopExaminingEvent()
13693 {
13694     /* Stop observing current game */
13695     SendToICS(ics_prefix);
13696     SendToICS("unexamine\n");
13697 }
13698
13699 void
13700 ForwardInner(target)
13701      int target;
13702 {
13703     int limit;
13704
13705     if (appData.debugMode)
13706         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13707                 target, currentMove, forwardMostMove);
13708
13709     if (gameMode == EditPosition)
13710       return;
13711
13712     if (gameMode == PlayFromGameFile && !pausing)
13713       PauseEvent();
13714
13715     if (gameMode == IcsExamining && pausing)
13716       limit = pauseExamForwardMostMove;
13717     else
13718       limit = forwardMostMove;
13719
13720     if (target > limit) target = limit;
13721
13722     if (target > 0 && moveList[target - 1][0]) {
13723         int fromX, fromY, toX, toY;
13724         toX = moveList[target - 1][2] - AAA;
13725         toY = moveList[target - 1][3] - ONE;
13726         if (moveList[target - 1][1] == '@') {
13727             if (appData.highlightLastMove) {
13728                 SetHighlights(-1, -1, toX, toY);
13729             }
13730         } else {
13731             fromX = moveList[target - 1][0] - AAA;
13732             fromY = moveList[target - 1][1] - ONE;
13733             if (target == currentMove + 1) {
13734                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13735             }
13736             if (appData.highlightLastMove) {
13737                 SetHighlights(fromX, fromY, toX, toY);
13738             }
13739         }
13740     }
13741     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13742         gameMode == Training || gameMode == PlayFromGameFile ||
13743         gameMode == AnalyzeFile) {
13744         while (currentMove < target) {
13745             SendMoveToProgram(currentMove++, &first);
13746         }
13747     } else {
13748         currentMove = target;
13749     }
13750
13751     if (gameMode == EditGame || gameMode == EndOfGame) {
13752         whiteTimeRemaining = timeRemaining[0][currentMove];
13753         blackTimeRemaining = timeRemaining[1][currentMove];
13754     }
13755     DisplayBothClocks();
13756     DisplayMove(currentMove - 1);
13757     DrawPosition(FALSE, boards[currentMove]);
13758     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13759     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13760         DisplayComment(currentMove - 1, commentList[currentMove]);
13761     }
13762     DisplayBook(currentMove);
13763 }
13764
13765
13766 void
13767 ForwardEvent()
13768 {
13769     if (gameMode == IcsExamining && !pausing) {
13770         SendToICS(ics_prefix);
13771         SendToICS("forward\n");
13772     } else {
13773         ForwardInner(currentMove + 1);
13774     }
13775 }
13776
13777 void
13778 ToEndEvent()
13779 {
13780     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13781         /* to optimze, we temporarily turn off analysis mode while we feed
13782          * the remaining moves to the engine. Otherwise we get analysis output
13783          * after each move.
13784          */
13785         if (first.analysisSupport) {
13786           SendToProgram("exit\nforce\n", &first);
13787           first.analyzing = FALSE;
13788         }
13789     }
13790
13791     if (gameMode == IcsExamining && !pausing) {
13792         SendToICS(ics_prefix);
13793         SendToICS("forward 999999\n");
13794     } else {
13795         ForwardInner(forwardMostMove);
13796     }
13797
13798     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13799         /* we have fed all the moves, so reactivate analysis mode */
13800         SendToProgram("analyze\n", &first);
13801         first.analyzing = TRUE;
13802         /*first.maybeThinking = TRUE;*/
13803         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13804     }
13805 }
13806
13807 void
13808 BackwardInner(target)
13809      int target;
13810 {
13811     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13812
13813     if (appData.debugMode)
13814         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13815                 target, currentMove, forwardMostMove);
13816
13817     if (gameMode == EditPosition) return;
13818     if (currentMove <= backwardMostMove) {
13819         ClearHighlights();
13820         DrawPosition(full_redraw, boards[currentMove]);
13821         return;
13822     }
13823     if (gameMode == PlayFromGameFile && !pausing)
13824       PauseEvent();
13825
13826     if (moveList[target][0]) {
13827         int fromX, fromY, toX, toY;
13828         toX = moveList[target][2] - AAA;
13829         toY = moveList[target][3] - ONE;
13830         if (moveList[target][1] == '@') {
13831             if (appData.highlightLastMove) {
13832                 SetHighlights(-1, -1, toX, toY);
13833             }
13834         } else {
13835             fromX = moveList[target][0] - AAA;
13836             fromY = moveList[target][1] - ONE;
13837             if (target == currentMove - 1) {
13838                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13839             }
13840             if (appData.highlightLastMove) {
13841                 SetHighlights(fromX, fromY, toX, toY);
13842             }
13843         }
13844     }
13845     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13846         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13847         while (currentMove > target) {
13848             SendToProgram("undo\n", &first);
13849             currentMove--;
13850         }
13851     } else {
13852         currentMove = target;
13853     }
13854
13855     if (gameMode == EditGame || gameMode == EndOfGame) {
13856         whiteTimeRemaining = timeRemaining[0][currentMove];
13857         blackTimeRemaining = timeRemaining[1][currentMove];
13858     }
13859     DisplayBothClocks();
13860     DisplayMove(currentMove - 1);
13861     DrawPosition(full_redraw, boards[currentMove]);
13862     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13863     // [HGM] PV info: routine tests if comment empty
13864     DisplayComment(currentMove - 1, commentList[currentMove]);
13865     DisplayBook(currentMove);
13866 }
13867
13868 void
13869 BackwardEvent()
13870 {
13871     if (gameMode == IcsExamining && !pausing) {
13872         SendToICS(ics_prefix);
13873         SendToICS("backward\n");
13874     } else {
13875         BackwardInner(currentMove - 1);
13876     }
13877 }
13878
13879 void
13880 ToStartEvent()
13881 {
13882     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13883         /* to optimize, we temporarily turn off analysis mode while we undo
13884          * all the moves. Otherwise we get analysis output after each undo.
13885          */
13886         if (first.analysisSupport) {
13887           SendToProgram("exit\nforce\n", &first);
13888           first.analyzing = FALSE;
13889         }
13890     }
13891
13892     if (gameMode == IcsExamining && !pausing) {
13893         SendToICS(ics_prefix);
13894         SendToICS("backward 999999\n");
13895     } else {
13896         BackwardInner(backwardMostMove);
13897     }
13898
13899     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13900         /* we have fed all the moves, so reactivate analysis mode */
13901         SendToProgram("analyze\n", &first);
13902         first.analyzing = TRUE;
13903         /*first.maybeThinking = TRUE;*/
13904         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13905     }
13906 }
13907
13908 void
13909 ToNrEvent(int to)
13910 {
13911   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13912   if (to >= forwardMostMove) to = forwardMostMove;
13913   if (to <= backwardMostMove) to = backwardMostMove;
13914   if (to < currentMove) {
13915     BackwardInner(to);
13916   } else {
13917     ForwardInner(to);
13918   }
13919 }
13920
13921 void
13922 RevertEvent(Boolean annotate)
13923 {
13924     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13925         return;
13926     }
13927     if (gameMode != IcsExamining) {
13928         DisplayError(_("You are not examining a game"), 0);
13929         return;
13930     }
13931     if (pausing) {
13932         DisplayError(_("You can't revert while pausing"), 0);
13933         return;
13934     }
13935     SendToICS(ics_prefix);
13936     SendToICS("revert\n");
13937 }
13938
13939 void
13940 RetractMoveEvent()
13941 {
13942     switch (gameMode) {
13943       case MachinePlaysWhite:
13944       case MachinePlaysBlack:
13945         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13946             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13947             return;
13948         }
13949         if (forwardMostMove < 2) return;
13950         currentMove = forwardMostMove = forwardMostMove - 2;
13951         whiteTimeRemaining = timeRemaining[0][currentMove];
13952         blackTimeRemaining = timeRemaining[1][currentMove];
13953         DisplayBothClocks();
13954         DisplayMove(currentMove - 1);
13955         ClearHighlights();/*!! could figure this out*/
13956         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13957         SendToProgram("remove\n", &first);
13958         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13959         break;
13960
13961       case BeginningOfGame:
13962       default:
13963         break;
13964
13965       case IcsPlayingWhite:
13966       case IcsPlayingBlack:
13967         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13968             SendToICS(ics_prefix);
13969             SendToICS("takeback 2\n");
13970         } else {
13971             SendToICS(ics_prefix);
13972             SendToICS("takeback 1\n");
13973         }
13974         break;
13975     }
13976 }
13977
13978 void
13979 MoveNowEvent()
13980 {
13981     ChessProgramState *cps;
13982
13983     switch (gameMode) {
13984       case MachinePlaysWhite:
13985         if (!WhiteOnMove(forwardMostMove)) {
13986             DisplayError(_("It is your turn"), 0);
13987             return;
13988         }
13989         cps = &first;
13990         break;
13991       case MachinePlaysBlack:
13992         if (WhiteOnMove(forwardMostMove)) {
13993             DisplayError(_("It is your turn"), 0);
13994             return;
13995         }
13996         cps = &first;
13997         break;
13998       case TwoMachinesPlay:
13999         if (WhiteOnMove(forwardMostMove) ==
14000             (first.twoMachinesColor[0] == 'w')) {
14001             cps = &first;
14002         } else {
14003             cps = &second;
14004         }
14005         break;
14006       case BeginningOfGame:
14007       default:
14008         return;
14009     }
14010     SendToProgram("?\n", cps);
14011 }
14012
14013 void
14014 TruncateGameEvent()
14015 {
14016     EditGameEvent();
14017     if (gameMode != EditGame) return;
14018     TruncateGame();
14019 }
14020
14021 void
14022 TruncateGame()
14023 {
14024     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14025     if (forwardMostMove > currentMove) {
14026         if (gameInfo.resultDetails != NULL) {
14027             free(gameInfo.resultDetails);
14028             gameInfo.resultDetails = NULL;
14029             gameInfo.result = GameUnfinished;
14030         }
14031         forwardMostMove = currentMove;
14032         HistorySet(parseList, backwardMostMove, forwardMostMove,
14033                    currentMove-1);
14034     }
14035 }
14036
14037 void
14038 HintEvent()
14039 {
14040     if (appData.noChessProgram) return;
14041     switch (gameMode) {
14042       case MachinePlaysWhite:
14043         if (WhiteOnMove(forwardMostMove)) {
14044             DisplayError(_("Wait until your turn"), 0);
14045             return;
14046         }
14047         break;
14048       case BeginningOfGame:
14049       case MachinePlaysBlack:
14050         if (!WhiteOnMove(forwardMostMove)) {
14051             DisplayError(_("Wait until your turn"), 0);
14052             return;
14053         }
14054         break;
14055       default:
14056         DisplayError(_("No hint available"), 0);
14057         return;
14058     }
14059     SendToProgram("hint\n", &first);
14060     hintRequested = TRUE;
14061 }
14062
14063 void
14064 BookEvent()
14065 {
14066     if (appData.noChessProgram) return;
14067     switch (gameMode) {
14068       case MachinePlaysWhite:
14069         if (WhiteOnMove(forwardMostMove)) {
14070             DisplayError(_("Wait until your turn"), 0);
14071             return;
14072         }
14073         break;
14074       case BeginningOfGame:
14075       case MachinePlaysBlack:
14076         if (!WhiteOnMove(forwardMostMove)) {
14077             DisplayError(_("Wait until your turn"), 0);
14078             return;
14079         }
14080         break;
14081       case EditPosition:
14082         EditPositionDone(TRUE);
14083         break;
14084       case TwoMachinesPlay:
14085         return;
14086       default:
14087         break;
14088     }
14089     SendToProgram("bk\n", &first);
14090     bookOutput[0] = NULLCHAR;
14091     bookRequested = TRUE;
14092 }
14093
14094 void
14095 AboutGameEvent()
14096 {
14097     char *tags = PGNTags(&gameInfo);
14098     TagsPopUp(tags, CmailMsg());
14099     free(tags);
14100 }
14101
14102 /* end button procedures */
14103
14104 void
14105 PrintPosition(fp, move)
14106      FILE *fp;
14107      int move;
14108 {
14109     int i, j;
14110
14111     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14112         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14113             char c = PieceToChar(boards[move][i][j]);
14114             fputc(c == 'x' ? '.' : c, fp);
14115             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14116         }
14117     }
14118     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14119       fprintf(fp, "white to play\n");
14120     else
14121       fprintf(fp, "black to play\n");
14122 }
14123
14124 void
14125 PrintOpponents(fp)
14126      FILE *fp;
14127 {
14128     if (gameInfo.white != NULL) {
14129         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14130     } else {
14131         fprintf(fp, "\n");
14132     }
14133 }
14134
14135 /* Find last component of program's own name, using some heuristics */
14136 void
14137 TidyProgramName(prog, host, buf)
14138      char *prog, *host, buf[MSG_SIZ];
14139 {
14140     char *p, *q;
14141     int local = (strcmp(host, "localhost") == 0);
14142     while (!local && (p = strchr(prog, ';')) != NULL) {
14143         p++;
14144         while (*p == ' ') p++;
14145         prog = p;
14146     }
14147     if (*prog == '"' || *prog == '\'') {
14148         q = strchr(prog + 1, *prog);
14149     } else {
14150         q = strchr(prog, ' ');
14151     }
14152     if (q == NULL) q = prog + strlen(prog);
14153     p = q;
14154     while (p >= prog && *p != '/' && *p != '\\') p--;
14155     p++;
14156     if(p == prog && *p == '"') p++;
14157     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14158     memcpy(buf, p, q - p);
14159     buf[q - p] = NULLCHAR;
14160     if (!local) {
14161         strcat(buf, "@");
14162         strcat(buf, host);
14163     }
14164 }
14165
14166 char *
14167 TimeControlTagValue()
14168 {
14169     char buf[MSG_SIZ];
14170     if (!appData.clockMode) {
14171       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14172     } else if (movesPerSession > 0) {
14173       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14174     } else if (timeIncrement == 0) {
14175       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14176     } else {
14177       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14178     }
14179     return StrSave(buf);
14180 }
14181
14182 void
14183 SetGameInfo()
14184 {
14185     /* This routine is used only for certain modes */
14186     VariantClass v = gameInfo.variant;
14187     ChessMove r = GameUnfinished;
14188     char *p = NULL;
14189
14190     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14191         r = gameInfo.result;
14192         p = gameInfo.resultDetails;
14193         gameInfo.resultDetails = NULL;
14194     }
14195     ClearGameInfo(&gameInfo);
14196     gameInfo.variant = v;
14197
14198     switch (gameMode) {
14199       case MachinePlaysWhite:
14200         gameInfo.event = StrSave( appData.pgnEventHeader );
14201         gameInfo.site = StrSave(HostName());
14202         gameInfo.date = PGNDate();
14203         gameInfo.round = StrSave("-");
14204         gameInfo.white = StrSave(first.tidy);
14205         gameInfo.black = StrSave(UserName());
14206         gameInfo.timeControl = TimeControlTagValue();
14207         break;
14208
14209       case MachinePlaysBlack:
14210         gameInfo.event = StrSave( appData.pgnEventHeader );
14211         gameInfo.site = StrSave(HostName());
14212         gameInfo.date = PGNDate();
14213         gameInfo.round = StrSave("-");
14214         gameInfo.white = StrSave(UserName());
14215         gameInfo.black = StrSave(first.tidy);
14216         gameInfo.timeControl = TimeControlTagValue();
14217         break;
14218
14219       case TwoMachinesPlay:
14220         gameInfo.event = StrSave( appData.pgnEventHeader );
14221         gameInfo.site = StrSave(HostName());
14222         gameInfo.date = PGNDate();
14223         if (roundNr > 0) {
14224             char buf[MSG_SIZ];
14225             snprintf(buf, MSG_SIZ, "%d", roundNr);
14226             gameInfo.round = StrSave(buf);
14227         } else {
14228             gameInfo.round = StrSave("-");
14229         }
14230         if (first.twoMachinesColor[0] == 'w') {
14231             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14232             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14233         } else {
14234             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14235             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14236         }
14237         gameInfo.timeControl = TimeControlTagValue();
14238         break;
14239
14240       case EditGame:
14241         gameInfo.event = StrSave("Edited game");
14242         gameInfo.site = StrSave(HostName());
14243         gameInfo.date = PGNDate();
14244         gameInfo.round = StrSave("-");
14245         gameInfo.white = StrSave("-");
14246         gameInfo.black = StrSave("-");
14247         gameInfo.result = r;
14248         gameInfo.resultDetails = p;
14249         break;
14250
14251       case EditPosition:
14252         gameInfo.event = StrSave("Edited position");
14253         gameInfo.site = StrSave(HostName());
14254         gameInfo.date = PGNDate();
14255         gameInfo.round = StrSave("-");
14256         gameInfo.white = StrSave("-");
14257         gameInfo.black = StrSave("-");
14258         break;
14259
14260       case IcsPlayingWhite:
14261       case IcsPlayingBlack:
14262       case IcsObserving:
14263       case IcsExamining:
14264         break;
14265
14266       case PlayFromGameFile:
14267         gameInfo.event = StrSave("Game from non-PGN file");
14268         gameInfo.site = StrSave(HostName());
14269         gameInfo.date = PGNDate();
14270         gameInfo.round = StrSave("-");
14271         gameInfo.white = StrSave("?");
14272         gameInfo.black = StrSave("?");
14273         break;
14274
14275       default:
14276         break;
14277     }
14278 }
14279
14280 void
14281 ReplaceComment(index, text)
14282      int index;
14283      char *text;
14284 {
14285     int len;
14286     char *p;
14287     float score;
14288
14289     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14290        pvInfoList[index-1].depth == len &&
14291        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14292        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14293     while (*text == '\n') text++;
14294     len = strlen(text);
14295     while (len > 0 && text[len - 1] == '\n') len--;
14296
14297     if (commentList[index] != NULL)
14298       free(commentList[index]);
14299
14300     if (len == 0) {
14301         commentList[index] = NULL;
14302         return;
14303     }
14304   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14305       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14306       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14307     commentList[index] = (char *) malloc(len + 2);
14308     strncpy(commentList[index], text, len);
14309     commentList[index][len] = '\n';
14310     commentList[index][len + 1] = NULLCHAR;
14311   } else {
14312     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14313     char *p;
14314     commentList[index] = (char *) malloc(len + 7);
14315     safeStrCpy(commentList[index], "{\n", 3);
14316     safeStrCpy(commentList[index]+2, text, len+1);
14317     commentList[index][len+2] = NULLCHAR;
14318     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14319     strcat(commentList[index], "\n}\n");
14320   }
14321 }
14322
14323 void
14324 CrushCRs(text)
14325      char *text;
14326 {
14327   char *p = text;
14328   char *q = text;
14329   char ch;
14330
14331   do {
14332     ch = *p++;
14333     if (ch == '\r') continue;
14334     *q++ = ch;
14335   } while (ch != '\0');
14336 }
14337
14338 void
14339 AppendComment(index, text, addBraces)
14340      int index;
14341      char *text;
14342      Boolean addBraces; // [HGM] braces: tells if we should add {}
14343 {
14344     int oldlen, len;
14345     char *old;
14346
14347 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14348     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14349
14350     CrushCRs(text);
14351     while (*text == '\n') text++;
14352     len = strlen(text);
14353     while (len > 0 && text[len - 1] == '\n') len--;
14354
14355     if (len == 0) return;
14356
14357     if (commentList[index] != NULL) {
14358         old = commentList[index];
14359         oldlen = strlen(old);
14360         while(commentList[index][oldlen-1] ==  '\n')
14361           commentList[index][--oldlen] = NULLCHAR;
14362         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14363         safeStrCpy(commentList[index], old, oldlen + len + 6);
14364         free(old);
14365         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14366         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14367           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14368           while (*text == '\n') { text++; len--; }
14369           commentList[index][--oldlen] = NULLCHAR;
14370       }
14371         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14372         else          strcat(commentList[index], "\n");
14373         strcat(commentList[index], text);
14374         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14375         else          strcat(commentList[index], "\n");
14376     } else {
14377         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14378         if(addBraces)
14379           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14380         else commentList[index][0] = NULLCHAR;
14381         strcat(commentList[index], text);
14382         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14383         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14384     }
14385 }
14386
14387 static char * FindStr( char * text, char * sub_text )
14388 {
14389     char * result = strstr( text, sub_text );
14390
14391     if( result != NULL ) {
14392         result += strlen( sub_text );
14393     }
14394
14395     return result;
14396 }
14397
14398 /* [AS] Try to extract PV info from PGN comment */
14399 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14400 char *GetInfoFromComment( int index, char * text )
14401 {
14402     char * sep = text, *p;
14403
14404     if( text != NULL && index > 0 ) {
14405         int score = 0;
14406         int depth = 0;
14407         int time = -1, sec = 0, deci;
14408         char * s_eval = FindStr( text, "[%eval " );
14409         char * s_emt = FindStr( text, "[%emt " );
14410
14411         if( s_eval != NULL || s_emt != NULL ) {
14412             /* New style */
14413             char delim;
14414
14415             if( s_eval != NULL ) {
14416                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14417                     return text;
14418                 }
14419
14420                 if( delim != ']' ) {
14421                     return text;
14422                 }
14423             }
14424
14425             if( s_emt != NULL ) {
14426             }
14427                 return text;
14428         }
14429         else {
14430             /* We expect something like: [+|-]nnn.nn/dd */
14431             int score_lo = 0;
14432
14433             if(*text != '{') return text; // [HGM] braces: must be normal comment
14434
14435             sep = strchr( text, '/' );
14436             if( sep == NULL || sep < (text+4) ) {
14437                 return text;
14438             }
14439
14440             p = text;
14441             if(p[1] == '(') { // comment starts with PV
14442                p = strchr(p, ')'); // locate end of PV
14443                if(p == NULL || sep < p+5) return text;
14444                // at this point we have something like "{(.*) +0.23/6 ..."
14445                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14446                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14447                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14448             }
14449             time = -1; sec = -1; deci = -1;
14450             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14451                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14452                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14453                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14454                 return text;
14455             }
14456
14457             if( score_lo < 0 || score_lo >= 100 ) {
14458                 return text;
14459             }
14460
14461             if(sec >= 0) time = 600*time + 10*sec; else
14462             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14463
14464             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14465
14466             /* [HGM] PV time: now locate end of PV info */
14467             while( *++sep >= '0' && *sep <= '9'); // strip depth
14468             if(time >= 0)
14469             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14470             if(sec >= 0)
14471             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14472             if(deci >= 0)
14473             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14474             while(*sep == ' ') sep++;
14475         }
14476
14477         if( depth <= 0 ) {
14478             return text;
14479         }
14480
14481         if( time < 0 ) {
14482             time = -1;
14483         }
14484
14485         pvInfoList[index-1].depth = depth;
14486         pvInfoList[index-1].score = score;
14487         pvInfoList[index-1].time  = 10*time; // centi-sec
14488         if(*sep == '}') *sep = 0; else *--sep = '{';
14489         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14490     }
14491     return sep;
14492 }
14493
14494 void
14495 SendToProgram(message, cps)
14496      char *message;
14497      ChessProgramState *cps;
14498 {
14499     int count, outCount, error;
14500     char buf[MSG_SIZ];
14501
14502     if (cps->pr == NULL) return;
14503     Attention(cps);
14504
14505     if (appData.debugMode) {
14506         TimeMark now;
14507         GetTimeMark(&now);
14508         fprintf(debugFP, "%ld >%-6s: %s",
14509                 SubtractTimeMarks(&now, &programStartTime),
14510                 cps->which, message);
14511     }
14512
14513     count = strlen(message);
14514     outCount = OutputToProcess(cps->pr, message, count, &error);
14515     if (outCount < count && !exiting
14516                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14517       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14518       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14519         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14520             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14521                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14522                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14523                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14524             } else {
14525                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14526                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14527                 gameInfo.result = res;
14528             }
14529             gameInfo.resultDetails = StrSave(buf);
14530         }
14531         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14532         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14533     }
14534 }
14535
14536 void
14537 ReceiveFromProgram(isr, closure, message, count, error)
14538      InputSourceRef isr;
14539      VOIDSTAR closure;
14540      char *message;
14541      int count;
14542      int error;
14543 {
14544     char *end_str;
14545     char buf[MSG_SIZ];
14546     ChessProgramState *cps = (ChessProgramState *)closure;
14547
14548     if (isr != cps->isr) return; /* Killed intentionally */
14549     if (count <= 0) {
14550         if (count == 0) {
14551             RemoveInputSource(cps->isr);
14552             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14553             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14554                     _(cps->which), cps->program);
14555         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14556                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14557                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14558                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14559                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14560                 } else {
14561                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14562                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14563                     gameInfo.result = res;
14564                 }
14565                 gameInfo.resultDetails = StrSave(buf);
14566             }
14567             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14568             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14569         } else {
14570             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14571                     _(cps->which), cps->program);
14572             RemoveInputSource(cps->isr);
14573
14574             /* [AS] Program is misbehaving badly... kill it */
14575             if( count == -2 ) {
14576                 DestroyChildProcess( cps->pr, 9 );
14577                 cps->pr = NoProc;
14578             }
14579
14580             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14581         }
14582         return;
14583     }
14584
14585     if ((end_str = strchr(message, '\r')) != NULL)
14586       *end_str = NULLCHAR;
14587     if ((end_str = strchr(message, '\n')) != NULL)
14588       *end_str = NULLCHAR;
14589
14590     if (appData.debugMode) {
14591         TimeMark now; int print = 1;
14592         char *quote = ""; char c; int i;
14593
14594         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14595                 char start = message[0];
14596                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14597                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14598                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14599                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14600                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14601                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14602                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14603                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14604                    sscanf(message, "hint: %c", &c)!=1 && 
14605                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14606                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14607                     print = (appData.engineComments >= 2);
14608                 }
14609                 message[0] = start; // restore original message
14610         }
14611         if(print) {
14612                 GetTimeMark(&now);
14613                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14614                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14615                         quote,
14616                         message);
14617         }
14618     }
14619
14620     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14621     if (appData.icsEngineAnalyze) {
14622         if (strstr(message, "whisper") != NULL ||
14623              strstr(message, "kibitz") != NULL ||
14624             strstr(message, "tellics") != NULL) return;
14625     }
14626
14627     HandleMachineMove(message, cps);
14628 }
14629
14630
14631 void
14632 SendTimeControl(cps, mps, tc, inc, sd, st)
14633      ChessProgramState *cps;
14634      int mps, inc, sd, st;
14635      long tc;
14636 {
14637     char buf[MSG_SIZ];
14638     int seconds;
14639
14640     if( timeControl_2 > 0 ) {
14641         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14642             tc = timeControl_2;
14643         }
14644     }
14645     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14646     inc /= cps->timeOdds;
14647     st  /= cps->timeOdds;
14648
14649     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14650
14651     if (st > 0) {
14652       /* Set exact time per move, normally using st command */
14653       if (cps->stKludge) {
14654         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14655         seconds = st % 60;
14656         if (seconds == 0) {
14657           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14658         } else {
14659           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14660         }
14661       } else {
14662         snprintf(buf, MSG_SIZ, "st %d\n", st);
14663       }
14664     } else {
14665       /* Set conventional or incremental time control, using level command */
14666       if (seconds == 0) {
14667         /* Note old gnuchess bug -- minutes:seconds used to not work.
14668            Fixed in later versions, but still avoid :seconds
14669            when seconds is 0. */
14670         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14671       } else {
14672         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14673                  seconds, inc/1000.);
14674       }
14675     }
14676     SendToProgram(buf, cps);
14677
14678     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14679     /* Orthogonally, limit search to given depth */
14680     if (sd > 0) {
14681       if (cps->sdKludge) {
14682         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14683       } else {
14684         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14685       }
14686       SendToProgram(buf, cps);
14687     }
14688
14689     if(cps->nps >= 0) { /* [HGM] nps */
14690         if(cps->supportsNPS == FALSE)
14691           cps->nps = -1; // don't use if engine explicitly says not supported!
14692         else {
14693           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14694           SendToProgram(buf, cps);
14695         }
14696     }
14697 }
14698
14699 ChessProgramState *WhitePlayer()
14700 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14701 {
14702     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14703        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14704         return &second;
14705     return &first;
14706 }
14707
14708 void
14709 SendTimeRemaining(cps, machineWhite)
14710      ChessProgramState *cps;
14711      int /*boolean*/ machineWhite;
14712 {
14713     char message[MSG_SIZ];
14714     long time, otime;
14715
14716     /* Note: this routine must be called when the clocks are stopped
14717        or when they have *just* been set or switched; otherwise
14718        it will be off by the time since the current tick started.
14719     */
14720     if (machineWhite) {
14721         time = whiteTimeRemaining / 10;
14722         otime = blackTimeRemaining / 10;
14723     } else {
14724         time = blackTimeRemaining / 10;
14725         otime = whiteTimeRemaining / 10;
14726     }
14727     /* [HGM] translate opponent's time by time-odds factor */
14728     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14729     if (appData.debugMode) {
14730         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14731     }
14732
14733     if (time <= 0) time = 1;
14734     if (otime <= 0) otime = 1;
14735
14736     snprintf(message, MSG_SIZ, "time %ld\n", time);
14737     SendToProgram(message, cps);
14738
14739     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14740     SendToProgram(message, cps);
14741 }
14742
14743 int
14744 BoolFeature(p, name, loc, cps)
14745      char **p;
14746      char *name;
14747      int *loc;
14748      ChessProgramState *cps;
14749 {
14750   char buf[MSG_SIZ];
14751   int len = strlen(name);
14752   int val;
14753
14754   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14755     (*p) += len + 1;
14756     sscanf(*p, "%d", &val);
14757     *loc = (val != 0);
14758     while (**p && **p != ' ')
14759       (*p)++;
14760     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14761     SendToProgram(buf, cps);
14762     return TRUE;
14763   }
14764   return FALSE;
14765 }
14766
14767 int
14768 IntFeature(p, name, loc, cps)
14769      char **p;
14770      char *name;
14771      int *loc;
14772      ChessProgramState *cps;
14773 {
14774   char buf[MSG_SIZ];
14775   int len = strlen(name);
14776   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14777     (*p) += len + 1;
14778     sscanf(*p, "%d", loc);
14779     while (**p && **p != ' ') (*p)++;
14780     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14781     SendToProgram(buf, cps);
14782     return TRUE;
14783   }
14784   return FALSE;
14785 }
14786
14787 int
14788 StringFeature(p, name, loc, cps)
14789      char **p;
14790      char *name;
14791      char loc[];
14792      ChessProgramState *cps;
14793 {
14794   char buf[MSG_SIZ];
14795   int len = strlen(name);
14796   if (strncmp((*p), name, len) == 0
14797       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14798     (*p) += len + 2;
14799     sscanf(*p, "%[^\"]", loc);
14800     while (**p && **p != '\"') (*p)++;
14801     if (**p == '\"') (*p)++;
14802     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14803     SendToProgram(buf, cps);
14804     return TRUE;
14805   }
14806   return FALSE;
14807 }
14808
14809 int
14810 ParseOption(Option *opt, ChessProgramState *cps)
14811 // [HGM] options: process the string that defines an engine option, and determine
14812 // name, type, default value, and allowed value range
14813 {
14814         char *p, *q, buf[MSG_SIZ];
14815         int n, min = (-1)<<31, max = 1<<31, def;
14816
14817         if(p = strstr(opt->name, " -spin ")) {
14818             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14819             if(max < min) max = min; // enforce consistency
14820             if(def < min) def = min;
14821             if(def > max) def = max;
14822             opt->value = def;
14823             opt->min = min;
14824             opt->max = max;
14825             opt->type = Spin;
14826         } else if((p = strstr(opt->name, " -slider "))) {
14827             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14828             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14829             if(max < min) max = min; // enforce consistency
14830             if(def < min) def = min;
14831             if(def > max) def = max;
14832             opt->value = def;
14833             opt->min = min;
14834             opt->max = max;
14835             opt->type = Spin; // Slider;
14836         } else if((p = strstr(opt->name, " -string "))) {
14837             opt->textValue = p+9;
14838             opt->type = TextBox;
14839         } else if((p = strstr(opt->name, " -file "))) {
14840             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14841             opt->textValue = p+7;
14842             opt->type = FileName; // FileName;
14843         } else if((p = strstr(opt->name, " -path "))) {
14844             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14845             opt->textValue = p+7;
14846             opt->type = PathName; // PathName;
14847         } else if(p = strstr(opt->name, " -check ")) {
14848             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14849             opt->value = (def != 0);
14850             opt->type = CheckBox;
14851         } else if(p = strstr(opt->name, " -combo ")) {
14852             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14853             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14854             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14855             opt->value = n = 0;
14856             while(q = StrStr(q, " /// ")) {
14857                 n++; *q = 0;    // count choices, and null-terminate each of them
14858                 q += 5;
14859                 if(*q == '*') { // remember default, which is marked with * prefix
14860                     q++;
14861                     opt->value = n;
14862                 }
14863                 cps->comboList[cps->comboCnt++] = q;
14864             }
14865             cps->comboList[cps->comboCnt++] = NULL;
14866             opt->max = n + 1;
14867             opt->type = ComboBox;
14868         } else if(p = strstr(opt->name, " -button")) {
14869             opt->type = Button;
14870         } else if(p = strstr(opt->name, " -save")) {
14871             opt->type = SaveButton;
14872         } else return FALSE;
14873         *p = 0; // terminate option name
14874         // now look if the command-line options define a setting for this engine option.
14875         if(cps->optionSettings && cps->optionSettings[0])
14876             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14877         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14878           snprintf(buf, MSG_SIZ, "option %s", p);
14879                 if(p = strstr(buf, ",")) *p = 0;
14880                 if(q = strchr(buf, '=')) switch(opt->type) {
14881                     case ComboBox:
14882                         for(n=0; n<opt->max; n++)
14883                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14884                         break;
14885                     case TextBox:
14886                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14887                         break;
14888                     case Spin:
14889                     case CheckBox:
14890                         opt->value = atoi(q+1);
14891                     default:
14892                         break;
14893                 }
14894                 strcat(buf, "\n");
14895                 SendToProgram(buf, cps);
14896         }
14897         return TRUE;
14898 }
14899
14900 void
14901 FeatureDone(cps, val)
14902      ChessProgramState* cps;
14903      int val;
14904 {
14905   DelayedEventCallback cb = GetDelayedEvent();
14906   if ((cb == InitBackEnd3 && cps == &first) ||
14907       (cb == SettingsMenuIfReady && cps == &second) ||
14908       (cb == LoadEngine) ||
14909       (cb == TwoMachinesEventIfReady)) {
14910     CancelDelayedEvent();
14911     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14912   }
14913   cps->initDone = val;
14914 }
14915
14916 /* Parse feature command from engine */
14917 void
14918 ParseFeatures(args, cps)
14919      char* args;
14920      ChessProgramState *cps;
14921 {
14922   char *p = args;
14923   char *q;
14924   int val;
14925   char buf[MSG_SIZ];
14926
14927   for (;;) {
14928     while (*p == ' ') p++;
14929     if (*p == NULLCHAR) return;
14930
14931     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14932     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14933     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14934     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14935     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14936     if (BoolFeature(&p, "reuse", &val, cps)) {
14937       /* Engine can disable reuse, but can't enable it if user said no */
14938       if (!val) cps->reuse = FALSE;
14939       continue;
14940     }
14941     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14942     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14943       if (gameMode == TwoMachinesPlay) {
14944         DisplayTwoMachinesTitle();
14945       } else {
14946         DisplayTitle("");
14947       }
14948       continue;
14949     }
14950     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14951     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14952     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14953     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14954     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14955     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14956     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14957     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14958     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14959     if (IntFeature(&p, "done", &val, cps)) {
14960       FeatureDone(cps, val);
14961       continue;
14962     }
14963     /* Added by Tord: */
14964     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14965     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14966     /* End of additions by Tord */
14967
14968     /* [HGM] added features: */
14969     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14970     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14971     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14972     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14973     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14974     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14975     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14976         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14977           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14978             SendToProgram(buf, cps);
14979             continue;
14980         }
14981         if(cps->nrOptions >= MAX_OPTIONS) {
14982             cps->nrOptions--;
14983             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14984             DisplayError(buf, 0);
14985         }
14986         continue;
14987     }
14988     /* End of additions by HGM */
14989
14990     /* unknown feature: complain and skip */
14991     q = p;
14992     while (*q && *q != '=') q++;
14993     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14994     SendToProgram(buf, cps);
14995     p = q;
14996     if (*p == '=') {
14997       p++;
14998       if (*p == '\"') {
14999         p++;
15000         while (*p && *p != '\"') p++;
15001         if (*p == '\"') p++;
15002       } else {
15003         while (*p && *p != ' ') p++;
15004       }
15005     }
15006   }
15007
15008 }
15009
15010 void
15011 PeriodicUpdatesEvent(newState)
15012      int newState;
15013 {
15014     if (newState == appData.periodicUpdates)
15015       return;
15016
15017     appData.periodicUpdates=newState;
15018
15019     /* Display type changes, so update it now */
15020 //    DisplayAnalysis();
15021
15022     /* Get the ball rolling again... */
15023     if (newState) {
15024         AnalysisPeriodicEvent(1);
15025         StartAnalysisClock();
15026     }
15027 }
15028
15029 void
15030 PonderNextMoveEvent(newState)
15031      int newState;
15032 {
15033     if (newState == appData.ponderNextMove) return;
15034     if (gameMode == EditPosition) EditPositionDone(TRUE);
15035     if (newState) {
15036         SendToProgram("hard\n", &first);
15037         if (gameMode == TwoMachinesPlay) {
15038             SendToProgram("hard\n", &second);
15039         }
15040     } else {
15041         SendToProgram("easy\n", &first);
15042         thinkOutput[0] = NULLCHAR;
15043         if (gameMode == TwoMachinesPlay) {
15044             SendToProgram("easy\n", &second);
15045         }
15046     }
15047     appData.ponderNextMove = newState;
15048 }
15049
15050 void
15051 NewSettingEvent(option, feature, command, value)
15052      char *command;
15053      int option, value, *feature;
15054 {
15055     char buf[MSG_SIZ];
15056
15057     if (gameMode == EditPosition) EditPositionDone(TRUE);
15058     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15059     if(feature == NULL || *feature) SendToProgram(buf, &first);
15060     if (gameMode == TwoMachinesPlay) {
15061         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15062     }
15063 }
15064
15065 void
15066 ShowThinkingEvent()
15067 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15068 {
15069     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15070     int newState = appData.showThinking
15071         // [HGM] thinking: other features now need thinking output as well
15072         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15073
15074     if (oldState == newState) return;
15075     oldState = newState;
15076     if (gameMode == EditPosition) EditPositionDone(TRUE);
15077     if (oldState) {
15078         SendToProgram("post\n", &first);
15079         if (gameMode == TwoMachinesPlay) {
15080             SendToProgram("post\n", &second);
15081         }
15082     } else {
15083         SendToProgram("nopost\n", &first);
15084         thinkOutput[0] = NULLCHAR;
15085         if (gameMode == TwoMachinesPlay) {
15086             SendToProgram("nopost\n", &second);
15087         }
15088     }
15089 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15090 }
15091
15092 void
15093 AskQuestionEvent(title, question, replyPrefix, which)
15094      char *title; char *question; char *replyPrefix; char *which;
15095 {
15096   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15097   if (pr == NoProc) return;
15098   AskQuestion(title, question, replyPrefix, pr);
15099 }
15100
15101 void
15102 TypeInEvent(char firstChar)
15103 {
15104     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15105         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15106         gameMode == AnalyzeMode || gameMode == EditGame || 
15107         gameMode == EditPosition || gameMode == IcsExamining ||
15108         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15109         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15110                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15111                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15112         gameMode == Training) PopUpMoveDialog(firstChar);
15113 }
15114
15115 void
15116 TypeInDoneEvent(char *move)
15117 {
15118         Board board;
15119         int n, fromX, fromY, toX, toY;
15120         char promoChar;
15121         ChessMove moveType;
15122
15123         // [HGM] FENedit
15124         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15125                 EditPositionPasteFEN(move);
15126                 return;
15127         }
15128         // [HGM] movenum: allow move number to be typed in any mode
15129         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15130           ToNrEvent(2*n-1);
15131           return;
15132         }
15133
15134       if (gameMode != EditGame && currentMove != forwardMostMove && 
15135         gameMode != Training) {
15136         DisplayMoveError(_("Displayed move is not current"));
15137       } else {
15138         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15139           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15140         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15141         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15142           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15143           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15144         } else {
15145           DisplayMoveError(_("Could not parse move"));
15146         }
15147       }
15148 }
15149
15150 void
15151 DisplayMove(moveNumber)
15152      int moveNumber;
15153 {
15154     char message[MSG_SIZ];
15155     char res[MSG_SIZ];
15156     char cpThinkOutput[MSG_SIZ];
15157
15158     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15159
15160     if (moveNumber == forwardMostMove - 1 ||
15161         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15162
15163         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15164
15165         if (strchr(cpThinkOutput, '\n')) {
15166             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15167         }
15168     } else {
15169         *cpThinkOutput = NULLCHAR;
15170     }
15171
15172     /* [AS] Hide thinking from human user */
15173     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15174         *cpThinkOutput = NULLCHAR;
15175         if( thinkOutput[0] != NULLCHAR ) {
15176             int i;
15177
15178             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15179                 cpThinkOutput[i] = '.';
15180             }
15181             cpThinkOutput[i] = NULLCHAR;
15182             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15183         }
15184     }
15185
15186     if (moveNumber == forwardMostMove - 1 &&
15187         gameInfo.resultDetails != NULL) {
15188         if (gameInfo.resultDetails[0] == NULLCHAR) {
15189           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15190         } else {
15191           snprintf(res, MSG_SIZ, " {%s} %s",
15192                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15193         }
15194     } else {
15195         res[0] = NULLCHAR;
15196     }
15197
15198     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15199         DisplayMessage(res, cpThinkOutput);
15200     } else {
15201       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15202                 WhiteOnMove(moveNumber) ? " " : ".. ",
15203                 parseList[moveNumber], res);
15204         DisplayMessage(message, cpThinkOutput);
15205     }
15206 }
15207
15208 void
15209 DisplayComment(moveNumber, text)
15210      int moveNumber;
15211      char *text;
15212 {
15213     char title[MSG_SIZ];
15214
15215     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15216       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15217     } else {
15218       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15219               WhiteOnMove(moveNumber) ? " " : ".. ",
15220               parseList[moveNumber]);
15221     }
15222     if (text != NULL && (appData.autoDisplayComment || commentUp))
15223         CommentPopUp(title, text);
15224 }
15225
15226 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15227  * might be busy thinking or pondering.  It can be omitted if your
15228  * gnuchess is configured to stop thinking immediately on any user
15229  * input.  However, that gnuchess feature depends on the FIONREAD
15230  * ioctl, which does not work properly on some flavors of Unix.
15231  */
15232 void
15233 Attention(cps)
15234      ChessProgramState *cps;
15235 {
15236 #if ATTENTION
15237     if (!cps->useSigint) return;
15238     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15239     switch (gameMode) {
15240       case MachinePlaysWhite:
15241       case MachinePlaysBlack:
15242       case TwoMachinesPlay:
15243       case IcsPlayingWhite:
15244       case IcsPlayingBlack:
15245       case AnalyzeMode:
15246       case AnalyzeFile:
15247         /* Skip if we know it isn't thinking */
15248         if (!cps->maybeThinking) return;
15249         if (appData.debugMode)
15250           fprintf(debugFP, "Interrupting %s\n", cps->which);
15251         InterruptChildProcess(cps->pr);
15252         cps->maybeThinking = FALSE;
15253         break;
15254       default:
15255         break;
15256     }
15257 #endif /*ATTENTION*/
15258 }
15259
15260 int
15261 CheckFlags()
15262 {
15263     if (whiteTimeRemaining <= 0) {
15264         if (!whiteFlag) {
15265             whiteFlag = TRUE;
15266             if (appData.icsActive) {
15267                 if (appData.autoCallFlag &&
15268                     gameMode == IcsPlayingBlack && !blackFlag) {
15269                   SendToICS(ics_prefix);
15270                   SendToICS("flag\n");
15271                 }
15272             } else {
15273                 if (blackFlag) {
15274                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15275                 } else {
15276                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15277                     if (appData.autoCallFlag) {
15278                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15279                         return TRUE;
15280                     }
15281                 }
15282             }
15283         }
15284     }
15285     if (blackTimeRemaining <= 0) {
15286         if (!blackFlag) {
15287             blackFlag = TRUE;
15288             if (appData.icsActive) {
15289                 if (appData.autoCallFlag &&
15290                     gameMode == IcsPlayingWhite && !whiteFlag) {
15291                   SendToICS(ics_prefix);
15292                   SendToICS("flag\n");
15293                 }
15294             } else {
15295                 if (whiteFlag) {
15296                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15297                 } else {
15298                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15299                     if (appData.autoCallFlag) {
15300                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15301                         return TRUE;
15302                     }
15303                 }
15304             }
15305         }
15306     }
15307     return FALSE;
15308 }
15309
15310 void
15311 CheckTimeControl()
15312 {
15313     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15314         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15315
15316     /*
15317      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15318      */
15319     if ( !WhiteOnMove(forwardMostMove) ) {
15320         /* White made time control */
15321         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15322         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15323         /* [HGM] time odds: correct new time quota for time odds! */
15324                                             / WhitePlayer()->timeOdds;
15325         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15326     } else {
15327         lastBlack -= blackTimeRemaining;
15328         /* Black made time control */
15329         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15330                                             / WhitePlayer()->other->timeOdds;
15331         lastWhite = whiteTimeRemaining;
15332     }
15333 }
15334
15335 void
15336 DisplayBothClocks()
15337 {
15338     int wom = gameMode == EditPosition ?
15339       !blackPlaysFirst : WhiteOnMove(currentMove);
15340     DisplayWhiteClock(whiteTimeRemaining, wom);
15341     DisplayBlackClock(blackTimeRemaining, !wom);
15342 }
15343
15344
15345 /* Timekeeping seems to be a portability nightmare.  I think everyone
15346    has ftime(), but I'm really not sure, so I'm including some ifdefs
15347    to use other calls if you don't.  Clocks will be less accurate if
15348    you have neither ftime nor gettimeofday.
15349 */
15350
15351 /* VS 2008 requires the #include outside of the function */
15352 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15353 #include <sys/timeb.h>
15354 #endif
15355
15356 /* Get the current time as a TimeMark */
15357 void
15358 GetTimeMark(tm)
15359      TimeMark *tm;
15360 {
15361 #if HAVE_GETTIMEOFDAY
15362
15363     struct timeval timeVal;
15364     struct timezone timeZone;
15365
15366     gettimeofday(&timeVal, &timeZone);
15367     tm->sec = (long) timeVal.tv_sec;
15368     tm->ms = (int) (timeVal.tv_usec / 1000L);
15369
15370 #else /*!HAVE_GETTIMEOFDAY*/
15371 #if HAVE_FTIME
15372
15373 // include <sys/timeb.h> / moved to just above start of function
15374     struct timeb timeB;
15375
15376     ftime(&timeB);
15377     tm->sec = (long) timeB.time;
15378     tm->ms = (int) timeB.millitm;
15379
15380 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15381     tm->sec = (long) time(NULL);
15382     tm->ms = 0;
15383 #endif
15384 #endif
15385 }
15386
15387 /* Return the difference in milliseconds between two
15388    time marks.  We assume the difference will fit in a long!
15389 */
15390 long
15391 SubtractTimeMarks(tm2, tm1)
15392      TimeMark *tm2, *tm1;
15393 {
15394     return 1000L*(tm2->sec - tm1->sec) +
15395            (long) (tm2->ms - tm1->ms);
15396 }
15397
15398
15399 /*
15400  * Code to manage the game clocks.
15401  *
15402  * In tournament play, black starts the clock and then white makes a move.
15403  * We give the human user a slight advantage if he is playing white---the
15404  * clocks don't run until he makes his first move, so it takes zero time.
15405  * Also, we don't account for network lag, so we could get out of sync
15406  * with GNU Chess's clock -- but then, referees are always right.
15407  */
15408
15409 static TimeMark tickStartTM;
15410 static long intendedTickLength;
15411
15412 long
15413 NextTickLength(timeRemaining)
15414      long timeRemaining;
15415 {
15416     long nominalTickLength, nextTickLength;
15417
15418     if (timeRemaining > 0L && timeRemaining <= 10000L)
15419       nominalTickLength = 100L;
15420     else
15421       nominalTickLength = 1000L;
15422     nextTickLength = timeRemaining % nominalTickLength;
15423     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15424
15425     return nextTickLength;
15426 }
15427
15428 /* Adjust clock one minute up or down */
15429 void
15430 AdjustClock(Boolean which, int dir)
15431 {
15432     if(which) blackTimeRemaining += 60000*dir;
15433     else      whiteTimeRemaining += 60000*dir;
15434     DisplayBothClocks();
15435 }
15436
15437 /* Stop clocks and reset to a fresh time control */
15438 void
15439 ResetClocks()
15440 {
15441     (void) StopClockTimer();
15442     if (appData.icsActive) {
15443         whiteTimeRemaining = blackTimeRemaining = 0;
15444     } else if (searchTime) {
15445         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15446         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15447     } else { /* [HGM] correct new time quote for time odds */
15448         whiteTC = blackTC = fullTimeControlString;
15449         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15450         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15451     }
15452     if (whiteFlag || blackFlag) {
15453         DisplayTitle("");
15454         whiteFlag = blackFlag = FALSE;
15455     }
15456     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15457     DisplayBothClocks();
15458 }
15459
15460 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15461
15462 /* Decrement running clock by amount of time that has passed */
15463 void
15464 DecrementClocks()
15465 {
15466     long timeRemaining;
15467     long lastTickLength, fudge;
15468     TimeMark now;
15469
15470     if (!appData.clockMode) return;
15471     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15472
15473     GetTimeMark(&now);
15474
15475     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15476
15477     /* Fudge if we woke up a little too soon */
15478     fudge = intendedTickLength - lastTickLength;
15479     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15480
15481     if (WhiteOnMove(forwardMostMove)) {
15482         if(whiteNPS >= 0) lastTickLength = 0;
15483         timeRemaining = whiteTimeRemaining -= lastTickLength;
15484         if(timeRemaining < 0 && !appData.icsActive) {
15485             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15486             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15487                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15488                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15489             }
15490         }
15491         DisplayWhiteClock(whiteTimeRemaining - fudge,
15492                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15493     } else {
15494         if(blackNPS >= 0) lastTickLength = 0;
15495         timeRemaining = blackTimeRemaining -= lastTickLength;
15496         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15497             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15498             if(suddenDeath) {
15499                 blackStartMove = forwardMostMove;
15500                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15501             }
15502         }
15503         DisplayBlackClock(blackTimeRemaining - fudge,
15504                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15505     }
15506     if (CheckFlags()) return;
15507
15508     tickStartTM = now;
15509     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15510     StartClockTimer(intendedTickLength);
15511
15512     /* if the time remaining has fallen below the alarm threshold, sound the
15513      * alarm. if the alarm has sounded and (due to a takeback or time control
15514      * with increment) the time remaining has increased to a level above the
15515      * threshold, reset the alarm so it can sound again.
15516      */
15517
15518     if (appData.icsActive && appData.icsAlarm) {
15519
15520         /* make sure we are dealing with the user's clock */
15521         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15522                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15523            )) return;
15524
15525         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15526             alarmSounded = FALSE;
15527         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15528             PlayAlarmSound();
15529             alarmSounded = TRUE;
15530         }
15531     }
15532 }
15533
15534
15535 /* A player has just moved, so stop the previously running
15536    clock and (if in clock mode) start the other one.
15537    We redisplay both clocks in case we're in ICS mode, because
15538    ICS gives us an update to both clocks after every move.
15539    Note that this routine is called *after* forwardMostMove
15540    is updated, so the last fractional tick must be subtracted
15541    from the color that is *not* on move now.
15542 */
15543 void
15544 SwitchClocks(int newMoveNr)
15545 {
15546     long lastTickLength;
15547     TimeMark now;
15548     int flagged = FALSE;
15549
15550     GetTimeMark(&now);
15551
15552     if (StopClockTimer() && appData.clockMode) {
15553         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15554         if (!WhiteOnMove(forwardMostMove)) {
15555             if(blackNPS >= 0) lastTickLength = 0;
15556             blackTimeRemaining -= lastTickLength;
15557            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15558 //         if(pvInfoList[forwardMostMove].time == -1)
15559                  pvInfoList[forwardMostMove].time =               // use GUI time
15560                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15561         } else {
15562            if(whiteNPS >= 0) lastTickLength = 0;
15563            whiteTimeRemaining -= lastTickLength;
15564            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15565 //         if(pvInfoList[forwardMostMove].time == -1)
15566                  pvInfoList[forwardMostMove].time =
15567                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15568         }
15569         flagged = CheckFlags();
15570     }
15571     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15572     CheckTimeControl();
15573
15574     if (flagged || !appData.clockMode) return;
15575
15576     switch (gameMode) {
15577       case MachinePlaysBlack:
15578       case MachinePlaysWhite:
15579       case BeginningOfGame:
15580         if (pausing) return;
15581         break;
15582
15583       case EditGame:
15584       case PlayFromGameFile:
15585       case IcsExamining:
15586         return;
15587
15588       default:
15589         break;
15590     }
15591
15592     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15593         if(WhiteOnMove(forwardMostMove))
15594              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15595         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15596     }
15597
15598     tickStartTM = now;
15599     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15600       whiteTimeRemaining : blackTimeRemaining);
15601     StartClockTimer(intendedTickLength);
15602 }
15603
15604
15605 /* Stop both clocks */
15606 void
15607 StopClocks()
15608 {
15609     long lastTickLength;
15610     TimeMark now;
15611
15612     if (!StopClockTimer()) return;
15613     if (!appData.clockMode) return;
15614
15615     GetTimeMark(&now);
15616
15617     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15618     if (WhiteOnMove(forwardMostMove)) {
15619         if(whiteNPS >= 0) lastTickLength = 0;
15620         whiteTimeRemaining -= lastTickLength;
15621         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15622     } else {
15623         if(blackNPS >= 0) lastTickLength = 0;
15624         blackTimeRemaining -= lastTickLength;
15625         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15626     }
15627     CheckFlags();
15628 }
15629
15630 /* Start clock of player on move.  Time may have been reset, so
15631    if clock is already running, stop and restart it. */
15632 void
15633 StartClocks()
15634 {
15635     (void) StopClockTimer(); /* in case it was running already */
15636     DisplayBothClocks();
15637     if (CheckFlags()) return;
15638
15639     if (!appData.clockMode) return;
15640     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15641
15642     GetTimeMark(&tickStartTM);
15643     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15644       whiteTimeRemaining : blackTimeRemaining);
15645
15646    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15647     whiteNPS = blackNPS = -1;
15648     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15649        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15650         whiteNPS = first.nps;
15651     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15652        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15653         blackNPS = first.nps;
15654     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15655         whiteNPS = second.nps;
15656     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15657         blackNPS = second.nps;
15658     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15659
15660     StartClockTimer(intendedTickLength);
15661 }
15662
15663 char *
15664 TimeString(ms)
15665      long ms;
15666 {
15667     long second, minute, hour, day;
15668     char *sign = "";
15669     static char buf[32];
15670
15671     if (ms > 0 && ms <= 9900) {
15672       /* convert milliseconds to tenths, rounding up */
15673       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15674
15675       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15676       return buf;
15677     }
15678
15679     /* convert milliseconds to seconds, rounding up */
15680     /* use floating point to avoid strangeness of integer division
15681        with negative dividends on many machines */
15682     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15683
15684     if (second < 0) {
15685         sign = "-";
15686         second = -second;
15687     }
15688
15689     day = second / (60 * 60 * 24);
15690     second = second % (60 * 60 * 24);
15691     hour = second / (60 * 60);
15692     second = second % (60 * 60);
15693     minute = second / 60;
15694     second = second % 60;
15695
15696     if (day > 0)
15697       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15698               sign, day, hour, minute, second);
15699     else if (hour > 0)
15700       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15701     else
15702       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15703
15704     return buf;
15705 }
15706
15707
15708 /*
15709  * This is necessary because some C libraries aren't ANSI C compliant yet.
15710  */
15711 char *
15712 StrStr(string, match)
15713      char *string, *match;
15714 {
15715     int i, length;
15716
15717     length = strlen(match);
15718
15719     for (i = strlen(string) - length; i >= 0; i--, string++)
15720       if (!strncmp(match, string, length))
15721         return string;
15722
15723     return NULL;
15724 }
15725
15726 char *
15727 StrCaseStr(string, match)
15728      char *string, *match;
15729 {
15730     int i, j, length;
15731
15732     length = strlen(match);
15733
15734     for (i = strlen(string) - length; i >= 0; i--, string++) {
15735         for (j = 0; j < length; j++) {
15736             if (ToLower(match[j]) != ToLower(string[j]))
15737               break;
15738         }
15739         if (j == length) return string;
15740     }
15741
15742     return NULL;
15743 }
15744
15745 #ifndef _amigados
15746 int
15747 StrCaseCmp(s1, s2)
15748      char *s1, *s2;
15749 {
15750     char c1, c2;
15751
15752     for (;;) {
15753         c1 = ToLower(*s1++);
15754         c2 = ToLower(*s2++);
15755         if (c1 > c2) return 1;
15756         if (c1 < c2) return -1;
15757         if (c1 == NULLCHAR) return 0;
15758     }
15759 }
15760
15761
15762 int
15763 ToLower(c)
15764      int c;
15765 {
15766     return isupper(c) ? tolower(c) : c;
15767 }
15768
15769
15770 int
15771 ToUpper(c)
15772      int c;
15773 {
15774     return islower(c) ? toupper(c) : c;
15775 }
15776 #endif /* !_amigados    */
15777
15778 char *
15779 StrSave(s)
15780      char *s;
15781 {
15782   char *ret;
15783
15784   if ((ret = (char *) malloc(strlen(s) + 1)))
15785     {
15786       safeStrCpy(ret, s, strlen(s)+1);
15787     }
15788   return ret;
15789 }
15790
15791 char *
15792 StrSavePtr(s, savePtr)
15793      char *s, **savePtr;
15794 {
15795     if (*savePtr) {
15796         free(*savePtr);
15797     }
15798     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15799       safeStrCpy(*savePtr, s, strlen(s)+1);
15800     }
15801     return(*savePtr);
15802 }
15803
15804 char *
15805 PGNDate()
15806 {
15807     time_t clock;
15808     struct tm *tm;
15809     char buf[MSG_SIZ];
15810
15811     clock = time((time_t *)NULL);
15812     tm = localtime(&clock);
15813     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15814             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15815     return StrSave(buf);
15816 }
15817
15818
15819 char *
15820 PositionToFEN(move, overrideCastling)
15821      int move;
15822      char *overrideCastling;
15823 {
15824     int i, j, fromX, fromY, toX, toY;
15825     int whiteToPlay;
15826     char buf[MSG_SIZ];
15827     char *p, *q;
15828     int emptycount;
15829     ChessSquare piece;
15830
15831     whiteToPlay = (gameMode == EditPosition) ?
15832       !blackPlaysFirst : (move % 2 == 0);
15833     p = buf;
15834
15835     /* Piece placement data */
15836     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15837         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
15838         emptycount = 0;
15839         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15840             if (boards[move][i][j] == EmptySquare) {
15841                 emptycount++;
15842             } else { ChessSquare piece = boards[move][i][j];
15843                 if (emptycount > 0) {
15844                     if(emptycount<10) /* [HGM] can be >= 10 */
15845                         *p++ = '0' + emptycount;
15846                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15847                     emptycount = 0;
15848                 }
15849                 if(PieceToChar(piece) == '+') {
15850                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15851                     *p++ = '+';
15852                     piece = (ChessSquare)(DEMOTED piece);
15853                 }
15854                 *p++ = PieceToChar(piece);
15855                 if(p[-1] == '~') {
15856                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15857                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15858                     *p++ = '~';
15859                 }
15860             }
15861         }
15862         if (emptycount > 0) {
15863             if(emptycount<10) /* [HGM] can be >= 10 */
15864                 *p++ = '0' + emptycount;
15865             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15866             emptycount = 0;
15867         }
15868         *p++ = '/';
15869     }
15870     *(p - 1) = ' ';
15871
15872     /* [HGM] print Crazyhouse or Shogi holdings */
15873     if( gameInfo.holdingsWidth ) {
15874         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15875         q = p;
15876         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15877             piece = boards[move][i][BOARD_WIDTH-1];
15878             if( piece != EmptySquare )
15879               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15880                   *p++ = PieceToChar(piece);
15881         }
15882         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15883             piece = boards[move][BOARD_HEIGHT-i-1][0];
15884             if( piece != EmptySquare )
15885               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15886                   *p++ = PieceToChar(piece);
15887         }
15888
15889         if( q == p ) *p++ = '-';
15890         *p++ = ']';
15891         *p++ = ' ';
15892     }
15893
15894     /* Active color */
15895     *p++ = whiteToPlay ? 'w' : 'b';
15896     *p++ = ' ';
15897
15898   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15899     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15900   } else {
15901   if(nrCastlingRights) {
15902      q = p;
15903      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15904        /* [HGM] write directly from rights */
15905            if(boards[move][CASTLING][2] != NoRights &&
15906               boards[move][CASTLING][0] != NoRights   )
15907                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15908            if(boards[move][CASTLING][2] != NoRights &&
15909               boards[move][CASTLING][1] != NoRights   )
15910                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15911            if(boards[move][CASTLING][5] != NoRights &&
15912               boards[move][CASTLING][3] != NoRights   )
15913                 *p++ = boards[move][CASTLING][3] + AAA;
15914            if(boards[move][CASTLING][5] != NoRights &&
15915               boards[move][CASTLING][4] != NoRights   )
15916                 *p++ = boards[move][CASTLING][4] + AAA;
15917      } else {
15918
15919         /* [HGM] write true castling rights */
15920         if( nrCastlingRights == 6 ) {
15921             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15922                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15923             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15924                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15925             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15926                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15927             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15928                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15929         }
15930      }
15931      if (q == p) *p++ = '-'; /* No castling rights */
15932      *p++ = ' ';
15933   }
15934
15935   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15936      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15937     /* En passant target square */
15938     if (move > backwardMostMove) {
15939         fromX = moveList[move - 1][0] - AAA;
15940         fromY = moveList[move - 1][1] - ONE;
15941         toX = moveList[move - 1][2] - AAA;
15942         toY = moveList[move - 1][3] - ONE;
15943         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15944             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15945             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15946             fromX == toX) {
15947             /* 2-square pawn move just happened */
15948             *p++ = toX + AAA;
15949             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15950         } else {
15951             *p++ = '-';
15952         }
15953     } else if(move == backwardMostMove) {
15954         // [HGM] perhaps we should always do it like this, and forget the above?
15955         if((signed char)boards[move][EP_STATUS] >= 0) {
15956             *p++ = boards[move][EP_STATUS] + AAA;
15957             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15958         } else {
15959             *p++ = '-';
15960         }
15961     } else {
15962         *p++ = '-';
15963     }
15964     *p++ = ' ';
15965   }
15966   }
15967
15968     /* [HGM] find reversible plies */
15969     {   int i = 0, j=move;
15970
15971         if (appData.debugMode) { int k;
15972             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15973             for(k=backwardMostMove; k<=forwardMostMove; k++)
15974                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15975
15976         }
15977
15978         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15979         if( j == backwardMostMove ) i += initialRulePlies;
15980         sprintf(p, "%d ", i);
15981         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15982     }
15983     /* Fullmove number */
15984     sprintf(p, "%d", (move / 2) + 1);
15985
15986     return StrSave(buf);
15987 }
15988
15989 Boolean
15990 ParseFEN(board, blackPlaysFirst, fen)
15991     Board board;
15992      int *blackPlaysFirst;
15993      char *fen;
15994 {
15995     int i, j;
15996     char *p, c;
15997     int emptycount;
15998     ChessSquare piece;
15999
16000     p = fen;
16001
16002     /* [HGM] by default clear Crazyhouse holdings, if present */
16003     if(gameInfo.holdingsWidth) {
16004        for(i=0; i<BOARD_HEIGHT; i++) {
16005            board[i][0]             = EmptySquare; /* black holdings */
16006            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16007            board[i][1]             = (ChessSquare) 0; /* black counts */
16008            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16009        }
16010     }
16011
16012     /* Piece placement data */
16013     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16014         j = 0;
16015         for (;;) {
16016             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16017                 if (*p == '/') p++;
16018                 emptycount = gameInfo.boardWidth - j;
16019                 while (emptycount--)
16020                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16021                 break;
16022 #if(BOARD_FILES >= 10)
16023             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16024                 p++; emptycount=10;
16025                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16026                 while (emptycount--)
16027                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16028 #endif
16029             } else if (isdigit(*p)) {
16030                 emptycount = *p++ - '0';
16031                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16032                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16033                 while (emptycount--)
16034                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16035             } else if (*p == '+' || isalpha(*p)) {
16036                 if (j >= gameInfo.boardWidth) return FALSE;
16037                 if(*p=='+') {
16038                     piece = CharToPiece(*++p);
16039                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16040                     piece = (ChessSquare) (PROMOTED piece ); p++;
16041                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16042                 } else piece = CharToPiece(*p++);
16043
16044                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16045                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16046                     piece = (ChessSquare) (PROMOTED piece);
16047                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16048                     p++;
16049                 }
16050                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16051             } else {
16052                 return FALSE;
16053             }
16054         }
16055     }
16056     while (*p == '/' || *p == ' ') p++;
16057
16058     /* [HGM] look for Crazyhouse holdings here */
16059     while(*p==' ') p++;
16060     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16061         if(*p == '[') p++;
16062         if(*p == '-' ) p++; /* empty holdings */ else {
16063             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16064             /* if we would allow FEN reading to set board size, we would   */
16065             /* have to add holdings and shift the board read so far here   */
16066             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16067                 p++;
16068                 if((int) piece >= (int) BlackPawn ) {
16069                     i = (int)piece - (int)BlackPawn;
16070                     i = PieceToNumber((ChessSquare)i);
16071                     if( i >= gameInfo.holdingsSize ) return FALSE;
16072                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16073                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16074                 } else {
16075                     i = (int)piece - (int)WhitePawn;
16076                     i = PieceToNumber((ChessSquare)i);
16077                     if( i >= gameInfo.holdingsSize ) return FALSE;
16078                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16079                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16080                 }
16081             }
16082         }
16083         if(*p == ']') p++;
16084     }
16085
16086     while(*p == ' ') p++;
16087
16088     /* Active color */
16089     c = *p++;
16090     if(appData.colorNickNames) {
16091       if( c == appData.colorNickNames[0] ) c = 'w'; else
16092       if( c == appData.colorNickNames[1] ) c = 'b';
16093     }
16094     switch (c) {
16095       case 'w':
16096         *blackPlaysFirst = FALSE;
16097         break;
16098       case 'b':
16099         *blackPlaysFirst = TRUE;
16100         break;
16101       default:
16102         return FALSE;
16103     }
16104
16105     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16106     /* return the extra info in global variiables             */
16107
16108     /* set defaults in case FEN is incomplete */
16109     board[EP_STATUS] = EP_UNKNOWN;
16110     for(i=0; i<nrCastlingRights; i++ ) {
16111         board[CASTLING][i] =
16112             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16113     }   /* assume possible unless obviously impossible */
16114     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16115     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16116     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16117                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16118     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16119     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16120     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16121                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16122     FENrulePlies = 0;
16123
16124     while(*p==' ') p++;
16125     if(nrCastlingRights) {
16126       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16127           /* castling indicator present, so default becomes no castlings */
16128           for(i=0; i<nrCastlingRights; i++ ) {
16129                  board[CASTLING][i] = NoRights;
16130           }
16131       }
16132       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16133              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16134              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16135              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16136         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16137
16138         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16139             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16140             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16141         }
16142         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16143             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16144         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16145                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16146         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16147                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16148         switch(c) {
16149           case'K':
16150               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16151               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16152               board[CASTLING][2] = whiteKingFile;
16153               break;
16154           case'Q':
16155               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16156               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16157               board[CASTLING][2] = whiteKingFile;
16158               break;
16159           case'k':
16160               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16161               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16162               board[CASTLING][5] = blackKingFile;
16163               break;
16164           case'q':
16165               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16166               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16167               board[CASTLING][5] = blackKingFile;
16168           case '-':
16169               break;
16170           default: /* FRC castlings */
16171               if(c >= 'a') { /* black rights */
16172                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16173                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16174                   if(i == BOARD_RGHT) break;
16175                   board[CASTLING][5] = i;
16176                   c -= AAA;
16177                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16178                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16179                   if(c > i)
16180                       board[CASTLING][3] = c;
16181                   else
16182                       board[CASTLING][4] = c;
16183               } else { /* white rights */
16184                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16185                     if(board[0][i] == WhiteKing) break;
16186                   if(i == BOARD_RGHT) break;
16187                   board[CASTLING][2] = i;
16188                   c -= AAA - 'a' + 'A';
16189                   if(board[0][c] >= WhiteKing) break;
16190                   if(c > i)
16191                       board[CASTLING][0] = c;
16192                   else
16193                       board[CASTLING][1] = c;
16194               }
16195         }
16196       }
16197       for(i=0; i<nrCastlingRights; i++)
16198         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16199     if (appData.debugMode) {
16200         fprintf(debugFP, "FEN castling rights:");
16201         for(i=0; i<nrCastlingRights; i++)
16202         fprintf(debugFP, " %d", board[CASTLING][i]);
16203         fprintf(debugFP, "\n");
16204     }
16205
16206       while(*p==' ') p++;
16207     }
16208
16209     /* read e.p. field in games that know e.p. capture */
16210     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16211        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16212       if(*p=='-') {
16213         p++; board[EP_STATUS] = EP_NONE;
16214       } else {
16215          char c = *p++ - AAA;
16216
16217          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16218          if(*p >= '0' && *p <='9') p++;
16219          board[EP_STATUS] = c;
16220       }
16221     }
16222
16223
16224     if(sscanf(p, "%d", &i) == 1) {
16225         FENrulePlies = i; /* 50-move ply counter */
16226         /* (The move number is still ignored)    */
16227     }
16228
16229     return TRUE;
16230 }
16231
16232 void
16233 EditPositionPasteFEN(char *fen)
16234 {
16235   if (fen != NULL) {
16236     Board initial_position;
16237
16238     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16239       DisplayError(_("Bad FEN position in clipboard"), 0);
16240       return ;
16241     } else {
16242       int savedBlackPlaysFirst = blackPlaysFirst;
16243       EditPositionEvent();
16244       blackPlaysFirst = savedBlackPlaysFirst;
16245       CopyBoard(boards[0], initial_position);
16246       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16247       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16248       DisplayBothClocks();
16249       DrawPosition(FALSE, boards[currentMove]);
16250     }
16251   }
16252 }
16253
16254 static char cseq[12] = "\\   ";
16255
16256 Boolean set_cont_sequence(char *new_seq)
16257 {
16258     int len;
16259     Boolean ret;
16260
16261     // handle bad attempts to set the sequence
16262         if (!new_seq)
16263                 return 0; // acceptable error - no debug
16264
16265     len = strlen(new_seq);
16266     ret = (len > 0) && (len < sizeof(cseq));
16267     if (ret)
16268       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16269     else if (appData.debugMode)
16270       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16271     return ret;
16272 }
16273
16274 /*
16275     reformat a source message so words don't cross the width boundary.  internal
16276     newlines are not removed.  returns the wrapped size (no null character unless
16277     included in source message).  If dest is NULL, only calculate the size required
16278     for the dest buffer.  lp argument indicats line position upon entry, and it's
16279     passed back upon exit.
16280 */
16281 int wrap(char *dest, char *src, int count, int width, int *lp)
16282 {
16283     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16284
16285     cseq_len = strlen(cseq);
16286     old_line = line = *lp;
16287     ansi = len = clen = 0;
16288
16289     for (i=0; i < count; i++)
16290     {
16291         if (src[i] == '\033')
16292             ansi = 1;
16293
16294         // if we hit the width, back up
16295         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16296         {
16297             // store i & len in case the word is too long
16298             old_i = i, old_len = len;
16299
16300             // find the end of the last word
16301             while (i && src[i] != ' ' && src[i] != '\n')
16302             {
16303                 i--;
16304                 len--;
16305             }
16306
16307             // word too long?  restore i & len before splitting it
16308             if ((old_i-i+clen) >= width)
16309             {
16310                 i = old_i;
16311                 len = old_len;
16312             }
16313
16314             // extra space?
16315             if (i && src[i-1] == ' ')
16316                 len--;
16317
16318             if (src[i] != ' ' && src[i] != '\n')
16319             {
16320                 i--;
16321                 if (len)
16322                     len--;
16323             }
16324
16325             // now append the newline and continuation sequence
16326             if (dest)
16327                 dest[len] = '\n';
16328             len++;
16329             if (dest)
16330                 strncpy(dest+len, cseq, cseq_len);
16331             len += cseq_len;
16332             line = cseq_len;
16333             clen = cseq_len;
16334             continue;
16335         }
16336
16337         if (dest)
16338             dest[len] = src[i];
16339         len++;
16340         if (!ansi)
16341             line++;
16342         if (src[i] == '\n')
16343             line = 0;
16344         if (src[i] == 'm')
16345             ansi = 0;
16346     }
16347     if (dest && appData.debugMode)
16348     {
16349         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16350             count, width, line, len, *lp);
16351         show_bytes(debugFP, src, count);
16352         fprintf(debugFP, "\ndest: ");
16353         show_bytes(debugFP, dest, len);
16354         fprintf(debugFP, "\n");
16355     }
16356     *lp = dest ? line : old_line;
16357
16358     return len;
16359 }
16360
16361 // [HGM] vari: routines for shelving variations
16362
16363 void
16364 PushInner(int firstMove, int lastMove)
16365 {
16366         int i, j, nrMoves = lastMove - firstMove;
16367
16368         // push current tail of game on stack
16369         savedResult[storedGames] = gameInfo.result;
16370         savedDetails[storedGames] = gameInfo.resultDetails;
16371         gameInfo.resultDetails = NULL;
16372         savedFirst[storedGames] = firstMove;
16373         savedLast [storedGames] = lastMove;
16374         savedFramePtr[storedGames] = framePtr;
16375         framePtr -= nrMoves; // reserve space for the boards
16376         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16377             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16378             for(j=0; j<MOVE_LEN; j++)
16379                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16380             for(j=0; j<2*MOVE_LEN; j++)
16381                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16382             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16383             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16384             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16385             pvInfoList[firstMove+i-1].depth = 0;
16386             commentList[framePtr+i] = commentList[firstMove+i];
16387             commentList[firstMove+i] = NULL;
16388         }
16389
16390         storedGames++;
16391         forwardMostMove = firstMove; // truncate game so we can start variation
16392 }
16393
16394 void
16395 PushTail(int firstMove, int lastMove)
16396 {
16397         if(appData.icsActive) { // only in local mode
16398                 forwardMostMove = currentMove; // mimic old ICS behavior
16399                 return;
16400         }
16401         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16402
16403         PushInner(firstMove, lastMove);
16404         if(storedGames == 1) GreyRevert(FALSE);
16405 }
16406
16407 void
16408 PopInner(Boolean annotate)
16409 {
16410         int i, j, nrMoves;
16411         char buf[8000], moveBuf[20];
16412
16413         storedGames--;
16414         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16415         nrMoves = savedLast[storedGames] - currentMove;
16416         if(annotate) {
16417                 int cnt = 10;
16418                 if(!WhiteOnMove(currentMove))
16419                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16420                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16421                 for(i=currentMove; i<forwardMostMove; i++) {
16422                         if(WhiteOnMove(i))
16423                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16424                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16425                         strcat(buf, moveBuf);
16426                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16427                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16428                 }
16429                 strcat(buf, ")");
16430         }
16431         for(i=1; i<=nrMoves; i++) { // copy last variation back
16432             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16433             for(j=0; j<MOVE_LEN; j++)
16434                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16435             for(j=0; j<2*MOVE_LEN; j++)
16436                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16437             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16438             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16439             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16440             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16441             commentList[currentMove+i] = commentList[framePtr+i];
16442             commentList[framePtr+i] = NULL;
16443         }
16444         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16445         framePtr = savedFramePtr[storedGames];
16446         gameInfo.result = savedResult[storedGames];
16447         if(gameInfo.resultDetails != NULL) {
16448             free(gameInfo.resultDetails);
16449       }
16450         gameInfo.resultDetails = savedDetails[storedGames];
16451         forwardMostMove = currentMove + nrMoves;
16452 }
16453
16454 Boolean
16455 PopTail(Boolean annotate)
16456 {
16457         if(appData.icsActive) return FALSE; // only in local mode
16458         if(!storedGames) return FALSE; // sanity
16459         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16460
16461         PopInner(annotate);
16462
16463         if(storedGames == 0) GreyRevert(TRUE);
16464         return TRUE;
16465 }
16466
16467 void
16468 CleanupTail()
16469 {       // remove all shelved variations
16470         int i;
16471         for(i=0; i<storedGames; i++) {
16472             if(savedDetails[i])
16473                 free(savedDetails[i]);
16474             savedDetails[i] = NULL;
16475         }
16476         for(i=framePtr; i<MAX_MOVES; i++) {
16477                 if(commentList[i]) free(commentList[i]);
16478                 commentList[i] = NULL;
16479         }
16480         framePtr = MAX_MOVES-1;
16481         storedGames = 0;
16482 }
16483
16484 void
16485 LoadVariation(int index, char *text)
16486 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16487         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16488         int level = 0, move;
16489
16490         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16491         // first find outermost bracketing variation
16492         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16493             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16494                 if(*p == '{') wait = '}'; else
16495                 if(*p == '[') wait = ']'; else
16496                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16497                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16498             }
16499             if(*p == wait) wait = NULLCHAR; // closing ]} found
16500             p++;
16501         }
16502         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16503         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16504         end[1] = NULLCHAR; // clip off comment beyond variation
16505         ToNrEvent(currentMove-1);
16506         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16507         // kludge: use ParsePV() to append variation to game
16508         move = currentMove;
16509         ParsePV(start, TRUE, TRUE);
16510         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16511         ClearPremoveHighlights();
16512         CommentPopDown();
16513         ToNrEvent(currentMove+1);
16514 }
16515