803bb7144c0d33d20edf21aceb4a0b87592b5ec9
[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, 2012 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 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy (char *dst, const char *src, size_t count)
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble (u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags (index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP, *serverFP;
396 char *currentDebugFile; // [HGM] debug split: to remember name
397
398 /*
399     [AS] Note: sometimes, the sscanf() function is used to parse the input
400     into a fixed-size buffer. Because of this, we must be prepared to
401     receive strings as long as the size of the input buffer, which is currently
402     set to 4K for Windows and 8K for the rest.
403     So, we must either allocate sufficiently large buffers here, or
404     reduce the size of the input buffer in the input reading part.
405 */
406
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
410
411 ChessProgramState first, second, pairing;
412
413 /* premove variables */
414 int premoveToX = 0;
415 int premoveToY = 0;
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
419 int gotPremove = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
422
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey; // [HGM] set by mouse handler
453
454 int have_sent_ICS_logon = 0;
455 int movesPerSession;
456 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
457 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
458 Boolean adjustedClock;
459 long timeControl_2; /* [AS] Allow separate time controls */
460 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
461 long timeRemaining[2][MAX_MOVES];
462 int matchGame = 0, nextGame = 0, roundNr = 0;
463 Boolean waitingForGame = FALSE;
464 TimeMark programStartTime, pauseStart;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
467
468 /* animateTraining preserves the state of appData.animate
469  * when Training mode is activated. This allows the
470  * response to be animated when appData.animate == TRUE and
471  * appData.animateDragging == TRUE.
472  */
473 Boolean animateTraining;
474
475 GameInfo gameInfo;
476
477 AppData appData;
478
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char  initialRights[BOARD_FILES];
483 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int   initialRulePlies, FENrulePlies;
485 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 int loadFlag = 0;
487 Boolean shuffleOpenings;
488 int mute; // mute all sounds
489
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int storedGames = 0;
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
499
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void PushInner P((int firstMove, int lastMove));
503 void PopInner P((Boolean annotate));
504 void CleanupTail P((void));
505
506 ChessSquare  FIDEArray[2][BOARD_FILES] = {
507     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
508         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
509     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
510         BlackKing, BlackBishop, BlackKnight, BlackRook }
511 };
512
513 ChessSquare twoKingsArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackKing, BlackKnight, BlackRook }
518 };
519
520 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
522         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
523     { BlackRook, BlackMan, BlackBishop, BlackQueen,
524         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
525 };
526
527 ChessSquare SpartanArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
529         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
530     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
531         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
532 };
533
534 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
538         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
539 };
540
541 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
542     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
543         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
545         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
546 };
547
548 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
550         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackMan, BlackFerz,
552         BlackKing, BlackMan, BlackKnight, BlackRook }
553 };
554
555
556 #if (BOARD_FILES>=10)
557 ChessSquare ShogiArray[2][BOARD_FILES] = {
558     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
559         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
560     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
561         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
562 };
563
564 ChessSquare XiangqiArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
566         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
567     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
568         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
569 };
570
571 ChessSquare CapablancaArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
575         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
576 };
577
578 ChessSquare GreatArray[2][BOARD_FILES] = {
579     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
580         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
581     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
582         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
583 };
584
585 ChessSquare JanusArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
587         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
588     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
589         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
590 };
591
592 ChessSquare GrandArray[2][BOARD_FILES] = {
593     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
594         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
595     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
596         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
597 };
598
599 #ifdef GOTHIC
600 ChessSquare GothicArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
602         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
604         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
605 };
606 #else // !GOTHIC
607 #define GothicArray CapablancaArray
608 #endif // !GOTHIC
609
610 #ifdef FALCON
611 ChessSquare FalconArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
613         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
615         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
616 };
617 #else // !FALCON
618 #define FalconArray CapablancaArray
619 #endif // !FALCON
620
621 #else // !(BOARD_FILES>=10)
622 #define XiangqiPosition FIDEArray
623 #define CapablancaArray FIDEArray
624 #define GothicArray FIDEArray
625 #define GreatArray FIDEArray
626 #endif // !(BOARD_FILES>=10)
627
628 #if (BOARD_FILES>=12)
629 ChessSquare CourierArray[2][BOARD_FILES] = {
630     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
631         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
632     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
633         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
634 };
635 #else // !(BOARD_FILES>=12)
636 #define CourierArray CapablancaArray
637 #endif // !(BOARD_FILES>=12)
638
639
640 Board initialPosition;
641
642
643 /* Convert str to a rating. Checks for special cases of "----",
644
645    "++++", etc. Also strips ()'s */
646 int
647 string_to_rating (char *str)
648 {
649   while(*str && !isdigit(*str)) ++str;
650   if (!*str)
651     return 0;   /* One of the special "no rating" cases */
652   else
653     return atoi(str);
654 }
655
656 void
657 ClearProgramStats ()
658 {
659     /* Init programStats */
660     programStats.movelist[0] = 0;
661     programStats.depth = 0;
662     programStats.nr_moves = 0;
663     programStats.moves_left = 0;
664     programStats.nodes = 0;
665     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
666     programStats.score = 0;
667     programStats.got_only_move = 0;
668     programStats.got_fail = 0;
669     programStats.line_is_book = 0;
670 }
671
672 void
673 CommonEngineInit ()
674 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675     if (appData.firstPlaysBlack) {
676         first.twoMachinesColor = "black\n";
677         second.twoMachinesColor = "white\n";
678     } else {
679         first.twoMachinesColor = "white\n";
680         second.twoMachinesColor = "black\n";
681     }
682
683     first.other = &second;
684     second.other = &first;
685
686     { float norm = 1;
687         if(appData.timeOddsMode) {
688             norm = appData.timeOdds[0];
689             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690         }
691         first.timeOdds  = appData.timeOdds[0]/norm;
692         second.timeOdds = appData.timeOdds[1]/norm;
693     }
694
695     if(programVersion) free(programVersion);
696     if (appData.noChessProgram) {
697         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698         sprintf(programVersion, "%s", PACKAGE_STRING);
699     } else {
700       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
703     }
704 }
705
706 void
707 UnloadEngine (ChessProgramState *cps)
708 {
709         /* Kill off first chess program */
710         if (cps->isr != NULL)
711           RemoveInputSource(cps->isr);
712         cps->isr = NULL;
713
714         if (cps->pr != NoProc) {
715             ExitAnalyzeMode();
716             DoSleep( appData.delayBeforeQuit );
717             SendToProgram("quit\n", cps);
718             DoSleep( appData.delayAfterQuit );
719             DestroyChildProcess(cps->pr, cps->useSigterm);
720         }
721         cps->pr = NoProc;
722         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
723 }
724
725 void
726 ClearOptions (ChessProgramState *cps)
727 {
728     int i;
729     cps->nrOptions = cps->comboCnt = 0;
730     for(i=0; i<MAX_OPTIONS; i++) {
731         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732         cps->option[i].textValue = 0;
733     }
734 }
735
736 char *engineNames[] = {
737   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
738      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
739 N_("first"),
740   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
741      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
742 N_("second")
743 };
744
745 void
746 InitEngine (ChessProgramState *cps, int n)
747 {   // [HGM] all engine initialiation put in a function that does one engine
748
749     ClearOptions(cps);
750
751     cps->which = engineNames[n];
752     cps->maybeThinking = FALSE;
753     cps->pr = NoProc;
754     cps->isr = NULL;
755     cps->sendTime = 2;
756     cps->sendDrawOffers = 1;
757
758     cps->program = appData.chessProgram[n];
759     cps->host = appData.host[n];
760     cps->dir = appData.directory[n];
761     cps->initString = appData.engInitString[n];
762     cps->computerString = appData.computerString[n];
763     cps->useSigint  = TRUE;
764     cps->useSigterm = TRUE;
765     cps->reuse = appData.reuse[n];
766     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
767     cps->useSetboard = FALSE;
768     cps->useSAN = FALSE;
769     cps->usePing = FALSE;
770     cps->lastPing = 0;
771     cps->lastPong = 0;
772     cps->usePlayother = FALSE;
773     cps->useColors = TRUE;
774     cps->useUsermove = FALSE;
775     cps->sendICS = FALSE;
776     cps->sendName = appData.icsActive;
777     cps->sdKludge = FALSE;
778     cps->stKludge = FALSE;
779     TidyProgramName(cps->program, cps->host, cps->tidy);
780     cps->matchWins = 0;
781     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782     cps->analysisSupport = 2; /* detect */
783     cps->analyzing = FALSE;
784     cps->initDone = FALSE;
785
786     /* New features added by Tord: */
787     cps->useFEN960 = FALSE;
788     cps->useOOCastle = TRUE;
789     /* End of new features added by Tord. */
790     cps->fenOverride  = appData.fenOverride[n];
791
792     /* [HGM] time odds: set factor for each machine */
793     cps->timeOdds  = appData.timeOdds[n];
794
795     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796     cps->accumulateTC = appData.accumulateTC[n];
797     cps->maxNrOfSessions = 1;
798
799     /* [HGM] debug */
800     cps->debug = FALSE;
801
802     cps->supportsNPS = UNKNOWN;
803     cps->memSize = FALSE;
804     cps->maxCores = FALSE;
805     cps->egtFormats[0] = NULLCHAR;
806
807     /* [HGM] options */
808     cps->optionSettings  = appData.engOptions[n];
809
810     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811     cps->isUCI = appData.isUCI[n]; /* [AS] */
812     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
813
814     if (appData.protocolVersion[n] > PROTOVER
815         || appData.protocolVersion[n] < 1)
816       {
817         char buf[MSG_SIZ];
818         int len;
819
820         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821                        appData.protocolVersion[n]);
822         if( (len >= MSG_SIZ) && appData.debugMode )
823           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
824
825         DisplayFatalError(buf, 0, 2);
826       }
827     else
828       {
829         cps->protocolVersion = appData.protocolVersion[n];
830       }
831
832     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
833     ParseFeatures(appData.featureDefaults, cps);
834 }
835
836 ChessProgramState *savCps;
837
838 void
839 LoadEngine ()
840 {
841     int i;
842     if(WaitForEngine(savCps, LoadEngine)) return;
843     CommonEngineInit(); // recalculate time odds
844     if(gameInfo.variant != StringToVariant(appData.variant)) {
845         // we changed variant when loading the engine; this forces us to reset
846         Reset(TRUE, savCps != &first);
847         EditGameEvent(); // for consistency with other path, as Reset changes mode
848     }
849     InitChessProgram(savCps, FALSE);
850     SendToProgram("force\n", savCps);
851     DisplayMessage("", "");
852     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
853     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
854     ThawUI();
855     SetGNUMode();
856 }
857
858 void
859 ReplaceEngine (ChessProgramState *cps, int n)
860 {
861     EditGameEvent();
862     UnloadEngine(cps);
863     appData.noChessProgram = FALSE;
864     appData.clockMode = TRUE;
865     InitEngine(cps, n);
866     UpdateLogos(TRUE);
867     if(n) return; // only startup first engine immediately; second can wait
868     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
869     LoadEngine();
870 }
871
872 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
873 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
874
875 static char resetOptions[] = 
876         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
877         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
878         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
879
880 void
881 FloatToFront(char **list, char *engineLine)
882 {
883     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
884     int i=0;
885     if(appData.recentEngines <= 0) return;
886     TidyProgramName(engineLine, "localhost", tidy+1);
887     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
888     strncpy(buf+1, *list, MSG_SIZ-50);
889     if(p = strstr(buf, tidy)) { // tidy name appears in list
890         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
891         while(*p++ = *++q); // squeeze out
892     }
893     strcat(tidy, buf+1); // put list behind tidy name
894     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
895     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
896     ASSIGN(*list, tidy+1);
897 }
898
899 void
900 Load (ChessProgramState *cps, int i)
901 {
902     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
903     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
904         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
905         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
906         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
907         appData.firstProtocolVersion = PROTOVER;
908         ParseArgsFromString(buf);
909         SwapEngines(i);
910         ReplaceEngine(cps, i);
911         FloatToFront(&appData.recentEngineList, engineLine);
912         return;
913     }
914     p = engineName;
915     while(q = strchr(p, SLASH)) p = q+1;
916     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
917     if(engineDir[0] != NULLCHAR)
918         appData.directory[i] = engineDir;
919     else if(p != engineName) { // derive directory from engine path, when not given
920         p[-1] = 0;
921         appData.directory[i] = strdup(engineName);
922         p[-1] = SLASH;
923     } else appData.directory[i] = ".";
924     if(params[0]) {
925         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
926         snprintf(command, MSG_SIZ, "%s %s", p, params);
927         p = command;
928     }
929     appData.chessProgram[i] = strdup(p);
930     appData.isUCI[i] = isUCI;
931     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
932     appData.hasOwnBookUCI[i] = hasBook;
933     if(!nickName[0]) useNick = FALSE;
934     if(useNick) ASSIGN(appData.pgnName[i], nickName);
935     if(addToList) {
936         int len;
937         char quote;
938         q = firstChessProgramNames;
939         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
940         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
941         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
942                         quote, p, quote, appData.directory[i], 
943                         useNick ? " -fn \"" : "",
944                         useNick ? nickName : "",
945                         useNick ? "\"" : "",
946                         v1 ? " -firstProtocolVersion 1" : "",
947                         hasBook ? "" : " -fNoOwnBookUCI",
948                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
949                         storeVariant ? " -variant " : "",
950                         storeVariant ? VariantName(gameInfo.variant) : "");
951         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
952         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
953         if(q)   free(q);
954         FloatToFront(&appData.recentEngineList, buf);
955     }
956     ReplaceEngine(cps, i);
957 }
958
959 void
960 InitTimeControls ()
961 {
962     int matched, min, sec;
963     /*
964      * Parse timeControl resource
965      */
966     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
967                           appData.movesPerSession)) {
968         char buf[MSG_SIZ];
969         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
970         DisplayFatalError(buf, 0, 2);
971     }
972
973     /*
974      * Parse searchTime resource
975      */
976     if (*appData.searchTime != NULLCHAR) {
977         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
978         if (matched == 1) {
979             searchTime = min * 60;
980         } else if (matched == 2) {
981             searchTime = min * 60 + sec;
982         } else {
983             char buf[MSG_SIZ];
984             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
985             DisplayFatalError(buf, 0, 2);
986         }
987     }
988 }
989
990 void
991 InitBackEnd1 ()
992 {
993
994     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
995     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
996
997     GetTimeMark(&programStartTime);
998     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
999     appData.seedBase = random() + (random()<<15);
1000     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1001
1002     ClearProgramStats();
1003     programStats.ok_to_send = 1;
1004     programStats.seen_stat = 0;
1005
1006     /*
1007      * Initialize game list
1008      */
1009     ListNew(&gameList);
1010
1011
1012     /*
1013      * Internet chess server status
1014      */
1015     if (appData.icsActive) {
1016         appData.matchMode = FALSE;
1017         appData.matchGames = 0;
1018 #if ZIPPY
1019         appData.noChessProgram = !appData.zippyPlay;
1020 #else
1021         appData.zippyPlay = FALSE;
1022         appData.zippyTalk = FALSE;
1023         appData.noChessProgram = TRUE;
1024 #endif
1025         if (*appData.icsHelper != NULLCHAR) {
1026             appData.useTelnet = TRUE;
1027             appData.telnetProgram = appData.icsHelper;
1028         }
1029     } else {
1030         appData.zippyTalk = appData.zippyPlay = FALSE;
1031     }
1032
1033     /* [AS] Initialize pv info list [HGM] and game state */
1034     {
1035         int i, j;
1036
1037         for( i=0; i<=framePtr; i++ ) {
1038             pvInfoList[i].depth = -1;
1039             boards[i][EP_STATUS] = EP_NONE;
1040             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1041         }
1042     }
1043
1044     InitTimeControls();
1045
1046     /* [AS] Adjudication threshold */
1047     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1048
1049     InitEngine(&first, 0);
1050     InitEngine(&second, 1);
1051     CommonEngineInit();
1052
1053     pairing.which = "pairing"; // pairing engine
1054     pairing.pr = NoProc;
1055     pairing.isr = NULL;
1056     pairing.program = appData.pairingEngine;
1057     pairing.host = "localhost";
1058     pairing.dir = ".";
1059
1060     if (appData.icsActive) {
1061         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1062     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1063         appData.clockMode = FALSE;
1064         first.sendTime = second.sendTime = 0;
1065     }
1066
1067 #if ZIPPY
1068     /* Override some settings from environment variables, for backward
1069        compatibility.  Unfortunately it's not feasible to have the env
1070        vars just set defaults, at least in xboard.  Ugh.
1071     */
1072     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1073       ZippyInit();
1074     }
1075 #endif
1076
1077     if (!appData.icsActive) {
1078       char buf[MSG_SIZ];
1079       int len;
1080
1081       /* Check for variants that are supported only in ICS mode,
1082          or not at all.  Some that are accepted here nevertheless
1083          have bugs; see comments below.
1084       */
1085       VariantClass variant = StringToVariant(appData.variant);
1086       switch (variant) {
1087       case VariantBughouse:     /* need four players and two boards */
1088       case VariantKriegspiel:   /* need to hide pieces and move details */
1089         /* case VariantFischeRandom: (Fabien: moved below) */
1090         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1091         if( (len >= MSG_SIZ) && appData.debugMode )
1092           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1093
1094         DisplayFatalError(buf, 0, 2);
1095         return;
1096
1097       case VariantUnknown:
1098       case VariantLoadable:
1099       case Variant29:
1100       case Variant30:
1101       case Variant31:
1102       case Variant32:
1103       case Variant33:
1104       case Variant34:
1105       case Variant35:
1106       case Variant36:
1107       default:
1108         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1109         if( (len >= MSG_SIZ) && appData.debugMode )
1110           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1111
1112         DisplayFatalError(buf, 0, 2);
1113         return;
1114
1115       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1116       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1117       case VariantGothic:     /* [HGM] should work */
1118       case VariantCapablanca: /* [HGM] should work */
1119       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1120       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1121       case VariantKnightmate: /* [HGM] should work */
1122       case VariantCylinder:   /* [HGM] untested */
1123       case VariantFalcon:     /* [HGM] untested */
1124       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1125                                  offboard interposition not understood */
1126       case VariantNormal:     /* definitely works! */
1127       case VariantWildCastle: /* pieces not automatically shuffled */
1128       case VariantNoCastle:   /* pieces not automatically shuffled */
1129       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1130       case VariantLosers:     /* should work except for win condition,
1131                                  and doesn't know captures are mandatory */
1132       case VariantSuicide:    /* should work except for win condition,
1133                                  and doesn't know captures are mandatory */
1134       case VariantGiveaway:   /* should work except for win condition,
1135                                  and doesn't know captures are mandatory */
1136       case VariantTwoKings:   /* should work */
1137       case VariantAtomic:     /* should work except for win condition */
1138       case Variant3Check:     /* should work except for win condition */
1139       case VariantShatranj:   /* should work except for all win conditions */
1140       case VariantMakruk:     /* should work except for draw countdown */
1141       case VariantBerolina:   /* might work if TestLegality is off */
1142       case VariantCapaRandom: /* should work */
1143       case VariantJanus:      /* should work */
1144       case VariantSuper:      /* experimental */
1145       case VariantGreat:      /* experimental, requires legality testing to be off */
1146       case VariantSChess:     /* S-Chess, should work */
1147       case VariantGrand:      /* should work */
1148       case VariantSpartan:    /* should work */
1149         break;
1150       }
1151     }
1152
1153 }
1154
1155 int
1156 NextIntegerFromString (char ** str, long * value)
1157 {
1158     int result = -1;
1159     char * s = *str;
1160
1161     while( *s == ' ' || *s == '\t' ) {
1162         s++;
1163     }
1164
1165     *value = 0;
1166
1167     if( *s >= '0' && *s <= '9' ) {
1168         while( *s >= '0' && *s <= '9' ) {
1169             *value = *value * 10 + (*s - '0');
1170             s++;
1171         }
1172
1173         result = 0;
1174     }
1175
1176     *str = s;
1177
1178     return result;
1179 }
1180
1181 int
1182 NextTimeControlFromString (char ** str, long * value)
1183 {
1184     long temp;
1185     int result = NextIntegerFromString( str, &temp );
1186
1187     if( result == 0 ) {
1188         *value = temp * 60; /* Minutes */
1189         if( **str == ':' ) {
1190             (*str)++;
1191             result = NextIntegerFromString( str, &temp );
1192             *value += temp; /* Seconds */
1193         }
1194     }
1195
1196     return result;
1197 }
1198
1199 int
1200 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1201 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1202     int result = -1, type = 0; long temp, temp2;
1203
1204     if(**str != ':') return -1; // old params remain in force!
1205     (*str)++;
1206     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1207     if( NextIntegerFromString( str, &temp ) ) return -1;
1208     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1209
1210     if(**str != '/') {
1211         /* time only: incremental or sudden-death time control */
1212         if(**str == '+') { /* increment follows; read it */
1213             (*str)++;
1214             if(**str == '!') type = *(*str)++; // Bronstein TC
1215             if(result = NextIntegerFromString( str, &temp2)) return -1;
1216             *inc = temp2 * 1000;
1217             if(**str == '.') { // read fraction of increment
1218                 char *start = ++(*str);
1219                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1220                 temp2 *= 1000;
1221                 while(start++ < *str) temp2 /= 10;
1222                 *inc += temp2;
1223             }
1224         } else *inc = 0;
1225         *moves = 0; *tc = temp * 1000; *incType = type;
1226         return 0;
1227     }
1228
1229     (*str)++; /* classical time control */
1230     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1231
1232     if(result == 0) {
1233         *moves = temp;
1234         *tc    = temp2 * 1000;
1235         *inc   = 0;
1236         *incType = type;
1237     }
1238     return result;
1239 }
1240
1241 int
1242 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1243 {   /* [HGM] get time to add from the multi-session time-control string */
1244     int incType, moves=1; /* kludge to force reading of first session */
1245     long time, increment;
1246     char *s = tcString;
1247
1248     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1249     do {
1250         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1251         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1252         if(movenr == -1) return time;    /* last move before new session     */
1253         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1254         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1255         if(!moves) return increment;     /* current session is incremental   */
1256         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1257     } while(movenr >= -1);               /* try again for next session       */
1258
1259     return 0; // no new time quota on this move
1260 }
1261
1262 int
1263 ParseTimeControl (char *tc, float ti, int mps)
1264 {
1265   long tc1;
1266   long tc2;
1267   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1268   int min, sec=0;
1269
1270   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1271   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1272       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1273   if(ti > 0) {
1274
1275     if(mps)
1276       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1277     else 
1278       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1279   } else {
1280     if(mps)
1281       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1282     else 
1283       snprintf(buf, MSG_SIZ, ":%s", mytc);
1284   }
1285   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1286   
1287   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1288     return FALSE;
1289   }
1290
1291   if( *tc == '/' ) {
1292     /* Parse second time control */
1293     tc++;
1294
1295     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1296       return FALSE;
1297     }
1298
1299     if( tc2 == 0 ) {
1300       return FALSE;
1301     }
1302
1303     timeControl_2 = tc2 * 1000;
1304   }
1305   else {
1306     timeControl_2 = 0;
1307   }
1308
1309   if( tc1 == 0 ) {
1310     return FALSE;
1311   }
1312
1313   timeControl = tc1 * 1000;
1314
1315   if (ti >= 0) {
1316     timeIncrement = ti * 1000;  /* convert to ms */
1317     movesPerSession = 0;
1318   } else {
1319     timeIncrement = 0;
1320     movesPerSession = mps;
1321   }
1322   return TRUE;
1323 }
1324
1325 void
1326 InitBackEnd2 ()
1327 {
1328     if (appData.debugMode) {
1329         fprintf(debugFP, "%s\n", programVersion);
1330     }
1331     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1332
1333     set_cont_sequence(appData.wrapContSeq);
1334     if (appData.matchGames > 0) {
1335         appData.matchMode = TRUE;
1336     } else if (appData.matchMode) {
1337         appData.matchGames = 1;
1338     }
1339     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1340         appData.matchGames = appData.sameColorGames;
1341     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1342         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1343         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1344     }
1345     Reset(TRUE, FALSE);
1346     if (appData.noChessProgram || first.protocolVersion == 1) {
1347       InitBackEnd3();
1348     } else {
1349       /* kludge: allow timeout for initial "feature" commands */
1350       FreezeUI();
1351       DisplayMessage("", _("Starting chess program"));
1352       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1353     }
1354 }
1355
1356 int
1357 CalculateIndex (int index, int gameNr)
1358 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1359     int res;
1360     if(index > 0) return index; // fixed nmber
1361     if(index == 0) return 1;
1362     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1363     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1364     return res;
1365 }
1366
1367 int
1368 LoadGameOrPosition (int gameNr)
1369 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1370     if (*appData.loadGameFile != NULLCHAR) {
1371         if (!LoadGameFromFile(appData.loadGameFile,
1372                 CalculateIndex(appData.loadGameIndex, gameNr),
1373                               appData.loadGameFile, FALSE)) {
1374             DisplayFatalError(_("Bad game file"), 0, 1);
1375             return 0;
1376         }
1377     } else if (*appData.loadPositionFile != NULLCHAR) {
1378         if (!LoadPositionFromFile(appData.loadPositionFile,
1379                 CalculateIndex(appData.loadPositionIndex, gameNr),
1380                                   appData.loadPositionFile)) {
1381             DisplayFatalError(_("Bad position file"), 0, 1);
1382             return 0;
1383         }
1384     }
1385     return 1;
1386 }
1387
1388 void
1389 ReserveGame (int gameNr, char resChar)
1390 {
1391     FILE *tf = fopen(appData.tourneyFile, "r+");
1392     char *p, *q, c, buf[MSG_SIZ];
1393     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1394     safeStrCpy(buf, lastMsg, MSG_SIZ);
1395     DisplayMessage(_("Pick new game"), "");
1396     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1397     ParseArgsFromFile(tf);
1398     p = q = appData.results;
1399     if(appData.debugMode) {
1400       char *r = appData.participants;
1401       fprintf(debugFP, "results = '%s'\n", p);
1402       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1403       fprintf(debugFP, "\n");
1404     }
1405     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1406     nextGame = q - p;
1407     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1408     safeStrCpy(q, p, strlen(p) + 2);
1409     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1410     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1411     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1412         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1413         q[nextGame] = '*';
1414     }
1415     fseek(tf, -(strlen(p)+4), SEEK_END);
1416     c = fgetc(tf);
1417     if(c != '"') // depending on DOS or Unix line endings we can be one off
1418          fseek(tf, -(strlen(p)+2), SEEK_END);
1419     else fseek(tf, -(strlen(p)+3), SEEK_END);
1420     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1421     DisplayMessage(buf, "");
1422     free(p); appData.results = q;
1423     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1424        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1425       int round = appData.defaultMatchGames * appData.tourneyType;
1426       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1427          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1428         UnloadEngine(&first);  // next game belongs to other pairing;
1429         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1430     }
1431     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1432 }
1433
1434 void
1435 MatchEvent (int mode)
1436 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1437         int dummy;
1438         if(matchMode) { // already in match mode: switch it off
1439             abortMatch = TRUE;
1440             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1441             return;
1442         }
1443 //      if(gameMode != BeginningOfGame) {
1444 //          DisplayError(_("You can only start a match from the initial position."), 0);
1445 //          return;
1446 //      }
1447         abortMatch = FALSE;
1448         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1449         /* Set up machine vs. machine match */
1450         nextGame = 0;
1451         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1452         if(appData.tourneyFile[0]) {
1453             ReserveGame(-1, 0);
1454             if(nextGame > appData.matchGames) {
1455                 char buf[MSG_SIZ];
1456                 if(strchr(appData.results, '*') == NULL) {
1457                     FILE *f;
1458                     appData.tourneyCycles++;
1459                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1460                         fclose(f);
1461                         NextTourneyGame(-1, &dummy);
1462                         ReserveGame(-1, 0);
1463                         if(nextGame <= appData.matchGames) {
1464                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1465                             matchMode = mode;
1466                             ScheduleDelayedEvent(NextMatchGame, 10000);
1467                             return;
1468                         }
1469                     }
1470                 }
1471                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1472                 DisplayError(buf, 0);
1473                 appData.tourneyFile[0] = 0;
1474                 return;
1475             }
1476         } else
1477         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1478             DisplayFatalError(_("Can't have a match with no chess programs"),
1479                               0, 2);
1480             return;
1481         }
1482         matchMode = mode;
1483         matchGame = roundNr = 1;
1484         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1485         NextMatchGame();
1486 }
1487
1488 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1489
1490 void
1491 InitBackEnd3 P((void))
1492 {
1493     GameMode initialMode;
1494     char buf[MSG_SIZ];
1495     int err, len;
1496
1497     InitChessProgram(&first, startedFromSetupPosition);
1498
1499     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1500         free(programVersion);
1501         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1502         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1503         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1504     }
1505
1506     if (appData.icsActive) {
1507 #ifdef WIN32
1508         /* [DM] Make a console window if needed [HGM] merged ifs */
1509         ConsoleCreate();
1510 #endif
1511         err = establish();
1512         if (err != 0)
1513           {
1514             if (*appData.icsCommPort != NULLCHAR)
1515               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1516                              appData.icsCommPort);
1517             else
1518               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1519                         appData.icsHost, appData.icsPort);
1520
1521             if( (len >= MSG_SIZ) && appData.debugMode )
1522               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1523
1524             DisplayFatalError(buf, err, 1);
1525             return;
1526         }
1527         SetICSMode();
1528         telnetISR =
1529           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1530         fromUserISR =
1531           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1532         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1533             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1534     } else if (appData.noChessProgram) {
1535         SetNCPMode();
1536     } else {
1537         SetGNUMode();
1538     }
1539
1540     if (*appData.cmailGameName != NULLCHAR) {
1541         SetCmailMode();
1542         OpenLoopback(&cmailPR);
1543         cmailISR =
1544           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1545     }
1546
1547     ThawUI();
1548     DisplayMessage("", "");
1549     if (StrCaseCmp(appData.initialMode, "") == 0) {
1550       initialMode = BeginningOfGame;
1551       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1552         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1553         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1554         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1555         ModeHighlight();
1556       }
1557     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1558       initialMode = TwoMachinesPlay;
1559     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1560       initialMode = AnalyzeFile;
1561     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1562       initialMode = AnalyzeMode;
1563     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1564       initialMode = MachinePlaysWhite;
1565     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1566       initialMode = MachinePlaysBlack;
1567     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1568       initialMode = EditGame;
1569     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1570       initialMode = EditPosition;
1571     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1572       initialMode = Training;
1573     } else {
1574       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1575       if( (len >= MSG_SIZ) && appData.debugMode )
1576         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1577
1578       DisplayFatalError(buf, 0, 2);
1579       return;
1580     }
1581
1582     if (appData.matchMode) {
1583         if(appData.tourneyFile[0]) { // start tourney from command line
1584             FILE *f;
1585             if(f = fopen(appData.tourneyFile, "r")) {
1586                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1587                 fclose(f);
1588                 appData.clockMode = TRUE;
1589                 SetGNUMode();
1590             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1591         }
1592         MatchEvent(TRUE);
1593     } else if (*appData.cmailGameName != NULLCHAR) {
1594         /* Set up cmail mode */
1595         ReloadCmailMsgEvent(TRUE);
1596     } else {
1597         /* Set up other modes */
1598         if (initialMode == AnalyzeFile) {
1599           if (*appData.loadGameFile == NULLCHAR) {
1600             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1601             return;
1602           }
1603         }
1604         if (*appData.loadGameFile != NULLCHAR) {
1605             (void) LoadGameFromFile(appData.loadGameFile,
1606                                     appData.loadGameIndex,
1607                                     appData.loadGameFile, TRUE);
1608         } else if (*appData.loadPositionFile != NULLCHAR) {
1609             (void) LoadPositionFromFile(appData.loadPositionFile,
1610                                         appData.loadPositionIndex,
1611                                         appData.loadPositionFile);
1612             /* [HGM] try to make self-starting even after FEN load */
1613             /* to allow automatic setup of fairy variants with wtm */
1614             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1615                 gameMode = BeginningOfGame;
1616                 setboardSpoiledMachineBlack = 1;
1617             }
1618             /* [HGM] loadPos: make that every new game uses the setup */
1619             /* from file as long as we do not switch variant          */
1620             if(!blackPlaysFirst) {
1621                 startedFromPositionFile = TRUE;
1622                 CopyBoard(filePosition, boards[0]);
1623             }
1624         }
1625         if (initialMode == AnalyzeMode) {
1626           if (appData.noChessProgram) {
1627             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1628             return;
1629           }
1630           if (appData.icsActive) {
1631             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1632             return;
1633           }
1634           AnalyzeModeEvent();
1635         } else if (initialMode == AnalyzeFile) {
1636           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1637           ShowThinkingEvent();
1638           AnalyzeFileEvent();
1639           AnalysisPeriodicEvent(1);
1640         } else if (initialMode == MachinePlaysWhite) {
1641           if (appData.noChessProgram) {
1642             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1643                               0, 2);
1644             return;
1645           }
1646           if (appData.icsActive) {
1647             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1648                               0, 2);
1649             return;
1650           }
1651           MachineWhiteEvent();
1652         } else if (initialMode == MachinePlaysBlack) {
1653           if (appData.noChessProgram) {
1654             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1655                               0, 2);
1656             return;
1657           }
1658           if (appData.icsActive) {
1659             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1660                               0, 2);
1661             return;
1662           }
1663           MachineBlackEvent();
1664         } else if (initialMode == TwoMachinesPlay) {
1665           if (appData.noChessProgram) {
1666             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1667                               0, 2);
1668             return;
1669           }
1670           if (appData.icsActive) {
1671             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1672                               0, 2);
1673             return;
1674           }
1675           TwoMachinesEvent();
1676         } else if (initialMode == EditGame) {
1677           EditGameEvent();
1678         } else if (initialMode == EditPosition) {
1679           EditPositionEvent();
1680         } else if (initialMode == Training) {
1681           if (*appData.loadGameFile == NULLCHAR) {
1682             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1683             return;
1684           }
1685           TrainingEvent();
1686         }
1687     }
1688 }
1689
1690 void
1691 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1692 {
1693     DisplayBook(current+1);
1694
1695     MoveHistorySet( movelist, first, last, current, pvInfoList );
1696
1697     EvalGraphSet( first, last, current, pvInfoList );
1698
1699     MakeEngineOutputTitle();
1700 }
1701
1702 /*
1703  * Establish will establish a contact to a remote host.port.
1704  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1705  *  used to talk to the host.
1706  * Returns 0 if okay, error code if not.
1707  */
1708 int
1709 establish ()
1710 {
1711     char buf[MSG_SIZ];
1712
1713     if (*appData.icsCommPort != NULLCHAR) {
1714         /* Talk to the host through a serial comm port */
1715         return OpenCommPort(appData.icsCommPort, &icsPR);
1716
1717     } else if (*appData.gateway != NULLCHAR) {
1718         if (*appData.remoteShell == NULLCHAR) {
1719             /* Use the rcmd protocol to run telnet program on a gateway host */
1720             snprintf(buf, sizeof(buf), "%s %s %s",
1721                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1722             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1723
1724         } else {
1725             /* Use the rsh program to run telnet program on a gateway host */
1726             if (*appData.remoteUser == NULLCHAR) {
1727                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1728                         appData.gateway, appData.telnetProgram,
1729                         appData.icsHost, appData.icsPort);
1730             } else {
1731                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1732                         appData.remoteShell, appData.gateway,
1733                         appData.remoteUser, appData.telnetProgram,
1734                         appData.icsHost, appData.icsPort);
1735             }
1736             return StartChildProcess(buf, "", &icsPR);
1737
1738         }
1739     } else if (appData.useTelnet) {
1740         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1741
1742     } else {
1743         /* TCP socket interface differs somewhat between
1744            Unix and NT; handle details in the front end.
1745            */
1746         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1747     }
1748 }
1749
1750 void
1751 EscapeExpand (char *p, char *q)
1752 {       // [HGM] initstring: routine to shape up string arguments
1753         while(*p++ = *q++) if(p[-1] == '\\')
1754             switch(*q++) {
1755                 case 'n': p[-1] = '\n'; break;
1756                 case 'r': p[-1] = '\r'; break;
1757                 case 't': p[-1] = '\t'; break;
1758                 case '\\': p[-1] = '\\'; break;
1759                 case 0: *p = 0; return;
1760                 default: p[-1] = q[-1]; break;
1761             }
1762 }
1763
1764 void
1765 show_bytes (FILE *fp, char *buf, int count)
1766 {
1767     while (count--) {
1768         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1769             fprintf(fp, "\\%03o", *buf & 0xff);
1770         } else {
1771             putc(*buf, fp);
1772         }
1773         buf++;
1774     }
1775     fflush(fp);
1776 }
1777
1778 /* Returns an errno value */
1779 int
1780 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1781 {
1782     char buf[8192], *p, *q, *buflim;
1783     int left, newcount, outcount;
1784
1785     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1786         *appData.gateway != NULLCHAR) {
1787         if (appData.debugMode) {
1788             fprintf(debugFP, ">ICS: ");
1789             show_bytes(debugFP, message, count);
1790             fprintf(debugFP, "\n");
1791         }
1792         return OutputToProcess(pr, message, count, outError);
1793     }
1794
1795     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1796     p = message;
1797     q = buf;
1798     left = count;
1799     newcount = 0;
1800     while (left) {
1801         if (q >= buflim) {
1802             if (appData.debugMode) {
1803                 fprintf(debugFP, ">ICS: ");
1804                 show_bytes(debugFP, buf, newcount);
1805                 fprintf(debugFP, "\n");
1806             }
1807             outcount = OutputToProcess(pr, buf, newcount, outError);
1808             if (outcount < newcount) return -1; /* to be sure */
1809             q = buf;
1810             newcount = 0;
1811         }
1812         if (*p == '\n') {
1813             *q++ = '\r';
1814             newcount++;
1815         } else if (((unsigned char) *p) == TN_IAC) {
1816             *q++ = (char) TN_IAC;
1817             newcount ++;
1818         }
1819         *q++ = *p++;
1820         newcount++;
1821         left--;
1822     }
1823     if (appData.debugMode) {
1824         fprintf(debugFP, ">ICS: ");
1825         show_bytes(debugFP, buf, newcount);
1826         fprintf(debugFP, "\n");
1827     }
1828     outcount = OutputToProcess(pr, buf, newcount, outError);
1829     if (outcount < newcount) return -1; /* to be sure */
1830     return count;
1831 }
1832
1833 void
1834 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1835 {
1836     int outError, outCount;
1837     static int gotEof = 0;
1838
1839     /* Pass data read from player on to ICS */
1840     if (count > 0) {
1841         gotEof = 0;
1842         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1843         if (outCount < count) {
1844             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1845         }
1846     } else if (count < 0) {
1847         RemoveInputSource(isr);
1848         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1849     } else if (gotEof++ > 0) {
1850         RemoveInputSource(isr);
1851         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1852     }
1853 }
1854
1855 void
1856 KeepAlive ()
1857 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1858     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1859     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1860     SendToICS("date\n");
1861     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1862 }
1863
1864 /* added routine for printf style output to ics */
1865 void
1866 ics_printf (char *format, ...)
1867 {
1868     char buffer[MSG_SIZ];
1869     va_list args;
1870
1871     va_start(args, format);
1872     vsnprintf(buffer, sizeof(buffer), format, args);
1873     buffer[sizeof(buffer)-1] = '\0';
1874     SendToICS(buffer);
1875     va_end(args);
1876 }
1877
1878 void
1879 SendToICS (char *s)
1880 {
1881     int count, outCount, outError;
1882
1883     if (icsPR == NoProc) return;
1884
1885     count = strlen(s);
1886     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1887     if (outCount < count) {
1888         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1889     }
1890 }
1891
1892 /* This is used for sending logon scripts to the ICS. Sending
1893    without a delay causes problems when using timestamp on ICC
1894    (at least on my machine). */
1895 void
1896 SendToICSDelayed (char *s, long msdelay)
1897 {
1898     int count, outCount, outError;
1899
1900     if (icsPR == NoProc) return;
1901
1902     count = strlen(s);
1903     if (appData.debugMode) {
1904         fprintf(debugFP, ">ICS: ");
1905         show_bytes(debugFP, s, count);
1906         fprintf(debugFP, "\n");
1907     }
1908     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1909                                       msdelay);
1910     if (outCount < count) {
1911         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1912     }
1913 }
1914
1915
1916 /* Remove all highlighting escape sequences in s
1917    Also deletes any suffix starting with '('
1918    */
1919 char *
1920 StripHighlightAndTitle (char *s)
1921 {
1922     static char retbuf[MSG_SIZ];
1923     char *p = retbuf;
1924
1925     while (*s != NULLCHAR) {
1926         while (*s == '\033') {
1927             while (*s != NULLCHAR && !isalpha(*s)) s++;
1928             if (*s != NULLCHAR) s++;
1929         }
1930         while (*s != NULLCHAR && *s != '\033') {
1931             if (*s == '(' || *s == '[') {
1932                 *p = NULLCHAR;
1933                 return retbuf;
1934             }
1935             *p++ = *s++;
1936         }
1937     }
1938     *p = NULLCHAR;
1939     return retbuf;
1940 }
1941
1942 /* Remove all highlighting escape sequences in s */
1943 char *
1944 StripHighlight (char *s)
1945 {
1946     static char retbuf[MSG_SIZ];
1947     char *p = retbuf;
1948
1949     while (*s != NULLCHAR) {
1950         while (*s == '\033') {
1951             while (*s != NULLCHAR && !isalpha(*s)) s++;
1952             if (*s != NULLCHAR) s++;
1953         }
1954         while (*s != NULLCHAR && *s != '\033') {
1955             *p++ = *s++;
1956         }
1957     }
1958     *p = NULLCHAR;
1959     return retbuf;
1960 }
1961
1962 char *variantNames[] = VARIANT_NAMES;
1963 char *
1964 VariantName (VariantClass v)
1965 {
1966     return variantNames[v];
1967 }
1968
1969
1970 /* Identify a variant from the strings the chess servers use or the
1971    PGN Variant tag names we use. */
1972 VariantClass
1973 StringToVariant (char *e)
1974 {
1975     char *p;
1976     int wnum = -1;
1977     VariantClass v = VariantNormal;
1978     int i, found = FALSE;
1979     char buf[MSG_SIZ];
1980     int len;
1981
1982     if (!e) return v;
1983
1984     /* [HGM] skip over optional board-size prefixes */
1985     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1986         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1987         while( *e++ != '_');
1988     }
1989
1990     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1991         v = VariantNormal;
1992         found = TRUE;
1993     } else
1994     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1995       if (StrCaseStr(e, variantNames[i])) {
1996         v = (VariantClass) i;
1997         found = TRUE;
1998         break;
1999       }
2000     }
2001
2002     if (!found) {
2003       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2004           || StrCaseStr(e, "wild/fr")
2005           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2006         v = VariantFischeRandom;
2007       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2008                  (i = 1, p = StrCaseStr(e, "w"))) {
2009         p += i;
2010         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2011         if (isdigit(*p)) {
2012           wnum = atoi(p);
2013         } else {
2014           wnum = -1;
2015         }
2016         switch (wnum) {
2017         case 0: /* FICS only, actually */
2018         case 1:
2019           /* Castling legal even if K starts on d-file */
2020           v = VariantWildCastle;
2021           break;
2022         case 2:
2023         case 3:
2024         case 4:
2025           /* Castling illegal even if K & R happen to start in
2026              normal positions. */
2027           v = VariantNoCastle;
2028           break;
2029         case 5:
2030         case 7:
2031         case 8:
2032         case 10:
2033         case 11:
2034         case 12:
2035         case 13:
2036         case 14:
2037         case 15:
2038         case 18:
2039         case 19:
2040           /* Castling legal iff K & R start in normal positions */
2041           v = VariantNormal;
2042           break;
2043         case 6:
2044         case 20:
2045         case 21:
2046           /* Special wilds for position setup; unclear what to do here */
2047           v = VariantLoadable;
2048           break;
2049         case 9:
2050           /* Bizarre ICC game */
2051           v = VariantTwoKings;
2052           break;
2053         case 16:
2054           v = VariantKriegspiel;
2055           break;
2056         case 17:
2057           v = VariantLosers;
2058           break;
2059         case 22:
2060           v = VariantFischeRandom;
2061           break;
2062         case 23:
2063           v = VariantCrazyhouse;
2064           break;
2065         case 24:
2066           v = VariantBughouse;
2067           break;
2068         case 25:
2069           v = Variant3Check;
2070           break;
2071         case 26:
2072           /* Not quite the same as FICS suicide! */
2073           v = VariantGiveaway;
2074           break;
2075         case 27:
2076           v = VariantAtomic;
2077           break;
2078         case 28:
2079           v = VariantShatranj;
2080           break;
2081
2082         /* Temporary names for future ICC types.  The name *will* change in
2083            the next xboard/WinBoard release after ICC defines it. */
2084         case 29:
2085           v = Variant29;
2086           break;
2087         case 30:
2088           v = Variant30;
2089           break;
2090         case 31:
2091           v = Variant31;
2092           break;
2093         case 32:
2094           v = Variant32;
2095           break;
2096         case 33:
2097           v = Variant33;
2098           break;
2099         case 34:
2100           v = Variant34;
2101           break;
2102         case 35:
2103           v = Variant35;
2104           break;
2105         case 36:
2106           v = Variant36;
2107           break;
2108         case 37:
2109           v = VariantShogi;
2110           break;
2111         case 38:
2112           v = VariantXiangqi;
2113           break;
2114         case 39:
2115           v = VariantCourier;
2116           break;
2117         case 40:
2118           v = VariantGothic;
2119           break;
2120         case 41:
2121           v = VariantCapablanca;
2122           break;
2123         case 42:
2124           v = VariantKnightmate;
2125           break;
2126         case 43:
2127           v = VariantFairy;
2128           break;
2129         case 44:
2130           v = VariantCylinder;
2131           break;
2132         case 45:
2133           v = VariantFalcon;
2134           break;
2135         case 46:
2136           v = VariantCapaRandom;
2137           break;
2138         case 47:
2139           v = VariantBerolina;
2140           break;
2141         case 48:
2142           v = VariantJanus;
2143           break;
2144         case 49:
2145           v = VariantSuper;
2146           break;
2147         case 50:
2148           v = VariantGreat;
2149           break;
2150         case -1:
2151           /* Found "wild" or "w" in the string but no number;
2152              must assume it's normal chess. */
2153           v = VariantNormal;
2154           break;
2155         default:
2156           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2157           if( (len >= MSG_SIZ) && appData.debugMode )
2158             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2159
2160           DisplayError(buf, 0);
2161           v = VariantUnknown;
2162           break;
2163         }
2164       }
2165     }
2166     if (appData.debugMode) {
2167       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2168               e, wnum, VariantName(v));
2169     }
2170     return v;
2171 }
2172
2173 static int leftover_start = 0, leftover_len = 0;
2174 char star_match[STAR_MATCH_N][MSG_SIZ];
2175
2176 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2177    advance *index beyond it, and set leftover_start to the new value of
2178    *index; else return FALSE.  If pattern contains the character '*', it
2179    matches any sequence of characters not containing '\r', '\n', or the
2180    character following the '*' (if any), and the matched sequence(s) are
2181    copied into star_match.
2182    */
2183 int
2184 looking_at ( char *buf, int *index, char *pattern)
2185 {
2186     char *bufp = &buf[*index], *patternp = pattern;
2187     int star_count = 0;
2188     char *matchp = star_match[0];
2189
2190     for (;;) {
2191         if (*patternp == NULLCHAR) {
2192             *index = leftover_start = bufp - buf;
2193             *matchp = NULLCHAR;
2194             return TRUE;
2195         }
2196         if (*bufp == NULLCHAR) return FALSE;
2197         if (*patternp == '*') {
2198             if (*bufp == *(patternp + 1)) {
2199                 *matchp = NULLCHAR;
2200                 matchp = star_match[++star_count];
2201                 patternp += 2;
2202                 bufp++;
2203                 continue;
2204             } else if (*bufp == '\n' || *bufp == '\r') {
2205                 patternp++;
2206                 if (*patternp == NULLCHAR)
2207                   continue;
2208                 else
2209                   return FALSE;
2210             } else {
2211                 *matchp++ = *bufp++;
2212                 continue;
2213             }
2214         }
2215         if (*patternp != *bufp) return FALSE;
2216         patternp++;
2217         bufp++;
2218     }
2219 }
2220
2221 void
2222 SendToPlayer (char *data, int length)
2223 {
2224     int error, outCount;
2225     outCount = OutputToProcess(NoProc, data, length, &error);
2226     if (outCount < length) {
2227         DisplayFatalError(_("Error writing to display"), error, 1);
2228     }
2229 }
2230
2231 void
2232 PackHolding (char packed[], char *holding)
2233 {
2234     char *p = holding;
2235     char *q = packed;
2236     int runlength = 0;
2237     int curr = 9999;
2238     do {
2239         if (*p == curr) {
2240             runlength++;
2241         } else {
2242             switch (runlength) {
2243               case 0:
2244                 break;
2245               case 1:
2246                 *q++ = curr;
2247                 break;
2248               case 2:
2249                 *q++ = curr;
2250                 *q++ = curr;
2251                 break;
2252               default:
2253                 sprintf(q, "%d", runlength);
2254                 while (*q) q++;
2255                 *q++ = curr;
2256                 break;
2257             }
2258             runlength = 1;
2259             curr = *p;
2260         }
2261     } while (*p++);
2262     *q = NULLCHAR;
2263 }
2264
2265 /* Telnet protocol requests from the front end */
2266 void
2267 TelnetRequest (unsigned char ddww, unsigned char option)
2268 {
2269     unsigned char msg[3];
2270     int outCount, outError;
2271
2272     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2273
2274     if (appData.debugMode) {
2275         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2276         switch (ddww) {
2277           case TN_DO:
2278             ddwwStr = "DO";
2279             break;
2280           case TN_DONT:
2281             ddwwStr = "DONT";
2282             break;
2283           case TN_WILL:
2284             ddwwStr = "WILL";
2285             break;
2286           case TN_WONT:
2287             ddwwStr = "WONT";
2288             break;
2289           default:
2290             ddwwStr = buf1;
2291             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2292             break;
2293         }
2294         switch (option) {
2295           case TN_ECHO:
2296             optionStr = "ECHO";
2297             break;
2298           default:
2299             optionStr = buf2;
2300             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2301             break;
2302         }
2303         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2304     }
2305     msg[0] = TN_IAC;
2306     msg[1] = ddww;
2307     msg[2] = option;
2308     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2309     if (outCount < 3) {
2310         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2311     }
2312 }
2313
2314 void
2315 DoEcho ()
2316 {
2317     if (!appData.icsActive) return;
2318     TelnetRequest(TN_DO, TN_ECHO);
2319 }
2320
2321 void
2322 DontEcho ()
2323 {
2324     if (!appData.icsActive) return;
2325     TelnetRequest(TN_DONT, TN_ECHO);
2326 }
2327
2328 void
2329 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2330 {
2331     /* put the holdings sent to us by the server on the board holdings area */
2332     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2333     char p;
2334     ChessSquare piece;
2335
2336     if(gameInfo.holdingsWidth < 2)  return;
2337     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2338         return; // prevent overwriting by pre-board holdings
2339
2340     if( (int)lowestPiece >= BlackPawn ) {
2341         holdingsColumn = 0;
2342         countsColumn = 1;
2343         holdingsStartRow = BOARD_HEIGHT-1;
2344         direction = -1;
2345     } else {
2346         holdingsColumn = BOARD_WIDTH-1;
2347         countsColumn = BOARD_WIDTH-2;
2348         holdingsStartRow = 0;
2349         direction = 1;
2350     }
2351
2352     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2353         board[i][holdingsColumn] = EmptySquare;
2354         board[i][countsColumn]   = (ChessSquare) 0;
2355     }
2356     while( (p=*holdings++) != NULLCHAR ) {
2357         piece = CharToPiece( ToUpper(p) );
2358         if(piece == EmptySquare) continue;
2359         /*j = (int) piece - (int) WhitePawn;*/
2360         j = PieceToNumber(piece);
2361         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2362         if(j < 0) continue;               /* should not happen */
2363         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2364         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2365         board[holdingsStartRow+j*direction][countsColumn]++;
2366     }
2367 }
2368
2369
2370 void
2371 VariantSwitch (Board board, VariantClass newVariant)
2372 {
2373    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2374    static Board oldBoard;
2375
2376    startedFromPositionFile = FALSE;
2377    if(gameInfo.variant == newVariant) return;
2378
2379    /* [HGM] This routine is called each time an assignment is made to
2380     * gameInfo.variant during a game, to make sure the board sizes
2381     * are set to match the new variant. If that means adding or deleting
2382     * holdings, we shift the playing board accordingly
2383     * This kludge is needed because in ICS observe mode, we get boards
2384     * of an ongoing game without knowing the variant, and learn about the
2385     * latter only later. This can be because of the move list we requested,
2386     * in which case the game history is refilled from the beginning anyway,
2387     * but also when receiving holdings of a crazyhouse game. In the latter
2388     * case we want to add those holdings to the already received position.
2389     */
2390
2391
2392    if (appData.debugMode) {
2393      fprintf(debugFP, "Switch board from %s to %s\n",
2394              VariantName(gameInfo.variant), VariantName(newVariant));
2395      setbuf(debugFP, NULL);
2396    }
2397    shuffleOpenings = 0;       /* [HGM] shuffle */
2398    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2399    switch(newVariant)
2400      {
2401      case VariantShogi:
2402        newWidth = 9;  newHeight = 9;
2403        gameInfo.holdingsSize = 7;
2404      case VariantBughouse:
2405      case VariantCrazyhouse:
2406        newHoldingsWidth = 2; break;
2407      case VariantGreat:
2408        newWidth = 10;
2409      case VariantSuper:
2410        newHoldingsWidth = 2;
2411        gameInfo.holdingsSize = 8;
2412        break;
2413      case VariantGothic:
2414      case VariantCapablanca:
2415      case VariantCapaRandom:
2416        newWidth = 10;
2417      default:
2418        newHoldingsWidth = gameInfo.holdingsSize = 0;
2419      };
2420
2421    if(newWidth  != gameInfo.boardWidth  ||
2422       newHeight != gameInfo.boardHeight ||
2423       newHoldingsWidth != gameInfo.holdingsWidth ) {
2424
2425      /* shift position to new playing area, if needed */
2426      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2427        for(i=0; i<BOARD_HEIGHT; i++)
2428          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2429            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2430              board[i][j];
2431        for(i=0; i<newHeight; i++) {
2432          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2433          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2434        }
2435      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2436        for(i=0; i<BOARD_HEIGHT; i++)
2437          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2438            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2439              board[i][j];
2440      }
2441      gameInfo.boardWidth  = newWidth;
2442      gameInfo.boardHeight = newHeight;
2443      gameInfo.holdingsWidth = newHoldingsWidth;
2444      gameInfo.variant = newVariant;
2445      InitDrawingSizes(-2, 0);
2446    } else gameInfo.variant = newVariant;
2447    CopyBoard(oldBoard, board);   // remember correctly formatted board
2448      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2449    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2450 }
2451
2452 static int loggedOn = FALSE;
2453
2454 /*-- Game start info cache: --*/
2455 int gs_gamenum;
2456 char gs_kind[MSG_SIZ];
2457 static char player1Name[128] = "";
2458 static char player2Name[128] = "";
2459 static char cont_seq[] = "\n\\   ";
2460 static int player1Rating = -1;
2461 static int player2Rating = -1;
2462 /*----------------------------*/
2463
2464 ColorClass curColor = ColorNormal;
2465 int suppressKibitz = 0;
2466
2467 // [HGM] seekgraph
2468 Boolean soughtPending = FALSE;
2469 Boolean seekGraphUp;
2470 #define MAX_SEEK_ADS 200
2471 #define SQUARE 0x80
2472 char *seekAdList[MAX_SEEK_ADS];
2473 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2474 float tcList[MAX_SEEK_ADS];
2475 char colorList[MAX_SEEK_ADS];
2476 int nrOfSeekAds = 0;
2477 int minRating = 1010, maxRating = 2800;
2478 int hMargin = 10, vMargin = 20, h, w;
2479 extern int squareSize, lineGap;
2480
2481 void
2482 PlotSeekAd (int i)
2483 {
2484         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2485         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2486         if(r < minRating+100 && r >=0 ) r = minRating+100;
2487         if(r > maxRating) r = maxRating;
2488         if(tc < 1.) tc = 1.;
2489         if(tc > 95.) tc = 95.;
2490         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2491         y = ((double)r - minRating)/(maxRating - minRating)
2492             * (h-vMargin-squareSize/8-1) + vMargin;
2493         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2494         if(strstr(seekAdList[i], " u ")) color = 1;
2495         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2496            !strstr(seekAdList[i], "bullet") &&
2497            !strstr(seekAdList[i], "blitz") &&
2498            !strstr(seekAdList[i], "standard") ) color = 2;
2499         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2500         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2501 }
2502
2503 void
2504 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2505 {
2506         char buf[MSG_SIZ], *ext = "";
2507         VariantClass v = StringToVariant(type);
2508         if(strstr(type, "wild")) {
2509             ext = type + 4; // append wild number
2510             if(v == VariantFischeRandom) type = "chess960"; else
2511             if(v == VariantLoadable) type = "setup"; else
2512             type = VariantName(v);
2513         }
2514         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2515         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2516             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2517             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2518             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2519             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2520             seekNrList[nrOfSeekAds] = nr;
2521             zList[nrOfSeekAds] = 0;
2522             seekAdList[nrOfSeekAds++] = StrSave(buf);
2523             if(plot) PlotSeekAd(nrOfSeekAds-1);
2524         }
2525 }
2526
2527 void
2528 EraseSeekDot (int i)
2529 {
2530     int x = xList[i], y = yList[i], d=squareSize/4, k;
2531     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2532     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2533     // now replot every dot that overlapped
2534     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2535         int xx = xList[k], yy = yList[k];
2536         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2537             DrawSeekDot(xx, yy, colorList[k]);
2538     }
2539 }
2540
2541 void
2542 RemoveSeekAd (int nr)
2543 {
2544         int i;
2545         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2546             EraseSeekDot(i);
2547             if(seekAdList[i]) free(seekAdList[i]);
2548             seekAdList[i] = seekAdList[--nrOfSeekAds];
2549             seekNrList[i] = seekNrList[nrOfSeekAds];
2550             ratingList[i] = ratingList[nrOfSeekAds];
2551             colorList[i]  = colorList[nrOfSeekAds];
2552             tcList[i] = tcList[nrOfSeekAds];
2553             xList[i]  = xList[nrOfSeekAds];
2554             yList[i]  = yList[nrOfSeekAds];
2555             zList[i]  = zList[nrOfSeekAds];
2556             seekAdList[nrOfSeekAds] = NULL;
2557             break;
2558         }
2559 }
2560
2561 Boolean
2562 MatchSoughtLine (char *line)
2563 {
2564     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2565     int nr, base, inc, u=0; char dummy;
2566
2567     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2568        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2569        (u=1) &&
2570        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2571         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2572         // match: compact and save the line
2573         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2574         return TRUE;
2575     }
2576     return FALSE;
2577 }
2578
2579 int
2580 DrawSeekGraph ()
2581 {
2582     int i;
2583     if(!seekGraphUp) return FALSE;
2584     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2585     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2586
2587     DrawSeekBackground(0, 0, w, h);
2588     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2589     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2590     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2591         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2592         yy = h-1-yy;
2593         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2594         if(i%500 == 0) {
2595             char buf[MSG_SIZ];
2596             snprintf(buf, MSG_SIZ, "%d", i);
2597             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2598         }
2599     }
2600     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2601     for(i=1; i<100; i+=(i<10?1:5)) {
2602         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2603         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2604         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2605             char buf[MSG_SIZ];
2606             snprintf(buf, MSG_SIZ, "%d", i);
2607             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2608         }
2609     }
2610     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2611     return TRUE;
2612 }
2613
2614 int
2615 SeekGraphClick (ClickType click, int x, int y, int moving)
2616 {
2617     static int lastDown = 0, displayed = 0, lastSecond;
2618     if(y < 0) return FALSE;
2619     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2620         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2621         if(!seekGraphUp) return FALSE;
2622         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2623         DrawPosition(TRUE, NULL);
2624         return TRUE;
2625     }
2626     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2627         if(click == Release || moving) return FALSE;
2628         nrOfSeekAds = 0;
2629         soughtPending = TRUE;
2630         SendToICS(ics_prefix);
2631         SendToICS("sought\n"); // should this be "sought all"?
2632     } else { // issue challenge based on clicked ad
2633         int dist = 10000; int i, closest = 0, second = 0;
2634         for(i=0; i<nrOfSeekAds; i++) {
2635             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2636             if(d < dist) { dist = d; closest = i; }
2637             second += (d - zList[i] < 120); // count in-range ads
2638             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2639         }
2640         if(dist < 120) {
2641             char buf[MSG_SIZ];
2642             second = (second > 1);
2643             if(displayed != closest || second != lastSecond) {
2644                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2645                 lastSecond = second; displayed = closest;
2646             }
2647             if(click == Press) {
2648                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2649                 lastDown = closest;
2650                 return TRUE;
2651             } // on press 'hit', only show info
2652             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2653             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2654             SendToICS(ics_prefix);
2655             SendToICS(buf);
2656             return TRUE; // let incoming board of started game pop down the graph
2657         } else if(click == Release) { // release 'miss' is ignored
2658             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2659             if(moving == 2) { // right up-click
2660                 nrOfSeekAds = 0; // refresh graph
2661                 soughtPending = TRUE;
2662                 SendToICS(ics_prefix);
2663                 SendToICS("sought\n"); // should this be "sought all"?
2664             }
2665             return TRUE;
2666         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2667         // press miss or release hit 'pop down' seek graph
2668         seekGraphUp = FALSE;
2669         DrawPosition(TRUE, NULL);
2670     }
2671     return TRUE;
2672 }
2673
2674 void
2675 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2676 {
2677 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2678 #define STARTED_NONE 0
2679 #define STARTED_MOVES 1
2680 #define STARTED_BOARD 2
2681 #define STARTED_OBSERVE 3
2682 #define STARTED_HOLDINGS 4
2683 #define STARTED_CHATTER 5
2684 #define STARTED_COMMENT 6
2685 #define STARTED_MOVES_NOHIDE 7
2686
2687     static int started = STARTED_NONE;
2688     static char parse[20000];
2689     static int parse_pos = 0;
2690     static char buf[BUF_SIZE + 1];
2691     static int firstTime = TRUE, intfSet = FALSE;
2692     static ColorClass prevColor = ColorNormal;
2693     static int savingComment = FALSE;
2694     static int cmatch = 0; // continuation sequence match
2695     char *bp;
2696     char str[MSG_SIZ];
2697     int i, oldi;
2698     int buf_len;
2699     int next_out;
2700     int tkind;
2701     int backup;    /* [DM] For zippy color lines */
2702     char *p;
2703     char talker[MSG_SIZ]; // [HGM] chat
2704     int channel;
2705
2706     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2707
2708     if (appData.debugMode) {
2709       if (!error) {
2710         fprintf(debugFP, "<ICS: ");
2711         show_bytes(debugFP, data, count);
2712         fprintf(debugFP, "\n");
2713       }
2714     }
2715
2716     if (appData.debugMode) { int f = forwardMostMove;
2717         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2718                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2719                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2720     }
2721     if (count > 0) {
2722         /* If last read ended with a partial line that we couldn't parse,
2723            prepend it to the new read and try again. */
2724         if (leftover_len > 0) {
2725             for (i=0; i<leftover_len; i++)
2726               buf[i] = buf[leftover_start + i];
2727         }
2728
2729     /* copy new characters into the buffer */
2730     bp = buf + leftover_len;
2731     buf_len=leftover_len;
2732     for (i=0; i<count; i++)
2733     {
2734         // ignore these
2735         if (data[i] == '\r')
2736             continue;
2737
2738         // join lines split by ICS?
2739         if (!appData.noJoin)
2740         {
2741             /*
2742                 Joining just consists of finding matches against the
2743                 continuation sequence, and discarding that sequence
2744                 if found instead of copying it.  So, until a match
2745                 fails, there's nothing to do since it might be the
2746                 complete sequence, and thus, something we don't want
2747                 copied.
2748             */
2749             if (data[i] == cont_seq[cmatch])
2750             {
2751                 cmatch++;
2752                 if (cmatch == strlen(cont_seq))
2753                 {
2754                     cmatch = 0; // complete match.  just reset the counter
2755
2756                     /*
2757                         it's possible for the ICS to not include the space
2758                         at the end of the last word, making our [correct]
2759                         join operation fuse two separate words.  the server
2760                         does this when the space occurs at the width setting.
2761                     */
2762                     if (!buf_len || buf[buf_len-1] != ' ')
2763                     {
2764                         *bp++ = ' ';
2765                         buf_len++;
2766                     }
2767                 }
2768                 continue;
2769             }
2770             else if (cmatch)
2771             {
2772                 /*
2773                     match failed, so we have to copy what matched before
2774                     falling through and copying this character.  In reality,
2775                     this will only ever be just the newline character, but
2776                     it doesn't hurt to be precise.
2777                 */
2778                 strncpy(bp, cont_seq, cmatch);
2779                 bp += cmatch;
2780                 buf_len += cmatch;
2781                 cmatch = 0;
2782             }
2783         }
2784
2785         // copy this char
2786         *bp++ = data[i];
2787         buf_len++;
2788     }
2789
2790         buf[buf_len] = NULLCHAR;
2791 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2792         next_out = 0;
2793         leftover_start = 0;
2794
2795         i = 0;
2796         while (i < buf_len) {
2797             /* Deal with part of the TELNET option negotiation
2798                protocol.  We refuse to do anything beyond the
2799                defaults, except that we allow the WILL ECHO option,
2800                which ICS uses to turn off password echoing when we are
2801                directly connected to it.  We reject this option
2802                if localLineEditing mode is on (always on in xboard)
2803                and we are talking to port 23, which might be a real
2804                telnet server that will try to keep WILL ECHO on permanently.
2805              */
2806             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2807                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2808                 unsigned char option;
2809                 oldi = i;
2810                 switch ((unsigned char) buf[++i]) {
2811                   case TN_WILL:
2812                     if (appData.debugMode)
2813                       fprintf(debugFP, "\n<WILL ");
2814                     switch (option = (unsigned char) buf[++i]) {
2815                       case TN_ECHO:
2816                         if (appData.debugMode)
2817                           fprintf(debugFP, "ECHO ");
2818                         /* Reply only if this is a change, according
2819                            to the protocol rules. */
2820                         if (remoteEchoOption) break;
2821                         if (appData.localLineEditing &&
2822                             atoi(appData.icsPort) == TN_PORT) {
2823                             TelnetRequest(TN_DONT, TN_ECHO);
2824                         } else {
2825                             EchoOff();
2826                             TelnetRequest(TN_DO, TN_ECHO);
2827                             remoteEchoOption = TRUE;
2828                         }
2829                         break;
2830                       default:
2831                         if (appData.debugMode)
2832                           fprintf(debugFP, "%d ", option);
2833                         /* Whatever this is, we don't want it. */
2834                         TelnetRequest(TN_DONT, option);
2835                         break;
2836                     }
2837                     break;
2838                   case TN_WONT:
2839                     if (appData.debugMode)
2840                       fprintf(debugFP, "\n<WONT ");
2841                     switch (option = (unsigned char) buf[++i]) {
2842                       case TN_ECHO:
2843                         if (appData.debugMode)
2844                           fprintf(debugFP, "ECHO ");
2845                         /* Reply only if this is a change, according
2846                            to the protocol rules. */
2847                         if (!remoteEchoOption) break;
2848                         EchoOn();
2849                         TelnetRequest(TN_DONT, TN_ECHO);
2850                         remoteEchoOption = FALSE;
2851                         break;
2852                       default:
2853                         if (appData.debugMode)
2854                           fprintf(debugFP, "%d ", (unsigned char) option);
2855                         /* Whatever this is, it must already be turned
2856                            off, because we never agree to turn on
2857                            anything non-default, so according to the
2858                            protocol rules, we don't reply. */
2859                         break;
2860                     }
2861                     break;
2862                   case TN_DO:
2863                     if (appData.debugMode)
2864                       fprintf(debugFP, "\n<DO ");
2865                     switch (option = (unsigned char) buf[++i]) {
2866                       default:
2867                         /* Whatever this is, we refuse to do it. */
2868                         if (appData.debugMode)
2869                           fprintf(debugFP, "%d ", option);
2870                         TelnetRequest(TN_WONT, option);
2871                         break;
2872                     }
2873                     break;
2874                   case TN_DONT:
2875                     if (appData.debugMode)
2876                       fprintf(debugFP, "\n<DONT ");
2877                     switch (option = (unsigned char) buf[++i]) {
2878                       default:
2879                         if (appData.debugMode)
2880                           fprintf(debugFP, "%d ", option);
2881                         /* Whatever this is, we are already not doing
2882                            it, because we never agree to do anything
2883                            non-default, so according to the protocol
2884                            rules, we don't reply. */
2885                         break;
2886                     }
2887                     break;
2888                   case TN_IAC:
2889                     if (appData.debugMode)
2890                       fprintf(debugFP, "\n<IAC ");
2891                     /* Doubled IAC; pass it through */
2892                     i--;
2893                     break;
2894                   default:
2895                     if (appData.debugMode)
2896                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2897                     /* Drop all other telnet commands on the floor */
2898                     break;
2899                 }
2900                 if (oldi > next_out)
2901                   SendToPlayer(&buf[next_out], oldi - next_out);
2902                 if (++i > next_out)
2903                   next_out = i;
2904                 continue;
2905             }
2906
2907             /* OK, this at least will *usually* work */
2908             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2909                 loggedOn = TRUE;
2910             }
2911
2912             if (loggedOn && !intfSet) {
2913                 if (ics_type == ICS_ICC) {
2914                   snprintf(str, MSG_SIZ,
2915                           "/set-quietly interface %s\n/set-quietly style 12\n",
2916                           programVersion);
2917                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2918                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2919                 } else if (ics_type == ICS_CHESSNET) {
2920                   snprintf(str, MSG_SIZ, "/style 12\n");
2921                 } else {
2922                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2923                   strcat(str, programVersion);
2924                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2925                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2926                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2927 #ifdef WIN32
2928                   strcat(str, "$iset nohighlight 1\n");
2929 #endif
2930                   strcat(str, "$iset lock 1\n$style 12\n");
2931                 }
2932                 SendToICS(str);
2933                 NotifyFrontendLogin();
2934                 intfSet = TRUE;
2935             }
2936
2937             if (started == STARTED_COMMENT) {
2938                 /* Accumulate characters in comment */
2939                 parse[parse_pos++] = buf[i];
2940                 if (buf[i] == '\n') {
2941                     parse[parse_pos] = NULLCHAR;
2942                     if(chattingPartner>=0) {
2943                         char mess[MSG_SIZ];
2944                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2945                         OutputChatMessage(chattingPartner, mess);
2946                         chattingPartner = -1;
2947                         next_out = i+1; // [HGM] suppress printing in ICS window
2948                     } else
2949                     if(!suppressKibitz) // [HGM] kibitz
2950                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2951                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2952                         int nrDigit = 0, nrAlph = 0, j;
2953                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2954                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2955                         parse[parse_pos] = NULLCHAR;
2956                         // try to be smart: if it does not look like search info, it should go to
2957                         // ICS interaction window after all, not to engine-output window.
2958                         for(j=0; j<parse_pos; j++) { // count letters and digits
2959                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2960                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2961                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2962                         }
2963                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2964                             int depth=0; float score;
2965                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2966                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2967                                 pvInfoList[forwardMostMove-1].depth = depth;
2968                                 pvInfoList[forwardMostMove-1].score = 100*score;
2969                             }
2970                             OutputKibitz(suppressKibitz, parse);
2971                         } else {
2972                             char tmp[MSG_SIZ];
2973                             if(gameMode == IcsObserving) // restore original ICS messages
2974                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2975                             else
2976                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2977                             SendToPlayer(tmp, strlen(tmp));
2978                         }
2979                         next_out = i+1; // [HGM] suppress printing in ICS window
2980                     }
2981                     started = STARTED_NONE;
2982                 } else {
2983                     /* Don't match patterns against characters in comment */
2984                     i++;
2985                     continue;
2986                 }
2987             }
2988             if (started == STARTED_CHATTER) {
2989                 if (buf[i] != '\n') {
2990                     /* Don't match patterns against characters in chatter */
2991                     i++;
2992                     continue;
2993                 }
2994                 started = STARTED_NONE;
2995                 if(suppressKibitz) next_out = i+1;
2996             }
2997
2998             /* Kludge to deal with rcmd protocol */
2999             if (firstTime && looking_at(buf, &i, "\001*")) {
3000                 DisplayFatalError(&buf[1], 0, 1);
3001                 continue;
3002             } else {
3003                 firstTime = FALSE;
3004             }
3005
3006             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3007                 ics_type = ICS_ICC;
3008                 ics_prefix = "/";
3009                 if (appData.debugMode)
3010                   fprintf(debugFP, "ics_type %d\n", ics_type);
3011                 continue;
3012             }
3013             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3014                 ics_type = ICS_FICS;
3015                 ics_prefix = "$";
3016                 if (appData.debugMode)
3017                   fprintf(debugFP, "ics_type %d\n", ics_type);
3018                 continue;
3019             }
3020             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3021                 ics_type = ICS_CHESSNET;
3022                 ics_prefix = "/";
3023                 if (appData.debugMode)
3024                   fprintf(debugFP, "ics_type %d\n", ics_type);
3025                 continue;
3026             }
3027
3028             if (!loggedOn &&
3029                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3030                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3031                  looking_at(buf, &i, "will be \"*\""))) {
3032               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3033               continue;
3034             }
3035
3036             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3037               char buf[MSG_SIZ];
3038               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3039               DisplayIcsInteractionTitle(buf);
3040               have_set_title = TRUE;
3041             }
3042
3043             /* skip finger notes */
3044             if (started == STARTED_NONE &&
3045                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3046                  (buf[i] == '1' && buf[i+1] == '0')) &&
3047                 buf[i+2] == ':' && buf[i+3] == ' ') {
3048               started = STARTED_CHATTER;
3049               i += 3;
3050               continue;
3051             }
3052
3053             oldi = i;
3054             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3055             if(appData.seekGraph) {
3056                 if(soughtPending && MatchSoughtLine(buf+i)) {
3057                     i = strstr(buf+i, "rated") - buf;
3058                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3059                     next_out = leftover_start = i;
3060                     started = STARTED_CHATTER;
3061                     suppressKibitz = TRUE;
3062                     continue;
3063                 }
3064                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3065                         && looking_at(buf, &i, "* ads displayed")) {
3066                     soughtPending = FALSE;
3067                     seekGraphUp = TRUE;
3068                     DrawSeekGraph();
3069                     continue;
3070                 }
3071                 if(appData.autoRefresh) {
3072                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3073                         int s = (ics_type == ICS_ICC); // ICC format differs
3074                         if(seekGraphUp)
3075                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3076                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3077                         looking_at(buf, &i, "*% "); // eat prompt
3078                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3079                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3080                         next_out = i; // suppress
3081                         continue;
3082                     }
3083                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3084                         char *p = star_match[0];
3085                         while(*p) {
3086                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3087                             while(*p && *p++ != ' '); // next
3088                         }
3089                         looking_at(buf, &i, "*% "); // eat prompt
3090                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3091                         next_out = i;
3092                         continue;
3093                     }
3094                 }
3095             }
3096
3097             /* skip formula vars */
3098             if (started == STARTED_NONE &&
3099                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3100               started = STARTED_CHATTER;
3101               i += 3;
3102               continue;
3103             }
3104
3105             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3106             if (appData.autoKibitz && started == STARTED_NONE &&
3107                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3108                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3109                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3110                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3111                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3112                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3113                         suppressKibitz = TRUE;
3114                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3115                         next_out = i;
3116                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3117                                 && (gameMode == IcsPlayingWhite)) ||
3118                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3119                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3120                             started = STARTED_CHATTER; // own kibitz we simply discard
3121                         else {
3122                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3123                             parse_pos = 0; parse[0] = NULLCHAR;
3124                             savingComment = TRUE;
3125                             suppressKibitz = gameMode != IcsObserving ? 2 :
3126                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3127                         }
3128                         continue;
3129                 } else
3130                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3131                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3132                          && atoi(star_match[0])) {
3133                     // suppress the acknowledgements of our own autoKibitz
3134                     char *p;
3135                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3136                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3137                     SendToPlayer(star_match[0], strlen(star_match[0]));
3138                     if(looking_at(buf, &i, "*% ")) // eat prompt
3139                         suppressKibitz = FALSE;
3140                     next_out = i;
3141                     continue;
3142                 }
3143             } // [HGM] kibitz: end of patch
3144
3145             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3146
3147             // [HGM] chat: intercept tells by users for which we have an open chat window
3148             channel = -1;
3149             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3150                                            looking_at(buf, &i, "* whispers:") ||
3151                                            looking_at(buf, &i, "* kibitzes:") ||
3152                                            looking_at(buf, &i, "* shouts:") ||
3153                                            looking_at(buf, &i, "* c-shouts:") ||
3154                                            looking_at(buf, &i, "--> * ") ||
3155                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3156                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3157                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3158                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3159                 int p;
3160                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3161                 chattingPartner = -1;
3162
3163                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3164                 for(p=0; p<MAX_CHAT; p++) {
3165                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3166                     talker[0] = '['; strcat(talker, "] ");
3167                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3168                     chattingPartner = p; break;
3169                     }
3170                 } else
3171                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3172                 for(p=0; p<MAX_CHAT; p++) {
3173                     if(!strcmp("kibitzes", chatPartner[p])) {
3174                         talker[0] = '['; strcat(talker, "] ");
3175                         chattingPartner = p; break;
3176                     }
3177                 } else
3178                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3179                 for(p=0; p<MAX_CHAT; p++) {
3180                     if(!strcmp("whispers", chatPartner[p])) {
3181                         talker[0] = '['; strcat(talker, "] ");
3182                         chattingPartner = p; break;
3183                     }
3184                 } else
3185                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3186                   if(buf[i-8] == '-' && buf[i-3] == 't')
3187                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3188                     if(!strcmp("c-shouts", chatPartner[p])) {
3189                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3190                         chattingPartner = p; break;
3191                     }
3192                   }
3193                   if(chattingPartner < 0)
3194                   for(p=0; p<MAX_CHAT; p++) {
3195                     if(!strcmp("shouts", chatPartner[p])) {
3196                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3197                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3198                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3199                         chattingPartner = p; break;
3200                     }
3201                   }
3202                 }
3203                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3204                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3205                     talker[0] = 0; Colorize(ColorTell, FALSE);
3206                     chattingPartner = p; break;
3207                 }
3208                 if(chattingPartner<0) i = oldi; else {
3209                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3210                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3211                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3212                     started = STARTED_COMMENT;
3213                     parse_pos = 0; parse[0] = NULLCHAR;
3214                     savingComment = 3 + chattingPartner; // counts as TRUE
3215                     suppressKibitz = TRUE;
3216                     continue;
3217                 }
3218             } // [HGM] chat: end of patch
3219
3220           backup = i;
3221             if (appData.zippyTalk || appData.zippyPlay) {
3222                 /* [DM] Backup address for color zippy lines */
3223 #if ZIPPY
3224                if (loggedOn == TRUE)
3225                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3226                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3227 #endif
3228             } // [DM] 'else { ' deleted
3229                 if (
3230                     /* Regular tells and says */
3231                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3232                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3233                     looking_at(buf, &i, "* says: ") ||
3234                     /* Don't color "message" or "messages" output */
3235                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3236                     looking_at(buf, &i, "*. * at *:*: ") ||
3237                     looking_at(buf, &i, "--* (*:*): ") ||
3238                     /* Message notifications (same color as tells) */
3239                     looking_at(buf, &i, "* has left a message ") ||
3240                     looking_at(buf, &i, "* just sent you a message:\n") ||
3241                     /* Whispers and kibitzes */
3242                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3243                     looking_at(buf, &i, "* kibitzes: ") ||
3244                     /* Channel tells */
3245                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3246
3247                   if (tkind == 1 && strchr(star_match[0], ':')) {
3248                       /* Avoid "tells you:" spoofs in channels */
3249                      tkind = 3;
3250                   }
3251                   if (star_match[0][0] == NULLCHAR ||
3252                       strchr(star_match[0], ' ') ||
3253                       (tkind == 3 && strchr(star_match[1], ' '))) {
3254                     /* Reject bogus matches */
3255                     i = oldi;
3256                   } else {
3257                     if (appData.colorize) {
3258                       if (oldi > next_out) {
3259                         SendToPlayer(&buf[next_out], oldi - next_out);
3260                         next_out = oldi;
3261                       }
3262                       switch (tkind) {
3263                       case 1:
3264                         Colorize(ColorTell, FALSE);
3265                         curColor = ColorTell;
3266                         break;
3267                       case 2:
3268                         Colorize(ColorKibitz, FALSE);
3269                         curColor = ColorKibitz;
3270                         break;
3271                       case 3:
3272                         p = strrchr(star_match[1], '(');
3273                         if (p == NULL) {
3274                           p = star_match[1];
3275                         } else {
3276                           p++;
3277                         }
3278                         if (atoi(p) == 1) {
3279                           Colorize(ColorChannel1, FALSE);
3280                           curColor = ColorChannel1;
3281                         } else {
3282                           Colorize(ColorChannel, FALSE);
3283                           curColor = ColorChannel;
3284                         }
3285                         break;
3286                       case 5:
3287                         curColor = ColorNormal;
3288                         break;
3289                       }
3290                     }
3291                     if (started == STARTED_NONE && appData.autoComment &&
3292                         (gameMode == IcsObserving ||
3293                          gameMode == IcsPlayingWhite ||
3294                          gameMode == IcsPlayingBlack)) {
3295                       parse_pos = i - oldi;
3296                       memcpy(parse, &buf[oldi], parse_pos);
3297                       parse[parse_pos] = NULLCHAR;
3298                       started = STARTED_COMMENT;
3299                       savingComment = TRUE;
3300                     } else {
3301                       started = STARTED_CHATTER;
3302                       savingComment = FALSE;
3303                     }
3304                     loggedOn = TRUE;
3305                     continue;
3306                   }
3307                 }
3308
3309                 if (looking_at(buf, &i, "* s-shouts: ") ||
3310                     looking_at(buf, &i, "* c-shouts: ")) {
3311                     if (appData.colorize) {
3312                         if (oldi > next_out) {
3313                             SendToPlayer(&buf[next_out], oldi - next_out);
3314                             next_out = oldi;
3315                         }
3316                         Colorize(ColorSShout, FALSE);
3317                         curColor = ColorSShout;
3318                     }
3319                     loggedOn = TRUE;
3320                     started = STARTED_CHATTER;
3321                     continue;
3322                 }
3323
3324                 if (looking_at(buf, &i, "--->")) {
3325                     loggedOn = TRUE;
3326                     continue;
3327                 }
3328
3329                 if (looking_at(buf, &i, "* shouts: ") ||
3330                     looking_at(buf, &i, "--> ")) {
3331                     if (appData.colorize) {
3332                         if (oldi > next_out) {
3333                             SendToPlayer(&buf[next_out], oldi - next_out);
3334                             next_out = oldi;
3335                         }
3336                         Colorize(ColorShout, FALSE);
3337                         curColor = ColorShout;
3338                     }
3339                     loggedOn = TRUE;
3340                     started = STARTED_CHATTER;
3341                     continue;
3342                 }
3343
3344                 if (looking_at( buf, &i, "Challenge:")) {
3345                     if (appData.colorize) {
3346                         if (oldi > next_out) {
3347                             SendToPlayer(&buf[next_out], oldi - next_out);
3348                             next_out = oldi;
3349                         }
3350                         Colorize(ColorChallenge, FALSE);
3351                         curColor = ColorChallenge;
3352                     }
3353                     loggedOn = TRUE;
3354                     continue;
3355                 }
3356
3357                 if (looking_at(buf, &i, "* offers you") ||
3358                     looking_at(buf, &i, "* offers to be") ||
3359                     looking_at(buf, &i, "* would like to") ||
3360                     looking_at(buf, &i, "* requests to") ||
3361                     looking_at(buf, &i, "Your opponent offers") ||
3362                     looking_at(buf, &i, "Your opponent requests")) {
3363
3364                     if (appData.colorize) {
3365                         if (oldi > next_out) {
3366                             SendToPlayer(&buf[next_out], oldi - next_out);
3367                             next_out = oldi;
3368                         }
3369                         Colorize(ColorRequest, FALSE);
3370                         curColor = ColorRequest;
3371                     }
3372                     continue;
3373                 }
3374
3375                 if (looking_at(buf, &i, "* (*) seeking")) {
3376                     if (appData.colorize) {
3377                         if (oldi > next_out) {
3378                             SendToPlayer(&buf[next_out], oldi - next_out);
3379                             next_out = oldi;
3380                         }
3381                         Colorize(ColorSeek, FALSE);
3382                         curColor = ColorSeek;
3383                     }
3384                     continue;
3385             }
3386
3387           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3388
3389             if (looking_at(buf, &i, "\\   ")) {
3390                 if (prevColor != ColorNormal) {
3391                     if (oldi > next_out) {
3392                         SendToPlayer(&buf[next_out], oldi - next_out);
3393                         next_out = oldi;
3394                     }
3395                     Colorize(prevColor, TRUE);
3396                     curColor = prevColor;
3397                 }
3398                 if (savingComment) {
3399                     parse_pos = i - oldi;
3400                     memcpy(parse, &buf[oldi], parse_pos);
3401                     parse[parse_pos] = NULLCHAR;
3402                     started = STARTED_COMMENT;
3403                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3404                         chattingPartner = savingComment - 3; // kludge to remember the box
3405                 } else {
3406                     started = STARTED_CHATTER;
3407                 }
3408                 continue;
3409             }
3410
3411             if (looking_at(buf, &i, "Black Strength :") ||
3412                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3413                 looking_at(buf, &i, "<10>") ||
3414                 looking_at(buf, &i, "#@#")) {
3415                 /* Wrong board style */
3416                 loggedOn = TRUE;
3417                 SendToICS(ics_prefix);
3418                 SendToICS("set style 12\n");
3419                 SendToICS(ics_prefix);
3420                 SendToICS("refresh\n");
3421                 continue;
3422             }
3423
3424             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3425                 ICSInitScript();
3426                 have_sent_ICS_logon = 1;
3427                 continue;
3428             }
3429
3430             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3431                 (looking_at(buf, &i, "\n<12> ") ||
3432                  looking_at(buf, &i, "<12> "))) {
3433                 loggedOn = TRUE;
3434                 if (oldi > next_out) {
3435                     SendToPlayer(&buf[next_out], oldi - next_out);
3436                 }
3437                 next_out = i;
3438                 started = STARTED_BOARD;
3439                 parse_pos = 0;
3440                 continue;
3441             }
3442
3443             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3444                 looking_at(buf, &i, "<b1> ")) {
3445                 if (oldi > next_out) {
3446                     SendToPlayer(&buf[next_out], oldi - next_out);
3447                 }
3448                 next_out = i;
3449                 started = STARTED_HOLDINGS;
3450                 parse_pos = 0;
3451                 continue;
3452             }
3453
3454             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3455                 loggedOn = TRUE;
3456                 /* Header for a move list -- first line */
3457
3458                 switch (ics_getting_history) {
3459                   case H_FALSE:
3460                     switch (gameMode) {
3461                       case IcsIdle:
3462                       case BeginningOfGame:
3463                         /* User typed "moves" or "oldmoves" while we
3464                            were idle.  Pretend we asked for these
3465                            moves and soak them up so user can step
3466                            through them and/or save them.
3467                            */
3468                         Reset(FALSE, TRUE);
3469                         gameMode = IcsObserving;
3470                         ModeHighlight();
3471                         ics_gamenum = -1;
3472                         ics_getting_history = H_GOT_UNREQ_HEADER;
3473                         break;
3474                       case EditGame: /*?*/
3475                       case EditPosition: /*?*/
3476                         /* Should above feature work in these modes too? */
3477                         /* For now it doesn't */
3478                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3479                         break;
3480                       default:
3481                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3482                         break;
3483                     }
3484                     break;
3485                   case H_REQUESTED:
3486                     /* Is this the right one? */
3487                     if (gameInfo.white && gameInfo.black &&
3488                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3489                         strcmp(gameInfo.black, star_match[2]) == 0) {
3490                         /* All is well */
3491                         ics_getting_history = H_GOT_REQ_HEADER;
3492                     }
3493                     break;
3494                   case H_GOT_REQ_HEADER:
3495                   case H_GOT_UNREQ_HEADER:
3496                   case H_GOT_UNWANTED_HEADER:
3497                   case H_GETTING_MOVES:
3498                     /* Should not happen */
3499                     DisplayError(_("Error gathering move list: two headers"), 0);
3500                     ics_getting_history = H_FALSE;
3501                     break;
3502                 }
3503
3504                 /* Save player ratings into gameInfo if needed */
3505                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3506                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3507                     (gameInfo.whiteRating == -1 ||
3508                      gameInfo.blackRating == -1)) {
3509
3510                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3511                     gameInfo.blackRating = string_to_rating(star_match[3]);
3512                     if (appData.debugMode)
3513                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3514                               gameInfo.whiteRating, gameInfo.blackRating);
3515                 }
3516                 continue;
3517             }
3518
3519             if (looking_at(buf, &i,
3520               "* * match, initial time: * minute*, increment: * second")) {
3521                 /* Header for a move list -- second line */
3522                 /* Initial board will follow if this is a wild game */
3523                 if (gameInfo.event != NULL) free(gameInfo.event);
3524                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3525                 gameInfo.event = StrSave(str);
3526                 /* [HGM] we switched variant. Translate boards if needed. */
3527                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3528                 continue;
3529             }
3530
3531             if (looking_at(buf, &i, "Move  ")) {
3532                 /* Beginning of a move list */
3533                 switch (ics_getting_history) {
3534                   case H_FALSE:
3535                     /* Normally should not happen */
3536                     /* Maybe user hit reset while we were parsing */
3537                     break;
3538                   case H_REQUESTED:
3539                     /* Happens if we are ignoring a move list that is not
3540                      * the one we just requested.  Common if the user
3541                      * tries to observe two games without turning off
3542                      * getMoveList */
3543                     break;
3544                   case H_GETTING_MOVES:
3545                     /* Should not happen */
3546                     DisplayError(_("Error gathering move list: nested"), 0);
3547                     ics_getting_history = H_FALSE;
3548                     break;
3549                   case H_GOT_REQ_HEADER:
3550                     ics_getting_history = H_GETTING_MOVES;
3551                     started = STARTED_MOVES;
3552                     parse_pos = 0;
3553                     if (oldi > next_out) {
3554                         SendToPlayer(&buf[next_out], oldi - next_out);
3555                     }
3556                     break;
3557                   case H_GOT_UNREQ_HEADER:
3558                     ics_getting_history = H_GETTING_MOVES;
3559                     started = STARTED_MOVES_NOHIDE;
3560                     parse_pos = 0;
3561                     break;
3562                   case H_GOT_UNWANTED_HEADER:
3563                     ics_getting_history = H_FALSE;
3564                     break;
3565                 }
3566                 continue;
3567             }
3568
3569             if (looking_at(buf, &i, "% ") ||
3570                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3571                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3572                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3573                     soughtPending = FALSE;
3574                     seekGraphUp = TRUE;
3575                     DrawSeekGraph();
3576                 }
3577                 if(suppressKibitz) next_out = i;
3578                 savingComment = FALSE;
3579                 suppressKibitz = 0;
3580                 switch (started) {
3581                   case STARTED_MOVES:
3582                   case STARTED_MOVES_NOHIDE:
3583                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3584                     parse[parse_pos + i - oldi] = NULLCHAR;
3585                     ParseGameHistory(parse);
3586 #if ZIPPY
3587                     if (appData.zippyPlay && first.initDone) {
3588                         FeedMovesToProgram(&first, forwardMostMove);
3589                         if (gameMode == IcsPlayingWhite) {
3590                             if (WhiteOnMove(forwardMostMove)) {
3591                                 if (first.sendTime) {
3592                                   if (first.useColors) {
3593                                     SendToProgram("black\n", &first);
3594                                   }
3595                                   SendTimeRemaining(&first, TRUE);
3596                                 }
3597                                 if (first.useColors) {
3598                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3599                                 }
3600                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3601                                 first.maybeThinking = TRUE;
3602                             } else {
3603                                 if (first.usePlayother) {
3604                                   if (first.sendTime) {
3605                                     SendTimeRemaining(&first, TRUE);
3606                                   }
3607                                   SendToProgram("playother\n", &first);
3608                                   firstMove = FALSE;
3609                                 } else {
3610                                   firstMove = TRUE;
3611                                 }
3612                             }
3613                         } else if (gameMode == IcsPlayingBlack) {
3614                             if (!WhiteOnMove(forwardMostMove)) {
3615                                 if (first.sendTime) {
3616                                   if (first.useColors) {
3617                                     SendToProgram("white\n", &first);
3618                                   }
3619                                   SendTimeRemaining(&first, FALSE);
3620                                 }
3621                                 if (first.useColors) {
3622                                   SendToProgram("black\n", &first);
3623                                 }
3624                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3625                                 first.maybeThinking = TRUE;
3626                             } else {
3627                                 if (first.usePlayother) {
3628                                   if (first.sendTime) {
3629                                     SendTimeRemaining(&first, FALSE);
3630                                   }
3631                                   SendToProgram("playother\n", &first);
3632                                   firstMove = FALSE;
3633                                 } else {
3634                                   firstMove = TRUE;
3635                                 }
3636                             }
3637                         }
3638                     }
3639 #endif
3640                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3641                         /* Moves came from oldmoves or moves command
3642                            while we weren't doing anything else.
3643                            */
3644                         currentMove = forwardMostMove;
3645                         ClearHighlights();/*!!could figure this out*/
3646                         flipView = appData.flipView;
3647                         DrawPosition(TRUE, boards[currentMove]);
3648                         DisplayBothClocks();
3649                         snprintf(str, MSG_SIZ, "%s %s %s",
3650                                 gameInfo.white, _("vs."),  gameInfo.black);
3651                         DisplayTitle(str);
3652                         gameMode = IcsIdle;
3653                     } else {
3654                         /* Moves were history of an active game */
3655                         if (gameInfo.resultDetails != NULL) {
3656                             free(gameInfo.resultDetails);
3657                             gameInfo.resultDetails = NULL;
3658                         }
3659                     }
3660                     HistorySet(parseList, backwardMostMove,
3661                                forwardMostMove, currentMove-1);
3662                     DisplayMove(currentMove - 1);
3663                     if (started == STARTED_MOVES) next_out = i;
3664                     started = STARTED_NONE;
3665                     ics_getting_history = H_FALSE;
3666                     break;
3667
3668                   case STARTED_OBSERVE:
3669                     started = STARTED_NONE;
3670                     SendToICS(ics_prefix);
3671                     SendToICS("refresh\n");
3672                     break;
3673
3674                   default:
3675                     break;
3676                 }
3677                 if(bookHit) { // [HGM] book: simulate book reply
3678                     static char bookMove[MSG_SIZ]; // a bit generous?
3679
3680                     programStats.nodes = programStats.depth = programStats.time =
3681                     programStats.score = programStats.got_only_move = 0;
3682                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3683
3684                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3685                     strcat(bookMove, bookHit);
3686                     HandleMachineMove(bookMove, &first);
3687                 }
3688                 continue;
3689             }
3690
3691             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3692                  started == STARTED_HOLDINGS ||
3693                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3694                 /* Accumulate characters in move list or board */
3695                 parse[parse_pos++] = buf[i];
3696             }
3697
3698             /* Start of game messages.  Mostly we detect start of game
3699                when the first board image arrives.  On some versions
3700                of the ICS, though, we need to do a "refresh" after starting
3701                to observe in order to get the current board right away. */
3702             if (looking_at(buf, &i, "Adding game * to observation list")) {
3703                 started = STARTED_OBSERVE;
3704                 continue;
3705             }
3706
3707             /* Handle auto-observe */
3708             if (appData.autoObserve &&
3709                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3710                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3711                 char *player;
3712                 /* Choose the player that was highlighted, if any. */
3713                 if (star_match[0][0] == '\033' ||
3714                     star_match[1][0] != '\033') {
3715                     player = star_match[0];
3716                 } else {
3717                     player = star_match[2];
3718                 }
3719                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3720                         ics_prefix, StripHighlightAndTitle(player));
3721                 SendToICS(str);
3722
3723                 /* Save ratings from notify string */
3724                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3725                 player1Rating = string_to_rating(star_match[1]);
3726                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3727                 player2Rating = string_to_rating(star_match[3]);
3728
3729                 if (appData.debugMode)
3730                   fprintf(debugFP,
3731                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3732                           player1Name, player1Rating,
3733                           player2Name, player2Rating);
3734
3735                 continue;
3736             }
3737
3738             /* Deal with automatic examine mode after a game,
3739                and with IcsObserving -> IcsExamining transition */
3740             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3741                 looking_at(buf, &i, "has made you an examiner of game *")) {
3742
3743                 int gamenum = atoi(star_match[0]);
3744                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3745                     gamenum == ics_gamenum) {
3746                     /* We were already playing or observing this game;
3747                        no need to refetch history */
3748                     gameMode = IcsExamining;
3749                     if (pausing) {
3750                         pauseExamForwardMostMove = forwardMostMove;
3751                     } else if (currentMove < forwardMostMove) {
3752                         ForwardInner(forwardMostMove);
3753                     }
3754                 } else {
3755                     /* I don't think this case really can happen */
3756                     SendToICS(ics_prefix);
3757                     SendToICS("refresh\n");
3758                 }
3759                 continue;
3760             }
3761
3762             /* Error messages */
3763 //          if (ics_user_moved) {
3764             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3765                 if (looking_at(buf, &i, "Illegal move") ||
3766                     looking_at(buf, &i, "Not a legal move") ||
3767                     looking_at(buf, &i, "Your king is in check") ||
3768                     looking_at(buf, &i, "It isn't your turn") ||
3769                     looking_at(buf, &i, "It is not your move")) {
3770                     /* Illegal move */
3771                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3772                         currentMove = forwardMostMove-1;
3773                         DisplayMove(currentMove - 1); /* before DMError */
3774                         DrawPosition(FALSE, boards[currentMove]);
3775                         SwitchClocks(forwardMostMove-1); // [HGM] race
3776                         DisplayBothClocks();
3777                     }
3778                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3779                     ics_user_moved = 0;
3780                     continue;
3781                 }
3782             }
3783
3784             if (looking_at(buf, &i, "still have time") ||
3785                 looking_at(buf, &i, "not out of time") ||
3786                 looking_at(buf, &i, "either player is out of time") ||
3787                 looking_at(buf, &i, "has timeseal; checking")) {
3788                 /* We must have called his flag a little too soon */
3789                 whiteFlag = blackFlag = FALSE;
3790                 continue;
3791             }
3792
3793             if (looking_at(buf, &i, "added * seconds to") ||
3794                 looking_at(buf, &i, "seconds were added to")) {
3795                 /* Update the clocks */
3796                 SendToICS(ics_prefix);
3797                 SendToICS("refresh\n");
3798                 continue;
3799             }
3800
3801             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3802                 ics_clock_paused = TRUE;
3803                 StopClocks();
3804                 continue;
3805             }
3806
3807             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3808                 ics_clock_paused = FALSE;
3809                 StartClocks();
3810                 continue;
3811             }
3812
3813             /* Grab player ratings from the Creating: message.
3814                Note we have to check for the special case when
3815                the ICS inserts things like [white] or [black]. */
3816             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3817                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3818                 /* star_matches:
3819                    0    player 1 name (not necessarily white)
3820                    1    player 1 rating
3821                    2    empty, white, or black (IGNORED)
3822                    3    player 2 name (not necessarily black)
3823                    4    player 2 rating
3824
3825                    The names/ratings are sorted out when the game
3826                    actually starts (below).
3827                 */
3828                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3829                 player1Rating = string_to_rating(star_match[1]);
3830                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3831                 player2Rating = string_to_rating(star_match[4]);
3832
3833                 if (appData.debugMode)
3834                   fprintf(debugFP,
3835                           "Ratings from 'Creating:' %s %d, %s %d\n",
3836                           player1Name, player1Rating,
3837                           player2Name, player2Rating);
3838
3839                 continue;
3840             }
3841
3842             /* Improved generic start/end-of-game messages */
3843             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3844                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3845                 /* If tkind == 0: */
3846                 /* star_match[0] is the game number */
3847                 /*           [1] is the white player's name */
3848                 /*           [2] is the black player's name */
3849                 /* For end-of-game: */
3850                 /*           [3] is the reason for the game end */
3851                 /*           [4] is a PGN end game-token, preceded by " " */
3852                 /* For start-of-game: */
3853                 /*           [3] begins with "Creating" or "Continuing" */
3854                 /*           [4] is " *" or empty (don't care). */
3855                 int gamenum = atoi(star_match[0]);
3856                 char *whitename, *blackname, *why, *endtoken;
3857                 ChessMove endtype = EndOfFile;
3858
3859                 if (tkind == 0) {
3860                   whitename = star_match[1];
3861                   blackname = star_match[2];
3862                   why = star_match[3];
3863                   endtoken = star_match[4];
3864                 } else {
3865                   whitename = star_match[1];
3866                   blackname = star_match[3];
3867                   why = star_match[5];
3868                   endtoken = star_match[6];
3869                 }
3870
3871                 /* Game start messages */
3872                 if (strncmp(why, "Creating ", 9) == 0 ||
3873                     strncmp(why, "Continuing ", 11) == 0) {
3874                     gs_gamenum = gamenum;
3875                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3876                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3877                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3878 #if ZIPPY
3879                     if (appData.zippyPlay) {
3880                         ZippyGameStart(whitename, blackname);
3881                     }
3882 #endif /*ZIPPY*/
3883                     partnerBoardValid = FALSE; // [HGM] bughouse
3884                     continue;
3885                 }
3886
3887                 /* Game end messages */
3888                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3889                     ics_gamenum != gamenum) {
3890                     continue;
3891                 }
3892                 while (endtoken[0] == ' ') endtoken++;
3893                 switch (endtoken[0]) {
3894                   case '*':
3895                   default:
3896                     endtype = GameUnfinished;
3897                     break;
3898                   case '0':
3899                     endtype = BlackWins;
3900                     break;
3901                   case '1':
3902                     if (endtoken[1] == '/')
3903                       endtype = GameIsDrawn;
3904                     else
3905                       endtype = WhiteWins;
3906                     break;
3907                 }
3908                 GameEnds(endtype, why, GE_ICS);
3909 #if ZIPPY
3910                 if (appData.zippyPlay && first.initDone) {
3911                     ZippyGameEnd(endtype, why);
3912                     if (first.pr == NoProc) {
3913                       /* Start the next process early so that we'll
3914                          be ready for the next challenge */
3915                       StartChessProgram(&first);
3916                     }
3917                     /* Send "new" early, in case this command takes
3918                        a long time to finish, so that we'll be ready
3919                        for the next challenge. */
3920                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3921                     Reset(TRUE, TRUE);
3922                 }
3923 #endif /*ZIPPY*/
3924                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3925                 continue;
3926             }
3927
3928             if (looking_at(buf, &i, "Removing game * from observation") ||
3929                 looking_at(buf, &i, "no longer observing game *") ||
3930                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3931                 if (gameMode == IcsObserving &&
3932                     atoi(star_match[0]) == ics_gamenum)
3933                   {
3934                       /* icsEngineAnalyze */
3935                       if (appData.icsEngineAnalyze) {
3936                             ExitAnalyzeMode();
3937                             ModeHighlight();
3938                       }
3939                       StopClocks();
3940                       gameMode = IcsIdle;
3941                       ics_gamenum = -1;
3942                       ics_user_moved = FALSE;
3943                   }
3944                 continue;
3945             }
3946
3947             if (looking_at(buf, &i, "no longer examining game *")) {
3948                 if (gameMode == IcsExamining &&
3949                     atoi(star_match[0]) == ics_gamenum)
3950                   {
3951                       gameMode = IcsIdle;
3952                       ics_gamenum = -1;
3953                       ics_user_moved = FALSE;
3954                   }
3955                 continue;
3956             }
3957
3958             /* Advance leftover_start past any newlines we find,
3959                so only partial lines can get reparsed */
3960             if (looking_at(buf, &i, "\n")) {
3961                 prevColor = curColor;
3962                 if (curColor != ColorNormal) {
3963                     if (oldi > next_out) {
3964                         SendToPlayer(&buf[next_out], oldi - next_out);
3965                         next_out = oldi;
3966                     }
3967                     Colorize(ColorNormal, FALSE);
3968                     curColor = ColorNormal;
3969                 }
3970                 if (started == STARTED_BOARD) {
3971                     started = STARTED_NONE;
3972                     parse[parse_pos] = NULLCHAR;
3973                     ParseBoard12(parse);
3974                     ics_user_moved = 0;
3975
3976                     /* Send premove here */
3977                     if (appData.premove) {
3978                       char str[MSG_SIZ];
3979                       if (currentMove == 0 &&
3980                           gameMode == IcsPlayingWhite &&
3981                           appData.premoveWhite) {
3982                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3983                         if (appData.debugMode)
3984                           fprintf(debugFP, "Sending premove:\n");
3985                         SendToICS(str);
3986                       } else if (currentMove == 1 &&
3987                                  gameMode == IcsPlayingBlack &&
3988                                  appData.premoveBlack) {
3989                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3990                         if (appData.debugMode)
3991                           fprintf(debugFP, "Sending premove:\n");
3992                         SendToICS(str);
3993                       } else if (gotPremove) {
3994                         gotPremove = 0;
3995                         ClearPremoveHighlights();
3996                         if (appData.debugMode)
3997                           fprintf(debugFP, "Sending premove:\n");
3998                           UserMoveEvent(premoveFromX, premoveFromY,
3999                                         premoveToX, premoveToY,
4000                                         premovePromoChar);
4001                       }
4002                     }
4003
4004                     /* Usually suppress following prompt */
4005                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4006                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4007                         if (looking_at(buf, &i, "*% ")) {
4008                             savingComment = FALSE;
4009                             suppressKibitz = 0;
4010                         }
4011                     }
4012                     next_out = i;
4013                 } else if (started == STARTED_HOLDINGS) {
4014                     int gamenum;
4015                     char new_piece[MSG_SIZ];
4016                     started = STARTED_NONE;
4017                     parse[parse_pos] = NULLCHAR;
4018                     if (appData.debugMode)
4019                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4020                                                         parse, currentMove);
4021                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4022                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4023                         if (gameInfo.variant == VariantNormal) {
4024                           /* [HGM] We seem to switch variant during a game!
4025                            * Presumably no holdings were displayed, so we have
4026                            * to move the position two files to the right to
4027                            * create room for them!
4028                            */
4029                           VariantClass newVariant;
4030                           switch(gameInfo.boardWidth) { // base guess on board width
4031                                 case 9:  newVariant = VariantShogi; break;
4032                                 case 10: newVariant = VariantGreat; break;
4033                                 default: newVariant = VariantCrazyhouse; break;
4034                           }
4035                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4036                           /* Get a move list just to see the header, which
4037                              will tell us whether this is really bug or zh */
4038                           if (ics_getting_history == H_FALSE) {
4039                             ics_getting_history = H_REQUESTED;
4040                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4041                             SendToICS(str);
4042                           }
4043                         }
4044                         new_piece[0] = NULLCHAR;
4045                         sscanf(parse, "game %d white [%s black [%s <- %s",
4046                                &gamenum, white_holding, black_holding,
4047                                new_piece);
4048                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4049                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4050                         /* [HGM] copy holdings to board holdings area */
4051                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4052                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4053                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4054 #if ZIPPY
4055                         if (appData.zippyPlay && first.initDone) {
4056                             ZippyHoldings(white_holding, black_holding,
4057                                           new_piece);
4058                         }
4059 #endif /*ZIPPY*/
4060                         if (tinyLayout || smallLayout) {
4061                             char wh[16], bh[16];
4062                             PackHolding(wh, white_holding);
4063                             PackHolding(bh, black_holding);
4064                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4065                                     gameInfo.white, gameInfo.black);
4066                         } else {
4067                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4068                                     gameInfo.white, white_holding, _("vs."),
4069                                     gameInfo.black, black_holding);
4070                         }
4071                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4072                         DrawPosition(FALSE, boards[currentMove]);
4073                         DisplayTitle(str);
4074                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4075                         sscanf(parse, "game %d white [%s black [%s <- %s",
4076                                &gamenum, white_holding, black_holding,
4077                                new_piece);
4078                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4079                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4080                         /* [HGM] copy holdings to partner-board holdings area */
4081                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4082                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4083                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4084                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4085                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4086                       }
4087                     }
4088                     /* Suppress following prompt */
4089                     if (looking_at(buf, &i, "*% ")) {
4090                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4091                         savingComment = FALSE;
4092                         suppressKibitz = 0;
4093                     }
4094                     next_out = i;
4095                 }
4096                 continue;
4097             }
4098
4099             i++;                /* skip unparsed character and loop back */
4100         }
4101
4102         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4103 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4104 //          SendToPlayer(&buf[next_out], i - next_out);
4105             started != STARTED_HOLDINGS && leftover_start > next_out) {
4106             SendToPlayer(&buf[next_out], leftover_start - next_out);
4107             next_out = i;
4108         }
4109
4110         leftover_len = buf_len - leftover_start;
4111         /* if buffer ends with something we couldn't parse,
4112            reparse it after appending the next read */
4113
4114     } else if (count == 0) {
4115         RemoveInputSource(isr);
4116         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4117     } else {
4118         DisplayFatalError(_("Error reading from ICS"), error, 1);
4119     }
4120 }
4121
4122
4123 /* Board style 12 looks like this:
4124
4125    <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
4126
4127  * The "<12> " is stripped before it gets to this routine.  The two
4128  * trailing 0's (flip state and clock ticking) are later addition, and
4129  * some chess servers may not have them, or may have only the first.
4130  * Additional trailing fields may be added in the future.
4131  */
4132
4133 #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"
4134
4135 #define RELATION_OBSERVING_PLAYED    0
4136 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4137 #define RELATION_PLAYING_MYMOVE      1
4138 #define RELATION_PLAYING_NOTMYMOVE  -1
4139 #define RELATION_EXAMINING           2
4140 #define RELATION_ISOLATED_BOARD     -3
4141 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4142
4143 void
4144 ParseBoard12 (char *string)
4145 {
4146     GameMode newGameMode;
4147     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4148     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4149     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4150     char to_play, board_chars[200];
4151     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4152     char black[32], white[32];
4153     Board board;
4154     int prevMove = currentMove;
4155     int ticking = 2;
4156     ChessMove moveType;
4157     int fromX, fromY, toX, toY;
4158     char promoChar;
4159     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4160     char *bookHit = NULL; // [HGM] book
4161     Boolean weird = FALSE, reqFlag = FALSE;
4162
4163     fromX = fromY = toX = toY = -1;
4164
4165     newGame = FALSE;
4166
4167     if (appData.debugMode)
4168       fprintf(debugFP, _("Parsing board: %s\n"), string);
4169
4170     move_str[0] = NULLCHAR;
4171     elapsed_time[0] = NULLCHAR;
4172     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4173         int  i = 0, j;
4174         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4175             if(string[i] == ' ') { ranks++; files = 0; }
4176             else files++;
4177             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4178             i++;
4179         }
4180         for(j = 0; j <i; j++) board_chars[j] = string[j];
4181         board_chars[i] = '\0';
4182         string += i + 1;
4183     }
4184     n = sscanf(string, PATTERN, &to_play, &double_push,
4185                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4186                &gamenum, white, black, &relation, &basetime, &increment,
4187                &white_stren, &black_stren, &white_time, &black_time,
4188                &moveNum, str, elapsed_time, move_str, &ics_flip,
4189                &ticking);
4190
4191     if (n < 21) {
4192         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4193         DisplayError(str, 0);
4194         return;
4195     }
4196
4197     /* Convert the move number to internal form */
4198     moveNum = (moveNum - 1) * 2;
4199     if (to_play == 'B') moveNum++;
4200     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4201       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4202                         0, 1);
4203       return;
4204     }
4205
4206     switch (relation) {
4207       case RELATION_OBSERVING_PLAYED:
4208       case RELATION_OBSERVING_STATIC:
4209         if (gamenum == -1) {
4210             /* Old ICC buglet */
4211             relation = RELATION_OBSERVING_STATIC;
4212         }
4213         newGameMode = IcsObserving;
4214         break;
4215       case RELATION_PLAYING_MYMOVE:
4216       case RELATION_PLAYING_NOTMYMOVE:
4217         newGameMode =
4218           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4219             IcsPlayingWhite : IcsPlayingBlack;
4220         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4221         break;
4222       case RELATION_EXAMINING:
4223         newGameMode = IcsExamining;
4224         break;
4225       case RELATION_ISOLATED_BOARD:
4226       default:
4227         /* Just display this board.  If user was doing something else,
4228            we will forget about it until the next board comes. */
4229         newGameMode = IcsIdle;
4230         break;
4231       case RELATION_STARTING_POSITION:
4232         newGameMode = gameMode;
4233         break;
4234     }
4235
4236     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4237          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4238       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4239       char *toSqr;
4240       for (k = 0; k < ranks; k++) {
4241         for (j = 0; j < files; j++)
4242           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4243         if(gameInfo.holdingsWidth > 1) {
4244              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4245              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4246         }
4247       }
4248       CopyBoard(partnerBoard, board);
4249       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4250         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4251         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4252       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4253       if(toSqr = strchr(str, '-')) {
4254         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4255         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4256       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4257       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4258       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4259       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4260       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4261       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4262                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4263       DisplayMessage(partnerStatus, "");
4264         partnerBoardValid = TRUE;
4265       return;
4266     }
4267
4268     /* Modify behavior for initial board display on move listing
4269        of wild games.
4270        */
4271     switch (ics_getting_history) {
4272       case H_FALSE:
4273       case H_REQUESTED:
4274         break;
4275       case H_GOT_REQ_HEADER:
4276       case H_GOT_UNREQ_HEADER:
4277         /* This is the initial position of the current game */
4278         gamenum = ics_gamenum;
4279         moveNum = 0;            /* old ICS bug workaround */
4280         if (to_play == 'B') {
4281           startedFromSetupPosition = TRUE;
4282           blackPlaysFirst = TRUE;
4283           moveNum = 1;
4284           if (forwardMostMove == 0) forwardMostMove = 1;
4285           if (backwardMostMove == 0) backwardMostMove = 1;
4286           if (currentMove == 0) currentMove = 1;
4287         }
4288         newGameMode = gameMode;
4289         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4290         break;
4291       case H_GOT_UNWANTED_HEADER:
4292         /* This is an initial board that we don't want */
4293         return;
4294       case H_GETTING_MOVES:
4295         /* Should not happen */
4296         DisplayError(_("Error gathering move list: extra board"), 0);
4297         ics_getting_history = H_FALSE;
4298         return;
4299     }
4300
4301    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4302                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4303      /* [HGM] We seem to have switched variant unexpectedly
4304       * Try to guess new variant from board size
4305       */
4306           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4307           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4308           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4309           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4310           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4311           if(!weird) newVariant = VariantNormal;
4312           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4313           /* Get a move list just to see the header, which
4314              will tell us whether this is really bug or zh */
4315           if (ics_getting_history == H_FALSE) {
4316             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4317             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4318             SendToICS(str);
4319           }
4320     }
4321
4322     /* Take action if this is the first board of a new game, or of a
4323        different game than is currently being displayed.  */
4324     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4325         relation == RELATION_ISOLATED_BOARD) {
4326
4327         /* Forget the old game and get the history (if any) of the new one */
4328         if (gameMode != BeginningOfGame) {
4329           Reset(TRUE, TRUE);
4330         }
4331         newGame = TRUE;
4332         if (appData.autoRaiseBoard) BoardToTop();
4333         prevMove = -3;
4334         if (gamenum == -1) {
4335             newGameMode = IcsIdle;
4336         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4337                    appData.getMoveList && !reqFlag) {
4338             /* Need to get game history */
4339             ics_getting_history = H_REQUESTED;
4340             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4341             SendToICS(str);
4342         }
4343
4344         /* Initially flip the board to have black on the bottom if playing
4345            black or if the ICS flip flag is set, but let the user change
4346            it with the Flip View button. */
4347         flipView = appData.autoFlipView ?
4348           (newGameMode == IcsPlayingBlack) || ics_flip :
4349           appData.flipView;
4350
4351         /* Done with values from previous mode; copy in new ones */
4352         gameMode = newGameMode;
4353         ModeHighlight();
4354         ics_gamenum = gamenum;
4355         if (gamenum == gs_gamenum) {
4356             int klen = strlen(gs_kind);
4357             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4358             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4359             gameInfo.event = StrSave(str);
4360         } else {
4361             gameInfo.event = StrSave("ICS game");
4362         }
4363         gameInfo.site = StrSave(appData.icsHost);
4364         gameInfo.date = PGNDate();
4365         gameInfo.round = StrSave("-");
4366         gameInfo.white = StrSave(white);
4367         gameInfo.black = StrSave(black);
4368         timeControl = basetime * 60 * 1000;
4369         timeControl_2 = 0;
4370         timeIncrement = increment * 1000;
4371         movesPerSession = 0;
4372         gameInfo.timeControl = TimeControlTagValue();
4373         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4374   if (appData.debugMode) {
4375     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4376     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4377     setbuf(debugFP, NULL);
4378   }
4379
4380         gameInfo.outOfBook = NULL;
4381
4382         /* Do we have the ratings? */
4383         if (strcmp(player1Name, white) == 0 &&
4384             strcmp(player2Name, black) == 0) {
4385             if (appData.debugMode)
4386               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4387                       player1Rating, player2Rating);
4388             gameInfo.whiteRating = player1Rating;
4389             gameInfo.blackRating = player2Rating;
4390         } else if (strcmp(player2Name, white) == 0 &&
4391                    strcmp(player1Name, black) == 0) {
4392             if (appData.debugMode)
4393               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4394                       player2Rating, player1Rating);
4395             gameInfo.whiteRating = player2Rating;
4396             gameInfo.blackRating = player1Rating;
4397         }
4398         player1Name[0] = player2Name[0] = NULLCHAR;
4399
4400         /* Silence shouts if requested */
4401         if (appData.quietPlay &&
4402             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4403             SendToICS(ics_prefix);
4404             SendToICS("set shout 0\n");
4405         }
4406     }
4407
4408     /* Deal with midgame name changes */
4409     if (!newGame) {
4410         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4411             if (gameInfo.white) free(gameInfo.white);
4412             gameInfo.white = StrSave(white);
4413         }
4414         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4415             if (gameInfo.black) free(gameInfo.black);
4416             gameInfo.black = StrSave(black);
4417         }
4418     }
4419
4420     /* Throw away game result if anything actually changes in examine mode */
4421     if (gameMode == IcsExamining && !newGame) {
4422         gameInfo.result = GameUnfinished;
4423         if (gameInfo.resultDetails != NULL) {
4424             free(gameInfo.resultDetails);
4425             gameInfo.resultDetails = NULL;
4426         }
4427     }
4428
4429     /* In pausing && IcsExamining mode, we ignore boards coming
4430        in if they are in a different variation than we are. */
4431     if (pauseExamInvalid) return;
4432     if (pausing && gameMode == IcsExamining) {
4433         if (moveNum <= pauseExamForwardMostMove) {
4434             pauseExamInvalid = TRUE;
4435             forwardMostMove = pauseExamForwardMostMove;
4436             return;
4437         }
4438     }
4439
4440   if (appData.debugMode) {
4441     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4442   }
4443     /* Parse the board */
4444     for (k = 0; k < ranks; k++) {
4445       for (j = 0; j < files; j++)
4446         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4447       if(gameInfo.holdingsWidth > 1) {
4448            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4449            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4450       }
4451     }
4452     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4453       board[5][BOARD_RGHT+1] = WhiteAngel;
4454       board[6][BOARD_RGHT+1] = WhiteMarshall;
4455       board[1][0] = BlackMarshall;
4456       board[2][0] = BlackAngel;
4457       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4458     }
4459     CopyBoard(boards[moveNum], board);
4460     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4461     if (moveNum == 0) {
4462         startedFromSetupPosition =
4463           !CompareBoards(board, initialPosition);
4464         if(startedFromSetupPosition)
4465             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4466     }
4467
4468     /* [HGM] Set castling rights. Take the outermost Rooks,
4469        to make it also work for FRC opening positions. Note that board12
4470        is really defective for later FRC positions, as it has no way to
4471        indicate which Rook can castle if they are on the same side of King.
4472        For the initial position we grant rights to the outermost Rooks,
4473        and remember thos rights, and we then copy them on positions
4474        later in an FRC game. This means WB might not recognize castlings with
4475        Rooks that have moved back to their original position as illegal,
4476        but in ICS mode that is not its job anyway.
4477     */
4478     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4479     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4480
4481         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4482             if(board[0][i] == WhiteRook) j = i;
4483         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4484         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4485             if(board[0][i] == WhiteRook) j = i;
4486         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4487         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4488             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4489         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4490         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4491             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4492         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4493
4494         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4495         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4496         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4497             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4498         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4499             if(board[BOARD_HEIGHT-1][k] == bKing)
4500                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4501         if(gameInfo.variant == VariantTwoKings) {
4502             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4503             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4504             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4505         }
4506     } else { int r;
4507         r = boards[moveNum][CASTLING][0] = initialRights[0];
4508         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4509         r = boards[moveNum][CASTLING][1] = initialRights[1];
4510         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4511         r = boards[moveNum][CASTLING][3] = initialRights[3];
4512         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4513         r = boards[moveNum][CASTLING][4] = initialRights[4];
4514         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4515         /* wildcastle kludge: always assume King has rights */
4516         r = boards[moveNum][CASTLING][2] = initialRights[2];
4517         r = boards[moveNum][CASTLING][5] = initialRights[5];
4518     }
4519     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4520     boards[moveNum][EP_STATUS] = EP_NONE;
4521     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4522     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4523     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4524
4525
4526     if (ics_getting_history == H_GOT_REQ_HEADER ||
4527         ics_getting_history == H_GOT_UNREQ_HEADER) {
4528         /* This was an initial position from a move list, not
4529            the current position */
4530         return;
4531     }
4532
4533     /* Update currentMove and known move number limits */
4534     newMove = newGame || moveNum > forwardMostMove;
4535
4536     if (newGame) {
4537         forwardMostMove = backwardMostMove = currentMove = moveNum;
4538         if (gameMode == IcsExamining && moveNum == 0) {
4539           /* Workaround for ICS limitation: we are not told the wild
4540              type when starting to examine a game.  But if we ask for
4541              the move list, the move list header will tell us */
4542             ics_getting_history = H_REQUESTED;
4543             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4544             SendToICS(str);
4545         }
4546     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4547                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4548 #if ZIPPY
4549         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4550         /* [HGM] applied this also to an engine that is silently watching        */
4551         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4552             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4553             gameInfo.variant == currentlyInitializedVariant) {
4554           takeback = forwardMostMove - moveNum;
4555           for (i = 0; i < takeback; i++) {
4556             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4557             SendToProgram("undo\n", &first);
4558           }
4559         }
4560 #endif
4561
4562         forwardMostMove = moveNum;
4563         if (!pausing || currentMove > forwardMostMove)
4564           currentMove = forwardMostMove;
4565     } else {
4566         /* New part of history that is not contiguous with old part */
4567         if (pausing && gameMode == IcsExamining) {
4568             pauseExamInvalid = TRUE;
4569             forwardMostMove = pauseExamForwardMostMove;
4570             return;
4571         }
4572         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4573 #if ZIPPY
4574             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4575                 // [HGM] when we will receive the move list we now request, it will be
4576                 // fed to the engine from the first move on. So if the engine is not
4577                 // in the initial position now, bring it there.
4578                 InitChessProgram(&first, 0);
4579             }
4580 #endif
4581             ics_getting_history = H_REQUESTED;
4582             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4583             SendToICS(str);
4584         }
4585         forwardMostMove = backwardMostMove = currentMove = moveNum;
4586     }
4587
4588     /* Update the clocks */
4589     if (strchr(elapsed_time, '.')) {
4590       /* Time is in ms */
4591       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4592       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4593     } else {
4594       /* Time is in seconds */
4595       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4596       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4597     }
4598
4599
4600 #if ZIPPY
4601     if (appData.zippyPlay && newGame &&
4602         gameMode != IcsObserving && gameMode != IcsIdle &&
4603         gameMode != IcsExamining)
4604       ZippyFirstBoard(moveNum, basetime, increment);
4605 #endif
4606
4607     /* Put the move on the move list, first converting
4608        to canonical algebraic form. */
4609     if (moveNum > 0) {
4610   if (appData.debugMode) {
4611     if (appData.debugMode) { int f = forwardMostMove;
4612         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4613                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4614                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4615     }
4616     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4617     fprintf(debugFP, "moveNum = %d\n", moveNum);
4618     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4619     setbuf(debugFP, NULL);
4620   }
4621         if (moveNum <= backwardMostMove) {
4622             /* We don't know what the board looked like before
4623                this move.  Punt. */
4624           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4625             strcat(parseList[moveNum - 1], " ");
4626             strcat(parseList[moveNum - 1], elapsed_time);
4627             moveList[moveNum - 1][0] = NULLCHAR;
4628         } else if (strcmp(move_str, "none") == 0) {
4629             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4630             /* Again, we don't know what the board looked like;
4631                this is really the start of the game. */
4632             parseList[moveNum - 1][0] = NULLCHAR;
4633             moveList[moveNum - 1][0] = NULLCHAR;
4634             backwardMostMove = moveNum;
4635             startedFromSetupPosition = TRUE;
4636             fromX = fromY = toX = toY = -1;
4637         } else {
4638           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4639           //                 So we parse the long-algebraic move string in stead of the SAN move
4640           int valid; char buf[MSG_SIZ], *prom;
4641
4642           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4643                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4644           // str looks something like "Q/a1-a2"; kill the slash
4645           if(str[1] == '/')
4646             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4647           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4648           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4649                 strcat(buf, prom); // long move lacks promo specification!
4650           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4651                 if(appData.debugMode)
4652                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4653                 safeStrCpy(move_str, buf, MSG_SIZ);
4654           }
4655           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4656                                 &fromX, &fromY, &toX, &toY, &promoChar)
4657                || ParseOneMove(buf, moveNum - 1, &moveType,
4658                                 &fromX, &fromY, &toX, &toY, &promoChar);
4659           // end of long SAN patch
4660           if (valid) {
4661             (void) CoordsToAlgebraic(boards[moveNum - 1],
4662                                      PosFlags(moveNum - 1),
4663                                      fromY, fromX, toY, toX, promoChar,
4664                                      parseList[moveNum-1]);
4665             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4666               case MT_NONE:
4667               case MT_STALEMATE:
4668               default:
4669                 break;
4670               case MT_CHECK:
4671                 if(gameInfo.variant != VariantShogi)
4672                     strcat(parseList[moveNum - 1], "+");
4673                 break;
4674               case MT_CHECKMATE:
4675               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4676                 strcat(parseList[moveNum - 1], "#");
4677                 break;
4678             }
4679             strcat(parseList[moveNum - 1], " ");
4680             strcat(parseList[moveNum - 1], elapsed_time);
4681             /* currentMoveString is set as a side-effect of ParseOneMove */
4682             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4683             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4684             strcat(moveList[moveNum - 1], "\n");
4685
4686             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4687                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4688               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4689                 ChessSquare old, new = boards[moveNum][k][j];
4690                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4691                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4692                   if(old == new) continue;
4693                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4694                   else if(new == WhiteWazir || new == BlackWazir) {
4695                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4696                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4697                       else boards[moveNum][k][j] = old; // preserve type of Gold
4698                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4699                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4700               }
4701           } else {
4702             /* Move from ICS was illegal!?  Punt. */
4703             if (appData.debugMode) {
4704               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4705               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4706             }
4707             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4708             strcat(parseList[moveNum - 1], " ");
4709             strcat(parseList[moveNum - 1], elapsed_time);
4710             moveList[moveNum - 1][0] = NULLCHAR;
4711             fromX = fromY = toX = toY = -1;
4712           }
4713         }
4714   if (appData.debugMode) {
4715     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4716     setbuf(debugFP, NULL);
4717   }
4718
4719 #if ZIPPY
4720         /* Send move to chess program (BEFORE animating it). */
4721         if (appData.zippyPlay && !newGame && newMove &&
4722            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4723
4724             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4725                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4726                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4727                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4728                             move_str);
4729                     DisplayError(str, 0);
4730                 } else {
4731                     if (first.sendTime) {
4732                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4733                     }
4734                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4735                     if (firstMove && !bookHit) {
4736                         firstMove = FALSE;
4737                         if (first.useColors) {
4738                           SendToProgram(gameMode == IcsPlayingWhite ?
4739                                         "white\ngo\n" :
4740                                         "black\ngo\n", &first);
4741                         } else {
4742                           SendToProgram("go\n", &first);
4743                         }
4744                         first.maybeThinking = TRUE;
4745                     }
4746                 }
4747             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4748               if (moveList[moveNum - 1][0] == NULLCHAR) {
4749                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4750                 DisplayError(str, 0);
4751               } else {
4752                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4753                 SendMoveToProgram(moveNum - 1, &first);
4754               }
4755             }
4756         }
4757 #endif
4758     }
4759
4760     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4761         /* If move comes from a remote source, animate it.  If it
4762            isn't remote, it will have already been animated. */
4763         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4764             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4765         }
4766         if (!pausing && appData.highlightLastMove) {
4767             SetHighlights(fromX, fromY, toX, toY);
4768         }
4769     }
4770
4771     /* Start the clocks */
4772     whiteFlag = blackFlag = FALSE;
4773     appData.clockMode = !(basetime == 0 && increment == 0);
4774     if (ticking == 0) {
4775       ics_clock_paused = TRUE;
4776       StopClocks();
4777     } else if (ticking == 1) {
4778       ics_clock_paused = FALSE;
4779     }
4780     if (gameMode == IcsIdle ||
4781         relation == RELATION_OBSERVING_STATIC ||
4782         relation == RELATION_EXAMINING ||
4783         ics_clock_paused)
4784       DisplayBothClocks();
4785     else
4786       StartClocks();
4787
4788     /* Display opponents and material strengths */
4789     if (gameInfo.variant != VariantBughouse &&
4790         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4791         if (tinyLayout || smallLayout) {
4792             if(gameInfo.variant == VariantNormal)
4793               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4794                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4795                     basetime, increment);
4796             else
4797               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4798                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4799                     basetime, increment, (int) gameInfo.variant);
4800         } else {
4801             if(gameInfo.variant == VariantNormal)
4802               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4803                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4804                     basetime, increment);
4805             else
4806               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4807                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4808                     basetime, increment, VariantName(gameInfo.variant));
4809         }
4810         DisplayTitle(str);
4811   if (appData.debugMode) {
4812     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4813   }
4814     }
4815
4816
4817     /* Display the board */
4818     if (!pausing && !appData.noGUI) {
4819
4820       if (appData.premove)
4821           if (!gotPremove ||
4822              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4823              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4824               ClearPremoveHighlights();
4825
4826       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4827         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4828       DrawPosition(j, boards[currentMove]);
4829
4830       DisplayMove(moveNum - 1);
4831       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4832             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4833               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4834         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4835       }
4836     }
4837
4838     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4839 #if ZIPPY
4840     if(bookHit) { // [HGM] book: simulate book reply
4841         static char bookMove[MSG_SIZ]; // a bit generous?
4842
4843         programStats.nodes = programStats.depth = programStats.time =
4844         programStats.score = programStats.got_only_move = 0;
4845         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4846
4847         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4848         strcat(bookMove, bookHit);
4849         HandleMachineMove(bookMove, &first);
4850     }
4851 #endif
4852 }
4853
4854 void
4855 GetMoveListEvent ()
4856 {
4857     char buf[MSG_SIZ];
4858     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4859         ics_getting_history = H_REQUESTED;
4860         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4861         SendToICS(buf);
4862     }
4863 }
4864
4865 void
4866 AnalysisPeriodicEvent (int force)
4867 {
4868     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4869          && !force) || !appData.periodicUpdates)
4870       return;
4871
4872     /* Send . command to Crafty to collect stats */
4873     SendToProgram(".\n", &first);
4874
4875     /* Don't send another until we get a response (this makes
4876        us stop sending to old Crafty's which don't understand
4877        the "." command (sending illegal cmds resets node count & time,
4878        which looks bad)) */
4879     programStats.ok_to_send = 0;
4880 }
4881
4882 void
4883 ics_update_width (int new_width)
4884 {
4885         ics_printf("set width %d\n", new_width);
4886 }
4887
4888 void
4889 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4890 {
4891     char buf[MSG_SIZ];
4892
4893     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4894         // null move in variant where engine does not understand it (for analysis purposes)
4895         SendBoard(cps, moveNum + 1); // send position after move in stead.
4896         return;
4897     }
4898     if (cps->useUsermove) {
4899       SendToProgram("usermove ", cps);
4900     }
4901     if (cps->useSAN) {
4902       char *space;
4903       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4904         int len = space - parseList[moveNum];
4905         memcpy(buf, parseList[moveNum], len);
4906         buf[len++] = '\n';
4907         buf[len] = NULLCHAR;
4908       } else {
4909         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4910       }
4911       SendToProgram(buf, cps);
4912     } else {
4913       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4914         AlphaRank(moveList[moveNum], 4);
4915         SendToProgram(moveList[moveNum], cps);
4916         AlphaRank(moveList[moveNum], 4); // and back
4917       } else
4918       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4919        * the engine. It would be nice to have a better way to identify castle
4920        * moves here. */
4921       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4922                                                                          && cps->useOOCastle) {
4923         int fromX = moveList[moveNum][0] - AAA;
4924         int fromY = moveList[moveNum][1] - ONE;
4925         int toX = moveList[moveNum][2] - AAA;
4926         int toY = moveList[moveNum][3] - ONE;
4927         if((boards[moveNum][fromY][fromX] == WhiteKing
4928             && boards[moveNum][toY][toX] == WhiteRook)
4929            || (boards[moveNum][fromY][fromX] == BlackKing
4930                && boards[moveNum][toY][toX] == BlackRook)) {
4931           if(toX > fromX) SendToProgram("O-O\n", cps);
4932           else SendToProgram("O-O-O\n", cps);
4933         }
4934         else SendToProgram(moveList[moveNum], cps);
4935       } else
4936       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4937         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4938           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4939           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4940                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4941         } else
4942           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4943                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4944         SendToProgram(buf, cps);
4945       }
4946       else SendToProgram(moveList[moveNum], cps);
4947       /* End of additions by Tord */
4948     }
4949
4950     /* [HGM] setting up the opening has brought engine in force mode! */
4951     /*       Send 'go' if we are in a mode where machine should play. */
4952     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4953         (gameMode == TwoMachinesPlay   ||
4954 #if ZIPPY
4955          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4956 #endif
4957          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4958         SendToProgram("go\n", cps);
4959   if (appData.debugMode) {
4960     fprintf(debugFP, "(extra)\n");
4961   }
4962     }
4963     setboardSpoiledMachineBlack = 0;
4964 }
4965
4966 void
4967 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4968 {
4969     char user_move[MSG_SIZ];
4970     char suffix[4];
4971
4972     if(gameInfo.variant == VariantSChess && promoChar) {
4973         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4974         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4975     } else suffix[0] = NULLCHAR;
4976
4977     switch (moveType) {
4978       default:
4979         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4980                 (int)moveType, fromX, fromY, toX, toY);
4981         DisplayError(user_move + strlen("say "), 0);
4982         break;
4983       case WhiteKingSideCastle:
4984       case BlackKingSideCastle:
4985       case WhiteQueenSideCastleWild:
4986       case BlackQueenSideCastleWild:
4987       /* PUSH Fabien */
4988       case WhiteHSideCastleFR:
4989       case BlackHSideCastleFR:
4990       /* POP Fabien */
4991         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4992         break;
4993       case WhiteQueenSideCastle:
4994       case BlackQueenSideCastle:
4995       case WhiteKingSideCastleWild:
4996       case BlackKingSideCastleWild:
4997       /* PUSH Fabien */
4998       case WhiteASideCastleFR:
4999       case BlackASideCastleFR:
5000       /* POP Fabien */
5001         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5002         break;
5003       case WhiteNonPromotion:
5004       case BlackNonPromotion:
5005         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5006         break;
5007       case WhitePromotion:
5008       case BlackPromotion:
5009         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5010           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5011                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5012                 PieceToChar(WhiteFerz));
5013         else if(gameInfo.variant == VariantGreat)
5014           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5015                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5016                 PieceToChar(WhiteMan));
5017         else
5018           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5019                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5020                 promoChar);
5021         break;
5022       case WhiteDrop:
5023       case BlackDrop:
5024       drop:
5025         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5026                  ToUpper(PieceToChar((ChessSquare) fromX)),
5027                  AAA + toX, ONE + toY);
5028         break;
5029       case IllegalMove:  /* could be a variant we don't quite understand */
5030         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5031       case NormalMove:
5032       case WhiteCapturesEnPassant:
5033       case BlackCapturesEnPassant:
5034         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5035                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5036         break;
5037     }
5038     SendToICS(user_move);
5039     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5040         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5041 }
5042
5043 void
5044 UploadGameEvent ()
5045 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5046     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5047     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5048     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5049       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5050       return;
5051     }
5052     if(gameMode != IcsExamining) { // is this ever not the case?
5053         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5054
5055         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5056           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5057         } else { // on FICS we must first go to general examine mode
5058           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5059         }
5060         if(gameInfo.variant != VariantNormal) {
5061             // try figure out wild number, as xboard names are not always valid on ICS
5062             for(i=1; i<=36; i++) {
5063               snprintf(buf, MSG_SIZ, "wild/%d", i);
5064                 if(StringToVariant(buf) == gameInfo.variant) break;
5065             }
5066             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5067             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5068             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5069         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5070         SendToICS(ics_prefix);
5071         SendToICS(buf);
5072         if(startedFromSetupPosition || backwardMostMove != 0) {
5073           fen = PositionToFEN(backwardMostMove, NULL);
5074           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5075             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5076             SendToICS(buf);
5077           } else { // FICS: everything has to set by separate bsetup commands
5078             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5079             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5080             SendToICS(buf);
5081             if(!WhiteOnMove(backwardMostMove)) {
5082                 SendToICS("bsetup tomove black\n");
5083             }
5084             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5085             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5086             SendToICS(buf);
5087             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5088             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5089             SendToICS(buf);
5090             i = boards[backwardMostMove][EP_STATUS];
5091             if(i >= 0) { // set e.p.
5092               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5093                 SendToICS(buf);
5094             }
5095             bsetup++;
5096           }
5097         }
5098       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5099             SendToICS("bsetup done\n"); // switch to normal examining.
5100     }
5101     for(i = backwardMostMove; i<last; i++) {
5102         char buf[20];
5103         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5104         SendToICS(buf);
5105     }
5106     SendToICS(ics_prefix);
5107     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5108 }
5109
5110 void
5111 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5112 {
5113     if (rf == DROP_RANK) {
5114       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5115       sprintf(move, "%c@%c%c\n",
5116                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5117     } else {
5118         if (promoChar == 'x' || promoChar == NULLCHAR) {
5119           sprintf(move, "%c%c%c%c\n",
5120                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5121         } else {
5122             sprintf(move, "%c%c%c%c%c\n",
5123                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5124         }
5125     }
5126 }
5127
5128 void
5129 ProcessICSInitScript (FILE *f)
5130 {
5131     char buf[MSG_SIZ];
5132
5133     while (fgets(buf, MSG_SIZ, f)) {
5134         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5135     }
5136
5137     fclose(f);
5138 }
5139
5140
5141 static int lastX, lastY, selectFlag, dragging;
5142
5143 void
5144 Sweep (int step)
5145 {
5146     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5147     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5148     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5149     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5150     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5151     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5152     do {
5153         promoSweep -= step;
5154         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5155         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5156         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5157         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5158         if(!step) step = -1;
5159     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5160             appData.testLegality && (promoSweep == king ||
5161             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5162     ChangeDragPiece(promoSweep);
5163 }
5164
5165 int
5166 PromoScroll (int x, int y)
5167 {
5168   int step = 0;
5169
5170   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5171   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5172   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5173   if(!step) return FALSE;
5174   lastX = x; lastY = y;
5175   if((promoSweep < BlackPawn) == flipView) step = -step;
5176   if(step > 0) selectFlag = 1;
5177   if(!selectFlag) Sweep(step);
5178   return FALSE;
5179 }
5180
5181 void
5182 NextPiece (int step)
5183 {
5184     ChessSquare piece = boards[currentMove][toY][toX];
5185     do {
5186         pieceSweep -= step;
5187         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5188         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5189         if(!step) step = -1;
5190     } while(PieceToChar(pieceSweep) == '.');
5191     boards[currentMove][toY][toX] = pieceSweep;
5192     DrawPosition(FALSE, boards[currentMove]);
5193     boards[currentMove][toY][toX] = piece;
5194 }
5195 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5196 void
5197 AlphaRank (char *move, int n)
5198 {
5199 //    char *p = move, c; int x, y;
5200
5201     if (appData.debugMode) {
5202         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5203     }
5204
5205     if(move[1]=='*' &&
5206        move[2]>='0' && move[2]<='9' &&
5207        move[3]>='a' && move[3]<='x'    ) {
5208         move[1] = '@';
5209         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5210         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5211     } else
5212     if(move[0]>='0' && move[0]<='9' &&
5213        move[1]>='a' && move[1]<='x' &&
5214        move[2]>='0' && move[2]<='9' &&
5215        move[3]>='a' && move[3]<='x'    ) {
5216         /* input move, Shogi -> normal */
5217         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5218         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5219         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5220         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5221     } else
5222     if(move[1]=='@' &&
5223        move[3]>='0' && move[3]<='9' &&
5224        move[2]>='a' && move[2]<='x'    ) {
5225         move[1] = '*';
5226         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5227         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5228     } else
5229     if(
5230        move[0]>='a' && move[0]<='x' &&
5231        move[3]>='0' && move[3]<='9' &&
5232        move[2]>='a' && move[2]<='x'    ) {
5233          /* output move, normal -> Shogi */
5234         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5235         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5236         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5237         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5238         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5239     }
5240     if (appData.debugMode) {
5241         fprintf(debugFP, "   out = '%s'\n", move);
5242     }
5243 }
5244
5245 char yy_textstr[8000];
5246
5247 /* Parser for moves from gnuchess, ICS, or user typein box */
5248 Boolean
5249 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5250 {
5251     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5252
5253     switch (*moveType) {
5254       case WhitePromotion:
5255       case BlackPromotion:
5256       case WhiteNonPromotion:
5257       case BlackNonPromotion:
5258       case NormalMove:
5259       case WhiteCapturesEnPassant:
5260       case BlackCapturesEnPassant:
5261       case WhiteKingSideCastle:
5262       case WhiteQueenSideCastle:
5263       case BlackKingSideCastle:
5264       case BlackQueenSideCastle:
5265       case WhiteKingSideCastleWild:
5266       case WhiteQueenSideCastleWild:
5267       case BlackKingSideCastleWild:
5268       case BlackQueenSideCastleWild:
5269       /* Code added by Tord: */
5270       case WhiteHSideCastleFR:
5271       case WhiteASideCastleFR:
5272       case BlackHSideCastleFR:
5273       case BlackASideCastleFR:
5274       /* End of code added by Tord */
5275       case IllegalMove:         /* bug or odd chess variant */
5276         *fromX = currentMoveString[0] - AAA;
5277         *fromY = currentMoveString[1] - ONE;
5278         *toX = currentMoveString[2] - AAA;
5279         *toY = currentMoveString[3] - ONE;
5280         *promoChar = currentMoveString[4];
5281         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5282             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5283     if (appData.debugMode) {
5284         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5285     }
5286             *fromX = *fromY = *toX = *toY = 0;
5287             return FALSE;
5288         }
5289         if (appData.testLegality) {
5290           return (*moveType != IllegalMove);
5291         } else {
5292           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5293                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5294         }
5295
5296       case WhiteDrop:
5297       case BlackDrop:
5298         *fromX = *moveType == WhiteDrop ?
5299           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5300           (int) CharToPiece(ToLower(currentMoveString[0]));
5301         *fromY = DROP_RANK;
5302         *toX = currentMoveString[2] - AAA;
5303         *toY = currentMoveString[3] - ONE;
5304         *promoChar = NULLCHAR;
5305         return TRUE;
5306
5307       case AmbiguousMove:
5308       case ImpossibleMove:
5309       case EndOfFile:
5310       case ElapsedTime:
5311       case Comment:
5312       case PGNTag:
5313       case NAG:
5314       case WhiteWins:
5315       case BlackWins:
5316       case GameIsDrawn:
5317       default:
5318     if (appData.debugMode) {
5319         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5320     }
5321         /* bug? */
5322         *fromX = *fromY = *toX = *toY = 0;
5323         *promoChar = NULLCHAR;
5324         return FALSE;
5325     }
5326 }
5327
5328 Boolean pushed = FALSE;
5329 char *lastParseAttempt;
5330
5331 void
5332 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5333 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5334   int fromX, fromY, toX, toY; char promoChar;
5335   ChessMove moveType;
5336   Boolean valid;
5337   int nr = 0;
5338
5339   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5340     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5341     pushed = TRUE;
5342   }
5343   endPV = forwardMostMove;
5344   do {
5345     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5346     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5347     lastParseAttempt = pv;
5348     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5349     if(!valid && nr == 0 &&
5350        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5351         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5352         // Hande case where played move is different from leading PV move
5353         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5354         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5355         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5356         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5357           endPV += 2; // if position different, keep this
5358           moveList[endPV-1][0] = fromX + AAA;
5359           moveList[endPV-1][1] = fromY + ONE;
5360           moveList[endPV-1][2] = toX + AAA;
5361           moveList[endPV-1][3] = toY + ONE;
5362           parseList[endPV-1][0] = NULLCHAR;
5363           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5364         }
5365       }
5366     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5367     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5368     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5369     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5370         valid++; // allow comments in PV
5371         continue;
5372     }
5373     nr++;
5374     if(endPV+1 > framePtr) break; // no space, truncate
5375     if(!valid) break;
5376     endPV++;
5377     CopyBoard(boards[endPV], boards[endPV-1]);
5378     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5379     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5380     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5381     CoordsToAlgebraic(boards[endPV - 1],
5382                              PosFlags(endPV - 1),
5383                              fromY, fromX, toY, toX, promoChar,
5384                              parseList[endPV - 1]);
5385   } while(valid);
5386   if(atEnd == 2) return; // used hidden, for PV conversion
5387   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5388   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5389   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5390                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5391   DrawPosition(TRUE, boards[currentMove]);
5392 }
5393
5394 int
5395 MultiPV (ChessProgramState *cps)
5396 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5397         int i;
5398         for(i=0; i<cps->nrOptions; i++)
5399             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5400                 return i;
5401         return -1;
5402 }
5403
5404 Boolean
5405 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5406 {
5407         int startPV, multi, lineStart, origIndex = index;
5408         char *p, buf2[MSG_SIZ];
5409
5410         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5411         lastX = x; lastY = y;
5412         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5413         lineStart = startPV = index;
5414         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5415         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5416         index = startPV;
5417         do{ while(buf[index] && buf[index] != '\n') index++;
5418         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5419         buf[index] = 0;
5420         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5421                 int n = first.option[multi].value;
5422                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5423                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5424                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5425                 first.option[multi].value = n;
5426                 *start = *end = 0;
5427                 return FALSE;
5428         }
5429         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5430         *start = startPV; *end = index-1;
5431         return TRUE;
5432 }
5433
5434 char *
5435 PvToSAN (char *pv)
5436 {
5437         static char buf[10*MSG_SIZ];
5438         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5439         *buf = NULLCHAR;
5440         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5441         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5442         for(i = forwardMostMove; i<endPV; i++){
5443             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5444             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5445             k += strlen(buf+k);
5446         }
5447         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5448         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5449         endPV = savedEnd;
5450         return buf;
5451 }
5452
5453 Boolean
5454 LoadPV (int x, int y)
5455 { // called on right mouse click to load PV
5456   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5457   lastX = x; lastY = y;
5458   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5459   return TRUE;
5460 }
5461
5462 void
5463 UnLoadPV ()
5464 {
5465   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5466   if(endPV < 0) return;
5467   if(appData.autoCopyPV) CopyFENToClipboard();
5468   endPV = -1;
5469   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5470         Boolean saveAnimate = appData.animate;
5471         if(pushed) {
5472             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5473                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5474             } else storedGames--; // abandon shelved tail of original game
5475         }
5476         pushed = FALSE;
5477         forwardMostMove = currentMove;
5478         currentMove = oldFMM;
5479         appData.animate = FALSE;
5480         ToNrEvent(forwardMostMove);
5481         appData.animate = saveAnimate;
5482   }
5483   currentMove = forwardMostMove;
5484   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5485   ClearPremoveHighlights();
5486   DrawPosition(TRUE, boards[currentMove]);
5487 }
5488
5489 void
5490 MovePV (int x, int y, int h)
5491 { // step through PV based on mouse coordinates (called on mouse move)
5492   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5493
5494   // we must somehow check if right button is still down (might be released off board!)
5495   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5496   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5497   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5498   if(!step) return;
5499   lastX = x; lastY = y;
5500
5501   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5502   if(endPV < 0) return;
5503   if(y < margin) step = 1; else
5504   if(y > h - margin) step = -1;
5505   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5506   currentMove += step;
5507   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5508   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5509                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5510   DrawPosition(FALSE, boards[currentMove]);
5511 }
5512
5513
5514 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5515 // All positions will have equal probability, but the current method will not provide a unique
5516 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5517 #define DARK 1
5518 #define LITE 2
5519 #define ANY 3
5520
5521 int squaresLeft[4];
5522 int piecesLeft[(int)BlackPawn];
5523 int seed, nrOfShuffles;
5524
5525 void
5526 GetPositionNumber ()
5527 {       // sets global variable seed
5528         int i;
5529
5530         seed = appData.defaultFrcPosition;
5531         if(seed < 0) { // randomize based on time for negative FRC position numbers
5532                 for(i=0; i<50; i++) seed += random();
5533                 seed = random() ^ random() >> 8 ^ random() << 8;
5534                 if(seed<0) seed = -seed;
5535         }
5536 }
5537
5538 int
5539 put (Board board, int pieceType, int rank, int n, int shade)
5540 // put the piece on the (n-1)-th empty squares of the given shade
5541 {
5542         int i;
5543
5544         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5545                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5546                         board[rank][i] = (ChessSquare) pieceType;
5547                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5548                         squaresLeft[ANY]--;
5549                         piecesLeft[pieceType]--;
5550                         return i;
5551                 }
5552         }
5553         return -1;
5554 }
5555
5556
5557 void
5558 AddOnePiece (Board board, int pieceType, int rank, int shade)
5559 // calculate where the next piece goes, (any empty square), and put it there
5560 {
5561         int i;
5562
5563         i = seed % squaresLeft[shade];
5564         nrOfShuffles *= squaresLeft[shade];
5565         seed /= squaresLeft[shade];
5566         put(board, pieceType, rank, i, shade);
5567 }
5568
5569 void
5570 AddTwoPieces (Board board, int pieceType, int rank)
5571 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5572 {
5573         int i, n=squaresLeft[ANY], j=n-1, k;
5574
5575         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5576         i = seed % k;  // pick one
5577         nrOfShuffles *= k;
5578         seed /= k;
5579         while(i >= j) i -= j--;
5580         j = n - 1 - j; i += j;
5581         put(board, pieceType, rank, j, ANY);
5582         put(board, pieceType, rank, i, ANY);
5583 }
5584
5585 void
5586 SetUpShuffle (Board board, int number)
5587 {
5588         int i, p, first=1;
5589
5590         GetPositionNumber(); nrOfShuffles = 1;
5591
5592         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5593         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5594         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5595
5596         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5597
5598         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5599             p = (int) board[0][i];
5600             if(p < (int) BlackPawn) piecesLeft[p] ++;
5601             board[0][i] = EmptySquare;
5602         }
5603
5604         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5605             // shuffles restricted to allow normal castling put KRR first
5606             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5607                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5608             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5609                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5610             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5611                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5612             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5613                 put(board, WhiteRook, 0, 0, ANY);
5614             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5615         }
5616
5617         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5618             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5619             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5620                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5621                 while(piecesLeft[p] >= 2) {
5622                     AddOnePiece(board, p, 0, LITE);
5623                     AddOnePiece(board, p, 0, DARK);
5624                 }
5625                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5626             }
5627
5628         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5629             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5630             // but we leave King and Rooks for last, to possibly obey FRC restriction
5631             if(p == (int)WhiteRook) continue;
5632             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5633             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5634         }
5635
5636         // now everything is placed, except perhaps King (Unicorn) and Rooks
5637
5638         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5639             // Last King gets castling rights
5640             while(piecesLeft[(int)WhiteUnicorn]) {
5641                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5642                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5643             }
5644
5645             while(piecesLeft[(int)WhiteKing]) {
5646                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5647                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5648             }
5649
5650
5651         } else {
5652             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5653             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5654         }
5655
5656         // Only Rooks can be left; simply place them all
5657         while(piecesLeft[(int)WhiteRook]) {
5658                 i = put(board, WhiteRook, 0, 0, ANY);
5659                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5660                         if(first) {
5661                                 first=0;
5662                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5663                         }
5664                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5665                 }
5666         }
5667         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5668             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5669         }
5670
5671         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5672 }
5673
5674 int
5675 SetCharTable (char *table, const char * map)
5676 /* [HGM] moved here from winboard.c because of its general usefulness */
5677 /*       Basically a safe strcpy that uses the last character as King */
5678 {
5679     int result = FALSE; int NrPieces;
5680
5681     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5682                     && NrPieces >= 12 && !(NrPieces&1)) {
5683         int i; /* [HGM] Accept even length from 12 to 34 */
5684
5685         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5686         for( i=0; i<NrPieces/2-1; i++ ) {
5687             table[i] = map[i];
5688             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5689         }
5690         table[(int) WhiteKing]  = map[NrPieces/2-1];
5691         table[(int) BlackKing]  = map[NrPieces-1];
5692
5693         result = TRUE;
5694     }
5695
5696     return result;
5697 }
5698
5699 void
5700 Prelude (Board board)
5701 {       // [HGM] superchess: random selection of exo-pieces
5702         int i, j, k; ChessSquare p;
5703         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5704
5705         GetPositionNumber(); // use FRC position number
5706
5707         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5708             SetCharTable(pieceToChar, appData.pieceToCharTable);
5709             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5710                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5711         }
5712
5713         j = seed%4;                 seed /= 4;
5714         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5715         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5716         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5717         j = seed%3 + (seed%3 >= j); seed /= 3;
5718         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5719         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5720         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5721         j = seed%3;                 seed /= 3;
5722         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5723         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5724         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5725         j = seed%2 + (seed%2 >= j); seed /= 2;
5726         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5727         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5728         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5729         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5730         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5731         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5732         put(board, exoPieces[0],    0, 0, ANY);
5733         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5734 }
5735
5736 void
5737 InitPosition (int redraw)
5738 {
5739     ChessSquare (* pieces)[BOARD_FILES];
5740     int i, j, pawnRow, overrule,
5741     oldx = gameInfo.boardWidth,
5742     oldy = gameInfo.boardHeight,
5743     oldh = gameInfo.holdingsWidth;
5744     static int oldv;
5745
5746     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5747
5748     /* [AS] Initialize pv info list [HGM] and game status */
5749     {
5750         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5751             pvInfoList[i].depth = 0;
5752             boards[i][EP_STATUS] = EP_NONE;
5753             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5754         }
5755
5756         initialRulePlies = 0; /* 50-move counter start */
5757
5758         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5759         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5760     }
5761
5762
5763     /* [HGM] logic here is completely changed. In stead of full positions */
5764     /* the initialized data only consist of the two backranks. The switch */
5765     /* selects which one we will use, which is than copied to the Board   */
5766     /* initialPosition, which for the rest is initialized by Pawns and    */
5767     /* empty squares. This initial position is then copied to boards[0],  */
5768     /* possibly after shuffling, so that it remains available.            */
5769
5770     gameInfo.holdingsWidth = 0; /* default board sizes */
5771     gameInfo.boardWidth    = 8;
5772     gameInfo.boardHeight   = 8;
5773     gameInfo.holdingsSize  = 0;
5774     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5775     for(i=0; i<BOARD_FILES-2; i++)
5776       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5777     initialPosition[EP_STATUS] = EP_NONE;
5778     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5779     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5780          SetCharTable(pieceNickName, appData.pieceNickNames);
5781     else SetCharTable(pieceNickName, "............");
5782     pieces = FIDEArray;
5783
5784     switch (gameInfo.variant) {
5785     case VariantFischeRandom:
5786       shuffleOpenings = TRUE;
5787     default:
5788       break;
5789     case VariantShatranj:
5790       pieces = ShatranjArray;
5791       nrCastlingRights = 0;
5792       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5793       break;
5794     case VariantMakruk:
5795       pieces = makrukArray;
5796       nrCastlingRights = 0;
5797       startedFromSetupPosition = TRUE;
5798       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5799       break;
5800     case VariantTwoKings:
5801       pieces = twoKingsArray;
5802       break;
5803     case VariantGrand:
5804       pieces = GrandArray;
5805       nrCastlingRights = 0;
5806       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5807       gameInfo.boardWidth = 10;
5808       gameInfo.boardHeight = 10;
5809       gameInfo.holdingsSize = 7;
5810       break;
5811     case VariantCapaRandom:
5812       shuffleOpenings = TRUE;
5813     case VariantCapablanca:
5814       pieces = CapablancaArray;
5815       gameInfo.boardWidth = 10;
5816       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5817       break;
5818     case VariantGothic:
5819       pieces = GothicArray;
5820       gameInfo.boardWidth = 10;
5821       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5822       break;
5823     case VariantSChess:
5824       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5825       gameInfo.holdingsSize = 7;
5826       break;
5827     case VariantJanus:
5828       pieces = JanusArray;
5829       gameInfo.boardWidth = 10;
5830       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5831       nrCastlingRights = 6;
5832         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5833         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5834         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5835         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5836         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5837         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5838       break;
5839     case VariantFalcon:
5840       pieces = FalconArray;
5841       gameInfo.boardWidth = 10;
5842       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5843       break;
5844     case VariantXiangqi:
5845       pieces = XiangqiArray;
5846       gameInfo.boardWidth  = 9;
5847       gameInfo.boardHeight = 10;
5848       nrCastlingRights = 0;
5849       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5850       break;
5851     case VariantShogi:
5852       pieces = ShogiArray;
5853       gameInfo.boardWidth  = 9;
5854       gameInfo.boardHeight = 9;
5855       gameInfo.holdingsSize = 7;
5856       nrCastlingRights = 0;
5857       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5858       break;
5859     case VariantCourier:
5860       pieces = CourierArray;
5861       gameInfo.boardWidth  = 12;
5862       nrCastlingRights = 0;
5863       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5864       break;
5865     case VariantKnightmate:
5866       pieces = KnightmateArray;
5867       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5868       break;
5869     case VariantSpartan:
5870       pieces = SpartanArray;
5871       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5872       break;
5873     case VariantFairy:
5874       pieces = fairyArray;
5875       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5876       break;
5877     case VariantGreat:
5878       pieces = GreatArray;
5879       gameInfo.boardWidth = 10;
5880       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5881       gameInfo.holdingsSize = 8;
5882       break;
5883     case VariantSuper:
5884       pieces = FIDEArray;
5885       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5886       gameInfo.holdingsSize = 8;
5887       startedFromSetupPosition = TRUE;
5888       break;
5889     case VariantCrazyhouse:
5890     case VariantBughouse:
5891       pieces = FIDEArray;
5892       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5893       gameInfo.holdingsSize = 5;
5894       break;
5895     case VariantWildCastle:
5896       pieces = FIDEArray;
5897       /* !!?shuffle with kings guaranteed to be on d or e file */
5898       shuffleOpenings = 1;
5899       break;
5900     case VariantNoCastle:
5901       pieces = FIDEArray;
5902       nrCastlingRights = 0;
5903       /* !!?unconstrained back-rank shuffle */
5904       shuffleOpenings = 1;
5905       break;
5906     }
5907
5908     overrule = 0;
5909     if(appData.NrFiles >= 0) {
5910         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5911         gameInfo.boardWidth = appData.NrFiles;
5912     }
5913     if(appData.NrRanks >= 0) {
5914         gameInfo.boardHeight = appData.NrRanks;
5915     }
5916     if(appData.holdingsSize >= 0) {
5917         i = appData.holdingsSize;
5918         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5919         gameInfo.holdingsSize = i;
5920     }
5921     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5922     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5923         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5924
5925     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5926     if(pawnRow < 1) pawnRow = 1;
5927     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5928
5929     /* User pieceToChar list overrules defaults */
5930     if(appData.pieceToCharTable != NULL)
5931         SetCharTable(pieceToChar, appData.pieceToCharTable);
5932
5933     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5934
5935         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5936             s = (ChessSquare) 0; /* account holding counts in guard band */
5937         for( i=0; i<BOARD_HEIGHT; i++ )
5938             initialPosition[i][j] = s;
5939
5940         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5941         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5942         initialPosition[pawnRow][j] = WhitePawn;
5943         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5944         if(gameInfo.variant == VariantXiangqi) {
5945             if(j&1) {
5946                 initialPosition[pawnRow][j] =
5947                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5948                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5949                    initialPosition[2][j] = WhiteCannon;
5950                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5951                 }
5952             }
5953         }
5954         if(gameInfo.variant == VariantGrand) {
5955             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5956                initialPosition[0][j] = WhiteRook;
5957                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5958             }
5959         }
5960         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5961     }
5962     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5963
5964             j=BOARD_LEFT+1;
5965             initialPosition[1][j] = WhiteBishop;
5966             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5967             j=BOARD_RGHT-2;
5968             initialPosition[1][j] = WhiteRook;
5969             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5970     }
5971
5972     if( nrCastlingRights == -1) {
5973         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5974         /*       This sets default castling rights from none to normal corners   */
5975         /* Variants with other castling rights must set them themselves above    */
5976         nrCastlingRights = 6;
5977
5978         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5979         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5980         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5981         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5982         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5983         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5984      }
5985
5986      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5987      if(gameInfo.variant == VariantGreat) { // promotion commoners
5988         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5989         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5990         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5991         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5992      }
5993      if( gameInfo.variant == VariantSChess ) {
5994       initialPosition[1][0] = BlackMarshall;
5995       initialPosition[2][0] = BlackAngel;
5996       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5997       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5998       initialPosition[1][1] = initialPosition[2][1] = 
5999       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6000      }
6001   if (appData.debugMode) {
6002     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6003   }
6004     if(shuffleOpenings) {
6005         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6006         startedFromSetupPosition = TRUE;
6007     }
6008     if(startedFromPositionFile) {
6009       /* [HGM] loadPos: use PositionFile for every new game */
6010       CopyBoard(initialPosition, filePosition);
6011       for(i=0; i<nrCastlingRights; i++)
6012           initialRights[i] = filePosition[CASTLING][i];
6013       startedFromSetupPosition = TRUE;
6014     }
6015
6016     CopyBoard(boards[0], initialPosition);
6017
6018     if(oldx != gameInfo.boardWidth ||
6019        oldy != gameInfo.boardHeight ||
6020        oldv != gameInfo.variant ||
6021        oldh != gameInfo.holdingsWidth
6022                                          )
6023             InitDrawingSizes(-2 ,0);
6024
6025     oldv = gameInfo.variant;
6026     if (redraw)
6027       DrawPosition(TRUE, boards[currentMove]);
6028 }
6029
6030 void
6031 SendBoard (ChessProgramState *cps, int moveNum)
6032 {
6033     char message[MSG_SIZ];
6034
6035     if (cps->useSetboard) {
6036       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6037       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6038       SendToProgram(message, cps);
6039       free(fen);
6040
6041     } else {
6042       ChessSquare *bp;
6043       int i, j, left=0, right=BOARD_WIDTH;
6044       /* Kludge to set black to move, avoiding the troublesome and now
6045        * deprecated "black" command.
6046        */
6047       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6048         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6049
6050       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6051
6052       SendToProgram("edit\n", cps);
6053       SendToProgram("#\n", cps);
6054       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6055         bp = &boards[moveNum][i][left];
6056         for (j = left; j < right; j++, bp++) {
6057           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6058           if ((int) *bp < (int) BlackPawn) {
6059             if(j == BOARD_RGHT+1)
6060                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6061             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6062             if(message[0] == '+' || message[0] == '~') {
6063               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6064                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6065                         AAA + j, ONE + i);
6066             }
6067             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6068                 message[1] = BOARD_RGHT   - 1 - j + '1';
6069                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6070             }
6071             SendToProgram(message, cps);
6072           }
6073         }
6074       }
6075
6076       SendToProgram("c\n", cps);
6077       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6078         bp = &boards[moveNum][i][left];
6079         for (j = left; j < right; j++, bp++) {
6080           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6081           if (((int) *bp != (int) EmptySquare)
6082               && ((int) *bp >= (int) BlackPawn)) {
6083             if(j == BOARD_LEFT-2)
6084                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6085             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6086                     AAA + j, ONE + i);
6087             if(message[0] == '+' || message[0] == '~') {
6088               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6089                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6090                         AAA + j, ONE + i);
6091             }
6092             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6093                 message[1] = BOARD_RGHT   - 1 - j + '1';
6094                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6095             }
6096             SendToProgram(message, cps);
6097           }
6098         }
6099       }
6100
6101       SendToProgram(".\n", cps);
6102     }
6103     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6104 }
6105
6106 ChessSquare
6107 DefaultPromoChoice (int white)
6108 {
6109     ChessSquare result;
6110     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6111         result = WhiteFerz; // no choice
6112     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6113         result= WhiteKing; // in Suicide Q is the last thing we want
6114     else if(gameInfo.variant == VariantSpartan)
6115         result = white ? WhiteQueen : WhiteAngel;
6116     else result = WhiteQueen;
6117     if(!white) result = WHITE_TO_BLACK result;
6118     return result;
6119 }
6120
6121 static int autoQueen; // [HGM] oneclick
6122
6123 int
6124 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6125 {
6126     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6127     /* [HGM] add Shogi promotions */
6128     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6129     ChessSquare piece;
6130     ChessMove moveType;
6131     Boolean premove;
6132
6133     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6134     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6135
6136     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6137       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6138         return FALSE;
6139
6140     piece = boards[currentMove][fromY][fromX];
6141     if(gameInfo.variant == VariantShogi) {
6142         promotionZoneSize = BOARD_HEIGHT/3;
6143         highestPromotingPiece = (int)WhiteFerz;
6144     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6145         promotionZoneSize = 3;
6146     }
6147
6148     // Treat Lance as Pawn when it is not representing Amazon
6149     if(gameInfo.variant != VariantSuper) {
6150         if(piece == WhiteLance) piece = WhitePawn; else
6151         if(piece == BlackLance) piece = BlackPawn;
6152     }
6153
6154     // next weed out all moves that do not touch the promotion zone at all
6155     if((int)piece >= BlackPawn) {
6156         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6157              return FALSE;
6158         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6159     } else {
6160         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6161            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6162     }
6163
6164     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6165
6166     // weed out mandatory Shogi promotions
6167     if(gameInfo.variant == VariantShogi) {
6168         if(piece >= BlackPawn) {
6169             if(toY == 0 && piece == BlackPawn ||
6170                toY == 0 && piece == BlackQueen ||
6171                toY <= 1 && piece == BlackKnight) {
6172                 *promoChoice = '+';
6173                 return FALSE;
6174             }
6175         } else {
6176             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6177                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6178                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6179                 *promoChoice = '+';
6180                 return FALSE;
6181             }
6182         }
6183     }
6184
6185     // weed out obviously illegal Pawn moves
6186     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6187         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6188         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6189         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6190         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6191         // note we are not allowed to test for valid (non-)capture, due to premove
6192     }
6193
6194     // we either have a choice what to promote to, or (in Shogi) whether to promote
6195     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6196         *promoChoice = PieceToChar(BlackFerz);  // no choice
6197         return FALSE;
6198     }
6199     // no sense asking what we must promote to if it is going to explode...
6200     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6201         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6202         return FALSE;
6203     }
6204     // give caller the default choice even if we will not make it
6205     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6206     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6207     if(        sweepSelect && gameInfo.variant != VariantGreat
6208                            && gameInfo.variant != VariantGrand
6209                            && gameInfo.variant != VariantSuper) return FALSE;
6210     if(autoQueen) return FALSE; // predetermined
6211
6212     // suppress promotion popup on illegal moves that are not premoves
6213     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6214               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6215     if(appData.testLegality && !premove) {
6216         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6217                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6218         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6219             return FALSE;
6220     }
6221
6222     return TRUE;
6223 }
6224
6225 int
6226 InPalace (int row, int column)
6227 {   /* [HGM] for Xiangqi */
6228     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6229          column < (BOARD_WIDTH + 4)/2 &&
6230          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6231     return FALSE;
6232 }
6233
6234 int
6235 PieceForSquare (int x, int y)
6236 {
6237   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6238      return -1;
6239   else
6240      return boards[currentMove][y][x];
6241 }
6242
6243 int
6244 OKToStartUserMove (int x, int y)
6245 {
6246     ChessSquare from_piece;
6247     int white_piece;
6248
6249     if (matchMode) return FALSE;
6250     if (gameMode == EditPosition) return TRUE;
6251
6252     if (x >= 0 && y >= 0)
6253       from_piece = boards[currentMove][y][x];
6254     else
6255       from_piece = EmptySquare;
6256
6257     if (from_piece == EmptySquare) return FALSE;
6258
6259     white_piece = (int)from_piece >= (int)WhitePawn &&
6260       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6261
6262     switch (gameMode) {
6263       case AnalyzeFile:
6264       case TwoMachinesPlay:
6265       case EndOfGame:
6266         return FALSE;
6267
6268       case IcsObserving:
6269       case IcsIdle:
6270         return FALSE;
6271
6272       case MachinePlaysWhite:
6273       case IcsPlayingBlack:
6274         if (appData.zippyPlay) return FALSE;
6275         if (white_piece) {
6276             DisplayMoveError(_("You are playing Black"));
6277             return FALSE;
6278         }
6279         break;
6280
6281       case MachinePlaysBlack:
6282       case IcsPlayingWhite:
6283         if (appData.zippyPlay) return FALSE;
6284         if (!white_piece) {
6285             DisplayMoveError(_("You are playing White"));
6286             return FALSE;
6287         }
6288         break;
6289
6290       case PlayFromGameFile:
6291             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6292       case EditGame:
6293         if (!white_piece && WhiteOnMove(currentMove)) {
6294             DisplayMoveError(_("It is White's turn"));
6295             return FALSE;
6296         }
6297         if (white_piece && !WhiteOnMove(currentMove)) {
6298             DisplayMoveError(_("It is Black's turn"));
6299             return FALSE;
6300         }
6301         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6302             /* Editing correspondence game history */
6303             /* Could disallow this or prompt for confirmation */
6304             cmailOldMove = -1;
6305         }
6306         break;
6307
6308       case BeginningOfGame:
6309         if (appData.icsActive) return FALSE;
6310         if (!appData.noChessProgram) {
6311             if (!white_piece) {
6312                 DisplayMoveError(_("You are playing White"));
6313                 return FALSE;
6314             }
6315         }
6316         break;
6317
6318       case Training:
6319         if (!white_piece && WhiteOnMove(currentMove)) {
6320             DisplayMoveError(_("It is White's turn"));
6321             return FALSE;
6322         }
6323         if (white_piece && !WhiteOnMove(currentMove)) {
6324             DisplayMoveError(_("It is Black's turn"));
6325             return FALSE;
6326         }
6327         break;
6328
6329       default:
6330       case IcsExamining:
6331         break;
6332     }
6333     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6334         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6335         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6336         && gameMode != AnalyzeFile && gameMode != Training) {
6337         DisplayMoveError(_("Displayed position is not current"));
6338         return FALSE;
6339     }
6340     return TRUE;
6341 }
6342
6343 Boolean
6344 OnlyMove (int *x, int *y, Boolean captures) 
6345 {
6346     DisambiguateClosure cl;
6347     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6348     switch(gameMode) {
6349       case MachinePlaysBlack:
6350       case IcsPlayingWhite:
6351       case BeginningOfGame:
6352         if(!WhiteOnMove(currentMove)) return FALSE;
6353         break;
6354       case MachinePlaysWhite:
6355       case IcsPlayingBlack:
6356         if(WhiteOnMove(currentMove)) return FALSE;
6357         break;
6358       case EditGame:
6359         break;
6360       default:
6361         return FALSE;
6362     }
6363     cl.pieceIn = EmptySquare;
6364     cl.rfIn = *y;
6365     cl.ffIn = *x;
6366     cl.rtIn = -1;
6367     cl.ftIn = -1;
6368     cl.promoCharIn = NULLCHAR;
6369     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6370     if( cl.kind == NormalMove ||
6371         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6372         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6373         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6374       fromX = cl.ff;
6375       fromY = cl.rf;
6376       *x = cl.ft;
6377       *y = cl.rt;
6378       return TRUE;
6379     }
6380     if(cl.kind != ImpossibleMove) return FALSE;
6381     cl.pieceIn = EmptySquare;
6382     cl.rfIn = -1;
6383     cl.ffIn = -1;
6384     cl.rtIn = *y;
6385     cl.ftIn = *x;
6386     cl.promoCharIn = NULLCHAR;
6387     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6388     if( cl.kind == NormalMove ||
6389         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6390         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6391         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6392       fromX = cl.ff;
6393       fromY = cl.rf;
6394       *x = cl.ft;
6395       *y = cl.rt;
6396       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6397       return TRUE;
6398     }
6399     return FALSE;
6400 }
6401
6402 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6403 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6404 int lastLoadGameUseList = FALSE;
6405 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6406 ChessMove lastLoadGameStart = EndOfFile;
6407
6408 void
6409 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6410 {
6411     ChessMove moveType;
6412     ChessSquare pdown, pup;
6413
6414     /* Check if the user is playing in turn.  This is complicated because we
6415        let the user "pick up" a piece before it is his turn.  So the piece he
6416        tried to pick up may have been captured by the time he puts it down!
6417        Therefore we use the color the user is supposed to be playing in this
6418        test, not the color of the piece that is currently on the starting
6419        square---except in EditGame mode, where the user is playing both
6420        sides; fortunately there the capture race can't happen.  (It can
6421        now happen in IcsExamining mode, but that's just too bad.  The user
6422        will get a somewhat confusing message in that case.)
6423        */
6424
6425     switch (gameMode) {
6426       case AnalyzeFile:
6427       case TwoMachinesPlay:
6428       case EndOfGame:
6429       case IcsObserving:
6430       case IcsIdle:
6431         /* We switched into a game mode where moves are not accepted,
6432            perhaps while the mouse button was down. */
6433         return;
6434
6435       case MachinePlaysWhite:
6436         /* User is moving for Black */
6437         if (WhiteOnMove(currentMove)) {
6438             DisplayMoveError(_("It is White's turn"));
6439             return;
6440         }
6441         break;
6442
6443       case MachinePlaysBlack:
6444         /* User is moving for White */
6445         if (!WhiteOnMove(currentMove)) {
6446             DisplayMoveError(_("It is Black's turn"));
6447             return;
6448         }
6449         break;
6450
6451       case PlayFromGameFile:
6452             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6453       case EditGame:
6454       case IcsExamining:
6455       case BeginningOfGame:
6456       case AnalyzeMode:
6457       case Training:
6458         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6459         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6460             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6461             /* User is moving for Black */
6462             if (WhiteOnMove(currentMove)) {
6463                 DisplayMoveError(_("It is White's turn"));
6464                 return;
6465             }
6466         } else {
6467             /* User is moving for White */
6468             if (!WhiteOnMove(currentMove)) {
6469                 DisplayMoveError(_("It is Black's turn"));
6470                 return;
6471             }
6472         }
6473         break;
6474
6475       case IcsPlayingBlack:
6476         /* User is moving for Black */
6477         if (WhiteOnMove(currentMove)) {
6478             if (!appData.premove) {
6479                 DisplayMoveError(_("It is White's turn"));
6480             } else if (toX >= 0 && toY >= 0) {
6481                 premoveToX = toX;
6482                 premoveToY = toY;
6483                 premoveFromX = fromX;
6484                 premoveFromY = fromY;
6485                 premovePromoChar = promoChar;
6486                 gotPremove = 1;
6487                 if (appData.debugMode)
6488                     fprintf(debugFP, "Got premove: fromX %d,"
6489                             "fromY %d, toX %d, toY %d\n",
6490                             fromX, fromY, toX, toY);
6491             }
6492             return;
6493         }
6494         break;
6495
6496       case IcsPlayingWhite:
6497         /* User is moving for White */
6498         if (!WhiteOnMove(currentMove)) {
6499             if (!appData.premove) {
6500                 DisplayMoveError(_("It is Black's turn"));
6501             } else if (toX >= 0 && toY >= 0) {
6502                 premoveToX = toX;
6503                 premoveToY = toY;
6504                 premoveFromX = fromX;
6505                 premoveFromY = fromY;
6506                 premovePromoChar = promoChar;
6507                 gotPremove = 1;
6508                 if (appData.debugMode)
6509                     fprintf(debugFP, "Got premove: fromX %d,"
6510                             "fromY %d, toX %d, toY %d\n",
6511                             fromX, fromY, toX, toY);
6512             }
6513             return;
6514         }
6515         break;
6516
6517       default:
6518         break;
6519
6520       case EditPosition:
6521         /* EditPosition, empty square, or different color piece;
6522            click-click move is possible */
6523         if (toX == -2 || toY == -2) {
6524             boards[0][fromY][fromX] = EmptySquare;
6525             DrawPosition(FALSE, boards[currentMove]);
6526             return;
6527         } else if (toX >= 0 && toY >= 0) {
6528             boards[0][toY][toX] = boards[0][fromY][fromX];
6529             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6530                 if(boards[0][fromY][0] != EmptySquare) {
6531                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6532                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6533                 }
6534             } else
6535             if(fromX == BOARD_RGHT+1) {
6536                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6537                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6538                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6539                 }
6540             } else
6541             boards[0][fromY][fromX] = EmptySquare;
6542             DrawPosition(FALSE, boards[currentMove]);
6543             return;
6544         }
6545         return;
6546     }
6547
6548     if(toX < 0 || toY < 0) return;
6549     pdown = boards[currentMove][fromY][fromX];
6550     pup = boards[currentMove][toY][toX];
6551
6552     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6553     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6554          if( pup != EmptySquare ) return;
6555          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6556            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6557                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6558            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6559            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6560            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6561            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6562          fromY = DROP_RANK;
6563     }
6564
6565     /* [HGM] always test for legality, to get promotion info */
6566     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6567                                          fromY, fromX, toY, toX, promoChar);
6568
6569     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6570
6571     /* [HGM] but possibly ignore an IllegalMove result */
6572     if (appData.testLegality) {
6573         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6574             DisplayMoveError(_("Illegal move"));
6575             return;
6576         }
6577     }
6578
6579     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6580 }
6581
6582 /* Common tail of UserMoveEvent and DropMenuEvent */
6583 int
6584 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6585 {
6586     char *bookHit = 0;
6587
6588     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6589         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6590         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6591         if(WhiteOnMove(currentMove)) {
6592             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6593         } else {
6594             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6595         }
6596     }
6597
6598     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6599        move type in caller when we know the move is a legal promotion */
6600     if(moveType == NormalMove && promoChar)
6601         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6602
6603     /* [HGM] <popupFix> The following if has been moved here from
6604        UserMoveEvent(). Because it seemed to belong here (why not allow
6605        piece drops in training games?), and because it can only be
6606        performed after it is known to what we promote. */
6607     if (gameMode == Training) {
6608       /* compare the move played on the board to the next move in the
6609        * game. If they match, display the move and the opponent's response.
6610        * If they don't match, display an error message.
6611        */
6612       int saveAnimate;
6613       Board testBoard;
6614       CopyBoard(testBoard, boards[currentMove]);
6615       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6616
6617       if (CompareBoards(testBoard, boards[currentMove+1])) {
6618         ForwardInner(currentMove+1);
6619
6620         /* Autoplay the opponent's response.
6621          * if appData.animate was TRUE when Training mode was entered,
6622          * the response will be animated.
6623          */
6624         saveAnimate = appData.animate;
6625         appData.animate = animateTraining;
6626         ForwardInner(currentMove+1);
6627         appData.animate = saveAnimate;
6628
6629         /* check for the end of the game */
6630         if (currentMove >= forwardMostMove) {
6631           gameMode = PlayFromGameFile;
6632           ModeHighlight();
6633           SetTrainingModeOff();
6634           DisplayInformation(_("End of game"));
6635         }
6636       } else {
6637         DisplayError(_("Incorrect move"), 0);
6638       }
6639       return 1;
6640     }
6641
6642   /* Ok, now we know that the move is good, so we can kill
6643      the previous line in Analysis Mode */
6644   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6645                                 && currentMove < forwardMostMove) {
6646     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6647     else forwardMostMove = currentMove;
6648   }
6649
6650   /* If we need the chess program but it's dead, restart it */
6651   ResurrectChessProgram();
6652
6653   /* A user move restarts a paused game*/
6654   if (pausing)
6655     PauseEvent();
6656
6657   thinkOutput[0] = NULLCHAR;
6658
6659   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6660
6661   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6662     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6663     return 1;
6664   }
6665
6666   if (gameMode == BeginningOfGame) {
6667     if (appData.noChessProgram) {
6668       gameMode = EditGame;
6669       SetGameInfo();
6670     } else {
6671       char buf[MSG_SIZ];
6672       gameMode = MachinePlaysBlack;
6673       StartClocks();
6674       SetGameInfo();
6675       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6676       DisplayTitle(buf);
6677       if (first.sendName) {
6678         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6679         SendToProgram(buf, &first);
6680       }
6681       StartClocks();
6682     }
6683     ModeHighlight();
6684   }
6685
6686   /* Relay move to ICS or chess engine */
6687   if (appData.icsActive) {
6688     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6689         gameMode == IcsExamining) {
6690       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6691         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6692         SendToICS("draw ");
6693         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6694       }
6695       // also send plain move, in case ICS does not understand atomic claims
6696       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6697       ics_user_moved = 1;
6698     }
6699   } else {
6700     if (first.sendTime && (gameMode == BeginningOfGame ||
6701                            gameMode == MachinePlaysWhite ||
6702                            gameMode == MachinePlaysBlack)) {
6703       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6704     }
6705     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6706          // [HGM] book: if program might be playing, let it use book
6707         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6708         first.maybeThinking = TRUE;
6709     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6710         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6711         SendBoard(&first, currentMove+1);
6712     } else SendMoveToProgram(forwardMostMove-1, &first);
6713     if (currentMove == cmailOldMove + 1) {
6714       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6715     }
6716   }
6717
6718   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6719
6720   switch (gameMode) {
6721   case EditGame:
6722     if(appData.testLegality)
6723     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6724     case MT_NONE:
6725     case MT_CHECK:
6726       break;
6727     case MT_CHECKMATE:
6728     case MT_STAINMATE:
6729       if (WhiteOnMove(currentMove)) {
6730         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6731       } else {
6732         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6733       }
6734       break;
6735     case MT_STALEMATE:
6736       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6737       break;
6738     }
6739     break;
6740
6741   case MachinePlaysBlack:
6742   case MachinePlaysWhite:
6743     /* disable certain menu options while machine is thinking */
6744     SetMachineThinkingEnables();
6745     break;
6746
6747   default:
6748     break;
6749   }
6750
6751   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6752   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6753
6754   if(bookHit) { // [HGM] book: simulate book reply
6755         static char bookMove[MSG_SIZ]; // a bit generous?
6756
6757         programStats.nodes = programStats.depth = programStats.time =
6758         programStats.score = programStats.got_only_move = 0;
6759         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6760
6761         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6762         strcat(bookMove, bookHit);
6763         HandleMachineMove(bookMove, &first);
6764   }
6765   return 1;
6766 }
6767
6768 void
6769 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6770 {
6771     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6772     Markers *m = (Markers *) closure;
6773     if(rf == fromY && ff == fromX)
6774         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6775                          || kind == WhiteCapturesEnPassant
6776                          || kind == BlackCapturesEnPassant);
6777     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6778 }
6779
6780 void
6781 MarkTargetSquares (int clear)
6782 {
6783   int x, y;
6784   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6785      !appData.testLegality || gameMode == EditPosition) return;
6786   if(clear) {
6787     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6788   } else {
6789     int capt = 0;
6790     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6791     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6792       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6793       if(capt)
6794       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6795     }
6796   }
6797   DrawPosition(TRUE, NULL);
6798 }
6799
6800 int
6801 Explode (Board board, int fromX, int fromY, int toX, int toY)
6802 {
6803     if(gameInfo.variant == VariantAtomic &&
6804        (board[toY][toX] != EmptySquare ||                     // capture?
6805         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6806                          board[fromY][fromX] == BlackPawn   )
6807       )) {
6808         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6809         return TRUE;
6810     }
6811     return FALSE;
6812 }
6813
6814 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6815
6816 int
6817 CanPromote (ChessSquare piece, int y)
6818 {
6819         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6820         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6821         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6822            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6823            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6824                                                   gameInfo.variant == VariantMakruk) return FALSE;
6825         return (piece == BlackPawn && y == 1 ||
6826                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6827                 piece == BlackLance && y == 1 ||
6828                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6829 }
6830
6831 void
6832 LeftClick (ClickType clickType, int xPix, int yPix)
6833 {
6834     int x, y;
6835     Boolean saveAnimate;
6836     static int second = 0, promotionChoice = 0, clearFlag = 0;
6837     char promoChoice = NULLCHAR;
6838     ChessSquare piece;
6839
6840     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6841
6842     if (clickType == Press) ErrorPopDown();
6843
6844     x = EventToSquare(xPix, BOARD_WIDTH);
6845     y = EventToSquare(yPix, BOARD_HEIGHT);
6846     if (!flipView && y >= 0) {
6847         y = BOARD_HEIGHT - 1 - y;
6848     }
6849     if (flipView && x >= 0) {
6850         x = BOARD_WIDTH - 1 - x;
6851     }
6852
6853     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6854         defaultPromoChoice = promoSweep;
6855         promoSweep = EmptySquare;   // terminate sweep
6856         promoDefaultAltered = TRUE;
6857         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6858     }
6859
6860     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6861         if(clickType == Release) return; // ignore upclick of click-click destination
6862         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6863         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6864         if(gameInfo.holdingsWidth &&
6865                 (WhiteOnMove(currentMove)
6866                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6867                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6868             // click in right holdings, for determining promotion piece
6869             ChessSquare p = boards[currentMove][y][x];
6870             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6871             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6872             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6873                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6874                 fromX = fromY = -1;
6875                 return;
6876             }
6877         }
6878         DrawPosition(FALSE, boards[currentMove]);
6879         return;
6880     }
6881
6882     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6883     if(clickType == Press
6884             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6885               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6886               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6887         return;
6888
6889     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6890         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6891
6892     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6893         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6894                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6895         defaultPromoChoice = DefaultPromoChoice(side);
6896     }
6897
6898     autoQueen = appData.alwaysPromoteToQueen;
6899
6900     if (fromX == -1) {
6901       int originalY = y;
6902       gatingPiece = EmptySquare;
6903       if (clickType != Press) {
6904         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6905             DragPieceEnd(xPix, yPix); dragging = 0;
6906             DrawPosition(FALSE, NULL);
6907         }
6908         return;
6909       }
6910       fromX = x; fromY = y; toX = toY = -1;
6911       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6912          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6913          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6914             /* First square */
6915             if (OKToStartUserMove(fromX, fromY)) {
6916                 second = 0;
6917                 MarkTargetSquares(0);
6918                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6919                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6920                     promoSweep = defaultPromoChoice;
6921                     selectFlag = 0; lastX = xPix; lastY = yPix;
6922                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6923                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6924                 }
6925                 if (appData.highlightDragging) {
6926                     SetHighlights(fromX, fromY, -1, -1);
6927                 }
6928             } else fromX = fromY = -1;
6929             return;
6930         }
6931     }
6932
6933     /* fromX != -1 */
6934     if (clickType == Press && gameMode != EditPosition) {
6935         ChessSquare fromP;
6936         ChessSquare toP;
6937         int frc;
6938
6939         // ignore off-board to clicks
6940         if(y < 0 || x < 0) return;
6941
6942         /* Check if clicking again on the same color piece */
6943         fromP = boards[currentMove][fromY][fromX];
6944         toP = boards[currentMove][y][x];
6945         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6946         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6947              WhitePawn <= toP && toP <= WhiteKing &&
6948              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6949              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6950             (BlackPawn <= fromP && fromP <= BlackKing &&
6951              BlackPawn <= toP && toP <= BlackKing &&
6952              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6953              !(fromP == BlackKing && toP == BlackRook && frc))) {
6954             /* Clicked again on same color piece -- changed his mind */
6955             second = (x == fromX && y == fromY);
6956             promoDefaultAltered = FALSE;
6957             MarkTargetSquares(1);
6958            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6959             if (appData.highlightDragging) {
6960                 SetHighlights(x, y, -1, -1);
6961             } else {
6962                 ClearHighlights();
6963             }
6964             if (OKToStartUserMove(x, y)) {
6965                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6966                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6967                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6968                  gatingPiece = boards[currentMove][fromY][fromX];
6969                 else gatingPiece = EmptySquare;
6970                 fromX = x;
6971                 fromY = y; dragging = 1;
6972                 MarkTargetSquares(0);
6973                 DragPieceBegin(xPix, yPix, FALSE);
6974                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6975                     promoSweep = defaultPromoChoice;
6976                     selectFlag = 0; lastX = xPix; lastY = yPix;
6977                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6978                 }
6979             }
6980            }
6981            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6982            second = FALSE; 
6983         }
6984         // ignore clicks on holdings
6985         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6986     }
6987
6988     if (clickType == Release && x == fromX && y == fromY) {
6989         DragPieceEnd(xPix, yPix); dragging = 0;
6990         if(clearFlag) {
6991             // a deferred attempt to click-click move an empty square on top of a piece
6992             boards[currentMove][y][x] = EmptySquare;
6993             ClearHighlights();
6994             DrawPosition(FALSE, boards[currentMove]);
6995             fromX = fromY = -1; clearFlag = 0;
6996             return;
6997         }
6998         if (appData.animateDragging) {
6999             /* Undo animation damage if any */
7000             DrawPosition(FALSE, NULL);
7001         }
7002         if (second) {
7003             /* Second up/down in same square; just abort move */
7004             second = 0;
7005             fromX = fromY = -1;
7006             gatingPiece = EmptySquare;
7007             ClearHighlights();
7008             gotPremove = 0;
7009             ClearPremoveHighlights();
7010         } else {
7011             /* First upclick in same square; start click-click mode */
7012             SetHighlights(x, y, -1, -1);
7013         }
7014         return;
7015     }
7016
7017     clearFlag = 0;
7018
7019     /* we now have a different from- and (possibly off-board) to-square */
7020     /* Completed move */
7021     toX = x;
7022     toY = y;
7023     saveAnimate = appData.animate;
7024     MarkTargetSquares(1);
7025     if (clickType == Press) {
7026         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7027             // must be Edit Position mode with empty-square selected
7028             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7029             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7030             return;
7031         }
7032         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7033             ChessSquare piece = boards[currentMove][fromY][fromX];
7034             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7035             promoSweep = defaultPromoChoice;
7036             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7037             selectFlag = 0; lastX = xPix; lastY = yPix;
7038             Sweep(0); // Pawn that is going to promote: preview promotion piece
7039             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7040             DrawPosition(FALSE, boards[currentMove]);
7041             return;
7042         }
7043         /* Finish clickclick move */
7044         if (appData.animate || appData.highlightLastMove) {
7045             SetHighlights(fromX, fromY, toX, toY);
7046         } else {
7047             ClearHighlights();
7048         }
7049     } else {
7050         /* Finish drag move */
7051         if (appData.highlightLastMove) {
7052             SetHighlights(fromX, fromY, toX, toY);
7053         } else {
7054             ClearHighlights();
7055         }
7056         DragPieceEnd(xPix, yPix); dragging = 0;
7057         /* Don't animate move and drag both */
7058         appData.animate = FALSE;
7059     }
7060
7061     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7062     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7063         ChessSquare piece = boards[currentMove][fromY][fromX];
7064         if(gameMode == EditPosition && piece != EmptySquare &&
7065            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7066             int n;
7067
7068             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7069                 n = PieceToNumber(piece - (int)BlackPawn);
7070                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7071                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7072                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7073             } else
7074             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7075                 n = PieceToNumber(piece);
7076                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7077                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7078                 boards[currentMove][n][BOARD_WIDTH-2]++;
7079             }
7080             boards[currentMove][fromY][fromX] = EmptySquare;
7081         }
7082         ClearHighlights();
7083         fromX = fromY = -1;
7084         DrawPosition(TRUE, boards[currentMove]);
7085         return;
7086     }
7087
7088     // off-board moves should not be highlighted
7089     if(x < 0 || y < 0) ClearHighlights();
7090
7091     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7092
7093     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7094         SetHighlights(fromX, fromY, toX, toY);
7095         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7096             // [HGM] super: promotion to captured piece selected from holdings
7097             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7098             promotionChoice = TRUE;
7099             // kludge follows to temporarily execute move on display, without promoting yet
7100             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7101             boards[currentMove][toY][toX] = p;
7102             DrawPosition(FALSE, boards[currentMove]);
7103             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7104             boards[currentMove][toY][toX] = q;
7105             DisplayMessage("Click in holdings to choose piece", "");
7106             return;
7107         }
7108         PromotionPopUp();
7109     } else {
7110         int oldMove = currentMove;
7111         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7112         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7113         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7114         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7115            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7116             DrawPosition(TRUE, boards[currentMove]);
7117         fromX = fromY = -1;
7118     }
7119     appData.animate = saveAnimate;
7120     if (appData.animate || appData.animateDragging) {
7121         /* Undo animation damage if needed */
7122         DrawPosition(FALSE, NULL);
7123     }
7124 }
7125
7126 int
7127 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7128 {   // front-end-free part taken out of PieceMenuPopup
7129     int whichMenu; int xSqr, ySqr;
7130
7131     if(seekGraphUp) { // [HGM] seekgraph
7132         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7133         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7134         return -2;
7135     }
7136
7137     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7138          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7139         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7140         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7141         if(action == Press)   {
7142             originalFlip = flipView;
7143             flipView = !flipView; // temporarily flip board to see game from partners perspective
7144             DrawPosition(TRUE, partnerBoard);
7145             DisplayMessage(partnerStatus, "");
7146             partnerUp = TRUE;
7147         } else if(action == Release) {
7148             flipView = originalFlip;
7149             DrawPosition(TRUE, boards[currentMove]);
7150             partnerUp = FALSE;
7151         }
7152         return -2;
7153     }
7154
7155     xSqr = EventToSquare(x, BOARD_WIDTH);
7156     ySqr = EventToSquare(y, BOARD_HEIGHT);
7157     if (action == Release) {
7158         if(pieceSweep != EmptySquare) {
7159             EditPositionMenuEvent(pieceSweep, toX, toY);
7160             pieceSweep = EmptySquare;
7161         } else UnLoadPV(); // [HGM] pv
7162     }
7163     if (action != Press) return -2; // return code to be ignored
7164     switch (gameMode) {
7165       case IcsExamining:
7166         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7167       case EditPosition:
7168         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7169         if (xSqr < 0 || ySqr < 0) return -1;
7170         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7171         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7172         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7173         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7174         NextPiece(0);
7175         return 2; // grab
7176       case IcsObserving:
7177         if(!appData.icsEngineAnalyze) return -1;
7178       case IcsPlayingWhite:
7179       case IcsPlayingBlack:
7180         if(!appData.zippyPlay) goto noZip;
7181       case AnalyzeMode:
7182       case AnalyzeFile:
7183       case MachinePlaysWhite:
7184       case MachinePlaysBlack:
7185       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7186         if (!appData.dropMenu) {
7187           LoadPV(x, y);
7188           return 2; // flag front-end to grab mouse events
7189         }
7190         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7191            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7192       case EditGame:
7193       noZip:
7194         if (xSqr < 0 || ySqr < 0) return -1;
7195         if (!appData.dropMenu || appData.testLegality &&
7196             gameInfo.variant != VariantBughouse &&
7197             gameInfo.variant != VariantCrazyhouse) return -1;
7198         whichMenu = 1; // drop menu
7199         break;
7200       default:
7201         return -1;
7202     }
7203
7204     if (((*fromX = xSqr) < 0) ||
7205         ((*fromY = ySqr) < 0)) {
7206         *fromX = *fromY = -1;
7207         return -1;
7208     }
7209     if (flipView)
7210       *fromX = BOARD_WIDTH - 1 - *fromX;
7211     else
7212       *fromY = BOARD_HEIGHT - 1 - *fromY;
7213
7214     return whichMenu;
7215 }
7216
7217 void
7218 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7219 {
7220 //    char * hint = lastHint;
7221     FrontEndProgramStats stats;
7222
7223     stats.which = cps == &first ? 0 : 1;
7224     stats.depth = cpstats->depth;
7225     stats.nodes = cpstats->nodes;
7226     stats.score = cpstats->score;
7227     stats.time = cpstats->time;
7228     stats.pv = cpstats->movelist;
7229     stats.hint = lastHint;
7230     stats.an_move_index = 0;
7231     stats.an_move_count = 0;
7232
7233     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7234         stats.hint = cpstats->move_name;
7235         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7236         stats.an_move_count = cpstats->nr_moves;
7237     }
7238
7239     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
7240
7241     SetProgramStats( &stats );
7242 }
7243
7244 void
7245 ClearEngineOutputPane (int which)
7246 {
7247     static FrontEndProgramStats dummyStats;
7248     dummyStats.which = which;
7249     dummyStats.pv = "#";
7250     SetProgramStats( &dummyStats );
7251 }
7252
7253 #define MAXPLAYERS 500
7254
7255 char *
7256 TourneyStandings (int display)
7257 {
7258     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7259     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7260     char result, *p, *names[MAXPLAYERS];
7261
7262     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7263         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7264     names[0] = p = strdup(appData.participants);
7265     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7266
7267     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7268
7269     while(result = appData.results[nr]) {
7270         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7271         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7272         wScore = bScore = 0;
7273         switch(result) {
7274           case '+': wScore = 2; break;
7275           case '-': bScore = 2; break;
7276           case '=': wScore = bScore = 1; break;
7277           case ' ':
7278           case '*': return strdup("busy"); // tourney not finished
7279         }
7280         score[w] += wScore;
7281         score[b] += bScore;
7282         games[w]++;
7283         games[b]++;
7284         nr++;
7285     }
7286     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7287     for(w=0; w<nPlayers; w++) {
7288         bScore = -1;
7289         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7290         ranking[w] = b; points[w] = bScore; score[b] = -2;
7291     }
7292     p = malloc(nPlayers*34+1);
7293     for(w=0; w<nPlayers && w<display; w++)
7294         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7295     free(names[0]);
7296     return p;
7297 }
7298
7299 void
7300 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7301 {       // count all piece types
7302         int p, f, r;
7303         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7304         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7305         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7306                 p = board[r][f];
7307                 pCnt[p]++;
7308                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7309                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7310                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7311                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7312                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7313                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7314         }
7315 }
7316
7317 int
7318 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7319 {
7320         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7321         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7322
7323         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7324         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7325         if(myPawns == 2 && nMine == 3) // KPP
7326             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7327         if(myPawns == 1 && nMine == 2) // KP
7328             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7329         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7330             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7331         if(myPawns) return FALSE;
7332         if(pCnt[WhiteRook+side])
7333             return pCnt[BlackRook-side] ||
7334                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7335                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7336                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7337         if(pCnt[WhiteCannon+side]) {
7338             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7339             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7340         }
7341         if(pCnt[WhiteKnight+side])
7342             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7343         return FALSE;
7344 }
7345
7346 int
7347 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7348 {
7349         VariantClass v = gameInfo.variant;
7350
7351         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7352         if(v == VariantShatranj) return TRUE; // always winnable through baring
7353         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7354         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7355
7356         if(v == VariantXiangqi) {
7357                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7358
7359                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7360                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7361                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7362                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7363                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7364                 if(stale) // we have at least one last-rank P plus perhaps C
7365                     return majors // KPKX
7366                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7367                 else // KCA*E*
7368                     return pCnt[WhiteFerz+side] // KCAK
7369                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7370                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7371                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7372
7373         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7374                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7375
7376                 if(nMine == 1) return FALSE; // bare King
7377                 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
7378                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7379                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7380                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7381                 if(pCnt[WhiteKnight+side])
7382                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7383                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7384                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7385                 if(nBishops)
7386                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7387                 if(pCnt[WhiteAlfil+side])
7388                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7389                 if(pCnt[WhiteWazir+side])
7390                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7391         }
7392
7393         return TRUE;
7394 }
7395
7396 int
7397 CompareWithRights (Board b1, Board b2)
7398 {
7399     int rights = 0;
7400     if(!CompareBoards(b1, b2)) return FALSE;
7401     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7402     /* compare castling rights */
7403     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7404            rights++; /* King lost rights, while rook still had them */
7405     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7406         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7407            rights++; /* but at least one rook lost them */
7408     }
7409     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7410            rights++;
7411     if( b1[CASTLING][5] != NoRights ) {
7412         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7413            rights++;
7414     }
7415     return rights == 0;
7416 }
7417
7418 int
7419 Adjudicate (ChessProgramState *cps)
7420 {       // [HGM] some adjudications useful with buggy engines
7421         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7422         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7423         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7424         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7425         int k, count = 0; static int bare = 1;
7426         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7427         Boolean canAdjudicate = !appData.icsActive;
7428
7429         // most tests only when we understand the game, i.e. legality-checking on
7430             if( appData.testLegality )
7431             {   /* [HGM] Some more adjudications for obstinate engines */
7432                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7433                 static int moveCount = 6;
7434                 ChessMove result;
7435                 char *reason = NULL;
7436
7437                 /* Count what is on board. */
7438                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7439
7440                 /* Some material-based adjudications that have to be made before stalemate test */
7441                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7442                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7443                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7444                      if(canAdjudicate && appData.checkMates) {
7445                          if(engineOpponent)
7446                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7447                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7448                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7449                          return 1;
7450                      }
7451                 }
7452
7453                 /* Bare King in Shatranj (loses) or Losers (wins) */
7454                 if( nrW == 1 || nrB == 1) {
7455                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7456                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7457                      if(canAdjudicate && appData.checkMates) {
7458                          if(engineOpponent)
7459                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7460                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7461                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7462                          return 1;
7463                      }
7464                   } else
7465                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7466                   {    /* bare King */
7467                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7468                         if(canAdjudicate && appData.checkMates) {
7469                             /* but only adjudicate if adjudication enabled */
7470                             if(engineOpponent)
7471                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7472                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7473                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7474                             return 1;
7475                         }
7476                   }
7477                 } else bare = 1;
7478
7479
7480             // don't wait for engine to announce game end if we can judge ourselves
7481             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7482               case MT_CHECK:
7483                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7484                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7485                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7486                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7487                             checkCnt++;
7488                         if(checkCnt >= 2) {
7489                             reason = "Xboard adjudication: 3rd check";
7490                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7491                             break;
7492                         }
7493                     }
7494                 }
7495               case MT_NONE:
7496               default:
7497                 break;
7498               case MT_STALEMATE:
7499               case MT_STAINMATE:
7500                 reason = "Xboard adjudication: Stalemate";
7501                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7502                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7503                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7504                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7505                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7506                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7507                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7508                                                                         EP_CHECKMATE : EP_WINS);
7509                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7510                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7511                 }
7512                 break;
7513               case MT_CHECKMATE:
7514                 reason = "Xboard adjudication: Checkmate";
7515                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7516                 break;
7517             }
7518
7519                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7520                     case EP_STALEMATE:
7521                         result = GameIsDrawn; break;
7522                     case EP_CHECKMATE:
7523                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7524                     case EP_WINS:
7525                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7526                     default:
7527                         result = EndOfFile;
7528                 }
7529                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7530                     if(engineOpponent)
7531                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7532                     GameEnds( result, reason, GE_XBOARD );
7533                     return 1;
7534                 }
7535
7536                 /* Next absolutely insufficient mating material. */
7537                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7538                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7539                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7540
7541                      /* always flag draws, for judging claims */
7542                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7543
7544                      if(canAdjudicate && appData.materialDraws) {
7545                          /* but only adjudicate them if adjudication enabled */
7546                          if(engineOpponent) {
7547                            SendToProgram("force\n", engineOpponent); // suppress reply
7548                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7549                          }
7550                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7551                          return 1;
7552                      }
7553                 }
7554
7555                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7556                 if(gameInfo.variant == VariantXiangqi ?
7557                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7558                  : nrW + nrB == 4 &&
7559                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7560                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7561                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7562                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7563                    ) ) {
7564                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7565                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7566                           if(engineOpponent) {
7567                             SendToProgram("force\n", engineOpponent); // suppress reply
7568                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7569                           }
7570                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7571                           return 1;
7572                      }
7573                 } else moveCount = 6;
7574             }
7575
7576         // Repetition draws and 50-move rule can be applied independently of legality testing
7577
7578                 /* Check for rep-draws */
7579                 count = 0;
7580                 for(k = forwardMostMove-2;
7581                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7582                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7583                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7584                     k-=2)
7585                 {   int rights=0;
7586                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7587                         /* compare castling rights */
7588                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7589                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7590                                 rights++; /* King lost rights, while rook still had them */
7591                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7592                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7593                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7594                                    rights++; /* but at least one rook lost them */
7595                         }
7596                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7597                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7598                                 rights++;
7599                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7600                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7601                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7602                                    rights++;
7603                         }
7604                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7605                             && appData.drawRepeats > 1) {
7606                              /* adjudicate after user-specified nr of repeats */
7607                              int result = GameIsDrawn;
7608                              char *details = "XBoard adjudication: repetition draw";
7609                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7610                                 // [HGM] xiangqi: check for forbidden perpetuals
7611                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7612                                 for(m=forwardMostMove; m>k; m-=2) {
7613                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7614                                         ourPerpetual = 0; // the current mover did not always check
7615                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7616                                         hisPerpetual = 0; // the opponent did not always check
7617                                 }
7618                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7619                                                                         ourPerpetual, hisPerpetual);
7620                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7621                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7622                                     details = "Xboard adjudication: perpetual checking";
7623                                 } else
7624                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7625                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7626                                 } else
7627                                 // Now check for perpetual chases
7628                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7629                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7630                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7631                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7632                                         static char resdet[MSG_SIZ];
7633                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7634                                         details = resdet;
7635                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7636                                     } else
7637                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7638                                         break; // Abort repetition-checking loop.
7639                                 }
7640                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7641                              }
7642                              if(engineOpponent) {
7643                                SendToProgram("force\n", engineOpponent); // suppress reply
7644                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7645                              }
7646                              GameEnds( result, details, GE_XBOARD );
7647                              return 1;
7648                         }
7649                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7650                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7651                     }
7652                 }
7653
7654                 /* Now we test for 50-move draws. Determine ply count */
7655                 count = forwardMostMove;
7656                 /* look for last irreversble move */
7657                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7658                     count--;
7659                 /* if we hit starting position, add initial plies */
7660                 if( count == backwardMostMove )
7661                     count -= initialRulePlies;
7662                 count = forwardMostMove - count;
7663                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7664                         // adjust reversible move counter for checks in Xiangqi
7665                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7666                         if(i < backwardMostMove) i = backwardMostMove;
7667                         while(i <= forwardMostMove) {
7668                                 lastCheck = inCheck; // check evasion does not count
7669                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7670                                 if(inCheck || lastCheck) count--; // check does not count
7671                                 i++;
7672                         }
7673                 }
7674                 if( count >= 100)
7675                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7676                          /* this is used to judge if draw claims are legal */
7677                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7678                          if(engineOpponent) {
7679                            SendToProgram("force\n", engineOpponent); // suppress reply
7680                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7681                          }
7682                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7683                          return 1;
7684                 }
7685
7686                 /* if draw offer is pending, treat it as a draw claim
7687                  * when draw condition present, to allow engines a way to
7688                  * claim draws before making their move to avoid a race
7689                  * condition occurring after their move
7690                  */
7691                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7692                          char *p = NULL;
7693                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7694                              p = "Draw claim: 50-move rule";
7695                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7696                              p = "Draw claim: 3-fold repetition";
7697                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7698                              p = "Draw claim: insufficient mating material";
7699                          if( p != NULL && canAdjudicate) {
7700                              if(engineOpponent) {
7701                                SendToProgram("force\n", engineOpponent); // suppress reply
7702                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7703                              }
7704                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7705                              return 1;
7706                          }
7707                 }
7708
7709                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7710                     if(engineOpponent) {
7711                       SendToProgram("force\n", engineOpponent); // suppress reply
7712                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7713                     }
7714                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7715                     return 1;
7716                 }
7717         return 0;
7718 }
7719
7720 char *
7721 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7722 {   // [HGM] book: this routine intercepts moves to simulate book replies
7723     char *bookHit = NULL;
7724
7725     //first determine if the incoming move brings opponent into his book
7726     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7727         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7728     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7729     if(bookHit != NULL && !cps->bookSuspend) {
7730         // make sure opponent is not going to reply after receiving move to book position
7731         SendToProgram("force\n", cps);
7732         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7733     }
7734     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7735     // now arrange restart after book miss
7736     if(bookHit) {
7737         // after a book hit we never send 'go', and the code after the call to this routine
7738         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7739         char buf[MSG_SIZ], *move = bookHit;
7740         if(cps->useSAN) {
7741             int fromX, fromY, toX, toY;
7742             char promoChar;
7743             ChessMove moveType;
7744             move = buf + 30;
7745             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7746                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7747                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7748                                     PosFlags(forwardMostMove),
7749                                     fromY, fromX, toY, toX, promoChar, move);
7750             } else {
7751                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7752                 bookHit = NULL;
7753             }
7754         }
7755         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7756         SendToProgram(buf, cps);
7757         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7758     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7759         SendToProgram("go\n", cps);
7760         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7761     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7762         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7763             SendToProgram("go\n", cps);
7764         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7765     }
7766     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7767 }
7768
7769 char *savedMessage;
7770 ChessProgramState *savedState;
7771 void
7772 DeferredBookMove (void)
7773 {
7774         if(savedState->lastPing != savedState->lastPong)
7775                     ScheduleDelayedEvent(DeferredBookMove, 10);
7776         else
7777         HandleMachineMove(savedMessage, savedState);
7778 }
7779
7780 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7781
7782 void
7783 HandleMachineMove (char *message, ChessProgramState *cps)
7784 {
7785     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7786     char realname[MSG_SIZ];
7787     int fromX, fromY, toX, toY;
7788     ChessMove moveType;
7789     char promoChar;
7790     char *p, *pv=buf1;
7791     int machineWhite;
7792     char *bookHit;
7793
7794     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7795         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7796         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7797             DisplayError(_("Invalid pairing from pairing engine"), 0);
7798             return;
7799         }
7800         pairingReceived = 1;
7801         NextMatchGame();
7802         return; // Skim the pairing messages here.
7803     }
7804
7805     cps->userError = 0;
7806
7807 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7808     /*
7809      * Kludge to ignore BEL characters
7810      */
7811     while (*message == '\007') message++;
7812
7813     /*
7814      * [HGM] engine debug message: ignore lines starting with '#' character
7815      */
7816     if(cps->debug && *message == '#') return;
7817
7818     /*
7819      * Look for book output
7820      */
7821     if (cps == &first && bookRequested) {
7822         if (message[0] == '\t' || message[0] == ' ') {
7823             /* Part of the book output is here; append it */
7824             strcat(bookOutput, message);
7825             strcat(bookOutput, "  \n");
7826             return;
7827         } else if (bookOutput[0] != NULLCHAR) {
7828             /* All of book output has arrived; display it */
7829             char *p = bookOutput;
7830             while (*p != NULLCHAR) {
7831                 if (*p == '\t') *p = ' ';
7832                 p++;
7833             }
7834             DisplayInformation(bookOutput);
7835             bookRequested = FALSE;
7836             /* Fall through to parse the current output */
7837         }
7838     }
7839
7840     /*
7841      * Look for machine move.
7842      */
7843     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7844         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7845     {
7846         /* This method is only useful on engines that support ping */
7847         if (cps->lastPing != cps->lastPong) {
7848           if (gameMode == BeginningOfGame) {
7849             /* Extra move from before last new; ignore */
7850             if (appData.debugMode) {
7851                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7852             }
7853           } else {
7854             if (appData.debugMode) {
7855                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7856                         cps->which, gameMode);
7857             }
7858
7859             SendToProgram("undo\n", cps);
7860           }
7861           return;
7862         }
7863
7864         switch (gameMode) {
7865           case BeginningOfGame:
7866             /* Extra move from before last reset; ignore */
7867             if (appData.debugMode) {
7868                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7869             }
7870             return;
7871
7872           case EndOfGame:
7873           case IcsIdle:
7874           default:
7875             /* Extra move after we tried to stop.  The mode test is
7876                not a reliable way of detecting this problem, but it's
7877                the best we can do on engines that don't support ping.
7878             */
7879             if (appData.debugMode) {
7880                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7881                         cps->which, gameMode);
7882             }
7883             SendToProgram("undo\n", cps);
7884             return;
7885
7886           case MachinePlaysWhite:
7887           case IcsPlayingWhite:
7888             machineWhite = TRUE;
7889             break;
7890
7891           case MachinePlaysBlack:
7892           case IcsPlayingBlack:
7893             machineWhite = FALSE;
7894             break;
7895
7896           case TwoMachinesPlay:
7897             machineWhite = (cps->twoMachinesColor[0] == 'w');
7898             break;
7899         }
7900         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7901             if (appData.debugMode) {
7902                 fprintf(debugFP,
7903                         "Ignoring move out of turn by %s, gameMode %d"
7904                         ", forwardMost %d\n",
7905                         cps->which, gameMode, forwardMostMove);
7906             }
7907             return;
7908         }
7909
7910         if(cps->alphaRank) AlphaRank(machineMove, 4);
7911         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7912                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7913             /* Machine move could not be parsed; ignore it. */
7914           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7915                     machineMove, _(cps->which));
7916             DisplayError(buf1, 0);
7917             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7918                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7919             if (gameMode == TwoMachinesPlay) {
7920               GameEnds(machineWhite ? BlackWins : WhiteWins,
7921                        buf1, GE_XBOARD);
7922             }
7923             return;
7924         }
7925
7926         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7927         /* So we have to redo legality test with true e.p. status here,  */
7928         /* to make sure an illegal e.p. capture does not slip through,   */
7929         /* to cause a forfeit on a justified illegal-move complaint      */
7930         /* of the opponent.                                              */
7931         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7932            ChessMove moveType;
7933            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7934                              fromY, fromX, toY, toX, promoChar);
7935             if(moveType == IllegalMove) {
7936               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7937                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7938                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7939                            buf1, GE_XBOARD);
7940                 return;
7941            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7942            /* [HGM] Kludge to handle engines that send FRC-style castling
7943               when they shouldn't (like TSCP-Gothic) */
7944            switch(moveType) {
7945              case WhiteASideCastleFR:
7946              case BlackASideCastleFR:
7947                toX+=2;
7948                currentMoveString[2]++;
7949                break;
7950              case WhiteHSideCastleFR:
7951              case BlackHSideCastleFR:
7952                toX--;
7953                currentMoveString[2]--;
7954                break;
7955              default: ; // nothing to do, but suppresses warning of pedantic compilers
7956            }
7957         }
7958         hintRequested = FALSE;
7959         lastHint[0] = NULLCHAR;
7960         bookRequested = FALSE;
7961         /* Program may be pondering now */
7962         cps->maybeThinking = TRUE;
7963         if (cps->sendTime == 2) cps->sendTime = 1;
7964         if (cps->offeredDraw) cps->offeredDraw--;
7965
7966         /* [AS] Save move info*/
7967         pvInfoList[ forwardMostMove ].score = programStats.score;
7968         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7969         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7970
7971         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7972
7973         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7974         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7975             int count = 0;
7976
7977             while( count < adjudicateLossPlies ) {
7978                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7979
7980                 if( count & 1 ) {
7981                     score = -score; /* Flip score for winning side */
7982                 }
7983
7984                 if( score > adjudicateLossThreshold ) {
7985                     break;
7986                 }
7987
7988                 count++;
7989             }
7990
7991             if( count >= adjudicateLossPlies ) {
7992                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7993
7994                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7995                     "Xboard adjudication",
7996                     GE_XBOARD );
7997
7998                 return;
7999             }
8000         }
8001
8002         if(Adjudicate(cps)) {
8003             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8004             return; // [HGM] adjudicate: for all automatic game ends
8005         }
8006
8007 #if ZIPPY
8008         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8009             first.initDone) {
8010           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8011                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8012                 SendToICS("draw ");
8013                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8014           }
8015           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8016           ics_user_moved = 1;
8017           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8018                 char buf[3*MSG_SIZ];
8019
8020                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8021                         programStats.score / 100.,
8022                         programStats.depth,
8023                         programStats.time / 100.,
8024                         (unsigned int)programStats.nodes,
8025                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8026                         programStats.movelist);
8027                 SendToICS(buf);
8028 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8029           }
8030         }
8031 #endif
8032
8033         /* [AS] Clear stats for next move */
8034         ClearProgramStats();
8035         thinkOutput[0] = NULLCHAR;
8036         hiddenThinkOutputState = 0;
8037
8038         bookHit = NULL;
8039         if (gameMode == TwoMachinesPlay) {
8040             /* [HGM] relaying draw offers moved to after reception of move */
8041             /* and interpreting offer as claim if it brings draw condition */
8042             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8043                 SendToProgram("draw\n", cps->other);
8044             }
8045             if (cps->other->sendTime) {
8046                 SendTimeRemaining(cps->other,
8047                                   cps->other->twoMachinesColor[0] == 'w');
8048             }
8049             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8050             if (firstMove && !bookHit) {
8051                 firstMove = FALSE;
8052                 if (cps->other->useColors) {
8053                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8054                 }
8055                 SendToProgram("go\n", cps->other);
8056             }
8057             cps->other->maybeThinking = TRUE;
8058         }
8059
8060         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8061
8062         if (!pausing && appData.ringBellAfterMoves) {
8063             RingBell();
8064         }
8065
8066         /*
8067          * Reenable menu items that were disabled while
8068          * machine was thinking
8069          */
8070         if (gameMode != TwoMachinesPlay)
8071             SetUserThinkingEnables();
8072
8073         // [HGM] book: after book hit opponent has received move and is now in force mode
8074         // force the book reply into it, and then fake that it outputted this move by jumping
8075         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8076         if(bookHit) {
8077                 static char bookMove[MSG_SIZ]; // a bit generous?
8078
8079                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8080                 strcat(bookMove, bookHit);
8081                 message = bookMove;
8082                 cps = cps->other;
8083                 programStats.nodes = programStats.depth = programStats.time =
8084                 programStats.score = programStats.got_only_move = 0;
8085                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8086
8087                 if(cps->lastPing != cps->lastPong) {
8088                     savedMessage = message; // args for deferred call
8089                     savedState = cps;
8090                     ScheduleDelayedEvent(DeferredBookMove, 10);
8091                     return;
8092                 }
8093                 goto FakeBookMove;
8094         }
8095
8096         return;
8097     }
8098
8099     /* Set special modes for chess engines.  Later something general
8100      *  could be added here; for now there is just one kludge feature,
8101      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8102      *  when "xboard" is given as an interactive command.
8103      */
8104     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8105         cps->useSigint = FALSE;
8106         cps->useSigterm = FALSE;
8107     }
8108     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8109       ParseFeatures(message+8, cps);
8110       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8111     }
8112
8113     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8114                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8115       int dummy, s=6; char buf[MSG_SIZ];
8116       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8117       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8118       if(startedFromSetupPosition) return;
8119       ParseFEN(boards[0], &dummy, message+s);
8120       DrawPosition(TRUE, boards[0]);
8121       startedFromSetupPosition = TRUE;
8122       return;
8123     }
8124     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8125      * want this, I was asked to put it in, and obliged.
8126      */
8127     if (!strncmp(message, "setboard ", 9)) {
8128         Board initial_position;
8129
8130         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8131
8132         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8133             DisplayError(_("Bad FEN received from engine"), 0);
8134             return ;
8135         } else {
8136            Reset(TRUE, FALSE);
8137            CopyBoard(boards[0], initial_position);
8138            initialRulePlies = FENrulePlies;
8139            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8140            else gameMode = MachinePlaysBlack;
8141            DrawPosition(FALSE, boards[currentMove]);
8142         }
8143         return;
8144     }
8145
8146     /*
8147      * Look for communication commands
8148      */
8149     if (!strncmp(message, "telluser ", 9)) {
8150         if(message[9] == '\\' && message[10] == '\\')
8151             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8152         PlayTellSound();
8153         DisplayNote(message + 9);
8154         return;
8155     }
8156     if (!strncmp(message, "tellusererror ", 14)) {
8157         cps->userError = 1;
8158         if(message[14] == '\\' && message[15] == '\\')
8159             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8160         PlayTellSound();
8161         DisplayError(message + 14, 0);
8162         return;
8163     }
8164     if (!strncmp(message, "tellopponent ", 13)) {
8165       if (appData.icsActive) {
8166         if (loggedOn) {
8167           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8168           SendToICS(buf1);
8169         }
8170       } else {
8171         DisplayNote(message + 13);
8172       }
8173       return;
8174     }
8175     if (!strncmp(message, "tellothers ", 11)) {
8176       if (appData.icsActive) {
8177         if (loggedOn) {
8178           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8179           SendToICS(buf1);
8180         }
8181       }
8182       return;
8183     }
8184     if (!strncmp(message, "tellall ", 8)) {
8185       if (appData.icsActive) {
8186         if (loggedOn) {
8187           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8188           SendToICS(buf1);
8189         }
8190       } else {
8191         DisplayNote(message + 8);
8192       }
8193       return;
8194     }
8195     if (strncmp(message, "warning", 7) == 0) {
8196         /* Undocumented feature, use tellusererror in new code */
8197         DisplayError(message, 0);
8198         return;
8199     }
8200     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8201         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8202         strcat(realname, " query");
8203         AskQuestion(realname, buf2, buf1, cps->pr);
8204         return;
8205     }
8206     /* Commands from the engine directly to ICS.  We don't allow these to be
8207      *  sent until we are logged on. Crafty kibitzes have been known to
8208      *  interfere with the login process.
8209      */
8210     if (loggedOn) {
8211         if (!strncmp(message, "tellics ", 8)) {
8212             SendToICS(message + 8);
8213             SendToICS("\n");
8214             return;
8215         }
8216         if (!strncmp(message, "tellicsnoalias ", 15)) {
8217             SendToICS(ics_prefix);
8218             SendToICS(message + 15);
8219             SendToICS("\n");
8220             return;
8221         }
8222         /* The following are for backward compatibility only */
8223         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8224             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8225             SendToICS(ics_prefix);
8226             SendToICS(message);
8227             SendToICS("\n");
8228             return;
8229         }
8230     }
8231     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8232         return;
8233     }
8234     /*
8235      * If the move is illegal, cancel it and redraw the board.
8236      * Also deal with other error cases.  Matching is rather loose
8237      * here to accommodate engines written before the spec.
8238      */
8239     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8240         strncmp(message, "Error", 5) == 0) {
8241         if (StrStr(message, "name") ||
8242             StrStr(message, "rating") || StrStr(message, "?") ||
8243             StrStr(message, "result") || StrStr(message, "board") ||
8244             StrStr(message, "bk") || StrStr(message, "computer") ||
8245             StrStr(message, "variant") || StrStr(message, "hint") ||
8246             StrStr(message, "random") || StrStr(message, "depth") ||
8247             StrStr(message, "accepted")) {
8248             return;
8249         }
8250         if (StrStr(message, "protover")) {
8251           /* Program is responding to input, so it's apparently done
8252              initializing, and this error message indicates it is
8253              protocol version 1.  So we don't need to wait any longer
8254              for it to initialize and send feature commands. */
8255           FeatureDone(cps, 1);
8256           cps->protocolVersion = 1;
8257           return;
8258         }
8259         cps->maybeThinking = FALSE;
8260
8261         if (StrStr(message, "draw")) {
8262             /* Program doesn't have "draw" command */
8263             cps->sendDrawOffers = 0;
8264             return;
8265         }
8266         if (cps->sendTime != 1 &&
8267             (StrStr(message, "time") || StrStr(message, "otim"))) {
8268           /* Program apparently doesn't have "time" or "otim" command */
8269           cps->sendTime = 0;
8270           return;
8271         }
8272         if (StrStr(message, "analyze")) {
8273             cps->analysisSupport = FALSE;
8274             cps->analyzing = FALSE;
8275 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8276             EditGameEvent(); // [HGM] try to preserve loaded game
8277             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8278             DisplayError(buf2, 0);
8279             return;
8280         }
8281         if (StrStr(message, "(no matching move)st")) {
8282           /* Special kludge for GNU Chess 4 only */
8283           cps->stKludge = TRUE;
8284           SendTimeControl(cps, movesPerSession, timeControl,
8285                           timeIncrement, appData.searchDepth,
8286                           searchTime);
8287           return;
8288         }
8289         if (StrStr(message, "(no matching move)sd")) {
8290           /* Special kludge for GNU Chess 4 only */
8291           cps->sdKludge = TRUE;
8292           SendTimeControl(cps, movesPerSession, timeControl,
8293                           timeIncrement, appData.searchDepth,
8294                           searchTime);
8295           return;
8296         }
8297         if (!StrStr(message, "llegal")) {
8298             return;
8299         }
8300         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8301             gameMode == IcsIdle) return;
8302         if (forwardMostMove <= backwardMostMove) return;
8303         if (pausing) PauseEvent();
8304       if(appData.forceIllegal) {
8305             // [HGM] illegal: machine refused move; force position after move into it
8306           SendToProgram("force\n", cps);
8307           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8308                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8309                 // when black is to move, while there might be nothing on a2 or black
8310                 // might already have the move. So send the board as if white has the move.
8311                 // But first we must change the stm of the engine, as it refused the last move
8312                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8313                 if(WhiteOnMove(forwardMostMove)) {
8314                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8315                     SendBoard(cps, forwardMostMove); // kludgeless board
8316                 } else {
8317                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8318                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8319                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8320                 }
8321           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8322             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8323                  gameMode == TwoMachinesPlay)
8324               SendToProgram("go\n", cps);
8325             return;
8326       } else
8327         if (gameMode == PlayFromGameFile) {
8328             /* Stop reading this game file */
8329             gameMode = EditGame;
8330             ModeHighlight();
8331         }
8332         /* [HGM] illegal-move claim should forfeit game when Xboard */
8333         /* only passes fully legal moves                            */
8334         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8335             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8336                                 "False illegal-move claim", GE_XBOARD );
8337             return; // do not take back move we tested as valid
8338         }
8339         currentMove = forwardMostMove-1;
8340         DisplayMove(currentMove-1); /* before DisplayMoveError */
8341         SwitchClocks(forwardMostMove-1); // [HGM] race
8342         DisplayBothClocks();
8343         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8344                 parseList[currentMove], _(cps->which));
8345         DisplayMoveError(buf1);
8346         DrawPosition(FALSE, boards[currentMove]);
8347
8348         SetUserThinkingEnables();
8349         return;
8350     }
8351     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8352         /* Program has a broken "time" command that
8353            outputs a string not ending in newline.
8354            Don't use it. */
8355         cps->sendTime = 0;
8356     }
8357
8358     /*
8359      * If chess program startup fails, exit with an error message.
8360      * Attempts to recover here are futile.
8361      */
8362     if ((StrStr(message, "unknown host") != NULL)
8363         || (StrStr(message, "No remote directory") != NULL)
8364         || (StrStr(message, "not found") != NULL)
8365         || (StrStr(message, "No such file") != NULL)
8366         || (StrStr(message, "can't alloc") != NULL)
8367         || (StrStr(message, "Permission denied") != NULL)) {
8368
8369         cps->maybeThinking = FALSE;
8370         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8371                 _(cps->which), cps->program, cps->host, message);
8372         RemoveInputSource(cps->isr);
8373         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8374             if(cps == &first) appData.noChessProgram = TRUE;
8375             DisplayError(buf1, 0);
8376         }
8377         return;
8378     }
8379
8380     /*
8381      * Look for hint output
8382      */
8383     if (sscanf(message, "Hint: %s", buf1) == 1) {
8384         if (cps == &first && hintRequested) {
8385             hintRequested = FALSE;
8386             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8387                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8388                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8389                                     PosFlags(forwardMostMove),
8390                                     fromY, fromX, toY, toX, promoChar, buf1);
8391                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8392                 DisplayInformation(buf2);
8393             } else {
8394                 /* Hint move could not be parsed!? */
8395               snprintf(buf2, sizeof(buf2),
8396                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8397                         buf1, _(cps->which));
8398                 DisplayError(buf2, 0);
8399             }
8400         } else {
8401           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8402         }
8403         return;
8404     }
8405
8406     /*
8407      * Ignore other messages if game is not in progress
8408      */
8409     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8410         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8411
8412     /*
8413      * look for win, lose, draw, or draw offer
8414      */
8415     if (strncmp(message, "1-0", 3) == 0) {
8416         char *p, *q, *r = "";
8417         p = strchr(message, '{');
8418         if (p) {
8419             q = strchr(p, '}');
8420             if (q) {
8421                 *q = NULLCHAR;
8422                 r = p + 1;
8423             }
8424         }
8425         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8426         return;
8427     } else if (strncmp(message, "0-1", 3) == 0) {
8428         char *p, *q, *r = "";
8429         p = strchr(message, '{');
8430         if (p) {
8431             q = strchr(p, '}');
8432             if (q) {
8433                 *q = NULLCHAR;
8434                 r = p + 1;
8435             }
8436         }
8437         /* Kludge for Arasan 4.1 bug */
8438         if (strcmp(r, "Black resigns") == 0) {
8439             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8440             return;
8441         }
8442         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8443         return;
8444     } else if (strncmp(message, "1/2", 3) == 0) {
8445         char *p, *q, *r = "";
8446         p = strchr(message, '{');
8447         if (p) {
8448             q = strchr(p, '}');
8449             if (q) {
8450                 *q = NULLCHAR;
8451                 r = p + 1;
8452             }
8453         }
8454
8455         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8456         return;
8457
8458     } else if (strncmp(message, "White resign", 12) == 0) {
8459         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8460         return;
8461     } else if (strncmp(message, "Black resign", 12) == 0) {
8462         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8463         return;
8464     } else if (strncmp(message, "White matches", 13) == 0 ||
8465                strncmp(message, "Black matches", 13) == 0   ) {
8466         /* [HGM] ignore GNUShogi noises */
8467         return;
8468     } else if (strncmp(message, "White", 5) == 0 &&
8469                message[5] != '(' &&
8470                StrStr(message, "Black") == NULL) {
8471         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8472         return;
8473     } else if (strncmp(message, "Black", 5) == 0 &&
8474                message[5] != '(') {
8475         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8476         return;
8477     } else if (strcmp(message, "resign") == 0 ||
8478                strcmp(message, "computer resigns") == 0) {
8479         switch (gameMode) {
8480           case MachinePlaysBlack:
8481           case IcsPlayingBlack:
8482             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8483             break;
8484           case MachinePlaysWhite:
8485           case IcsPlayingWhite:
8486             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8487             break;
8488           case TwoMachinesPlay:
8489             if (cps->twoMachinesColor[0] == 'w')
8490               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8491             else
8492               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8493             break;
8494           default:
8495             /* can't happen */
8496             break;
8497         }
8498         return;
8499     } else if (strncmp(message, "opponent mates", 14) == 0) {
8500         switch (gameMode) {
8501           case MachinePlaysBlack:
8502           case IcsPlayingBlack:
8503             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8504             break;
8505           case MachinePlaysWhite:
8506           case IcsPlayingWhite:
8507             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8508             break;
8509           case TwoMachinesPlay:
8510             if (cps->twoMachinesColor[0] == 'w')
8511               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8512             else
8513               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8514             break;
8515           default:
8516             /* can't happen */
8517             break;
8518         }
8519         return;
8520     } else if (strncmp(message, "computer mates", 14) == 0) {
8521         switch (gameMode) {
8522           case MachinePlaysBlack:
8523           case IcsPlayingBlack:
8524             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8525             break;
8526           case MachinePlaysWhite:
8527           case IcsPlayingWhite:
8528             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8529             break;
8530           case TwoMachinesPlay:
8531             if (cps->twoMachinesColor[0] == 'w')
8532               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8533             else
8534               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8535             break;
8536           default:
8537             /* can't happen */
8538             break;
8539         }
8540         return;
8541     } else if (strncmp(message, "checkmate", 9) == 0) {
8542         if (WhiteOnMove(forwardMostMove)) {
8543             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8544         } else {
8545             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8546         }
8547         return;
8548     } else if (strstr(message, "Draw") != NULL ||
8549                strstr(message, "game is a draw") != NULL) {
8550         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8551         return;
8552     } else if (strstr(message, "offer") != NULL &&
8553                strstr(message, "draw") != NULL) {
8554 #if ZIPPY
8555         if (appData.zippyPlay && first.initDone) {
8556             /* Relay offer to ICS */
8557             SendToICS(ics_prefix);
8558             SendToICS("draw\n");
8559         }
8560 #endif
8561         cps->offeredDraw = 2; /* valid until this engine moves twice */
8562         if (gameMode == TwoMachinesPlay) {
8563             if (cps->other->offeredDraw) {
8564                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8565             /* [HGM] in two-machine mode we delay relaying draw offer      */
8566             /* until after we also have move, to see if it is really claim */
8567             }
8568         } else if (gameMode == MachinePlaysWhite ||
8569                    gameMode == MachinePlaysBlack) {
8570           if (userOfferedDraw) {
8571             DisplayInformation(_("Machine accepts your draw offer"));
8572             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8573           } else {
8574             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8575           }
8576         }
8577     }
8578
8579
8580     /*
8581      * Look for thinking output
8582      */
8583     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8584           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8585                                 ) {
8586         int plylev, mvleft, mvtot, curscore, time;
8587         char mvname[MOVE_LEN];
8588         u64 nodes; // [DM]
8589         char plyext;
8590         int ignore = FALSE;
8591         int prefixHint = FALSE;
8592         mvname[0] = NULLCHAR;
8593
8594         switch (gameMode) {
8595           case MachinePlaysBlack:
8596           case IcsPlayingBlack:
8597             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8598             break;
8599           case MachinePlaysWhite:
8600           case IcsPlayingWhite:
8601             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8602             break;
8603           case AnalyzeMode:
8604           case AnalyzeFile:
8605             break;
8606           case IcsObserving: /* [DM] icsEngineAnalyze */
8607             if (!appData.icsEngineAnalyze) ignore = TRUE;
8608             break;
8609           case TwoMachinesPlay:
8610             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8611                 ignore = TRUE;
8612             }
8613             break;
8614           default:
8615             ignore = TRUE;
8616             break;
8617         }
8618
8619         if (!ignore) {
8620             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8621             buf1[0] = NULLCHAR;
8622             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8623                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8624
8625                 if (plyext != ' ' && plyext != '\t') {
8626                     time *= 100;
8627                 }
8628
8629                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8630                 if( cps->scoreIsAbsolute &&
8631                     ( gameMode == MachinePlaysBlack ||
8632                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8633                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8634                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8635                      !WhiteOnMove(currentMove)
8636                     ) )
8637                 {
8638                     curscore = -curscore;
8639                 }
8640
8641                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8642
8643                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8644                         char buf[MSG_SIZ];
8645                         FILE *f;
8646                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8647                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8648                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8649                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8650                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8651                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8652                                 fclose(f);
8653                         } else DisplayError(_("failed writing PV"), 0);
8654                 }
8655
8656                 tempStats.depth = plylev;
8657                 tempStats.nodes = nodes;
8658                 tempStats.time = time;
8659                 tempStats.score = curscore;
8660                 tempStats.got_only_move = 0;
8661
8662                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8663                         int ticklen;
8664
8665                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8666                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8667                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8668                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8669                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8670                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8671                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8672                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8673                 }
8674
8675                 /* Buffer overflow protection */
8676                 if (pv[0] != NULLCHAR) {
8677                     if (strlen(pv) >= sizeof(tempStats.movelist)
8678                         && appData.debugMode) {
8679                         fprintf(debugFP,
8680                                 "PV is too long; using the first %u bytes.\n",
8681                                 (unsigned) sizeof(tempStats.movelist) - 1);
8682                     }
8683
8684                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8685                 } else {
8686                     sprintf(tempStats.movelist, " no PV\n");
8687                 }
8688
8689                 if (tempStats.seen_stat) {
8690                     tempStats.ok_to_send = 1;
8691                 }
8692
8693                 if (strchr(tempStats.movelist, '(') != NULL) {
8694                     tempStats.line_is_book = 1;
8695                     tempStats.nr_moves = 0;
8696                     tempStats.moves_left = 0;
8697                 } else {
8698                     tempStats.line_is_book = 0;
8699                 }
8700
8701                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8702                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8703
8704                 SendProgramStatsToFrontend( cps, &tempStats );
8705
8706                 /*
8707                     [AS] Protect the thinkOutput buffer from overflow... this
8708                     is only useful if buf1 hasn't overflowed first!
8709                 */
8710                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8711                          plylev,
8712                          (gameMode == TwoMachinesPlay ?
8713                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8714                          ((double) curscore) / 100.0,
8715                          prefixHint ? lastHint : "",
8716                          prefixHint ? " " : "" );
8717
8718                 if( buf1[0] != NULLCHAR ) {
8719                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8720
8721                     if( strlen(pv) > max_len ) {
8722                         if( appData.debugMode) {
8723                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8724                         }
8725                         pv[max_len+1] = '\0';
8726                     }
8727
8728                     strcat( thinkOutput, pv);
8729                 }
8730
8731                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8732                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8733                     DisplayMove(currentMove - 1);
8734                 }
8735                 return;
8736
8737             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8738                 /* crafty (9.25+) says "(only move) <move>"
8739                  * if there is only 1 legal move
8740                  */
8741                 sscanf(p, "(only move) %s", buf1);
8742                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8743                 sprintf(programStats.movelist, "%s (only move)", buf1);
8744                 programStats.depth = 1;
8745                 programStats.nr_moves = 1;
8746                 programStats.moves_left = 1;
8747                 programStats.nodes = 1;
8748                 programStats.time = 1;
8749                 programStats.got_only_move = 1;
8750
8751                 /* Not really, but we also use this member to
8752                    mean "line isn't going to change" (Crafty
8753                    isn't searching, so stats won't change) */
8754                 programStats.line_is_book = 1;
8755
8756                 SendProgramStatsToFrontend( cps, &programStats );
8757
8758                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8759                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8760                     DisplayMove(currentMove - 1);
8761                 }
8762                 return;
8763             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8764                               &time, &nodes, &plylev, &mvleft,
8765                               &mvtot, mvname) >= 5) {
8766                 /* The stat01: line is from Crafty (9.29+) in response
8767                    to the "." command */
8768                 programStats.seen_stat = 1;
8769                 cps->maybeThinking = TRUE;
8770
8771                 if (programStats.got_only_move || !appData.periodicUpdates)
8772                   return;
8773
8774                 programStats.depth = plylev;
8775                 programStats.time = time;
8776                 programStats.nodes = nodes;
8777                 programStats.moves_left = mvleft;
8778                 programStats.nr_moves = mvtot;
8779                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8780                 programStats.ok_to_send = 1;
8781                 programStats.movelist[0] = '\0';
8782
8783                 SendProgramStatsToFrontend( cps, &programStats );
8784
8785                 return;
8786
8787             } else if (strncmp(message,"++",2) == 0) {
8788                 /* Crafty 9.29+ outputs this */
8789                 programStats.got_fail = 2;
8790                 return;
8791
8792             } else if (strncmp(message,"--",2) == 0) {
8793                 /* Crafty 9.29+ outputs this */
8794                 programStats.got_fail = 1;
8795                 return;
8796
8797             } else if (thinkOutput[0] != NULLCHAR &&
8798                        strncmp(message, "    ", 4) == 0) {
8799                 unsigned message_len;
8800
8801                 p = message;
8802                 while (*p && *p == ' ') p++;
8803
8804                 message_len = strlen( p );
8805
8806                 /* [AS] Avoid buffer overflow */
8807                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8808                     strcat(thinkOutput, " ");
8809                     strcat(thinkOutput, p);
8810                 }
8811
8812                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8813                     strcat(programStats.movelist, " ");
8814                     strcat(programStats.movelist, p);
8815                 }
8816
8817                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8818                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8819                     DisplayMove(currentMove - 1);
8820                 }
8821                 return;
8822             }
8823         }
8824         else {
8825             buf1[0] = NULLCHAR;
8826
8827             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8828                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8829             {
8830                 ChessProgramStats cpstats;
8831
8832                 if (plyext != ' ' && plyext != '\t') {
8833                     time *= 100;
8834                 }
8835
8836                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8837                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8838                     curscore = -curscore;
8839                 }
8840
8841                 cpstats.depth = plylev;
8842                 cpstats.nodes = nodes;
8843                 cpstats.time = time;
8844                 cpstats.score = curscore;
8845                 cpstats.got_only_move = 0;
8846                 cpstats.movelist[0] = '\0';
8847
8848                 if (buf1[0] != NULLCHAR) {
8849                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8850                 }
8851
8852                 cpstats.ok_to_send = 0;
8853                 cpstats.line_is_book = 0;
8854                 cpstats.nr_moves = 0;
8855                 cpstats.moves_left = 0;
8856
8857                 SendProgramStatsToFrontend( cps, &cpstats );
8858             }
8859         }
8860     }
8861 }
8862
8863
8864 /* Parse a game score from the character string "game", and
8865    record it as the history of the current game.  The game
8866    score is NOT assumed to start from the standard position.
8867    The display is not updated in any way.
8868    */
8869 void
8870 ParseGameHistory (char *game)
8871 {
8872     ChessMove moveType;
8873     int fromX, fromY, toX, toY, boardIndex;
8874     char promoChar;
8875     char *p, *q;
8876     char buf[MSG_SIZ];
8877
8878     if (appData.debugMode)
8879       fprintf(debugFP, "Parsing game history: %s\n", game);
8880
8881     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8882     gameInfo.site = StrSave(appData.icsHost);
8883     gameInfo.date = PGNDate();
8884     gameInfo.round = StrSave("-");
8885
8886     /* Parse out names of players */
8887     while (*game == ' ') game++;
8888     p = buf;
8889     while (*game != ' ') *p++ = *game++;
8890     *p = NULLCHAR;
8891     gameInfo.white = StrSave(buf);
8892     while (*game == ' ') game++;
8893     p = buf;
8894     while (*game != ' ' && *game != '\n') *p++ = *game++;
8895     *p = NULLCHAR;
8896     gameInfo.black = StrSave(buf);
8897
8898     /* Parse moves */
8899     boardIndex = blackPlaysFirst ? 1 : 0;
8900     yynewstr(game);
8901     for (;;) {
8902         yyboardindex = boardIndex;
8903         moveType = (ChessMove) Myylex();
8904         switch (moveType) {
8905           case IllegalMove:             /* maybe suicide chess, etc. */
8906   if (appData.debugMode) {
8907     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8908     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8909     setbuf(debugFP, NULL);
8910   }
8911           case WhitePromotion:
8912           case BlackPromotion:
8913           case WhiteNonPromotion:
8914           case BlackNonPromotion:
8915           case NormalMove:
8916           case WhiteCapturesEnPassant:
8917           case BlackCapturesEnPassant:
8918           case WhiteKingSideCastle:
8919           case WhiteQueenSideCastle:
8920           case BlackKingSideCastle:
8921           case BlackQueenSideCastle:
8922           case WhiteKingSideCastleWild:
8923           case WhiteQueenSideCastleWild:
8924           case BlackKingSideCastleWild:
8925           case BlackQueenSideCastleWild:
8926           /* PUSH Fabien */
8927           case WhiteHSideCastleFR:
8928           case WhiteASideCastleFR:
8929           case BlackHSideCastleFR:
8930           case BlackASideCastleFR:
8931           /* POP Fabien */
8932             fromX = currentMoveString[0] - AAA;
8933             fromY = currentMoveString[1] - ONE;
8934             toX = currentMoveString[2] - AAA;
8935             toY = currentMoveString[3] - ONE;
8936             promoChar = currentMoveString[4];
8937             break;
8938           case WhiteDrop:
8939           case BlackDrop:
8940             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8941             fromX = moveType == WhiteDrop ?
8942               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8943             (int) CharToPiece(ToLower(currentMoveString[0]));
8944             fromY = DROP_RANK;
8945             toX = currentMoveString[2] - AAA;
8946             toY = currentMoveString[3] - ONE;
8947             promoChar = NULLCHAR;
8948             break;
8949           case AmbiguousMove:
8950             /* bug? */
8951             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8952   if (appData.debugMode) {
8953     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8954     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8955     setbuf(debugFP, NULL);
8956   }
8957             DisplayError(buf, 0);
8958             return;
8959           case ImpossibleMove:
8960             /* bug? */
8961             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8962   if (appData.debugMode) {
8963     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8964     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8965     setbuf(debugFP, NULL);
8966   }
8967             DisplayError(buf, 0);
8968             return;
8969           case EndOfFile:
8970             if (boardIndex < backwardMostMove) {
8971                 /* Oops, gap.  How did that happen? */
8972                 DisplayError(_("Gap in move list"), 0);
8973                 return;
8974             }
8975             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8976             if (boardIndex > forwardMostMove) {
8977                 forwardMostMove = boardIndex;
8978             }
8979             return;
8980           case ElapsedTime:
8981             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8982                 strcat(parseList[boardIndex-1], " ");
8983                 strcat(parseList[boardIndex-1], yy_text);
8984             }
8985             continue;
8986           case Comment:
8987           case PGNTag:
8988           case NAG:
8989           default:
8990             /* ignore */
8991             continue;
8992           case WhiteWins:
8993           case BlackWins:
8994           case GameIsDrawn:
8995           case GameUnfinished:
8996             if (gameMode == IcsExamining) {
8997                 if (boardIndex < backwardMostMove) {
8998                     /* Oops, gap.  How did that happen? */
8999                     return;
9000                 }
9001                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9002                 return;
9003             }
9004             gameInfo.result = moveType;
9005             p = strchr(yy_text, '{');
9006             if (p == NULL) p = strchr(yy_text, '(');
9007             if (p == NULL) {
9008                 p = yy_text;
9009                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9010             } else {
9011                 q = strchr(p, *p == '{' ? '}' : ')');
9012                 if (q != NULL) *q = NULLCHAR;
9013                 p++;
9014             }
9015             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9016             gameInfo.resultDetails = StrSave(p);
9017             continue;
9018         }
9019         if (boardIndex >= forwardMostMove &&
9020             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9021             backwardMostMove = blackPlaysFirst ? 1 : 0;
9022             return;
9023         }
9024         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9025                                  fromY, fromX, toY, toX, promoChar,
9026                                  parseList[boardIndex]);
9027         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9028         /* currentMoveString is set as a side-effect of yylex */
9029         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9030         strcat(moveList[boardIndex], "\n");
9031         boardIndex++;
9032         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9033         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9034           case MT_NONE:
9035           case MT_STALEMATE:
9036           default:
9037             break;
9038           case MT_CHECK:
9039             if(gameInfo.variant != VariantShogi)
9040                 strcat(parseList[boardIndex - 1], "+");
9041             break;
9042           case MT_CHECKMATE:
9043           case MT_STAINMATE:
9044             strcat(parseList[boardIndex - 1], "#");
9045             break;
9046         }
9047     }
9048 }
9049
9050
9051 /* Apply a move to the given board  */
9052 void
9053 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9054 {
9055   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9056   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9057
9058     /* [HGM] compute & store e.p. status and castling rights for new position */
9059     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9060
9061       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9062       oldEP = (signed char)board[EP_STATUS];
9063       board[EP_STATUS] = EP_NONE;
9064
9065   if (fromY == DROP_RANK) {
9066         /* must be first */
9067         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9068             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9069             return;
9070         }
9071         piece = board[toY][toX] = (ChessSquare) fromX;
9072   } else {
9073       int i;
9074
9075       if( board[toY][toX] != EmptySquare )
9076            board[EP_STATUS] = EP_CAPTURE;
9077
9078       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9079            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9080                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9081       } else
9082       if( board[fromY][fromX] == WhitePawn ) {
9083            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9084                board[EP_STATUS] = EP_PAWN_MOVE;
9085            if( toY-fromY==2) {
9086                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9087                         gameInfo.variant != VariantBerolina || toX < fromX)
9088                       board[EP_STATUS] = toX | berolina;
9089                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9090                         gameInfo.variant != VariantBerolina || toX > fromX)
9091                       board[EP_STATUS] = toX;
9092            }
9093       } else
9094       if( board[fromY][fromX] == BlackPawn ) {
9095            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9096                board[EP_STATUS] = EP_PAWN_MOVE;
9097            if( toY-fromY== -2) {
9098                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9099                         gameInfo.variant != VariantBerolina || toX < fromX)
9100                       board[EP_STATUS] = toX | berolina;
9101                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9102                         gameInfo.variant != VariantBerolina || toX > fromX)
9103                       board[EP_STATUS] = toX;
9104            }
9105        }
9106
9107        for(i=0; i<nrCastlingRights; i++) {
9108            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9109               board[CASTLING][i] == toX   && castlingRank[i] == toY
9110              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9111        }
9112
9113      if (fromX == toX && fromY == toY) return;
9114
9115      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9116      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9117      if(gameInfo.variant == VariantKnightmate)
9118          king += (int) WhiteUnicorn - (int) WhiteKing;
9119
9120     /* Code added by Tord: */
9121     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9122     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9123         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9124       board[fromY][fromX] = EmptySquare;
9125       board[toY][toX] = EmptySquare;
9126       if((toX > fromX) != (piece == WhiteRook)) {
9127         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9128       } else {
9129         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9130       }
9131     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9132                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9133       board[fromY][fromX] = EmptySquare;
9134       board[toY][toX] = EmptySquare;
9135       if((toX > fromX) != (piece == BlackRook)) {
9136         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9137       } else {
9138         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9139       }
9140     /* End of code added by Tord */
9141
9142     } else if (board[fromY][fromX] == king
9143         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9144         && toY == fromY && toX > fromX+1) {
9145         board[fromY][fromX] = EmptySquare;
9146         board[toY][toX] = king;
9147         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9148         board[fromY][BOARD_RGHT-1] = EmptySquare;
9149     } else if (board[fromY][fromX] == king
9150         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9151                && toY == fromY && toX < fromX-1) {
9152         board[fromY][fromX] = EmptySquare;
9153         board[toY][toX] = king;
9154         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9155         board[fromY][BOARD_LEFT] = EmptySquare;
9156     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9157                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9158                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9159                ) {
9160         /* white pawn promotion */
9161         board[toY][toX] = CharToPiece(ToUpper(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] == WhitePawn)
9171                && (board[toY][toX] == EmptySquare)) {
9172         board[fromY][fromX] = EmptySquare;
9173         board[toY][toX] = WhitePawn;
9174         captured = board[toY - 1][toX];
9175         board[toY - 1][toX] = EmptySquare;
9176     } else if ((fromY == BOARD_HEIGHT-4)
9177                && (toX == fromX)
9178                && gameInfo.variant == VariantBerolina
9179                && (board[fromY][fromX] == WhitePawn)
9180                && (board[toY][toX] == EmptySquare)) {
9181         board[fromY][fromX] = EmptySquare;
9182         board[toY][toX] = WhitePawn;
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 if (board[fromY][fromX] == king
9190         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9191                && toY == fromY && toX > fromX+1) {
9192         board[fromY][fromX] = EmptySquare;
9193         board[toY][toX] = king;
9194         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9195         board[fromY][BOARD_RGHT-1] = EmptySquare;
9196     } else if (board[fromY][fromX] == king
9197         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9198                && toY == fromY && toX < fromX-1) {
9199         board[fromY][fromX] = EmptySquare;
9200         board[toY][toX] = king;
9201         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9202         board[fromY][BOARD_LEFT] = EmptySquare;
9203     } else if (fromY == 7 && fromX == 3
9204                && board[fromY][fromX] == BlackKing
9205                && toY == 7 && toX == 5) {
9206         board[fromY][fromX] = EmptySquare;
9207         board[toY][toX] = BlackKing;
9208         board[fromY][7] = EmptySquare;
9209         board[toY][4] = BlackRook;
9210     } else if (fromY == 7 && fromX == 3
9211                && board[fromY][fromX] == BlackKing
9212                && toY == 7 && toX == 1) {
9213         board[fromY][fromX] = EmptySquare;
9214         board[toY][toX] = BlackKing;
9215         board[fromY][0] = EmptySquare;
9216         board[toY][2] = BlackRook;
9217     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9218                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9219                && toY < promoRank && promoChar
9220                ) {
9221         /* black pawn promotion */
9222         board[toY][toX] = CharToPiece(ToLower(promoChar));
9223         if(gameInfo.variant==VariantBughouse ||
9224            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9225             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9226         board[fromY][fromX] = EmptySquare;
9227     } else if ((fromY < BOARD_HEIGHT>>1)
9228                && (toX != fromX)
9229                && gameInfo.variant != VariantXiangqi
9230                && gameInfo.variant != VariantBerolina
9231                && (board[fromY][fromX] == BlackPawn)
9232                && (board[toY][toX] == EmptySquare)) {
9233         board[fromY][fromX] = EmptySquare;
9234         board[toY][toX] = BlackPawn;
9235         captured = board[toY + 1][toX];
9236         board[toY + 1][toX] = EmptySquare;
9237     } else if ((fromY == 3)
9238                && (toX == fromX)
9239                && gameInfo.variant == VariantBerolina
9240                && (board[fromY][fromX] == BlackPawn)
9241                && (board[toY][toX] == EmptySquare)) {
9242         board[fromY][fromX] = EmptySquare;
9243         board[toY][toX] = BlackPawn;
9244         if(oldEP & EP_BEROLIN_A) {
9245                 captured = board[fromY][fromX-1];
9246                 board[fromY][fromX-1] = EmptySquare;
9247         }else{  captured = board[fromY][fromX+1];
9248                 board[fromY][fromX+1] = EmptySquare;
9249         }
9250     } else {
9251         board[toY][toX] = board[fromY][fromX];
9252         board[fromY][fromX] = EmptySquare;
9253     }
9254   }
9255
9256     if (gameInfo.holdingsWidth != 0) {
9257
9258       /* !!A lot more code needs to be written to support holdings  */
9259       /* [HGM] OK, so I have written it. Holdings are stored in the */
9260       /* penultimate board files, so they are automaticlly stored   */
9261       /* in the game history.                                       */
9262       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9263                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9264         /* Delete from holdings, by decreasing count */
9265         /* and erasing image if necessary            */
9266         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9267         if(p < (int) BlackPawn) { /* white drop */
9268              p -= (int)WhitePawn;
9269                  p = PieceToNumber((ChessSquare)p);
9270              if(p >= gameInfo.holdingsSize) p = 0;
9271              if(--board[p][BOARD_WIDTH-2] <= 0)
9272                   board[p][BOARD_WIDTH-1] = EmptySquare;
9273              if((int)board[p][BOARD_WIDTH-2] < 0)
9274                         board[p][BOARD_WIDTH-2] = 0;
9275         } else {                  /* black drop */
9276              p -= (int)BlackPawn;
9277                  p = PieceToNumber((ChessSquare)p);
9278              if(p >= gameInfo.holdingsSize) p = 0;
9279              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9280                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9281              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9282                         board[BOARD_HEIGHT-1-p][1] = 0;
9283         }
9284       }
9285       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9286           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9287         /* [HGM] holdings: Add to holdings, if holdings exist */
9288         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9289                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9290                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9291         }
9292         p = (int) captured;
9293         if (p >= (int) BlackPawn) {
9294           p -= (int)BlackPawn;
9295           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9296                   /* in Shogi restore piece to its original  first */
9297                   captured = (ChessSquare) (DEMOTED captured);
9298                   p = DEMOTED p;
9299           }
9300           p = PieceToNumber((ChessSquare)p);
9301           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9302           board[p][BOARD_WIDTH-2]++;
9303           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9304         } else {
9305           p -= (int)WhitePawn;
9306           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9307                   captured = (ChessSquare) (DEMOTED captured);
9308                   p = DEMOTED p;
9309           }
9310           p = PieceToNumber((ChessSquare)p);
9311           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9312           board[BOARD_HEIGHT-1-p][1]++;
9313           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9314         }
9315       }
9316     } else if (gameInfo.variant == VariantAtomic) {
9317       if (captured != EmptySquare) {
9318         int y, x;
9319         for (y = toY-1; y <= toY+1; y++) {
9320           for (x = toX-1; x <= toX+1; x++) {
9321             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9322                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9323               board[y][x] = EmptySquare;
9324             }
9325           }
9326         }
9327         board[toY][toX] = EmptySquare;
9328       }
9329     }
9330     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9331         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9332     } else
9333     if(promoChar == '+') {
9334         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9335         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9336     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9337         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9338     }
9339     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9340                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9341         // [HGM] superchess: take promotion piece out of holdings
9342         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9343         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9344             if(!--board[k][BOARD_WIDTH-2])
9345                 board[k][BOARD_WIDTH-1] = EmptySquare;
9346         } else {
9347             if(!--board[BOARD_HEIGHT-1-k][1])
9348                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9349         }
9350     }
9351
9352 }
9353
9354 /* Updates forwardMostMove */
9355 void
9356 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9357 {
9358 //    forwardMostMove++; // [HGM] bare: moved downstream
9359
9360     (void) CoordsToAlgebraic(boards[forwardMostMove],
9361                              PosFlags(forwardMostMove),
9362                              fromY, fromX, toY, toX, promoChar,
9363                              parseList[forwardMostMove]);
9364
9365     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9366         int timeLeft; static int lastLoadFlag=0; int king, piece;
9367         piece = boards[forwardMostMove][fromY][fromX];
9368         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9369         if(gameInfo.variant == VariantKnightmate)
9370             king += (int) WhiteUnicorn - (int) WhiteKing;
9371         if(forwardMostMove == 0) {
9372             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9373                 fprintf(serverMoves, "%s;", UserName());
9374             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9375                 fprintf(serverMoves, "%s;", second.tidy);
9376             fprintf(serverMoves, "%s;", first.tidy);
9377             if(gameMode == MachinePlaysWhite)
9378                 fprintf(serverMoves, "%s;", UserName());
9379             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9380                 fprintf(serverMoves, "%s;", second.tidy);
9381         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9382         lastLoadFlag = loadFlag;
9383         // print base move
9384         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9385         // print castling suffix
9386         if( toY == fromY && piece == king ) {
9387             if(toX-fromX > 1)
9388                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9389             if(fromX-toX >1)
9390                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9391         }
9392         // e.p. suffix
9393         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9394              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9395              boards[forwardMostMove][toY][toX] == EmptySquare
9396              && fromX != toX && fromY != toY)
9397                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9398         // promotion suffix
9399         if(promoChar != NULLCHAR)
9400                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9401         if(!loadFlag) {
9402                 char buf[MOVE_LEN*2], *p; int len;
9403             fprintf(serverMoves, "/%d/%d",
9404                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9405             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9406             else                      timeLeft = blackTimeRemaining/1000;
9407             fprintf(serverMoves, "/%d", timeLeft);
9408                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9409                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9410                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9411             fprintf(serverMoves, "/%s", buf);
9412         }
9413         fflush(serverMoves);
9414     }
9415
9416     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9417         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9418       return;
9419     }
9420     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9421     if (commentList[forwardMostMove+1] != NULL) {
9422         free(commentList[forwardMostMove+1]);
9423         commentList[forwardMostMove+1] = NULL;
9424     }
9425     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9426     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9427     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9428     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9429     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9430     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9431     adjustedClock = FALSE;
9432     gameInfo.result = GameUnfinished;
9433     if (gameInfo.resultDetails != NULL) {
9434         free(gameInfo.resultDetails);
9435         gameInfo.resultDetails = NULL;
9436     }
9437     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9438                               moveList[forwardMostMove - 1]);
9439     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9440       case MT_NONE:
9441       case MT_STALEMATE:
9442       default:
9443         break;
9444       case MT_CHECK:
9445         if(gameInfo.variant != VariantShogi)
9446             strcat(parseList[forwardMostMove - 1], "+");
9447         break;
9448       case MT_CHECKMATE:
9449       case MT_STAINMATE:
9450         strcat(parseList[forwardMostMove - 1], "#");
9451         break;
9452     }
9453
9454 }
9455
9456 /* Updates currentMove if not pausing */
9457 void
9458 ShowMove (int fromX, int fromY, int toX, int toY)
9459 {
9460     int instant = (gameMode == PlayFromGameFile) ?
9461         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9462     if(appData.noGUI) return;
9463     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9464         if (!instant) {
9465             if (forwardMostMove == currentMove + 1) {
9466                 AnimateMove(boards[forwardMostMove - 1],
9467                             fromX, fromY, toX, toY);
9468             }
9469             if (appData.highlightLastMove) {
9470                 SetHighlights(fromX, fromY, toX, toY);
9471             }
9472         }
9473         currentMove = forwardMostMove;
9474     }
9475
9476     if (instant) return;
9477
9478     DisplayMove(currentMove - 1);
9479     DrawPosition(FALSE, boards[currentMove]);
9480     DisplayBothClocks();
9481     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9482 }
9483
9484 void
9485 SendEgtPath (ChessProgramState *cps)
9486 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9487         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9488
9489         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9490
9491         while(*p) {
9492             char c, *q = name+1, *r, *s;
9493
9494             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9495             while(*p && *p != ',') *q++ = *p++;
9496             *q++ = ':'; *q = 0;
9497             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9498                 strcmp(name, ",nalimov:") == 0 ) {
9499                 // take nalimov path from the menu-changeable option first, if it is defined
9500               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9501                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9502             } else
9503             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9504                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9505                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9506                 s = r = StrStr(s, ":") + 1; // beginning of path info
9507                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9508                 c = *r; *r = 0;             // temporarily null-terminate path info
9509                     *--q = 0;               // strip of trailig ':' from name
9510                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9511                 *r = c;
9512                 SendToProgram(buf,cps);     // send egtbpath command for this format
9513             }
9514             if(*p == ',') p++; // read away comma to position for next format name
9515         }
9516 }
9517
9518 void
9519 InitChessProgram (ChessProgramState *cps, int setup)
9520 /* setup needed to setup FRC opening position */
9521 {
9522     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9523     if (appData.noChessProgram) return;
9524     hintRequested = FALSE;
9525     bookRequested = FALSE;
9526
9527     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9528     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9529     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9530     if(cps->memSize) { /* [HGM] memory */
9531       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9532         SendToProgram(buf, cps);
9533     }
9534     SendEgtPath(cps); /* [HGM] EGT */
9535     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9536       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9537         SendToProgram(buf, cps);
9538     }
9539
9540     SendToProgram(cps->initString, cps);
9541     if (gameInfo.variant != VariantNormal &&
9542         gameInfo.variant != VariantLoadable
9543         /* [HGM] also send variant if board size non-standard */
9544         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9545                                             ) {
9546       char *v = VariantName(gameInfo.variant);
9547       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9548         /* [HGM] in protocol 1 we have to assume all variants valid */
9549         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9550         DisplayFatalError(buf, 0, 1);
9551         return;
9552       }
9553
9554       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9555       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9556       if( gameInfo.variant == VariantXiangqi )
9557            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9558       if( gameInfo.variant == VariantShogi )
9559            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9560       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9561            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9562       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9563           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9564            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9565       if( gameInfo.variant == VariantCourier )
9566            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9567       if( gameInfo.variant == VariantSuper )
9568            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9569       if( gameInfo.variant == VariantGreat )
9570            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9571       if( gameInfo.variant == VariantSChess )
9572            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9573       if( gameInfo.variant == VariantGrand )
9574            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9575
9576       if(overruled) {
9577         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9578                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9579            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9580            if(StrStr(cps->variants, b) == NULL) {
9581                // specific sized variant not known, check if general sizing allowed
9582                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9583                    if(StrStr(cps->variants, "boardsize") == NULL) {
9584                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9585                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9586                        DisplayFatalError(buf, 0, 1);
9587                        return;
9588                    }
9589                    /* [HGM] here we really should compare with the maximum supported board size */
9590                }
9591            }
9592       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9593       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9594       SendToProgram(buf, cps);
9595     }
9596     currentlyInitializedVariant = gameInfo.variant;
9597
9598     /* [HGM] send opening position in FRC to first engine */
9599     if(setup) {
9600           SendToProgram("force\n", cps);
9601           SendBoard(cps, 0);
9602           /* engine is now in force mode! Set flag to wake it up after first move. */
9603           setboardSpoiledMachineBlack = 1;
9604     }
9605
9606     if (cps->sendICS) {
9607       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9608       SendToProgram(buf, cps);
9609     }
9610     cps->maybeThinking = FALSE;
9611     cps->offeredDraw = 0;
9612     if (!appData.icsActive) {
9613         SendTimeControl(cps, movesPerSession, timeControl,
9614                         timeIncrement, appData.searchDepth,
9615                         searchTime);
9616     }
9617     if (appData.showThinking
9618         // [HGM] thinking: four options require thinking output to be sent
9619         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9620                                 ) {
9621         SendToProgram("post\n", cps);
9622     }
9623     SendToProgram("hard\n", cps);
9624     if (!appData.ponderNextMove) {
9625         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9626            it without being sure what state we are in first.  "hard"
9627            is not a toggle, so that one is OK.
9628          */
9629         SendToProgram("easy\n", cps);
9630     }
9631     if (cps->usePing) {
9632       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9633       SendToProgram(buf, cps);
9634     }
9635     cps->initDone = TRUE;
9636     ClearEngineOutputPane(cps == &second);
9637 }
9638
9639
9640 void
9641 StartChessProgram (ChessProgramState *cps)
9642 {
9643     char buf[MSG_SIZ];
9644     int err;
9645
9646     if (appData.noChessProgram) return;
9647     cps->initDone = FALSE;
9648
9649     if (strcmp(cps->host, "localhost") == 0) {
9650         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9651     } else if (*appData.remoteShell == NULLCHAR) {
9652         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9653     } else {
9654         if (*appData.remoteUser == NULLCHAR) {
9655           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9656                     cps->program);
9657         } else {
9658           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9659                     cps->host, appData.remoteUser, cps->program);
9660         }
9661         err = StartChildProcess(buf, "", &cps->pr);
9662     }
9663
9664     if (err != 0) {
9665       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9666         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9667         if(cps != &first) return;
9668         appData.noChessProgram = TRUE;
9669         ThawUI();
9670         SetNCPMode();
9671 //      DisplayFatalError(buf, err, 1);
9672 //      cps->pr = NoProc;
9673 //      cps->isr = NULL;
9674         return;
9675     }
9676
9677     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9678     if (cps->protocolVersion > 1) {
9679       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9680       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9681       cps->comboCnt = 0;  //                and values of combo boxes
9682       SendToProgram(buf, cps);
9683     } else {
9684       SendToProgram("xboard\n", cps);
9685     }
9686 }
9687
9688 void
9689 TwoMachinesEventIfReady P((void))
9690 {
9691   static int curMess = 0;
9692   if (first.lastPing != first.lastPong) {
9693     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9694     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9695     return;
9696   }
9697   if (second.lastPing != second.lastPong) {
9698     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9699     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9700     return;
9701   }
9702   DisplayMessage("", ""); curMess = 0;
9703   ThawUI();
9704   TwoMachinesEvent();
9705 }
9706
9707 char *
9708 MakeName (char *template)
9709 {
9710     time_t clock;
9711     struct tm *tm;
9712     static char buf[MSG_SIZ];
9713     char *p = buf;
9714     int i;
9715
9716     clock = time((time_t *)NULL);
9717     tm = localtime(&clock);
9718
9719     while(*p++ = *template++) if(p[-1] == '%') {
9720         switch(*template++) {
9721           case 0:   *p = 0; return buf;
9722           case 'Y': i = tm->tm_year+1900; break;
9723           case 'y': i = tm->tm_year-100; break;
9724           case 'M': i = tm->tm_mon+1; break;
9725           case 'd': i = tm->tm_mday; break;
9726           case 'h': i = tm->tm_hour; break;
9727           case 'm': i = tm->tm_min; break;
9728           case 's': i = tm->tm_sec; break;
9729           default:  i = 0;
9730         }
9731         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9732     }
9733     return buf;
9734 }
9735
9736 int
9737 CountPlayers (char *p)
9738 {
9739     int n = 0;
9740     while(p = strchr(p, '\n')) p++, n++; // count participants
9741     return n;
9742 }
9743
9744 FILE *
9745 WriteTourneyFile (char *results, FILE *f)
9746 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9747     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9748     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9749         // create a file with tournament description
9750         fprintf(f, "-participants {%s}\n", appData.participants);
9751         fprintf(f, "-seedBase %d\n", appData.seedBase);
9752         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9753         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9754         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9755         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9756         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9757         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9758         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9759         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9760         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9761         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9762         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9763         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9764         if(searchTime > 0)
9765                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9766         else {
9767                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9768                 fprintf(f, "-tc %s\n", appData.timeControl);
9769                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9770         }
9771         fprintf(f, "-results \"%s\"\n", results);
9772     }
9773     return f;
9774 }
9775
9776 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9777
9778 void
9779 Substitute (char *participants, int expunge)
9780 {
9781     int i, changed, changes=0, nPlayers=0;
9782     char *p, *q, *r, buf[MSG_SIZ];
9783     if(participants == NULL) return;
9784     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9785     r = p = participants; q = appData.participants;
9786     while(*p && *p == *q) {
9787         if(*p == '\n') r = p+1, nPlayers++;
9788         p++; q++;
9789     }
9790     if(*p) { // difference
9791         while(*p && *p++ != '\n');
9792         while(*q && *q++ != '\n');
9793       changed = nPlayers;
9794         changes = 1 + (strcmp(p, q) != 0);
9795     }
9796     if(changes == 1) { // a single engine mnemonic was changed
9797         q = r; while(*q) nPlayers += (*q++ == '\n');
9798         p = buf; while(*r && (*p = *r++) != '\n') p++;
9799         *p = NULLCHAR;
9800         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9801         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9802         if(mnemonic[i]) { // The substitute is valid
9803             FILE *f;
9804             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9805                 flock(fileno(f), LOCK_EX);
9806                 ParseArgsFromFile(f);
9807                 fseek(f, 0, SEEK_SET);
9808                 FREE(appData.participants); appData.participants = participants;
9809                 if(expunge) { // erase results of replaced engine
9810                     int len = strlen(appData.results), w, b, dummy;
9811                     for(i=0; i<len; i++) {
9812                         Pairing(i, nPlayers, &w, &b, &dummy);
9813                         if((w == changed || b == changed) && appData.results[i] == '*') {
9814                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9815                             fclose(f);
9816                             return;
9817                         }
9818                     }
9819                     for(i=0; i<len; i++) {
9820                         Pairing(i, nPlayers, &w, &b, &dummy);
9821                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9822                     }
9823                 }
9824                 WriteTourneyFile(appData.results, f);
9825                 fclose(f); // release lock
9826                 return;
9827             }
9828         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9829     }
9830     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9831     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9832     free(participants);
9833     return;
9834 }
9835
9836 int
9837 CreateTourney (char *name)
9838 {
9839         FILE *f;
9840         if(matchMode && strcmp(name, appData.tourneyFile)) {
9841              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9842         }
9843         if(name[0] == NULLCHAR) {
9844             if(appData.participants[0])
9845                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9846             return 0;
9847         }
9848         f = fopen(name, "r");
9849         if(f) { // file exists
9850             ASSIGN(appData.tourneyFile, name);
9851             ParseArgsFromFile(f); // parse it
9852         } else {
9853             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9854             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9855                 DisplayError(_("Not enough participants"), 0);
9856                 return 0;
9857             }
9858             ASSIGN(appData.tourneyFile, name);
9859             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9860             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9861         }
9862         fclose(f);
9863         appData.noChessProgram = FALSE;
9864         appData.clockMode = TRUE;
9865         SetGNUMode();
9866         return 1;
9867 }
9868
9869 int
9870 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9871 {
9872     char buf[MSG_SIZ], *p, *q;
9873     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9874     skip = !all && group[0]; // if group requested, we start in skip mode
9875     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9876         p = names; q = buf; header = 0;
9877         while(*p && *p != '\n') *q++ = *p++;
9878         *q = 0;
9879         if(*p == '\n') p++;
9880         if(buf[0] == '#') {
9881             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9882             depth++; // we must be entering a new group
9883             if(all) continue; // suppress printing group headers when complete list requested
9884             header = 1;
9885             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9886         }
9887         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9888         if(engineList[i]) free(engineList[i]);
9889         engineList[i] = strdup(buf);
9890         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9891         if(engineMnemonic[i]) free(engineMnemonic[i]);
9892         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9893             strcat(buf, " (");
9894             sscanf(q + 8, "%s", buf + strlen(buf));
9895             strcat(buf, ")");
9896         }
9897         engineMnemonic[i] = strdup(buf);
9898         i++;
9899     }
9900     engineList[i] = engineMnemonic[i] = NULL;
9901     return i;
9902 }
9903
9904 // following implemented as macro to avoid type limitations
9905 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9906
9907 void
9908 SwapEngines (int n)
9909 {   // swap settings for first engine and other engine (so far only some selected options)
9910     int h;
9911     char *p;
9912     if(n == 0) return;
9913     SWAP(directory, p)
9914     SWAP(chessProgram, p)
9915     SWAP(isUCI, h)
9916     SWAP(hasOwnBookUCI, h)
9917     SWAP(protocolVersion, h)
9918     SWAP(reuse, h)
9919     SWAP(scoreIsAbsolute, h)
9920     SWAP(timeOdds, h)
9921     SWAP(logo, p)
9922     SWAP(pgnName, p)
9923     SWAP(pvSAN, h)
9924     SWAP(engOptions, p)
9925 }
9926
9927 int
9928 SetPlayer (int player, char *p)
9929 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9930     int i;
9931     char buf[MSG_SIZ], *engineName;
9932     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9933     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9934     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9935     if(mnemonic[i]) {
9936         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9937         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9938         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9939         ParseArgsFromString(buf);
9940     }
9941     free(engineName);
9942     return i;
9943 }
9944
9945 char *recentEngines;
9946
9947 void
9948 RecentEngineEvent (int nr)
9949 {
9950     int n;
9951 //    SwapEngines(1); // bump first to second
9952 //    ReplaceEngine(&second, 1); // and load it there
9953     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9954     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9955     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9956         ReplaceEngine(&first, 0);
9957         FloatToFront(&appData.recentEngineList, command[n]);
9958     }
9959 }
9960
9961 int
9962 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9963 {   // determine players from game number
9964     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9965
9966     if(appData.tourneyType == 0) {
9967         roundsPerCycle = (nPlayers - 1) | 1;
9968         pairingsPerRound = nPlayers / 2;
9969     } else if(appData.tourneyType > 0) {
9970         roundsPerCycle = nPlayers - appData.tourneyType;
9971         pairingsPerRound = appData.tourneyType;
9972     }
9973     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9974     gamesPerCycle = gamesPerRound * roundsPerCycle;
9975     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9976     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9977     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9978     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9979     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9980     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9981
9982     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9983     if(appData.roundSync) *syncInterval = gamesPerRound;
9984
9985     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9986
9987     if(appData.tourneyType == 0) {
9988         if(curPairing == (nPlayers-1)/2 ) {
9989             *whitePlayer = curRound;
9990             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9991         } else {
9992             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9993             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9994             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9995             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9996         }
9997     } else if(appData.tourneyType > 1) {
9998         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
9999         *whitePlayer = curRound + appData.tourneyType;
10000     } else if(appData.tourneyType > 0) {
10001         *whitePlayer = curPairing;
10002         *blackPlayer = curRound + appData.tourneyType;
10003     }
10004
10005     // take care of white/black alternation per round. 
10006     // For cycles and games this is already taken care of by default, derived from matchGame!
10007     return curRound & 1;
10008 }
10009
10010 int
10011 NextTourneyGame (int nr, int *swapColors)
10012 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10013     char *p, *q;
10014     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10015     FILE *tf;
10016     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10017     tf = fopen(appData.tourneyFile, "r");
10018     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10019     ParseArgsFromFile(tf); fclose(tf);
10020     InitTimeControls(); // TC might be altered from tourney file
10021
10022     nPlayers = CountPlayers(appData.participants); // count participants
10023     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10024     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10025
10026     if(syncInterval) {
10027         p = q = appData.results;
10028         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10029         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10030             DisplayMessage(_("Waiting for other game(s)"),"");
10031             waitingForGame = TRUE;
10032             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10033             return 0;
10034         }
10035         waitingForGame = FALSE;
10036     }
10037
10038     if(appData.tourneyType < 0) {
10039         if(nr>=0 && !pairingReceived) {
10040             char buf[1<<16];
10041             if(pairing.pr == NoProc) {
10042                 if(!appData.pairingEngine[0]) {
10043                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10044                     return 0;
10045                 }
10046                 StartChessProgram(&pairing); // starts the pairing engine
10047             }
10048             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10049             SendToProgram(buf, &pairing);
10050             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10051             SendToProgram(buf, &pairing);
10052             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10053         }
10054         pairingReceived = 0;                              // ... so we continue here 
10055         *swapColors = 0;
10056         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10057         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10058         matchGame = 1; roundNr = nr / syncInterval + 1;
10059     }
10060
10061     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10062
10063     // redefine engines, engine dir, etc.
10064     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10065     if(first.pr == NoProc) {
10066       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10067       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10068     }
10069     if(second.pr == NoProc) {
10070       SwapEngines(1);
10071       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10072       SwapEngines(1);         // and make that valid for second engine by swapping
10073       InitEngine(&second, 1);
10074     }
10075     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10076     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10077     return 1;
10078 }
10079
10080 void
10081 NextMatchGame ()
10082 {   // performs game initialization that does not invoke engines, and then tries to start the game
10083     int res, firstWhite, swapColors = 0;
10084     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10085     if(matchMode && appData.debugMode) { // [HGM] debug split: game is part of a match; we might have to create a debug file just for this game
10086         char buf[MSG_SIZ];
10087         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10088         if(strcmp(buf, currentDebugFile)) { // name has changed
10089             FILE *f = fopen(buf, "w");
10090             if(f) { // if opening the new file failed, just keep using the old one
10091                 ASSIGN(currentDebugFile, buf);
10092                 fclose(debugFP);
10093                 debugFP = f;
10094             }
10095             if(appData.serverFileName) {
10096                 if(serverFP) fclose(serverFP);
10097                 serverFP = fopen(appData.serverFileName, "w");
10098                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10099                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10100             }
10101         }
10102     }
10103     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10104     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10105     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10106     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10107     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10108     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10109     Reset(FALSE, first.pr != NoProc);
10110     res = LoadGameOrPosition(matchGame); // setup game
10111     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10112     if(!res) return; // abort when bad game/pos file
10113     TwoMachinesEvent();
10114 }
10115
10116 void
10117 UserAdjudicationEvent (int result)
10118 {
10119     ChessMove gameResult = GameIsDrawn;
10120
10121     if( result > 0 ) {
10122         gameResult = WhiteWins;
10123     }
10124     else if( result < 0 ) {
10125         gameResult = BlackWins;
10126     }
10127
10128     if( gameMode == TwoMachinesPlay ) {
10129         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10130     }
10131 }
10132
10133
10134 // [HGM] save: calculate checksum of game to make games easily identifiable
10135 int
10136 StringCheckSum (char *s)
10137 {
10138         int i = 0;
10139         if(s==NULL) return 0;
10140         while(*s) i = i*259 + *s++;
10141         return i;
10142 }
10143
10144 int
10145 GameCheckSum ()
10146 {
10147         int i, sum=0;
10148         for(i=backwardMostMove; i<forwardMostMove; i++) {
10149                 sum += pvInfoList[i].depth;
10150                 sum += StringCheckSum(parseList[i]);
10151                 sum += StringCheckSum(commentList[i]);
10152                 sum *= 261;
10153         }
10154         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10155         return sum + StringCheckSum(commentList[i]);
10156 } // end of save patch
10157
10158 void
10159 GameEnds (ChessMove result, char *resultDetails, int whosays)
10160 {
10161     GameMode nextGameMode;
10162     int isIcsGame;
10163     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10164
10165     if(endingGame) return; /* [HGM] crash: forbid recursion */
10166     endingGame = 1;
10167     if(twoBoards) { // [HGM] dual: switch back to one board
10168         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10169         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10170     }
10171     if (appData.debugMode) {
10172       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10173               result, resultDetails ? resultDetails : "(null)", whosays);
10174     }
10175
10176     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10177
10178     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10179         /* If we are playing on ICS, the server decides when the
10180            game is over, but the engine can offer to draw, claim
10181            a draw, or resign.
10182          */
10183 #if ZIPPY
10184         if (appData.zippyPlay && first.initDone) {
10185             if (result == GameIsDrawn) {
10186                 /* In case draw still needs to be claimed */
10187                 SendToICS(ics_prefix);
10188                 SendToICS("draw\n");
10189             } else if (StrCaseStr(resultDetails, "resign")) {
10190                 SendToICS(ics_prefix);
10191                 SendToICS("resign\n");
10192             }
10193         }
10194 #endif
10195         endingGame = 0; /* [HGM] crash */
10196         return;
10197     }
10198
10199     /* If we're loading the game from a file, stop */
10200     if (whosays == GE_FILE) {
10201       (void) StopLoadGameTimer();
10202       gameFileFP = NULL;
10203     }
10204
10205     /* Cancel draw offers */
10206     first.offeredDraw = second.offeredDraw = 0;
10207
10208     /* If this is an ICS game, only ICS can really say it's done;
10209        if not, anyone can. */
10210     isIcsGame = (gameMode == IcsPlayingWhite ||
10211                  gameMode == IcsPlayingBlack ||
10212                  gameMode == IcsObserving    ||
10213                  gameMode == IcsExamining);
10214
10215     if (!isIcsGame || whosays == GE_ICS) {
10216         /* OK -- not an ICS game, or ICS said it was done */
10217         StopClocks();
10218         if (!isIcsGame && !appData.noChessProgram)
10219           SetUserThinkingEnables();
10220
10221         /* [HGM] if a machine claims the game end we verify this claim */
10222         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10223             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10224                 char claimer;
10225                 ChessMove trueResult = (ChessMove) -1;
10226
10227                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10228                                             first.twoMachinesColor[0] :
10229                                             second.twoMachinesColor[0] ;
10230
10231                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10232                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10233                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10234                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10235                 } else
10236                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10237                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10238                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10239                 } else
10240                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10241                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10242                 }
10243
10244                 // now verify win claims, but not in drop games, as we don't understand those yet
10245                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10246                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10247                     (result == WhiteWins && claimer == 'w' ||
10248                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10249                       if (appData.debugMode) {
10250                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10251                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10252                       }
10253                       if(result != trueResult) {
10254                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10255                               result = claimer == 'w' ? BlackWins : WhiteWins;
10256                               resultDetails = buf;
10257                       }
10258                 } else
10259                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10260                     && (forwardMostMove <= backwardMostMove ||
10261                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10262                         (claimer=='b')==(forwardMostMove&1))
10263                                                                                   ) {
10264                       /* [HGM] verify: draws that were not flagged are false claims */
10265                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10266                       result = claimer == 'w' ? BlackWins : WhiteWins;
10267                       resultDetails = buf;
10268                 }
10269                 /* (Claiming a loss is accepted no questions asked!) */
10270             }
10271             /* [HGM] bare: don't allow bare King to win */
10272             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10273                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10274                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10275                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10276                && result != GameIsDrawn)
10277             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10278                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10279                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10280                         if(p >= 0 && p <= (int)WhiteKing) k++;
10281                 }
10282                 if (appData.debugMode) {
10283                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10284                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10285                 }
10286                 if(k <= 1) {
10287                         result = GameIsDrawn;
10288                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10289                         resultDetails = buf;
10290                 }
10291             }
10292         }
10293
10294
10295         if(serverMoves != NULL && !loadFlag) { char c = '=';
10296             if(result==WhiteWins) c = '+';
10297             if(result==BlackWins) c = '-';
10298             if(resultDetails != NULL)
10299                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10300         }
10301         if (resultDetails != NULL) {
10302             gameInfo.result = result;
10303             gameInfo.resultDetails = StrSave(resultDetails);
10304
10305             /* display last move only if game was not loaded from file */
10306             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10307                 DisplayMove(currentMove - 1);
10308
10309             if (forwardMostMove != 0) {
10310                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10311                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10312                                                                 ) {
10313                     if (*appData.saveGameFile != NULLCHAR) {
10314                         SaveGameToFile(appData.saveGameFile, TRUE);
10315                     } else if (appData.autoSaveGames) {
10316                         AutoSaveGame();
10317                     }
10318                     if (*appData.savePositionFile != NULLCHAR) {
10319                         SavePositionToFile(appData.savePositionFile);
10320                     }
10321                 }
10322             }
10323
10324             /* Tell program how game ended in case it is learning */
10325             /* [HGM] Moved this to after saving the PGN, just in case */
10326             /* engine died and we got here through time loss. In that */
10327             /* case we will get a fatal error writing the pipe, which */
10328             /* would otherwise lose us the PGN.                       */
10329             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10330             /* output during GameEnds should never be fatal anymore   */
10331             if (gameMode == MachinePlaysWhite ||
10332                 gameMode == MachinePlaysBlack ||
10333                 gameMode == TwoMachinesPlay ||
10334                 gameMode == IcsPlayingWhite ||
10335                 gameMode == IcsPlayingBlack ||
10336                 gameMode == BeginningOfGame) {
10337                 char buf[MSG_SIZ];
10338                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10339                         resultDetails);
10340                 if (first.pr != NoProc) {
10341                     SendToProgram(buf, &first);
10342                 }
10343                 if (second.pr != NoProc &&
10344                     gameMode == TwoMachinesPlay) {
10345                     SendToProgram(buf, &second);
10346                 }
10347             }
10348         }
10349
10350         if (appData.icsActive) {
10351             if (appData.quietPlay &&
10352                 (gameMode == IcsPlayingWhite ||
10353                  gameMode == IcsPlayingBlack)) {
10354                 SendToICS(ics_prefix);
10355                 SendToICS("set shout 1\n");
10356             }
10357             nextGameMode = IcsIdle;
10358             ics_user_moved = FALSE;
10359             /* clean up premove.  It's ugly when the game has ended and the
10360              * premove highlights are still on the board.
10361              */
10362             if (gotPremove) {
10363               gotPremove = FALSE;
10364               ClearPremoveHighlights();
10365               DrawPosition(FALSE, boards[currentMove]);
10366             }
10367             if (whosays == GE_ICS) {
10368                 switch (result) {
10369                 case WhiteWins:
10370                     if (gameMode == IcsPlayingWhite)
10371                         PlayIcsWinSound();
10372                     else if(gameMode == IcsPlayingBlack)
10373                         PlayIcsLossSound();
10374                     break;
10375                 case BlackWins:
10376                     if (gameMode == IcsPlayingBlack)
10377                         PlayIcsWinSound();
10378                     else if(gameMode == IcsPlayingWhite)
10379                         PlayIcsLossSound();
10380                     break;
10381                 case GameIsDrawn:
10382                     PlayIcsDrawSound();
10383                     break;
10384                 default:
10385                     PlayIcsUnfinishedSound();
10386                 }
10387             }
10388         } else if (gameMode == EditGame ||
10389                    gameMode == PlayFromGameFile ||
10390                    gameMode == AnalyzeMode ||
10391                    gameMode == AnalyzeFile) {
10392             nextGameMode = gameMode;
10393         } else {
10394             nextGameMode = EndOfGame;
10395         }
10396         pausing = FALSE;
10397         ModeHighlight();
10398     } else {
10399         nextGameMode = gameMode;
10400     }
10401
10402     if (appData.noChessProgram) {
10403         gameMode = nextGameMode;
10404         ModeHighlight();
10405         endingGame = 0; /* [HGM] crash */
10406         return;
10407     }
10408
10409     if (first.reuse) {
10410         /* Put first chess program into idle state */
10411         if (first.pr != NoProc &&
10412             (gameMode == MachinePlaysWhite ||
10413              gameMode == MachinePlaysBlack ||
10414              gameMode == TwoMachinesPlay ||
10415              gameMode == IcsPlayingWhite ||
10416              gameMode == IcsPlayingBlack ||
10417              gameMode == BeginningOfGame)) {
10418             SendToProgram("force\n", &first);
10419             if (first.usePing) {
10420               char buf[MSG_SIZ];
10421               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10422               SendToProgram(buf, &first);
10423             }
10424         }
10425     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10426         /* Kill off first chess program */
10427         if (first.isr != NULL)
10428           RemoveInputSource(first.isr);
10429         first.isr = NULL;
10430
10431         if (first.pr != NoProc) {
10432             ExitAnalyzeMode();
10433             DoSleep( appData.delayBeforeQuit );
10434             SendToProgram("quit\n", &first);
10435             DoSleep( appData.delayAfterQuit );
10436             DestroyChildProcess(first.pr, first.useSigterm);
10437         }
10438         first.pr = NoProc;
10439     }
10440     if (second.reuse) {
10441         /* Put second chess program into idle state */
10442         if (second.pr != NoProc &&
10443             gameMode == TwoMachinesPlay) {
10444             SendToProgram("force\n", &second);
10445             if (second.usePing) {
10446               char buf[MSG_SIZ];
10447               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10448               SendToProgram(buf, &second);
10449             }
10450         }
10451     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10452         /* Kill off second chess program */
10453         if (second.isr != NULL)
10454           RemoveInputSource(second.isr);
10455         second.isr = NULL;
10456
10457         if (second.pr != NoProc) {
10458             DoSleep( appData.delayBeforeQuit );
10459             SendToProgram("quit\n", &second);
10460             DoSleep( appData.delayAfterQuit );
10461             DestroyChildProcess(second.pr, second.useSigterm);
10462         }
10463         second.pr = NoProc;
10464     }
10465
10466     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10467         char resChar = '=';
10468         switch (result) {
10469         case WhiteWins:
10470           resChar = '+';
10471           if (first.twoMachinesColor[0] == 'w') {
10472             first.matchWins++;
10473           } else {
10474             second.matchWins++;
10475           }
10476           break;
10477         case BlackWins:
10478           resChar = '-';
10479           if (first.twoMachinesColor[0] == 'b') {
10480             first.matchWins++;
10481           } else {
10482             second.matchWins++;
10483           }
10484           break;
10485         case GameUnfinished:
10486           resChar = ' ';
10487         default:
10488           break;
10489         }
10490
10491         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10492         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10493             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10494             ReserveGame(nextGame, resChar); // sets nextGame
10495             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10496             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10497         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10498
10499         if (nextGame <= appData.matchGames && !abortMatch) {
10500             gameMode = nextGameMode;
10501             matchGame = nextGame; // this will be overruled in tourney mode!
10502             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10503             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10504             endingGame = 0; /* [HGM] crash */
10505             return;
10506         } else {
10507             gameMode = nextGameMode;
10508             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10509                      first.tidy, second.tidy,
10510                      first.matchWins, second.matchWins,
10511                      appData.matchGames - (first.matchWins + second.matchWins));
10512             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10513             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10514             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10515             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10516                 first.twoMachinesColor = "black\n";
10517                 second.twoMachinesColor = "white\n";
10518             } else {
10519                 first.twoMachinesColor = "white\n";
10520                 second.twoMachinesColor = "black\n";
10521             }
10522         }
10523     }
10524     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10525         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10526       ExitAnalyzeMode();
10527     gameMode = nextGameMode;
10528     ModeHighlight();
10529     endingGame = 0;  /* [HGM] crash */
10530     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10531         if(matchMode == TRUE) { // match through command line: exit with or without popup
10532             if(ranking) {
10533                 ToNrEvent(forwardMostMove);
10534                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10535                 else ExitEvent(0);
10536             } else DisplayFatalError(buf, 0, 0);
10537         } else { // match through menu; just stop, with or without popup
10538             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10539             ModeHighlight();
10540             if(ranking){
10541                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10542             } else DisplayNote(buf);
10543       }
10544       if(ranking) free(ranking);
10545     }
10546 }
10547
10548 /* Assumes program was just initialized (initString sent).
10549    Leaves program in force mode. */
10550 void
10551 FeedMovesToProgram (ChessProgramState *cps, int upto)
10552 {
10553     int i;
10554
10555     if (appData.debugMode)
10556       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10557               startedFromSetupPosition ? "position and " : "",
10558               backwardMostMove, upto, cps->which);
10559     if(currentlyInitializedVariant != gameInfo.variant) {
10560       char buf[MSG_SIZ];
10561         // [HGM] variantswitch: make engine aware of new variant
10562         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10563                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10564         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10565         SendToProgram(buf, cps);
10566         currentlyInitializedVariant = gameInfo.variant;
10567     }
10568     SendToProgram("force\n", cps);
10569     if (startedFromSetupPosition) {
10570         SendBoard(cps, backwardMostMove);
10571     if (appData.debugMode) {
10572         fprintf(debugFP, "feedMoves\n");
10573     }
10574     }
10575     for (i = backwardMostMove; i < upto; i++) {
10576         SendMoveToProgram(i, cps);
10577     }
10578 }
10579
10580
10581 int
10582 ResurrectChessProgram ()
10583 {
10584      /* The chess program may have exited.
10585         If so, restart it and feed it all the moves made so far. */
10586     static int doInit = 0;
10587
10588     if (appData.noChessProgram) return 1;
10589
10590     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10591         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10592         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10593         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10594     } else {
10595         if (first.pr != NoProc) return 1;
10596         StartChessProgram(&first);
10597     }
10598     InitChessProgram(&first, FALSE);
10599     FeedMovesToProgram(&first, currentMove);
10600
10601     if (!first.sendTime) {
10602         /* can't tell gnuchess what its clock should read,
10603            so we bow to its notion. */
10604         ResetClocks();
10605         timeRemaining[0][currentMove] = whiteTimeRemaining;
10606         timeRemaining[1][currentMove] = blackTimeRemaining;
10607     }
10608
10609     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10610                 appData.icsEngineAnalyze) && first.analysisSupport) {
10611       SendToProgram("analyze\n", &first);
10612       first.analyzing = TRUE;
10613     }
10614     return 1;
10615 }
10616
10617 /*
10618  * Button procedures
10619  */
10620 void
10621 Reset (int redraw, int init)
10622 {
10623     int i;
10624
10625     if (appData.debugMode) {
10626         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10627                 redraw, init, gameMode);
10628     }
10629     CleanupTail(); // [HGM] vari: delete any stored variations
10630     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10631     pausing = pauseExamInvalid = FALSE;
10632     startedFromSetupPosition = blackPlaysFirst = FALSE;
10633     firstMove = TRUE;
10634     whiteFlag = blackFlag = FALSE;
10635     userOfferedDraw = FALSE;
10636     hintRequested = bookRequested = FALSE;
10637     first.maybeThinking = FALSE;
10638     second.maybeThinking = FALSE;
10639     first.bookSuspend = FALSE; // [HGM] book
10640     second.bookSuspend = FALSE;
10641     thinkOutput[0] = NULLCHAR;
10642     lastHint[0] = NULLCHAR;
10643     ClearGameInfo(&gameInfo);
10644     gameInfo.variant = StringToVariant(appData.variant);
10645     ics_user_moved = ics_clock_paused = FALSE;
10646     ics_getting_history = H_FALSE;
10647     ics_gamenum = -1;
10648     white_holding[0] = black_holding[0] = NULLCHAR;
10649     ClearProgramStats();
10650     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10651
10652     ResetFrontEnd();
10653     ClearHighlights();
10654     flipView = appData.flipView;
10655     ClearPremoveHighlights();
10656     gotPremove = FALSE;
10657     alarmSounded = FALSE;
10658
10659     GameEnds(EndOfFile, NULL, GE_PLAYER);
10660     if(appData.serverMovesName != NULL) {
10661         /* [HGM] prepare to make moves file for broadcasting */
10662         clock_t t = clock();
10663         if(serverMoves != NULL) fclose(serverMoves);
10664         serverMoves = fopen(appData.serverMovesName, "r");
10665         if(serverMoves != NULL) {
10666             fclose(serverMoves);
10667             /* delay 15 sec before overwriting, so all clients can see end */
10668             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10669         }
10670         serverMoves = fopen(appData.serverMovesName, "w");
10671     }
10672
10673     ExitAnalyzeMode();
10674     gameMode = BeginningOfGame;
10675     ModeHighlight();
10676     if(appData.icsActive) gameInfo.variant = VariantNormal;
10677     currentMove = forwardMostMove = backwardMostMove = 0;
10678     MarkTargetSquares(1);
10679     InitPosition(redraw);
10680     for (i = 0; i < MAX_MOVES; i++) {
10681         if (commentList[i] != NULL) {
10682             free(commentList[i]);
10683             commentList[i] = NULL;
10684         }
10685     }
10686     ResetClocks();
10687     timeRemaining[0][0] = whiteTimeRemaining;
10688     timeRemaining[1][0] = blackTimeRemaining;
10689
10690     if (first.pr == NoProc) {
10691         StartChessProgram(&first);
10692     }
10693     if (init) {
10694             InitChessProgram(&first, startedFromSetupPosition);
10695     }
10696     DisplayTitle("");
10697     DisplayMessage("", "");
10698     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10699     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10700 }
10701
10702 void
10703 AutoPlayGameLoop ()
10704 {
10705     for (;;) {
10706         if (!AutoPlayOneMove())
10707           return;
10708         if (matchMode || appData.timeDelay == 0)
10709           continue;
10710         if (appData.timeDelay < 0)
10711           return;
10712         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10713         break;
10714     }
10715 }
10716
10717
10718 int
10719 AutoPlayOneMove ()
10720 {
10721     int fromX, fromY, toX, toY;
10722
10723     if (appData.debugMode) {
10724       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10725     }
10726
10727     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10728       return FALSE;
10729
10730     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10731       pvInfoList[currentMove].depth = programStats.depth;
10732       pvInfoList[currentMove].score = programStats.score;
10733       pvInfoList[currentMove].time  = 0;
10734       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10735     }
10736
10737     if (currentMove >= forwardMostMove) {
10738       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10739 //      gameMode = EndOfGame;
10740 //      ModeHighlight();
10741
10742       /* [AS] Clear current move marker at the end of a game */
10743       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10744
10745       return FALSE;
10746     }
10747
10748     toX = moveList[currentMove][2] - AAA;
10749     toY = moveList[currentMove][3] - ONE;
10750
10751     if (moveList[currentMove][1] == '@') {
10752         if (appData.highlightLastMove) {
10753             SetHighlights(-1, -1, toX, toY);
10754         }
10755     } else {
10756         fromX = moveList[currentMove][0] - AAA;
10757         fromY = moveList[currentMove][1] - ONE;
10758
10759         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10760
10761         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10762
10763         if (appData.highlightLastMove) {
10764             SetHighlights(fromX, fromY, toX, toY);
10765         }
10766     }
10767     DisplayMove(currentMove);
10768     SendMoveToProgram(currentMove++, &first);
10769     DisplayBothClocks();
10770     DrawPosition(FALSE, boards[currentMove]);
10771     // [HGM] PV info: always display, routine tests if empty
10772     DisplayComment(currentMove - 1, commentList[currentMove]);
10773     return TRUE;
10774 }
10775
10776
10777 int
10778 LoadGameOneMove (ChessMove readAhead)
10779 {
10780     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10781     char promoChar = NULLCHAR;
10782     ChessMove moveType;
10783     char move[MSG_SIZ];
10784     char *p, *q;
10785
10786     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10787         gameMode != AnalyzeMode && gameMode != Training) {
10788         gameFileFP = NULL;
10789         return FALSE;
10790     }
10791
10792     yyboardindex = forwardMostMove;
10793     if (readAhead != EndOfFile) {
10794       moveType = readAhead;
10795     } else {
10796       if (gameFileFP == NULL)
10797           return FALSE;
10798       moveType = (ChessMove) Myylex();
10799     }
10800
10801     done = FALSE;
10802     switch (moveType) {
10803       case Comment:
10804         if (appData.debugMode)
10805           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10806         p = yy_text;
10807
10808         /* append the comment but don't display it */
10809         AppendComment(currentMove, p, FALSE);
10810         return TRUE;
10811
10812       case WhiteCapturesEnPassant:
10813       case BlackCapturesEnPassant:
10814       case WhitePromotion:
10815       case BlackPromotion:
10816       case WhiteNonPromotion:
10817       case BlackNonPromotion:
10818       case NormalMove:
10819       case WhiteKingSideCastle:
10820       case WhiteQueenSideCastle:
10821       case BlackKingSideCastle:
10822       case BlackQueenSideCastle:
10823       case WhiteKingSideCastleWild:
10824       case WhiteQueenSideCastleWild:
10825       case BlackKingSideCastleWild:
10826       case BlackQueenSideCastleWild:
10827       /* PUSH Fabien */
10828       case WhiteHSideCastleFR:
10829       case WhiteASideCastleFR:
10830       case BlackHSideCastleFR:
10831       case BlackASideCastleFR:
10832       /* POP Fabien */
10833         if (appData.debugMode)
10834           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10835         fromX = currentMoveString[0] - AAA;
10836         fromY = currentMoveString[1] - ONE;
10837         toX = currentMoveString[2] - AAA;
10838         toY = currentMoveString[3] - ONE;
10839         promoChar = currentMoveString[4];
10840         break;
10841
10842       case WhiteDrop:
10843       case BlackDrop:
10844         if (appData.debugMode)
10845           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10846         fromX = moveType == WhiteDrop ?
10847           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10848         (int) CharToPiece(ToLower(currentMoveString[0]));
10849         fromY = DROP_RANK;
10850         toX = currentMoveString[2] - AAA;
10851         toY = currentMoveString[3] - ONE;
10852         break;
10853
10854       case WhiteWins:
10855       case BlackWins:
10856       case GameIsDrawn:
10857       case GameUnfinished:
10858         if (appData.debugMode)
10859           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10860         p = strchr(yy_text, '{');
10861         if (p == NULL) p = strchr(yy_text, '(');
10862         if (p == NULL) {
10863             p = yy_text;
10864             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10865         } else {
10866             q = strchr(p, *p == '{' ? '}' : ')');
10867             if (q != NULL) *q = NULLCHAR;
10868             p++;
10869         }
10870         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10871         GameEnds(moveType, p, GE_FILE);
10872         done = TRUE;
10873         if (cmailMsgLoaded) {
10874             ClearHighlights();
10875             flipView = WhiteOnMove(currentMove);
10876             if (moveType == GameUnfinished) flipView = !flipView;
10877             if (appData.debugMode)
10878               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10879         }
10880         break;
10881
10882       case EndOfFile:
10883         if (appData.debugMode)
10884           fprintf(debugFP, "Parser hit end of file\n");
10885         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10886           case MT_NONE:
10887           case MT_CHECK:
10888             break;
10889           case MT_CHECKMATE:
10890           case MT_STAINMATE:
10891             if (WhiteOnMove(currentMove)) {
10892                 GameEnds(BlackWins, "Black mates", GE_FILE);
10893             } else {
10894                 GameEnds(WhiteWins, "White mates", GE_FILE);
10895             }
10896             break;
10897           case MT_STALEMATE:
10898             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10899             break;
10900         }
10901         done = TRUE;
10902         break;
10903
10904       case MoveNumberOne:
10905         if (lastLoadGameStart == GNUChessGame) {
10906             /* GNUChessGames have numbers, but they aren't move numbers */
10907             if (appData.debugMode)
10908               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10909                       yy_text, (int) moveType);
10910             return LoadGameOneMove(EndOfFile); /* tail recursion */
10911         }
10912         /* else fall thru */
10913
10914       case XBoardGame:
10915       case GNUChessGame:
10916       case PGNTag:
10917         /* Reached start of next game in file */
10918         if (appData.debugMode)
10919           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10920         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10921           case MT_NONE:
10922           case MT_CHECK:
10923             break;
10924           case MT_CHECKMATE:
10925           case MT_STAINMATE:
10926             if (WhiteOnMove(currentMove)) {
10927                 GameEnds(BlackWins, "Black mates", GE_FILE);
10928             } else {
10929                 GameEnds(WhiteWins, "White mates", GE_FILE);
10930             }
10931             break;
10932           case MT_STALEMATE:
10933             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10934             break;
10935         }
10936         done = TRUE;
10937         break;
10938
10939       case PositionDiagram:     /* should not happen; ignore */
10940       case ElapsedTime:         /* ignore */
10941       case NAG:                 /* ignore */
10942         if (appData.debugMode)
10943           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10944                   yy_text, (int) moveType);
10945         return LoadGameOneMove(EndOfFile); /* tail recursion */
10946
10947       case IllegalMove:
10948         if (appData.testLegality) {
10949             if (appData.debugMode)
10950               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10951             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10952                     (forwardMostMove / 2) + 1,
10953                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10954             DisplayError(move, 0);
10955             done = TRUE;
10956         } else {
10957             if (appData.debugMode)
10958               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10959                       yy_text, currentMoveString);
10960             fromX = currentMoveString[0] - AAA;
10961             fromY = currentMoveString[1] - ONE;
10962             toX = currentMoveString[2] - AAA;
10963             toY = currentMoveString[3] - ONE;
10964             promoChar = currentMoveString[4];
10965         }
10966         break;
10967
10968       case AmbiguousMove:
10969         if (appData.debugMode)
10970           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10971         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10972                 (forwardMostMove / 2) + 1,
10973                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10974         DisplayError(move, 0);
10975         done = TRUE;
10976         break;
10977
10978       default:
10979       case ImpossibleMove:
10980         if (appData.debugMode)
10981           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10982         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10983                 (forwardMostMove / 2) + 1,
10984                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10985         DisplayError(move, 0);
10986         done = TRUE;
10987         break;
10988     }
10989
10990     if (done) {
10991         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10992             DrawPosition(FALSE, boards[currentMove]);
10993             DisplayBothClocks();
10994             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10995               DisplayComment(currentMove - 1, commentList[currentMove]);
10996         }
10997         (void) StopLoadGameTimer();
10998         gameFileFP = NULL;
10999         cmailOldMove = forwardMostMove;
11000         return FALSE;
11001     } else {
11002         /* currentMoveString is set as a side-effect of yylex */
11003
11004         thinkOutput[0] = NULLCHAR;
11005         MakeMove(fromX, fromY, toX, toY, promoChar);
11006         currentMove = forwardMostMove;
11007         return TRUE;
11008     }
11009 }
11010
11011 /* Load the nth game from the given file */
11012 int
11013 LoadGameFromFile (char *filename, int n, char *title, int useList)
11014 {
11015     FILE *f;
11016     char buf[MSG_SIZ];
11017
11018     if (strcmp(filename, "-") == 0) {
11019         f = stdin;
11020         title = "stdin";
11021     } else {
11022         f = fopen(filename, "rb");
11023         if (f == NULL) {
11024           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11025             DisplayError(buf, errno);
11026             return FALSE;
11027         }
11028     }
11029     if (fseek(f, 0, 0) == -1) {
11030         /* f is not seekable; probably a pipe */
11031         useList = FALSE;
11032     }
11033     if (useList && n == 0) {
11034         int error = GameListBuild(f);
11035         if (error) {
11036             DisplayError(_("Cannot build game list"), error);
11037         } else if (!ListEmpty(&gameList) &&
11038                    ((ListGame *) gameList.tailPred)->number > 1) {
11039             GameListPopUp(f, title);
11040             return TRUE;
11041         }
11042         GameListDestroy();
11043         n = 1;
11044     }
11045     if (n == 0) n = 1;
11046     return LoadGame(f, n, title, FALSE);
11047 }
11048
11049
11050 void
11051 MakeRegisteredMove ()
11052 {
11053     int fromX, fromY, toX, toY;
11054     char promoChar;
11055     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11056         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11057           case CMAIL_MOVE:
11058           case CMAIL_DRAW:
11059             if (appData.debugMode)
11060               fprintf(debugFP, "Restoring %s for game %d\n",
11061                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11062
11063             thinkOutput[0] = NULLCHAR;
11064             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11065             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11066             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11067             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11068             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11069             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11070             MakeMove(fromX, fromY, toX, toY, promoChar);
11071             ShowMove(fromX, fromY, toX, toY);
11072
11073             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11074               case MT_NONE:
11075               case MT_CHECK:
11076                 break;
11077
11078               case MT_CHECKMATE:
11079               case MT_STAINMATE:
11080                 if (WhiteOnMove(currentMove)) {
11081                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11082                 } else {
11083                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11084                 }
11085                 break;
11086
11087               case MT_STALEMATE:
11088                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11089                 break;
11090             }
11091
11092             break;
11093
11094           case CMAIL_RESIGN:
11095             if (WhiteOnMove(currentMove)) {
11096                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11097             } else {
11098                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11099             }
11100             break;
11101
11102           case CMAIL_ACCEPT:
11103             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11104             break;
11105
11106           default:
11107             break;
11108         }
11109     }
11110
11111     return;
11112 }
11113
11114 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11115 int
11116 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11117 {
11118     int retVal;
11119
11120     if (gameNumber > nCmailGames) {
11121         DisplayError(_("No more games in this message"), 0);
11122         return FALSE;
11123     }
11124     if (f == lastLoadGameFP) {
11125         int offset = gameNumber - lastLoadGameNumber;
11126         if (offset == 0) {
11127             cmailMsg[0] = NULLCHAR;
11128             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11129                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11130                 nCmailMovesRegistered--;
11131             }
11132             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11133             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11134                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11135             }
11136         } else {
11137             if (! RegisterMove()) return FALSE;
11138         }
11139     }
11140
11141     retVal = LoadGame(f, gameNumber, title, useList);
11142
11143     /* Make move registered during previous look at this game, if any */
11144     MakeRegisteredMove();
11145
11146     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11147         commentList[currentMove]
11148           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11149         DisplayComment(currentMove - 1, commentList[currentMove]);
11150     }
11151
11152     return retVal;
11153 }
11154
11155 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11156 int
11157 ReloadGame (int offset)
11158 {
11159     int gameNumber = lastLoadGameNumber + offset;
11160     if (lastLoadGameFP == NULL) {
11161         DisplayError(_("No game has been loaded yet"), 0);
11162         return FALSE;
11163     }
11164     if (gameNumber <= 0) {
11165         DisplayError(_("Can't back up any further"), 0);
11166         return FALSE;
11167     }
11168     if (cmailMsgLoaded) {
11169         return CmailLoadGame(lastLoadGameFP, gameNumber,
11170                              lastLoadGameTitle, lastLoadGameUseList);
11171     } else {
11172         return LoadGame(lastLoadGameFP, gameNumber,
11173                         lastLoadGameTitle, lastLoadGameUseList);
11174     }
11175 }
11176
11177 int keys[EmptySquare+1];
11178
11179 int
11180 PositionMatches (Board b1, Board b2)
11181 {
11182     int r, f, sum=0;
11183     switch(appData.searchMode) {
11184         case 1: return CompareWithRights(b1, b2);
11185         case 2:
11186             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11187                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11188             }
11189             return TRUE;
11190         case 3:
11191             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11192               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11193                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11194             }
11195             return sum==0;
11196         case 4:
11197             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11198                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11199             }
11200             return sum==0;
11201     }
11202     return TRUE;
11203 }
11204
11205 #define Q_PROMO  4
11206 #define Q_EP     3
11207 #define Q_BCASTL 2
11208 #define Q_WCASTL 1
11209
11210 int pieceList[256], quickBoard[256];
11211 ChessSquare pieceType[256] = { EmptySquare };
11212 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11213 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11214 int soughtTotal, turn;
11215 Boolean epOK, flipSearch;
11216
11217 typedef struct {
11218     unsigned char piece, to;
11219 } Move;
11220
11221 #define DSIZE (250000)
11222
11223 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11224 Move *moveDatabase = initialSpace;
11225 unsigned int movePtr, dataSize = DSIZE;
11226
11227 int
11228 MakePieceList (Board board, int *counts)
11229 {
11230     int r, f, n=Q_PROMO, total=0;
11231     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11232     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11233         int sq = f + (r<<4);
11234         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11235             quickBoard[sq] = ++n;
11236             pieceList[n] = sq;
11237             pieceType[n] = board[r][f];
11238             counts[board[r][f]]++;
11239             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11240             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11241             total++;
11242         }
11243     }
11244     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11245     return total;
11246 }
11247
11248 void
11249 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11250 {
11251     int sq = fromX + (fromY<<4);
11252     int piece = quickBoard[sq];
11253     quickBoard[sq] = 0;
11254     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11255     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11256         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11257         moveDatabase[movePtr++].piece = Q_WCASTL;
11258         quickBoard[sq] = piece;
11259         piece = quickBoard[from]; quickBoard[from] = 0;
11260         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11261     } else
11262     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11263         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11264         moveDatabase[movePtr++].piece = Q_BCASTL;
11265         quickBoard[sq] = piece;
11266         piece = quickBoard[from]; quickBoard[from] = 0;
11267         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11268     } else
11269     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11270         quickBoard[(fromY<<4)+toX] = 0;
11271         moveDatabase[movePtr].piece = Q_EP;
11272         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11273         moveDatabase[movePtr].to = sq;
11274     } else
11275     if(promoPiece != pieceType[piece]) {
11276         moveDatabase[movePtr++].piece = Q_PROMO;
11277         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11278     }
11279     moveDatabase[movePtr].piece = piece;
11280     quickBoard[sq] = piece;
11281     movePtr++;
11282 }
11283
11284 int
11285 PackGame (Board board)
11286 {
11287     Move *newSpace = NULL;
11288     moveDatabase[movePtr].piece = 0; // terminate previous game
11289     if(movePtr > dataSize) {
11290         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11291         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11292         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11293         if(newSpace) {
11294             int i;
11295             Move *p = moveDatabase, *q = newSpace;
11296             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11297             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11298             moveDatabase = newSpace;
11299         } else { // calloc failed, we must be out of memory. Too bad...
11300             dataSize = 0; // prevent calloc events for all subsequent games
11301             return 0;     // and signal this one isn't cached
11302         }
11303     }
11304     movePtr++;
11305     MakePieceList(board, counts);
11306     return movePtr;
11307 }
11308
11309 int
11310 QuickCompare (Board board, int *minCounts, int *maxCounts)
11311 {   // compare according to search mode
11312     int r, f;
11313     switch(appData.searchMode)
11314     {
11315       case 1: // exact position match
11316         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11317         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11318             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11319         }
11320         break;
11321       case 2: // can have extra material on empty squares
11322         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11323             if(board[r][f] == EmptySquare) continue;
11324             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11325         }
11326         break;
11327       case 3: // material with exact Pawn structure
11328         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11329             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11330             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11331         } // fall through to material comparison
11332       case 4: // exact material
11333         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11334         break;
11335       case 6: // material range with given imbalance
11336         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11337         // fall through to range comparison
11338       case 5: // material range
11339         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11340     }
11341     return TRUE;
11342 }
11343
11344 int
11345 QuickScan (Board board, Move *move)
11346 {   // reconstruct game,and compare all positions in it
11347     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11348     do {
11349         int piece = move->piece;
11350         int to = move->to, from = pieceList[piece];
11351         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11352           if(!piece) return -1;
11353           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11354             piece = (++move)->piece;
11355             from = pieceList[piece];
11356             counts[pieceType[piece]]--;
11357             pieceType[piece] = (ChessSquare) move->to;
11358             counts[move->to]++;
11359           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11360             counts[pieceType[quickBoard[to]]]--;
11361             quickBoard[to] = 0; total--;
11362             move++;
11363             continue;
11364           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11365             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11366             from  = pieceList[piece]; // so this must be King
11367             quickBoard[from] = 0;
11368             quickBoard[to] = piece;
11369             pieceList[piece] = to;
11370             move++;
11371             continue;
11372           }
11373         }
11374         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11375         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11376         quickBoard[from] = 0;
11377         quickBoard[to] = piece;
11378         pieceList[piece] = to;
11379         cnt++; turn ^= 3;
11380         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11381            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11382            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11383                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11384           ) {
11385             static int lastCounts[EmptySquare+1];
11386             int i;
11387             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11388             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11389         } else stretch = 0;
11390         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11391         move++;
11392     } while(1);
11393 }
11394
11395 void
11396 InitSearch ()
11397 {
11398     int r, f;
11399     flipSearch = FALSE;
11400     CopyBoard(soughtBoard, boards[currentMove]);
11401     soughtTotal = MakePieceList(soughtBoard, maxSought);
11402     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11403     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11404     CopyBoard(reverseBoard, boards[currentMove]);
11405     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11406         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11407         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11408         reverseBoard[r][f] = piece;
11409     }
11410     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11411     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11412     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11413                  || (boards[currentMove][CASTLING][2] == NoRights || 
11414                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11415                  && (boards[currentMove][CASTLING][5] == NoRights || 
11416                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11417       ) {
11418         flipSearch = TRUE;
11419         CopyBoard(flipBoard, soughtBoard);
11420         CopyBoard(rotateBoard, reverseBoard);
11421         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11422             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11423             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11424         }
11425     }
11426     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11427     if(appData.searchMode >= 5) {
11428         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11429         MakePieceList(soughtBoard, minSought);
11430         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11431     }
11432     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11433         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11434 }
11435
11436 GameInfo dummyInfo;
11437
11438 int
11439 GameContainsPosition (FILE *f, ListGame *lg)
11440 {
11441     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11442     int fromX, fromY, toX, toY;
11443     char promoChar;
11444     static int initDone=FALSE;
11445
11446     // weed out games based on numerical tag comparison
11447     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11448     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11449     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11450     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11451     if(!initDone) {
11452         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11453         initDone = TRUE;
11454     }
11455     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11456     else CopyBoard(boards[scratch], initialPosition); // default start position
11457     if(lg->moves) {
11458         turn = btm + 1;
11459         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11460         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11461     }
11462     if(btm) plyNr++;
11463     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11464     fseek(f, lg->offset, 0);
11465     yynewfile(f);
11466     while(1) {
11467         yyboardindex = scratch;
11468         quickFlag = plyNr+1;
11469         next = Myylex();
11470         quickFlag = 0;
11471         switch(next) {
11472             case PGNTag:
11473                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11474             default:
11475                 continue;
11476
11477             case XBoardGame:
11478             case GNUChessGame:
11479                 if(plyNr) return -1; // after we have seen moves, this is for new game
11480               continue;
11481
11482             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11483             case ImpossibleMove:
11484             case WhiteWins: // game ends here with these four
11485             case BlackWins:
11486             case GameIsDrawn:
11487             case GameUnfinished:
11488                 return -1;
11489
11490             case IllegalMove:
11491                 if(appData.testLegality) return -1;
11492             case WhiteCapturesEnPassant:
11493             case BlackCapturesEnPassant:
11494             case WhitePromotion:
11495             case BlackPromotion:
11496             case WhiteNonPromotion:
11497             case BlackNonPromotion:
11498             case NormalMove:
11499             case WhiteKingSideCastle:
11500             case WhiteQueenSideCastle:
11501             case BlackKingSideCastle:
11502             case BlackQueenSideCastle:
11503             case WhiteKingSideCastleWild:
11504             case WhiteQueenSideCastleWild:
11505             case BlackKingSideCastleWild:
11506             case BlackQueenSideCastleWild:
11507             case WhiteHSideCastleFR:
11508             case WhiteASideCastleFR:
11509             case BlackHSideCastleFR:
11510             case BlackASideCastleFR:
11511                 fromX = currentMoveString[0] - AAA;
11512                 fromY = currentMoveString[1] - ONE;
11513                 toX = currentMoveString[2] - AAA;
11514                 toY = currentMoveString[3] - ONE;
11515                 promoChar = currentMoveString[4];
11516                 break;
11517             case WhiteDrop:
11518             case BlackDrop:
11519                 fromX = next == WhiteDrop ?
11520                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11521                   (int) CharToPiece(ToLower(currentMoveString[0]));
11522                 fromY = DROP_RANK;
11523                 toX = currentMoveString[2] - AAA;
11524                 toY = currentMoveString[3] - ONE;
11525                 promoChar = 0;
11526                 break;
11527         }
11528         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11529         plyNr++;
11530         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11531         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11532         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11533         if(appData.findMirror) {
11534             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11535             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11536         }
11537     }
11538 }
11539
11540 /* Load the nth game from open file f */
11541 int
11542 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11543 {
11544     ChessMove cm;
11545     char buf[MSG_SIZ];
11546     int gn = gameNumber;
11547     ListGame *lg = NULL;
11548     int numPGNTags = 0;
11549     int err, pos = -1;
11550     GameMode oldGameMode;
11551     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11552
11553     if (appData.debugMode)
11554         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11555
11556     if (gameMode == Training )
11557         SetTrainingModeOff();
11558
11559     oldGameMode = gameMode;
11560     if (gameMode != BeginningOfGame) {
11561       Reset(FALSE, TRUE);
11562     }
11563
11564     gameFileFP = f;
11565     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11566         fclose(lastLoadGameFP);
11567     }
11568
11569     if (useList) {
11570         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11571
11572         if (lg) {
11573             fseek(f, lg->offset, 0);
11574             GameListHighlight(gameNumber);
11575             pos = lg->position;
11576             gn = 1;
11577         }
11578         else {
11579             DisplayError(_("Game number out of range"), 0);
11580             return FALSE;
11581         }
11582     } else {
11583         GameListDestroy();
11584         if (fseek(f, 0, 0) == -1) {
11585             if (f == lastLoadGameFP ?
11586                 gameNumber == lastLoadGameNumber + 1 :
11587                 gameNumber == 1) {
11588                 gn = 1;
11589             } else {
11590                 DisplayError(_("Can't seek on game file"), 0);
11591                 return FALSE;
11592             }
11593         }
11594     }
11595     lastLoadGameFP = f;
11596     lastLoadGameNumber = gameNumber;
11597     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11598     lastLoadGameUseList = useList;
11599
11600     yynewfile(f);
11601
11602     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11603       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11604                 lg->gameInfo.black);
11605             DisplayTitle(buf);
11606     } else if (*title != NULLCHAR) {
11607         if (gameNumber > 1) {
11608           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11609             DisplayTitle(buf);
11610         } else {
11611             DisplayTitle(title);
11612         }
11613     }
11614
11615     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11616         gameMode = PlayFromGameFile;
11617         ModeHighlight();
11618     }
11619
11620     currentMove = forwardMostMove = backwardMostMove = 0;
11621     CopyBoard(boards[0], initialPosition);
11622     StopClocks();
11623
11624     /*
11625      * Skip the first gn-1 games in the file.
11626      * Also skip over anything that precedes an identifiable
11627      * start of game marker, to avoid being confused by
11628      * garbage at the start of the file.  Currently
11629      * recognized start of game markers are the move number "1",
11630      * the pattern "gnuchess .* game", the pattern
11631      * "^[#;%] [^ ]* game file", and a PGN tag block.
11632      * A game that starts with one of the latter two patterns
11633      * will also have a move number 1, possibly
11634      * following a position diagram.
11635      * 5-4-02: Let's try being more lenient and allowing a game to
11636      * start with an unnumbered move.  Does that break anything?
11637      */
11638     cm = lastLoadGameStart = EndOfFile;
11639     while (gn > 0) {
11640         yyboardindex = forwardMostMove;
11641         cm = (ChessMove) Myylex();
11642         switch (cm) {
11643           case EndOfFile:
11644             if (cmailMsgLoaded) {
11645                 nCmailGames = CMAIL_MAX_GAMES - gn;
11646             } else {
11647                 Reset(TRUE, TRUE);
11648                 DisplayError(_("Game not found in file"), 0);
11649             }
11650             return FALSE;
11651
11652           case GNUChessGame:
11653           case XBoardGame:
11654             gn--;
11655             lastLoadGameStart = cm;
11656             break;
11657
11658           case MoveNumberOne:
11659             switch (lastLoadGameStart) {
11660               case GNUChessGame:
11661               case XBoardGame:
11662               case PGNTag:
11663                 break;
11664               case MoveNumberOne:
11665               case EndOfFile:
11666                 gn--;           /* count this game */
11667                 lastLoadGameStart = cm;
11668                 break;
11669               default:
11670                 /* impossible */
11671                 break;
11672             }
11673             break;
11674
11675           case PGNTag:
11676             switch (lastLoadGameStart) {
11677               case GNUChessGame:
11678               case PGNTag:
11679               case MoveNumberOne:
11680               case EndOfFile:
11681                 gn--;           /* count this game */
11682                 lastLoadGameStart = cm;
11683                 break;
11684               case XBoardGame:
11685                 lastLoadGameStart = cm; /* game counted already */
11686                 break;
11687               default:
11688                 /* impossible */
11689                 break;
11690             }
11691             if (gn > 0) {
11692                 do {
11693                     yyboardindex = forwardMostMove;
11694                     cm = (ChessMove) Myylex();
11695                 } while (cm == PGNTag || cm == Comment);
11696             }
11697             break;
11698
11699           case WhiteWins:
11700           case BlackWins:
11701           case GameIsDrawn:
11702             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11703                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11704                     != CMAIL_OLD_RESULT) {
11705                     nCmailResults ++ ;
11706                     cmailResult[  CMAIL_MAX_GAMES
11707                                 - gn - 1] = CMAIL_OLD_RESULT;
11708                 }
11709             }
11710             break;
11711
11712           case NormalMove:
11713             /* Only a NormalMove can be at the start of a game
11714              * without a position diagram. */
11715             if (lastLoadGameStart == EndOfFile ) {
11716               gn--;
11717               lastLoadGameStart = MoveNumberOne;
11718             }
11719             break;
11720
11721           default:
11722             break;
11723         }
11724     }
11725
11726     if (appData.debugMode)
11727       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11728
11729     if (cm == XBoardGame) {
11730         /* Skip any header junk before position diagram and/or move 1 */
11731         for (;;) {
11732             yyboardindex = forwardMostMove;
11733             cm = (ChessMove) Myylex();
11734
11735             if (cm == EndOfFile ||
11736                 cm == GNUChessGame || cm == XBoardGame) {
11737                 /* Empty game; pretend end-of-file and handle later */
11738                 cm = EndOfFile;
11739                 break;
11740             }
11741
11742             if (cm == MoveNumberOne || cm == PositionDiagram ||
11743                 cm == PGNTag || cm == Comment)
11744               break;
11745         }
11746     } else if (cm == GNUChessGame) {
11747         if (gameInfo.event != NULL) {
11748             free(gameInfo.event);
11749         }
11750         gameInfo.event = StrSave(yy_text);
11751     }
11752
11753     startedFromSetupPosition = FALSE;
11754     while (cm == PGNTag) {
11755         if (appData.debugMode)
11756           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11757         err = ParsePGNTag(yy_text, &gameInfo);
11758         if (!err) numPGNTags++;
11759
11760         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11761         if(gameInfo.variant != oldVariant) {
11762             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11763             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11764             InitPosition(TRUE);
11765             oldVariant = gameInfo.variant;
11766             if (appData.debugMode)
11767               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11768         }
11769
11770
11771         if (gameInfo.fen != NULL) {
11772           Board initial_position;
11773           startedFromSetupPosition = TRUE;
11774           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11775             Reset(TRUE, TRUE);
11776             DisplayError(_("Bad FEN position in file"), 0);
11777             return FALSE;
11778           }
11779           CopyBoard(boards[0], initial_position);
11780           if (blackPlaysFirst) {
11781             currentMove = forwardMostMove = backwardMostMove = 1;
11782             CopyBoard(boards[1], initial_position);
11783             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11784             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11785             timeRemaining[0][1] = whiteTimeRemaining;
11786             timeRemaining[1][1] = blackTimeRemaining;
11787             if (commentList[0] != NULL) {
11788               commentList[1] = commentList[0];
11789               commentList[0] = NULL;
11790             }
11791           } else {
11792             currentMove = forwardMostMove = backwardMostMove = 0;
11793           }
11794           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11795           {   int i;
11796               initialRulePlies = FENrulePlies;
11797               for( i=0; i< nrCastlingRights; i++ )
11798                   initialRights[i] = initial_position[CASTLING][i];
11799           }
11800           yyboardindex = forwardMostMove;
11801           free(gameInfo.fen);
11802           gameInfo.fen = NULL;
11803         }
11804
11805         yyboardindex = forwardMostMove;
11806         cm = (ChessMove) Myylex();
11807
11808         /* Handle comments interspersed among the tags */
11809         while (cm == Comment) {
11810             char *p;
11811             if (appData.debugMode)
11812               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11813             p = yy_text;
11814             AppendComment(currentMove, p, FALSE);
11815             yyboardindex = forwardMostMove;
11816             cm = (ChessMove) Myylex();
11817         }
11818     }
11819
11820     /* don't rely on existence of Event tag since if game was
11821      * pasted from clipboard the Event tag may not exist
11822      */
11823     if (numPGNTags > 0){
11824         char *tags;
11825         if (gameInfo.variant == VariantNormal) {
11826           VariantClass v = StringToVariant(gameInfo.event);
11827           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11828           if(v < VariantShogi) gameInfo.variant = v;
11829         }
11830         if (!matchMode) {
11831           if( appData.autoDisplayTags ) {
11832             tags = PGNTags(&gameInfo);
11833             TagsPopUp(tags, CmailMsg());
11834             free(tags);
11835           }
11836         }
11837     } else {
11838         /* Make something up, but don't display it now */
11839         SetGameInfo();
11840         TagsPopDown();
11841     }
11842
11843     if (cm == PositionDiagram) {
11844         int i, j;
11845         char *p;
11846         Board initial_position;
11847
11848         if (appData.debugMode)
11849           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11850
11851         if (!startedFromSetupPosition) {
11852             p = yy_text;
11853             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11854               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11855                 switch (*p) {
11856                   case '{':
11857                   case '[':
11858                   case '-':
11859                   case ' ':
11860                   case '\t':
11861                   case '\n':
11862                   case '\r':
11863                     break;
11864                   default:
11865                     initial_position[i][j++] = CharToPiece(*p);
11866                     break;
11867                 }
11868             while (*p == ' ' || *p == '\t' ||
11869                    *p == '\n' || *p == '\r') p++;
11870
11871             if (strncmp(p, "black", strlen("black"))==0)
11872               blackPlaysFirst = TRUE;
11873             else
11874               blackPlaysFirst = FALSE;
11875             startedFromSetupPosition = TRUE;
11876
11877             CopyBoard(boards[0], initial_position);
11878             if (blackPlaysFirst) {
11879                 currentMove = forwardMostMove = backwardMostMove = 1;
11880                 CopyBoard(boards[1], initial_position);
11881                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11882                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11883                 timeRemaining[0][1] = whiteTimeRemaining;
11884                 timeRemaining[1][1] = blackTimeRemaining;
11885                 if (commentList[0] != NULL) {
11886                     commentList[1] = commentList[0];
11887                     commentList[0] = NULL;
11888                 }
11889             } else {
11890                 currentMove = forwardMostMove = backwardMostMove = 0;
11891             }
11892         }
11893         yyboardindex = forwardMostMove;
11894         cm = (ChessMove) Myylex();
11895     }
11896
11897     if (first.pr == NoProc) {
11898         StartChessProgram(&first);
11899     }
11900     InitChessProgram(&first, FALSE);
11901     SendToProgram("force\n", &first);
11902     if (startedFromSetupPosition) {
11903         SendBoard(&first, forwardMostMove);
11904     if (appData.debugMode) {
11905         fprintf(debugFP, "Load Game\n");
11906     }
11907         DisplayBothClocks();
11908     }
11909
11910     /* [HGM] server: flag to write setup moves in broadcast file as one */
11911     loadFlag = appData.suppressLoadMoves;
11912
11913     while (cm == Comment) {
11914         char *p;
11915         if (appData.debugMode)
11916           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11917         p = yy_text;
11918         AppendComment(currentMove, p, FALSE);
11919         yyboardindex = forwardMostMove;
11920         cm = (ChessMove) Myylex();
11921     }
11922
11923     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11924         cm == WhiteWins || cm == BlackWins ||
11925         cm == GameIsDrawn || cm == GameUnfinished) {
11926         DisplayMessage("", _("No moves in game"));
11927         if (cmailMsgLoaded) {
11928             if (appData.debugMode)
11929               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11930             ClearHighlights();
11931             flipView = FALSE;
11932         }
11933         DrawPosition(FALSE, boards[currentMove]);
11934         DisplayBothClocks();
11935         gameMode = EditGame;
11936         ModeHighlight();
11937         gameFileFP = NULL;
11938         cmailOldMove = 0;
11939         return TRUE;
11940     }
11941
11942     // [HGM] PV info: routine tests if comment empty
11943     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11944         DisplayComment(currentMove - 1, commentList[currentMove]);
11945     }
11946     if (!matchMode && appData.timeDelay != 0)
11947       DrawPosition(FALSE, boards[currentMove]);
11948
11949     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11950       programStats.ok_to_send = 1;
11951     }
11952
11953     /* if the first token after the PGN tags is a move
11954      * and not move number 1, retrieve it from the parser
11955      */
11956     if (cm != MoveNumberOne)
11957         LoadGameOneMove(cm);
11958
11959     /* load the remaining moves from the file */
11960     while (LoadGameOneMove(EndOfFile)) {
11961       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11962       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11963     }
11964
11965     /* rewind to the start of the game */
11966     currentMove = backwardMostMove;
11967
11968     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11969
11970     if (oldGameMode == AnalyzeFile ||
11971         oldGameMode == AnalyzeMode) {
11972       AnalyzeFileEvent();
11973     }
11974
11975     if (!matchMode && pos >= 0) {
11976         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11977     } else
11978     if (matchMode || appData.timeDelay == 0) {
11979       ToEndEvent();
11980     } else if (appData.timeDelay > 0) {
11981       AutoPlayGameLoop();
11982     }
11983
11984     if (appData.debugMode)
11985         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11986
11987     loadFlag = 0; /* [HGM] true game starts */
11988     return TRUE;
11989 }
11990
11991 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11992 int
11993 ReloadPosition (int offset)
11994 {
11995     int positionNumber = lastLoadPositionNumber + offset;
11996     if (lastLoadPositionFP == NULL) {
11997         DisplayError(_("No position has been loaded yet"), 0);
11998         return FALSE;
11999     }
12000     if (positionNumber <= 0) {
12001         DisplayError(_("Can't back up any further"), 0);
12002         return FALSE;
12003     }
12004     return LoadPosition(lastLoadPositionFP, positionNumber,
12005                         lastLoadPositionTitle);
12006 }
12007
12008 /* Load the nth position from the given file */
12009 int
12010 LoadPositionFromFile (char *filename, int n, char *title)
12011 {
12012     FILE *f;
12013     char buf[MSG_SIZ];
12014
12015     if (strcmp(filename, "-") == 0) {
12016         return LoadPosition(stdin, n, "stdin");
12017     } else {
12018         f = fopen(filename, "rb");
12019         if (f == NULL) {
12020             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12021             DisplayError(buf, errno);
12022             return FALSE;
12023         } else {
12024             return LoadPosition(f, n, title);
12025         }
12026     }
12027 }
12028
12029 /* Load the nth position from the given open file, and close it */
12030 int
12031 LoadPosition (FILE *f, int positionNumber, char *title)
12032 {
12033     char *p, line[MSG_SIZ];
12034     Board initial_position;
12035     int i, j, fenMode, pn;
12036
12037     if (gameMode == Training )
12038         SetTrainingModeOff();
12039
12040     if (gameMode != BeginningOfGame) {
12041         Reset(FALSE, TRUE);
12042     }
12043     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12044         fclose(lastLoadPositionFP);
12045     }
12046     if (positionNumber == 0) positionNumber = 1;
12047     lastLoadPositionFP = f;
12048     lastLoadPositionNumber = positionNumber;
12049     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12050     if (first.pr == NoProc && !appData.noChessProgram) {
12051       StartChessProgram(&first);
12052       InitChessProgram(&first, FALSE);
12053     }
12054     pn = positionNumber;
12055     if (positionNumber < 0) {
12056         /* Negative position number means to seek to that byte offset */
12057         if (fseek(f, -positionNumber, 0) == -1) {
12058             DisplayError(_("Can't seek on position file"), 0);
12059             return FALSE;
12060         };
12061         pn = 1;
12062     } else {
12063         if (fseek(f, 0, 0) == -1) {
12064             if (f == lastLoadPositionFP ?
12065                 positionNumber == lastLoadPositionNumber + 1 :
12066                 positionNumber == 1) {
12067                 pn = 1;
12068             } else {
12069                 DisplayError(_("Can't seek on position file"), 0);
12070                 return FALSE;
12071             }
12072         }
12073     }
12074     /* See if this file is FEN or old-style xboard */
12075     if (fgets(line, MSG_SIZ, f) == NULL) {
12076         DisplayError(_("Position not found in file"), 0);
12077         return FALSE;
12078     }
12079     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12080     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12081
12082     if (pn >= 2) {
12083         if (fenMode || line[0] == '#') pn--;
12084         while (pn > 0) {
12085             /* skip positions before number pn */
12086             if (fgets(line, MSG_SIZ, f) == NULL) {
12087                 Reset(TRUE, TRUE);
12088                 DisplayError(_("Position not found in file"), 0);
12089                 return FALSE;
12090             }
12091             if (fenMode || line[0] == '#') pn--;
12092         }
12093     }
12094
12095     if (fenMode) {
12096         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12097             DisplayError(_("Bad FEN position in file"), 0);
12098             return FALSE;
12099         }
12100     } else {
12101         (void) fgets(line, MSG_SIZ, f);
12102         (void) fgets(line, MSG_SIZ, f);
12103
12104         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12105             (void) fgets(line, MSG_SIZ, f);
12106             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12107                 if (*p == ' ')
12108                   continue;
12109                 initial_position[i][j++] = CharToPiece(*p);
12110             }
12111         }
12112
12113         blackPlaysFirst = FALSE;
12114         if (!feof(f)) {
12115             (void) fgets(line, MSG_SIZ, f);
12116             if (strncmp(line, "black", strlen("black"))==0)
12117               blackPlaysFirst = TRUE;
12118         }
12119     }
12120     startedFromSetupPosition = TRUE;
12121
12122     CopyBoard(boards[0], initial_position);
12123     if (blackPlaysFirst) {
12124         currentMove = forwardMostMove = backwardMostMove = 1;
12125         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12126         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12127         CopyBoard(boards[1], initial_position);
12128         DisplayMessage("", _("Black to play"));
12129     } else {
12130         currentMove = forwardMostMove = backwardMostMove = 0;
12131         DisplayMessage("", _("White to play"));
12132     }
12133     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12134     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12135         SendToProgram("force\n", &first);
12136         SendBoard(&first, forwardMostMove);
12137     }
12138     if (appData.debugMode) {
12139 int i, j;
12140   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12141   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12142         fprintf(debugFP, "Load Position\n");
12143     }
12144
12145     if (positionNumber > 1) {
12146       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12147         DisplayTitle(line);
12148     } else {
12149         DisplayTitle(title);
12150     }
12151     gameMode = EditGame;
12152     ModeHighlight();
12153     ResetClocks();
12154     timeRemaining[0][1] = whiteTimeRemaining;
12155     timeRemaining[1][1] = blackTimeRemaining;
12156     DrawPosition(FALSE, boards[currentMove]);
12157
12158     return TRUE;
12159 }
12160
12161
12162 void
12163 CopyPlayerNameIntoFileName (char **dest, char *src)
12164 {
12165     while (*src != NULLCHAR && *src != ',') {
12166         if (*src == ' ') {
12167             *(*dest)++ = '_';
12168             src++;
12169         } else {
12170             *(*dest)++ = *src++;
12171         }
12172     }
12173 }
12174
12175 char *
12176 DefaultFileName (char *ext)
12177 {
12178     static char def[MSG_SIZ];
12179     char *p;
12180
12181     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12182         p = def;
12183         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12184         *p++ = '-';
12185         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12186         *p++ = '.';
12187         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12188     } else {
12189         def[0] = NULLCHAR;
12190     }
12191     return def;
12192 }
12193
12194 /* Save the current game to the given file */
12195 int
12196 SaveGameToFile (char *filename, int append)
12197 {
12198     FILE *f;
12199     char buf[MSG_SIZ];
12200     int result, i, t,tot=0;
12201
12202     if (strcmp(filename, "-") == 0) {
12203         return SaveGame(stdout, 0, NULL);
12204     } else {
12205         for(i=0; i<10; i++) { // upto 10 tries
12206              f = fopen(filename, append ? "a" : "w");
12207              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12208              if(f || errno != 13) break;
12209              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12210              tot += t;
12211         }
12212         if (f == NULL) {
12213             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12214             DisplayError(buf, errno);
12215             return FALSE;
12216         } else {
12217             safeStrCpy(buf, lastMsg, MSG_SIZ);
12218             DisplayMessage(_("Waiting for access to save file"), "");
12219             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12220             DisplayMessage(_("Saving game"), "");
12221             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12222             result = SaveGame(f, 0, NULL);
12223             DisplayMessage(buf, "");
12224             return result;
12225         }
12226     }
12227 }
12228
12229 char *
12230 SavePart (char *str)
12231 {
12232     static char buf[MSG_SIZ];
12233     char *p;
12234
12235     p = strchr(str, ' ');
12236     if (p == NULL) return str;
12237     strncpy(buf, str, p - str);
12238     buf[p - str] = NULLCHAR;
12239     return buf;
12240 }
12241
12242 #define PGN_MAX_LINE 75
12243
12244 #define PGN_SIDE_WHITE  0
12245 #define PGN_SIDE_BLACK  1
12246
12247 static int
12248 FindFirstMoveOutOfBook (int side)
12249 {
12250     int result = -1;
12251
12252     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12253         int index = backwardMostMove;
12254         int has_book_hit = 0;
12255
12256         if( (index % 2) != side ) {
12257             index++;
12258         }
12259
12260         while( index < forwardMostMove ) {
12261             /* Check to see if engine is in book */
12262             int depth = pvInfoList[index].depth;
12263             int score = pvInfoList[index].score;
12264             int in_book = 0;
12265
12266             if( depth <= 2 ) {
12267                 in_book = 1;
12268             }
12269             else if( score == 0 && depth == 63 ) {
12270                 in_book = 1; /* Zappa */
12271             }
12272             else if( score == 2 && depth == 99 ) {
12273                 in_book = 1; /* Abrok */
12274             }
12275
12276             has_book_hit += in_book;
12277
12278             if( ! in_book ) {
12279                 result = index;
12280
12281                 break;
12282             }
12283
12284             index += 2;
12285         }
12286     }
12287
12288     return result;
12289 }
12290
12291 void
12292 GetOutOfBookInfo (char * buf)
12293 {
12294     int oob[2];
12295     int i;
12296     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12297
12298     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12299     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12300
12301     *buf = '\0';
12302
12303     if( oob[0] >= 0 || oob[1] >= 0 ) {
12304         for( i=0; i<2; i++ ) {
12305             int idx = oob[i];
12306
12307             if( idx >= 0 ) {
12308                 if( i > 0 && oob[0] >= 0 ) {
12309                     strcat( buf, "   " );
12310                 }
12311
12312                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12313                 sprintf( buf+strlen(buf), "%s%.2f",
12314                     pvInfoList[idx].score >= 0 ? "+" : "",
12315                     pvInfoList[idx].score / 100.0 );
12316             }
12317         }
12318     }
12319 }
12320
12321 /* Save game in PGN style and close the file */
12322 int
12323 SaveGamePGN (FILE *f)
12324 {
12325     int i, offset, linelen, newblock;
12326     time_t tm;
12327 //    char *movetext;
12328     char numtext[32];
12329     int movelen, numlen, blank;
12330     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12331
12332     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12333
12334     tm = time((time_t *) NULL);
12335
12336     PrintPGNTags(f, &gameInfo);
12337
12338     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12339
12340     if (backwardMostMove > 0 || startedFromSetupPosition) {
12341         char *fen = PositionToFEN(backwardMostMove, NULL);
12342         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12343         fprintf(f, "\n{--------------\n");
12344         PrintPosition(f, backwardMostMove);
12345         fprintf(f, "--------------}\n");
12346         free(fen);
12347     }
12348     else {
12349         /* [AS] Out of book annotation */
12350         if( appData.saveOutOfBookInfo ) {
12351             char buf[64];
12352
12353             GetOutOfBookInfo( buf );
12354
12355             if( buf[0] != '\0' ) {
12356                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12357             }
12358         }
12359
12360         fprintf(f, "\n");
12361     }
12362
12363     i = backwardMostMove;
12364     linelen = 0;
12365     newblock = TRUE;
12366
12367     while (i < forwardMostMove) {
12368         /* Print comments preceding this move */
12369         if (commentList[i] != NULL) {
12370             if (linelen > 0) fprintf(f, "\n");
12371             fprintf(f, "%s", commentList[i]);
12372             linelen = 0;
12373             newblock = TRUE;
12374         }
12375
12376         /* Format move number */
12377         if ((i % 2) == 0)
12378           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12379         else
12380           if (newblock)
12381             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12382           else
12383             numtext[0] = NULLCHAR;
12384
12385         numlen = strlen(numtext);
12386         newblock = FALSE;
12387
12388         /* Print move number */
12389         blank = linelen > 0 && numlen > 0;
12390         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12391             fprintf(f, "\n");
12392             linelen = 0;
12393             blank = 0;
12394         }
12395         if (blank) {
12396             fprintf(f, " ");
12397             linelen++;
12398         }
12399         fprintf(f, "%s", numtext);
12400         linelen += numlen;
12401
12402         /* Get move */
12403         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12404         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12405
12406         /* Print move */
12407         blank = linelen > 0 && movelen > 0;
12408         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12409             fprintf(f, "\n");
12410             linelen = 0;
12411             blank = 0;
12412         }
12413         if (blank) {
12414             fprintf(f, " ");
12415             linelen++;
12416         }
12417         fprintf(f, "%s", move_buffer);
12418         linelen += movelen;
12419
12420         /* [AS] Add PV info if present */
12421         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12422             /* [HGM] add time */
12423             char buf[MSG_SIZ]; int seconds;
12424
12425             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12426
12427             if( seconds <= 0)
12428               buf[0] = 0;
12429             else
12430               if( seconds < 30 )
12431                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12432               else
12433                 {
12434                   seconds = (seconds + 4)/10; // round to full seconds
12435                   if( seconds < 60 )
12436                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12437                   else
12438                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12439                 }
12440
12441             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12442                       pvInfoList[i].score >= 0 ? "+" : "",
12443                       pvInfoList[i].score / 100.0,
12444                       pvInfoList[i].depth,
12445                       buf );
12446
12447             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12448
12449             /* Print score/depth */
12450             blank = linelen > 0 && movelen > 0;
12451             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12452                 fprintf(f, "\n");
12453                 linelen = 0;
12454                 blank = 0;
12455             }
12456             if (blank) {
12457                 fprintf(f, " ");
12458                 linelen++;
12459             }
12460             fprintf(f, "%s", move_buffer);
12461             linelen += movelen;
12462         }
12463
12464         i++;
12465     }
12466
12467     /* Start a new line */
12468     if (linelen > 0) fprintf(f, "\n");
12469
12470     /* Print comments after last move */
12471     if (commentList[i] != NULL) {
12472         fprintf(f, "%s\n", commentList[i]);
12473     }
12474
12475     /* Print result */
12476     if (gameInfo.resultDetails != NULL &&
12477         gameInfo.resultDetails[0] != NULLCHAR) {
12478         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12479                 PGNResult(gameInfo.result));
12480     } else {
12481         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12482     }
12483
12484     fclose(f);
12485     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12486     return TRUE;
12487 }
12488
12489 /* Save game in old style and close the file */
12490 int
12491 SaveGameOldStyle (FILE *f)
12492 {
12493     int i, offset;
12494     time_t tm;
12495
12496     tm = time((time_t *) NULL);
12497
12498     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12499     PrintOpponents(f);
12500
12501     if (backwardMostMove > 0 || startedFromSetupPosition) {
12502         fprintf(f, "\n[--------------\n");
12503         PrintPosition(f, backwardMostMove);
12504         fprintf(f, "--------------]\n");
12505     } else {
12506         fprintf(f, "\n");
12507     }
12508
12509     i = backwardMostMove;
12510     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12511
12512     while (i < forwardMostMove) {
12513         if (commentList[i] != NULL) {
12514             fprintf(f, "[%s]\n", commentList[i]);
12515         }
12516
12517         if ((i % 2) == 1) {
12518             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12519             i++;
12520         } else {
12521             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12522             i++;
12523             if (commentList[i] != NULL) {
12524                 fprintf(f, "\n");
12525                 continue;
12526             }
12527             if (i >= forwardMostMove) {
12528                 fprintf(f, "\n");
12529                 break;
12530             }
12531             fprintf(f, "%s\n", parseList[i]);
12532             i++;
12533         }
12534     }
12535
12536     if (commentList[i] != NULL) {
12537         fprintf(f, "[%s]\n", commentList[i]);
12538     }
12539
12540     /* This isn't really the old style, but it's close enough */
12541     if (gameInfo.resultDetails != NULL &&
12542         gameInfo.resultDetails[0] != NULLCHAR) {
12543         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12544                 gameInfo.resultDetails);
12545     } else {
12546         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12547     }
12548
12549     fclose(f);
12550     return TRUE;
12551 }
12552
12553 /* Save the current game to open file f and close the file */
12554 int
12555 SaveGame (FILE *f, int dummy, char *dummy2)
12556 {
12557     if (gameMode == EditPosition) EditPositionDone(TRUE);
12558     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12559     if (appData.oldSaveStyle)
12560       return SaveGameOldStyle(f);
12561     else
12562       return SaveGamePGN(f);
12563 }
12564
12565 /* Save the current position to the given file */
12566 int
12567 SavePositionToFile (char *filename)
12568 {
12569     FILE *f;
12570     char buf[MSG_SIZ];
12571
12572     if (strcmp(filename, "-") == 0) {
12573         return SavePosition(stdout, 0, NULL);
12574     } else {
12575         f = fopen(filename, "a");
12576         if (f == NULL) {
12577             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12578             DisplayError(buf, errno);
12579             return FALSE;
12580         } else {
12581             safeStrCpy(buf, lastMsg, MSG_SIZ);
12582             DisplayMessage(_("Waiting for access to save file"), "");
12583             flock(fileno(f), LOCK_EX); // [HGM] lock
12584             DisplayMessage(_("Saving position"), "");
12585             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12586             SavePosition(f, 0, NULL);
12587             DisplayMessage(buf, "");
12588             return TRUE;
12589         }
12590     }
12591 }
12592
12593 /* Save the current position to the given open file and close the file */
12594 int
12595 SavePosition (FILE *f, int dummy, char *dummy2)
12596 {
12597     time_t tm;
12598     char *fen;
12599
12600     if (gameMode == EditPosition) EditPositionDone(TRUE);
12601     if (appData.oldSaveStyle) {
12602         tm = time((time_t *) NULL);
12603
12604         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12605         PrintOpponents(f);
12606         fprintf(f, "[--------------\n");
12607         PrintPosition(f, currentMove);
12608         fprintf(f, "--------------]\n");
12609     } else {
12610         fen = PositionToFEN(currentMove, NULL);
12611         fprintf(f, "%s\n", fen);
12612         free(fen);
12613     }
12614     fclose(f);
12615     return TRUE;
12616 }
12617
12618 void
12619 ReloadCmailMsgEvent (int unregister)
12620 {
12621 #if !WIN32
12622     static char *inFilename = NULL;
12623     static char *outFilename;
12624     int i;
12625     struct stat inbuf, outbuf;
12626     int status;
12627
12628     /* Any registered moves are unregistered if unregister is set, */
12629     /* i.e. invoked by the signal handler */
12630     if (unregister) {
12631         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12632             cmailMoveRegistered[i] = FALSE;
12633             if (cmailCommentList[i] != NULL) {
12634                 free(cmailCommentList[i]);
12635                 cmailCommentList[i] = NULL;
12636             }
12637         }
12638         nCmailMovesRegistered = 0;
12639     }
12640
12641     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12642         cmailResult[i] = CMAIL_NOT_RESULT;
12643     }
12644     nCmailResults = 0;
12645
12646     if (inFilename == NULL) {
12647         /* Because the filenames are static they only get malloced once  */
12648         /* and they never get freed                                      */
12649         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12650         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12651
12652         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12653         sprintf(outFilename, "%s.out", appData.cmailGameName);
12654     }
12655
12656     status = stat(outFilename, &outbuf);
12657     if (status < 0) {
12658         cmailMailedMove = FALSE;
12659     } else {
12660         status = stat(inFilename, &inbuf);
12661         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12662     }
12663
12664     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12665        counts the games, notes how each one terminated, etc.
12666
12667        It would be nice to remove this kludge and instead gather all
12668        the information while building the game list.  (And to keep it
12669        in the game list nodes instead of having a bunch of fixed-size
12670        parallel arrays.)  Note this will require getting each game's
12671        termination from the PGN tags, as the game list builder does
12672        not process the game moves.  --mann
12673        */
12674     cmailMsgLoaded = TRUE;
12675     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12676
12677     /* Load first game in the file or popup game menu */
12678     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12679
12680 #endif /* !WIN32 */
12681     return;
12682 }
12683
12684 int
12685 RegisterMove ()
12686 {
12687     FILE *f;
12688     char string[MSG_SIZ];
12689
12690     if (   cmailMailedMove
12691         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12692         return TRUE;            /* Allow free viewing  */
12693     }
12694
12695     /* Unregister move to ensure that we don't leave RegisterMove        */
12696     /* with the move registered when the conditions for registering no   */
12697     /* longer hold                                                       */
12698     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12699         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12700         nCmailMovesRegistered --;
12701
12702         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12703           {
12704               free(cmailCommentList[lastLoadGameNumber - 1]);
12705               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12706           }
12707     }
12708
12709     if (cmailOldMove == -1) {
12710         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12711         return FALSE;
12712     }
12713
12714     if (currentMove > cmailOldMove + 1) {
12715         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12716         return FALSE;
12717     }
12718
12719     if (currentMove < cmailOldMove) {
12720         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12721         return FALSE;
12722     }
12723
12724     if (forwardMostMove > currentMove) {
12725         /* Silently truncate extra moves */
12726         TruncateGame();
12727     }
12728
12729     if (   (currentMove == cmailOldMove + 1)
12730         || (   (currentMove == cmailOldMove)
12731             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12732                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12733         if (gameInfo.result != GameUnfinished) {
12734             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12735         }
12736
12737         if (commentList[currentMove] != NULL) {
12738             cmailCommentList[lastLoadGameNumber - 1]
12739               = StrSave(commentList[currentMove]);
12740         }
12741         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12742
12743         if (appData.debugMode)
12744           fprintf(debugFP, "Saving %s for game %d\n",
12745                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12746
12747         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12748
12749         f = fopen(string, "w");
12750         if (appData.oldSaveStyle) {
12751             SaveGameOldStyle(f); /* also closes the file */
12752
12753             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12754             f = fopen(string, "w");
12755             SavePosition(f, 0, NULL); /* also closes the file */
12756         } else {
12757             fprintf(f, "{--------------\n");
12758             PrintPosition(f, currentMove);
12759             fprintf(f, "--------------}\n\n");
12760
12761             SaveGame(f, 0, NULL); /* also closes the file*/
12762         }
12763
12764         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12765         nCmailMovesRegistered ++;
12766     } else if (nCmailGames == 1) {
12767         DisplayError(_("You have not made a move yet"), 0);
12768         return FALSE;
12769     }
12770
12771     return TRUE;
12772 }
12773
12774 void
12775 MailMoveEvent ()
12776 {
12777 #if !WIN32
12778     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12779     FILE *commandOutput;
12780     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12781     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12782     int nBuffers;
12783     int i;
12784     int archived;
12785     char *arcDir;
12786
12787     if (! cmailMsgLoaded) {
12788         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12789         return;
12790     }
12791
12792     if (nCmailGames == nCmailResults) {
12793         DisplayError(_("No unfinished games"), 0);
12794         return;
12795     }
12796
12797 #if CMAIL_PROHIBIT_REMAIL
12798     if (cmailMailedMove) {
12799       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);
12800         DisplayError(msg, 0);
12801         return;
12802     }
12803 #endif
12804
12805     if (! (cmailMailedMove || RegisterMove())) return;
12806
12807     if (   cmailMailedMove
12808         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12809       snprintf(string, MSG_SIZ, partCommandString,
12810                appData.debugMode ? " -v" : "", appData.cmailGameName);
12811         commandOutput = popen(string, "r");
12812
12813         if (commandOutput == NULL) {
12814             DisplayError(_("Failed to invoke cmail"), 0);
12815         } else {
12816             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12817                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12818             }
12819             if (nBuffers > 1) {
12820                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12821                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12822                 nBytes = MSG_SIZ - 1;
12823             } else {
12824                 (void) memcpy(msg, buffer, nBytes);
12825             }
12826             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12827
12828             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12829                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12830
12831                 archived = TRUE;
12832                 for (i = 0; i < nCmailGames; i ++) {
12833                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12834                         archived = FALSE;
12835                     }
12836                 }
12837                 if (   archived
12838                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12839                         != NULL)) {
12840                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12841                            arcDir,
12842                            appData.cmailGameName,
12843                            gameInfo.date);
12844                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12845                     cmailMsgLoaded = FALSE;
12846                 }
12847             }
12848
12849             DisplayInformation(msg);
12850             pclose(commandOutput);
12851         }
12852     } else {
12853         if ((*cmailMsg) != '\0') {
12854             DisplayInformation(cmailMsg);
12855         }
12856     }
12857
12858     return;
12859 #endif /* !WIN32 */
12860 }
12861
12862 char *
12863 CmailMsg ()
12864 {
12865 #if WIN32
12866     return NULL;
12867 #else
12868     int  prependComma = 0;
12869     char number[5];
12870     char string[MSG_SIZ];       /* Space for game-list */
12871     int  i;
12872
12873     if (!cmailMsgLoaded) return "";
12874
12875     if (cmailMailedMove) {
12876       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12877     } else {
12878         /* Create a list of games left */
12879       snprintf(string, MSG_SIZ, "[");
12880         for (i = 0; i < nCmailGames; i ++) {
12881             if (! (   cmailMoveRegistered[i]
12882                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12883                 if (prependComma) {
12884                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12885                 } else {
12886                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12887                     prependComma = 1;
12888                 }
12889
12890                 strcat(string, number);
12891             }
12892         }
12893         strcat(string, "]");
12894
12895         if (nCmailMovesRegistered + nCmailResults == 0) {
12896             switch (nCmailGames) {
12897               case 1:
12898                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12899                 break;
12900
12901               case 2:
12902                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12903                 break;
12904
12905               default:
12906                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12907                          nCmailGames);
12908                 break;
12909             }
12910         } else {
12911             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12912               case 1:
12913                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12914                          string);
12915                 break;
12916
12917               case 0:
12918                 if (nCmailResults == nCmailGames) {
12919                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12920                 } else {
12921                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12922                 }
12923                 break;
12924
12925               default:
12926                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12927                          string);
12928             }
12929         }
12930     }
12931     return cmailMsg;
12932 #endif /* WIN32 */
12933 }
12934
12935 void
12936 ResetGameEvent ()
12937 {
12938     if (gameMode == Training)
12939       SetTrainingModeOff();
12940
12941     Reset(TRUE, TRUE);
12942     cmailMsgLoaded = FALSE;
12943     if (appData.icsActive) {
12944       SendToICS(ics_prefix);
12945       SendToICS("refresh\n");
12946     }
12947 }
12948
12949 void
12950 ExitEvent (int status)
12951 {
12952     exiting++;
12953     if (exiting > 2) {
12954       /* Give up on clean exit */
12955       exit(status);
12956     }
12957     if (exiting > 1) {
12958       /* Keep trying for clean exit */
12959       return;
12960     }
12961
12962     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12963
12964     if (telnetISR != NULL) {
12965       RemoveInputSource(telnetISR);
12966     }
12967     if (icsPR != NoProc) {
12968       DestroyChildProcess(icsPR, TRUE);
12969     }
12970
12971     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12972     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12973
12974     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12975     /* make sure this other one finishes before killing it!                  */
12976     if(endingGame) { int count = 0;
12977         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12978         while(endingGame && count++ < 10) DoSleep(1);
12979         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12980     }
12981
12982     /* Kill off chess programs */
12983     if (first.pr != NoProc) {
12984         ExitAnalyzeMode();
12985
12986         DoSleep( appData.delayBeforeQuit );
12987         SendToProgram("quit\n", &first);
12988         DoSleep( appData.delayAfterQuit );
12989         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12990     }
12991     if (second.pr != NoProc) {
12992         DoSleep( appData.delayBeforeQuit );
12993         SendToProgram("quit\n", &second);
12994         DoSleep( appData.delayAfterQuit );
12995         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12996     }
12997     if (first.isr != NULL) {
12998         RemoveInputSource(first.isr);
12999     }
13000     if (second.isr != NULL) {
13001         RemoveInputSource(second.isr);
13002     }
13003
13004     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13005     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13006
13007     ShutDownFrontEnd();
13008     exit(status);
13009 }
13010
13011 void
13012 PauseEvent ()
13013 {
13014     if (appData.debugMode)
13015         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13016     if (pausing) {
13017         pausing = FALSE;
13018         ModeHighlight();
13019         if (gameMode == MachinePlaysWhite ||
13020             gameMode == MachinePlaysBlack) {
13021             StartClocks();
13022         } else {
13023             DisplayBothClocks();
13024         }
13025         if (gameMode == PlayFromGameFile) {
13026             if (appData.timeDelay >= 0)
13027                 AutoPlayGameLoop();
13028         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13029             Reset(FALSE, TRUE);
13030             SendToICS(ics_prefix);
13031             SendToICS("refresh\n");
13032         } else if (currentMove < forwardMostMove) {
13033             ForwardInner(forwardMostMove);
13034         }
13035         pauseExamInvalid = FALSE;
13036     } else {
13037         switch (gameMode) {
13038           default:
13039             return;
13040           case IcsExamining:
13041             pauseExamForwardMostMove = forwardMostMove;
13042             pauseExamInvalid = FALSE;
13043             /* fall through */
13044           case IcsObserving:
13045           case IcsPlayingWhite:
13046           case IcsPlayingBlack:
13047             pausing = TRUE;
13048             ModeHighlight();
13049             return;
13050           case PlayFromGameFile:
13051             (void) StopLoadGameTimer();
13052             pausing = TRUE;
13053             ModeHighlight();
13054             break;
13055           case BeginningOfGame:
13056             if (appData.icsActive) return;
13057             /* else fall through */
13058           case MachinePlaysWhite:
13059           case MachinePlaysBlack:
13060           case TwoMachinesPlay:
13061             if (forwardMostMove == 0)
13062               return;           /* don't pause if no one has moved */
13063             if ((gameMode == MachinePlaysWhite &&
13064                  !WhiteOnMove(forwardMostMove)) ||
13065                 (gameMode == MachinePlaysBlack &&
13066                  WhiteOnMove(forwardMostMove))) {
13067                 StopClocks();
13068             }
13069             pausing = TRUE;
13070             ModeHighlight();
13071             break;
13072         }
13073     }
13074 }
13075
13076 void
13077 EditCommentEvent ()
13078 {
13079     char title[MSG_SIZ];
13080
13081     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13082       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13083     } else {
13084       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13085                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13086                parseList[currentMove - 1]);
13087     }
13088
13089     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13090 }
13091
13092
13093 void
13094 EditTagsEvent ()
13095 {
13096     char *tags = PGNTags(&gameInfo);
13097     bookUp = FALSE;
13098     EditTagsPopUp(tags, NULL);
13099     free(tags);
13100 }
13101
13102 void
13103 AnalyzeModeEvent ()
13104 {
13105     if (appData.noChessProgram || gameMode == AnalyzeMode)
13106       return;
13107
13108     if (gameMode != AnalyzeFile) {
13109         if (!appData.icsEngineAnalyze) {
13110                EditGameEvent();
13111                if (gameMode != EditGame) return;
13112         }
13113         ResurrectChessProgram();
13114         SendToProgram("analyze\n", &first);
13115         first.analyzing = TRUE;
13116         /*first.maybeThinking = TRUE;*/
13117         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13118         EngineOutputPopUp();
13119     }
13120     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13121     pausing = FALSE;
13122     ModeHighlight();
13123     SetGameInfo();
13124
13125     StartAnalysisClock();
13126     GetTimeMark(&lastNodeCountTime);
13127     lastNodeCount = 0;
13128 }
13129
13130 void
13131 AnalyzeFileEvent ()
13132 {
13133     if (appData.noChessProgram || gameMode == AnalyzeFile)
13134       return;
13135
13136     if (gameMode != AnalyzeMode) {
13137         EditGameEvent();
13138         if (gameMode != EditGame) return;
13139         ResurrectChessProgram();
13140         SendToProgram("analyze\n", &first);
13141         first.analyzing = TRUE;
13142         /*first.maybeThinking = TRUE;*/
13143         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13144         EngineOutputPopUp();
13145     }
13146     gameMode = AnalyzeFile;
13147     pausing = FALSE;
13148     ModeHighlight();
13149     SetGameInfo();
13150
13151     StartAnalysisClock();
13152     GetTimeMark(&lastNodeCountTime);
13153     lastNodeCount = 0;
13154     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13155 }
13156
13157 void
13158 MachineWhiteEvent ()
13159 {
13160     char buf[MSG_SIZ];
13161     char *bookHit = NULL;
13162
13163     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13164       return;
13165
13166
13167     if (gameMode == PlayFromGameFile ||
13168         gameMode == TwoMachinesPlay  ||
13169         gameMode == Training         ||
13170         gameMode == AnalyzeMode      ||
13171         gameMode == EndOfGame)
13172         EditGameEvent();
13173
13174     if (gameMode == EditPosition)
13175         EditPositionDone(TRUE);
13176
13177     if (!WhiteOnMove(currentMove)) {
13178         DisplayError(_("It is not White's turn"), 0);
13179         return;
13180     }
13181
13182     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13183       ExitAnalyzeMode();
13184
13185     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13186         gameMode == AnalyzeFile)
13187         TruncateGame();
13188
13189     ResurrectChessProgram();    /* in case it isn't running */
13190     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13191         gameMode = MachinePlaysWhite;
13192         ResetClocks();
13193     } else
13194     gameMode = MachinePlaysWhite;
13195     pausing = FALSE;
13196     ModeHighlight();
13197     SetGameInfo();
13198     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13199     DisplayTitle(buf);
13200     if (first.sendName) {
13201       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13202       SendToProgram(buf, &first);
13203     }
13204     if (first.sendTime) {
13205       if (first.useColors) {
13206         SendToProgram("black\n", &first); /*gnu kludge*/
13207       }
13208       SendTimeRemaining(&first, TRUE);
13209     }
13210     if (first.useColors) {
13211       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13212     }
13213     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13214     SetMachineThinkingEnables();
13215     first.maybeThinking = TRUE;
13216     StartClocks();
13217     firstMove = FALSE;
13218
13219     if (appData.autoFlipView && !flipView) {
13220       flipView = !flipView;
13221       DrawPosition(FALSE, NULL);
13222       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13223     }
13224
13225     if(bookHit) { // [HGM] book: simulate book reply
13226         static char bookMove[MSG_SIZ]; // a bit generous?
13227
13228         programStats.nodes = programStats.depth = programStats.time =
13229         programStats.score = programStats.got_only_move = 0;
13230         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13231
13232         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13233         strcat(bookMove, bookHit);
13234         HandleMachineMove(bookMove, &first);
13235     }
13236 }
13237
13238 void
13239 MachineBlackEvent ()
13240 {
13241   char buf[MSG_SIZ];
13242   char *bookHit = NULL;
13243
13244     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13245         return;
13246
13247
13248     if (gameMode == PlayFromGameFile ||
13249         gameMode == TwoMachinesPlay  ||
13250         gameMode == Training         ||
13251         gameMode == AnalyzeMode      ||
13252         gameMode == EndOfGame)
13253         EditGameEvent();
13254
13255     if (gameMode == EditPosition)
13256         EditPositionDone(TRUE);
13257
13258     if (WhiteOnMove(currentMove)) {
13259         DisplayError(_("It is not Black's turn"), 0);
13260         return;
13261     }
13262
13263     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13264       ExitAnalyzeMode();
13265
13266     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13267         gameMode == AnalyzeFile)
13268         TruncateGame();
13269
13270     ResurrectChessProgram();    /* in case it isn't running */
13271     gameMode = MachinePlaysBlack;
13272     pausing = FALSE;
13273     ModeHighlight();
13274     SetGameInfo();
13275     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13276     DisplayTitle(buf);
13277     if (first.sendName) {
13278       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13279       SendToProgram(buf, &first);
13280     }
13281     if (first.sendTime) {
13282       if (first.useColors) {
13283         SendToProgram("white\n", &first); /*gnu kludge*/
13284       }
13285       SendTimeRemaining(&first, FALSE);
13286     }
13287     if (first.useColors) {
13288       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13289     }
13290     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13291     SetMachineThinkingEnables();
13292     first.maybeThinking = TRUE;
13293     StartClocks();
13294
13295     if (appData.autoFlipView && flipView) {
13296       flipView = !flipView;
13297       DrawPosition(FALSE, NULL);
13298       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13299     }
13300     if(bookHit) { // [HGM] book: simulate book reply
13301         static char bookMove[MSG_SIZ]; // a bit generous?
13302
13303         programStats.nodes = programStats.depth = programStats.time =
13304         programStats.score = programStats.got_only_move = 0;
13305         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13306
13307         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13308         strcat(bookMove, bookHit);
13309         HandleMachineMove(bookMove, &first);
13310     }
13311 }
13312
13313
13314 void
13315 DisplayTwoMachinesTitle ()
13316 {
13317     char buf[MSG_SIZ];
13318     if (appData.matchGames > 0) {
13319         if(appData.tourneyFile[0]) {
13320           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13321                    gameInfo.white, _("vs."), gameInfo.black,
13322                    nextGame+1, appData.matchGames+1,
13323                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13324         } else 
13325         if (first.twoMachinesColor[0] == 'w') {
13326           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13327                    gameInfo.white, _("vs."),  gameInfo.black,
13328                    first.matchWins, second.matchWins,
13329                    matchGame - 1 - (first.matchWins + second.matchWins));
13330         } else {
13331           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13332                    gameInfo.white, _("vs."), gameInfo.black,
13333                    second.matchWins, first.matchWins,
13334                    matchGame - 1 - (first.matchWins + second.matchWins));
13335         }
13336     } else {
13337       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13338     }
13339     DisplayTitle(buf);
13340 }
13341
13342 void
13343 SettingsMenuIfReady ()
13344 {
13345   if (second.lastPing != second.lastPong) {
13346     DisplayMessage("", _("Waiting for second chess program"));
13347     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13348     return;
13349   }
13350   ThawUI();
13351   DisplayMessage("", "");
13352   SettingsPopUp(&second);
13353 }
13354
13355 int
13356 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13357 {
13358     char buf[MSG_SIZ];
13359     if (cps->pr == NoProc) {
13360         StartChessProgram(cps);
13361         if (cps->protocolVersion == 1) {
13362           retry();
13363         } else {
13364           /* kludge: allow timeout for initial "feature" command */
13365           FreezeUI();
13366           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13367           DisplayMessage("", buf);
13368           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13369         }
13370         return 1;
13371     }
13372     return 0;
13373 }
13374
13375 void
13376 TwoMachinesEvent P((void))
13377 {
13378     int i;
13379     char buf[MSG_SIZ];
13380     ChessProgramState *onmove;
13381     char *bookHit = NULL;
13382     static int stalling = 0;
13383     TimeMark now;
13384     long wait;
13385
13386     if (appData.noChessProgram) return;
13387
13388     switch (gameMode) {
13389       case TwoMachinesPlay:
13390         return;
13391       case MachinePlaysWhite:
13392       case MachinePlaysBlack:
13393         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13394             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13395             return;
13396         }
13397         /* fall through */
13398       case BeginningOfGame:
13399       case PlayFromGameFile:
13400       case EndOfGame:
13401         EditGameEvent();
13402         if (gameMode != EditGame) return;
13403         break;
13404       case EditPosition:
13405         EditPositionDone(TRUE);
13406         break;
13407       case AnalyzeMode:
13408       case AnalyzeFile:
13409         ExitAnalyzeMode();
13410         break;
13411       case EditGame:
13412       default:
13413         break;
13414     }
13415
13416 //    forwardMostMove = currentMove;
13417     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13418
13419     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13420
13421     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13422     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13423       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13424       return;
13425     }
13426     if(!stalling) {
13427       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13428       SendToProgram("force\n", &second);
13429       stalling = 1;
13430       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13431       return;
13432     }
13433     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13434     if(appData.matchPause>10000 || appData.matchPause<10)
13435                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13436     wait = SubtractTimeMarks(&now, &pauseStart);
13437     if(wait < appData.matchPause) {
13438         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13439         return;
13440     }
13441     // we are now committed to starting the game
13442     stalling = 0;
13443     DisplayMessage("", "");
13444     if (startedFromSetupPosition) {
13445         SendBoard(&second, backwardMostMove);
13446     if (appData.debugMode) {
13447         fprintf(debugFP, "Two Machines\n");
13448     }
13449     }
13450     for (i = backwardMostMove; i < forwardMostMove; i++) {
13451         SendMoveToProgram(i, &second);
13452     }
13453
13454     gameMode = TwoMachinesPlay;
13455     pausing = FALSE;
13456     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13457     SetGameInfo();
13458     DisplayTwoMachinesTitle();
13459     firstMove = TRUE;
13460     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13461         onmove = &first;
13462     } else {
13463         onmove = &second;
13464     }
13465     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13466     SendToProgram(first.computerString, &first);
13467     if (first.sendName) {
13468       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13469       SendToProgram(buf, &first);
13470     }
13471     SendToProgram(second.computerString, &second);
13472     if (second.sendName) {
13473       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13474       SendToProgram(buf, &second);
13475     }
13476
13477     ResetClocks();
13478     if (!first.sendTime || !second.sendTime) {
13479         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13480         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13481     }
13482     if (onmove->sendTime) {
13483       if (onmove->useColors) {
13484         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13485       }
13486       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13487     }
13488     if (onmove->useColors) {
13489       SendToProgram(onmove->twoMachinesColor, onmove);
13490     }
13491     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13492 //    SendToProgram("go\n", onmove);
13493     onmove->maybeThinking = TRUE;
13494     SetMachineThinkingEnables();
13495
13496     StartClocks();
13497
13498     if(bookHit) { // [HGM] book: simulate book reply
13499         static char bookMove[MSG_SIZ]; // a bit generous?
13500
13501         programStats.nodes = programStats.depth = programStats.time =
13502         programStats.score = programStats.got_only_move = 0;
13503         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13504
13505         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13506         strcat(bookMove, bookHit);
13507         savedMessage = bookMove; // args for deferred call
13508         savedState = onmove;
13509         ScheduleDelayedEvent(DeferredBookMove, 1);
13510     }
13511 }
13512
13513 void
13514 TrainingEvent ()
13515 {
13516     if (gameMode == Training) {
13517       SetTrainingModeOff();
13518       gameMode = PlayFromGameFile;
13519       DisplayMessage("", _("Training mode off"));
13520     } else {
13521       gameMode = Training;
13522       animateTraining = appData.animate;
13523
13524       /* make sure we are not already at the end of the game */
13525       if (currentMove < forwardMostMove) {
13526         SetTrainingModeOn();
13527         DisplayMessage("", _("Training mode on"));
13528       } else {
13529         gameMode = PlayFromGameFile;
13530         DisplayError(_("Already at end of game"), 0);
13531       }
13532     }
13533     ModeHighlight();
13534 }
13535
13536 void
13537 IcsClientEvent ()
13538 {
13539     if (!appData.icsActive) return;
13540     switch (gameMode) {
13541       case IcsPlayingWhite:
13542       case IcsPlayingBlack:
13543       case IcsObserving:
13544       case IcsIdle:
13545       case BeginningOfGame:
13546       case IcsExamining:
13547         return;
13548
13549       case EditGame:
13550         break;
13551
13552       case EditPosition:
13553         EditPositionDone(TRUE);
13554         break;
13555
13556       case AnalyzeMode:
13557       case AnalyzeFile:
13558         ExitAnalyzeMode();
13559         break;
13560
13561       default:
13562         EditGameEvent();
13563         break;
13564     }
13565
13566     gameMode = IcsIdle;
13567     ModeHighlight();
13568     return;
13569 }
13570
13571 void
13572 EditGameEvent ()
13573 {
13574     int i;
13575
13576     switch (gameMode) {
13577       case Training:
13578         SetTrainingModeOff();
13579         break;
13580       case MachinePlaysWhite:
13581       case MachinePlaysBlack:
13582       case BeginningOfGame:
13583         SendToProgram("force\n", &first);
13584         SetUserThinkingEnables();
13585         break;
13586       case PlayFromGameFile:
13587         (void) StopLoadGameTimer();
13588         if (gameFileFP != NULL) {
13589             gameFileFP = NULL;
13590         }
13591         break;
13592       case EditPosition:
13593         EditPositionDone(TRUE);
13594         break;
13595       case AnalyzeMode:
13596       case AnalyzeFile:
13597         ExitAnalyzeMode();
13598         SendToProgram("force\n", &first);
13599         break;
13600       case TwoMachinesPlay:
13601         GameEnds(EndOfFile, NULL, GE_PLAYER);
13602         ResurrectChessProgram();
13603         SetUserThinkingEnables();
13604         break;
13605       case EndOfGame:
13606         ResurrectChessProgram();
13607         break;
13608       case IcsPlayingBlack:
13609       case IcsPlayingWhite:
13610         DisplayError(_("Warning: You are still playing a game"), 0);
13611         break;
13612       case IcsObserving:
13613         DisplayError(_("Warning: You are still observing a game"), 0);
13614         break;
13615       case IcsExamining:
13616         DisplayError(_("Warning: You are still examining a game"), 0);
13617         break;
13618       case IcsIdle:
13619         break;
13620       case EditGame:
13621       default:
13622         return;
13623     }
13624
13625     pausing = FALSE;
13626     StopClocks();
13627     first.offeredDraw = second.offeredDraw = 0;
13628
13629     if (gameMode == PlayFromGameFile) {
13630         whiteTimeRemaining = timeRemaining[0][currentMove];
13631         blackTimeRemaining = timeRemaining[1][currentMove];
13632         DisplayTitle("");
13633     }
13634
13635     if (gameMode == MachinePlaysWhite ||
13636         gameMode == MachinePlaysBlack ||
13637         gameMode == TwoMachinesPlay ||
13638         gameMode == EndOfGame) {
13639         i = forwardMostMove;
13640         while (i > currentMove) {
13641             SendToProgram("undo\n", &first);
13642             i--;
13643         }
13644         if(!adjustedClock) {
13645         whiteTimeRemaining = timeRemaining[0][currentMove];
13646         blackTimeRemaining = timeRemaining[1][currentMove];
13647         DisplayBothClocks();
13648         }
13649         if (whiteFlag || blackFlag) {
13650             whiteFlag = blackFlag = 0;
13651         }
13652         DisplayTitle("");
13653     }
13654
13655     gameMode = EditGame;
13656     ModeHighlight();
13657     SetGameInfo();
13658 }
13659
13660
13661 void
13662 EditPositionEvent ()
13663 {
13664     if (gameMode == EditPosition) {
13665         EditGameEvent();
13666         return;
13667     }
13668
13669     EditGameEvent();
13670     if (gameMode != EditGame) return;
13671
13672     gameMode = EditPosition;
13673     ModeHighlight();
13674     SetGameInfo();
13675     if (currentMove > 0)
13676       CopyBoard(boards[0], boards[currentMove]);
13677
13678     blackPlaysFirst = !WhiteOnMove(currentMove);
13679     ResetClocks();
13680     currentMove = forwardMostMove = backwardMostMove = 0;
13681     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13682     DisplayMove(-1);
13683 }
13684
13685 void
13686 ExitAnalyzeMode ()
13687 {
13688     /* [DM] icsEngineAnalyze - possible call from other functions */
13689     if (appData.icsEngineAnalyze) {
13690         appData.icsEngineAnalyze = FALSE;
13691
13692         DisplayMessage("",_("Close ICS engine analyze..."));
13693     }
13694     if (first.analysisSupport && first.analyzing) {
13695       SendToProgram("exit\n", &first);
13696       first.analyzing = FALSE;
13697     }
13698     thinkOutput[0] = NULLCHAR;
13699 }
13700
13701 void
13702 EditPositionDone (Boolean fakeRights)
13703 {
13704     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13705
13706     startedFromSetupPosition = TRUE;
13707     InitChessProgram(&first, FALSE);
13708     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13709       boards[0][EP_STATUS] = EP_NONE;
13710       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13711     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13712         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13713         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13714       } else boards[0][CASTLING][2] = NoRights;
13715     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13716         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13717         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13718       } else boards[0][CASTLING][5] = NoRights;
13719     }
13720     SendToProgram("force\n", &first);
13721     if (blackPlaysFirst) {
13722         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13723         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13724         currentMove = forwardMostMove = backwardMostMove = 1;
13725         CopyBoard(boards[1], boards[0]);
13726     } else {
13727         currentMove = forwardMostMove = backwardMostMove = 0;
13728     }
13729     SendBoard(&first, forwardMostMove);
13730     if (appData.debugMode) {
13731         fprintf(debugFP, "EditPosDone\n");
13732     }
13733     DisplayTitle("");
13734     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13735     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13736     gameMode = EditGame;
13737     ModeHighlight();
13738     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13739     ClearHighlights(); /* [AS] */
13740 }
13741
13742 /* Pause for `ms' milliseconds */
13743 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13744 void
13745 TimeDelay (long ms)
13746 {
13747     TimeMark m1, m2;
13748
13749     GetTimeMark(&m1);
13750     do {
13751         GetTimeMark(&m2);
13752     } while (SubtractTimeMarks(&m2, &m1) < ms);
13753 }
13754
13755 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13756 void
13757 SendMultiLineToICS (char *buf)
13758 {
13759     char temp[MSG_SIZ+1], *p;
13760     int len;
13761
13762     len = strlen(buf);
13763     if (len > MSG_SIZ)
13764       len = MSG_SIZ;
13765
13766     strncpy(temp, buf, len);
13767     temp[len] = 0;
13768
13769     p = temp;
13770     while (*p) {
13771         if (*p == '\n' || *p == '\r')
13772           *p = ' ';
13773         ++p;
13774     }
13775
13776     strcat(temp, "\n");
13777     SendToICS(temp);
13778     SendToPlayer(temp, strlen(temp));
13779 }
13780
13781 void
13782 SetWhiteToPlayEvent ()
13783 {
13784     if (gameMode == EditPosition) {
13785         blackPlaysFirst = FALSE;
13786         DisplayBothClocks();    /* works because currentMove is 0 */
13787     } else if (gameMode == IcsExamining) {
13788         SendToICS(ics_prefix);
13789         SendToICS("tomove white\n");
13790     }
13791 }
13792
13793 void
13794 SetBlackToPlayEvent ()
13795 {
13796     if (gameMode == EditPosition) {
13797         blackPlaysFirst = TRUE;
13798         currentMove = 1;        /* kludge */
13799         DisplayBothClocks();
13800         currentMove = 0;
13801     } else if (gameMode == IcsExamining) {
13802         SendToICS(ics_prefix);
13803         SendToICS("tomove black\n");
13804     }
13805 }
13806
13807 void
13808 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13809 {
13810     char buf[MSG_SIZ];
13811     ChessSquare piece = boards[0][y][x];
13812
13813     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13814
13815     switch (selection) {
13816       case ClearBoard:
13817         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13818             SendToICS(ics_prefix);
13819             SendToICS("bsetup clear\n");
13820         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13821             SendToICS(ics_prefix);
13822             SendToICS("clearboard\n");
13823         } else {
13824             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13825                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13826                 for (y = 0; y < BOARD_HEIGHT; y++) {
13827                     if (gameMode == IcsExamining) {
13828                         if (boards[currentMove][y][x] != EmptySquare) {
13829                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13830                                     AAA + x, ONE + y);
13831                             SendToICS(buf);
13832                         }
13833                     } else {
13834                         boards[0][y][x] = p;
13835                     }
13836                 }
13837             }
13838         }
13839         if (gameMode == EditPosition) {
13840             DrawPosition(FALSE, boards[0]);
13841         }
13842         break;
13843
13844       case WhitePlay:
13845         SetWhiteToPlayEvent();
13846         break;
13847
13848       case BlackPlay:
13849         SetBlackToPlayEvent();
13850         break;
13851
13852       case EmptySquare:
13853         if (gameMode == IcsExamining) {
13854             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13855             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13856             SendToICS(buf);
13857         } else {
13858             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13859                 if(x == BOARD_LEFT-2) {
13860                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13861                     boards[0][y][1] = 0;
13862                 } else
13863                 if(x == BOARD_RGHT+1) {
13864                     if(y >= gameInfo.holdingsSize) break;
13865                     boards[0][y][BOARD_WIDTH-2] = 0;
13866                 } else break;
13867             }
13868             boards[0][y][x] = EmptySquare;
13869             DrawPosition(FALSE, boards[0]);
13870         }
13871         break;
13872
13873       case PromotePiece:
13874         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13875            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13876             selection = (ChessSquare) (PROMOTED piece);
13877         } else if(piece == EmptySquare) selection = WhiteSilver;
13878         else selection = (ChessSquare)((int)piece - 1);
13879         goto defaultlabel;
13880
13881       case DemotePiece:
13882         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13883            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13884             selection = (ChessSquare) (DEMOTED piece);
13885         } else if(piece == EmptySquare) selection = BlackSilver;
13886         else selection = (ChessSquare)((int)piece + 1);
13887         goto defaultlabel;
13888
13889       case WhiteQueen:
13890       case BlackQueen:
13891         if(gameInfo.variant == VariantShatranj ||
13892            gameInfo.variant == VariantXiangqi  ||
13893            gameInfo.variant == VariantCourier  ||
13894            gameInfo.variant == VariantMakruk     )
13895             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13896         goto defaultlabel;
13897
13898       case WhiteKing:
13899       case BlackKing:
13900         if(gameInfo.variant == VariantXiangqi)
13901             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13902         if(gameInfo.variant == VariantKnightmate)
13903             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13904       default:
13905         defaultlabel:
13906         if (gameMode == IcsExamining) {
13907             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13908             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13909                      PieceToChar(selection), AAA + x, ONE + y);
13910             SendToICS(buf);
13911         } else {
13912             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13913                 int n;
13914                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13915                     n = PieceToNumber(selection - BlackPawn);
13916                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13917                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13918                     boards[0][BOARD_HEIGHT-1-n][1]++;
13919                 } else
13920                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13921                     n = PieceToNumber(selection);
13922                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13923                     boards[0][n][BOARD_WIDTH-1] = selection;
13924                     boards[0][n][BOARD_WIDTH-2]++;
13925                 }
13926             } else
13927             boards[0][y][x] = selection;
13928             DrawPosition(TRUE, boards[0]);
13929             ClearHighlights();
13930             fromX = fromY = -1;
13931         }
13932         break;
13933     }
13934 }
13935
13936
13937 void
13938 DropMenuEvent (ChessSquare selection, int x, int y)
13939 {
13940     ChessMove moveType;
13941
13942     switch (gameMode) {
13943       case IcsPlayingWhite:
13944       case MachinePlaysBlack:
13945         if (!WhiteOnMove(currentMove)) {
13946             DisplayMoveError(_("It is Black's turn"));
13947             return;
13948         }
13949         moveType = WhiteDrop;
13950         break;
13951       case IcsPlayingBlack:
13952       case MachinePlaysWhite:
13953         if (WhiteOnMove(currentMove)) {
13954             DisplayMoveError(_("It is White's turn"));
13955             return;
13956         }
13957         moveType = BlackDrop;
13958         break;
13959       case EditGame:
13960         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13961         break;
13962       default:
13963         return;
13964     }
13965
13966     if (moveType == BlackDrop && selection < BlackPawn) {
13967       selection = (ChessSquare) ((int) selection
13968                                  + (int) BlackPawn - (int) WhitePawn);
13969     }
13970     if (boards[currentMove][y][x] != EmptySquare) {
13971         DisplayMoveError(_("That square is occupied"));
13972         return;
13973     }
13974
13975     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13976 }
13977
13978 void
13979 AcceptEvent ()
13980 {
13981     /* Accept a pending offer of any kind from opponent */
13982
13983     if (appData.icsActive) {
13984         SendToICS(ics_prefix);
13985         SendToICS("accept\n");
13986     } else if (cmailMsgLoaded) {
13987         if (currentMove == cmailOldMove &&
13988             commentList[cmailOldMove] != NULL &&
13989             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13990                    "Black offers a draw" : "White offers a draw")) {
13991             TruncateGame();
13992             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13993             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13994         } else {
13995             DisplayError(_("There is no pending offer on this move"), 0);
13996             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13997         }
13998     } else {
13999         /* Not used for offers from chess program */
14000     }
14001 }
14002
14003 void
14004 DeclineEvent ()
14005 {
14006     /* Decline a pending offer of any kind from opponent */
14007
14008     if (appData.icsActive) {
14009         SendToICS(ics_prefix);
14010         SendToICS("decline\n");
14011     } else if (cmailMsgLoaded) {
14012         if (currentMove == cmailOldMove &&
14013             commentList[cmailOldMove] != NULL &&
14014             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14015                    "Black offers a draw" : "White offers a draw")) {
14016 #ifdef NOTDEF
14017             AppendComment(cmailOldMove, "Draw declined", TRUE);
14018             DisplayComment(cmailOldMove - 1, "Draw declined");
14019 #endif /*NOTDEF*/
14020         } else {
14021             DisplayError(_("There is no pending offer on this move"), 0);
14022         }
14023     } else {
14024         /* Not used for offers from chess program */
14025     }
14026 }
14027
14028 void
14029 RematchEvent ()
14030 {
14031     /* Issue ICS rematch command */
14032     if (appData.icsActive) {
14033         SendToICS(ics_prefix);
14034         SendToICS("rematch\n");
14035     }
14036 }
14037
14038 void
14039 CallFlagEvent ()
14040 {
14041     /* Call your opponent's flag (claim a win on time) */
14042     if (appData.icsActive) {
14043         SendToICS(ics_prefix);
14044         SendToICS("flag\n");
14045     } else {
14046         switch (gameMode) {
14047           default:
14048             return;
14049           case MachinePlaysWhite:
14050             if (whiteFlag) {
14051                 if (blackFlag)
14052                   GameEnds(GameIsDrawn, "Both players ran out of time",
14053                            GE_PLAYER);
14054                 else
14055                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14056             } else {
14057                 DisplayError(_("Your opponent is not out of time"), 0);
14058             }
14059             break;
14060           case MachinePlaysBlack:
14061             if (blackFlag) {
14062                 if (whiteFlag)
14063                   GameEnds(GameIsDrawn, "Both players ran out of time",
14064                            GE_PLAYER);
14065                 else
14066                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14067             } else {
14068                 DisplayError(_("Your opponent is not out of time"), 0);
14069             }
14070             break;
14071         }
14072     }
14073 }
14074
14075 void
14076 ClockClick (int which)
14077 {       // [HGM] code moved to back-end from winboard.c
14078         if(which) { // black clock
14079           if (gameMode == EditPosition || gameMode == IcsExamining) {
14080             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14081             SetBlackToPlayEvent();
14082           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14083           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14084           } else if (shiftKey) {
14085             AdjustClock(which, -1);
14086           } else if (gameMode == IcsPlayingWhite ||
14087                      gameMode == MachinePlaysBlack) {
14088             CallFlagEvent();
14089           }
14090         } else { // white clock
14091           if (gameMode == EditPosition || gameMode == IcsExamining) {
14092             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14093             SetWhiteToPlayEvent();
14094           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14095           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14096           } else if (shiftKey) {
14097             AdjustClock(which, -1);
14098           } else if (gameMode == IcsPlayingBlack ||
14099                    gameMode == MachinePlaysWhite) {
14100             CallFlagEvent();
14101           }
14102         }
14103 }
14104
14105 void
14106 DrawEvent ()
14107 {
14108     /* Offer draw or accept pending draw offer from opponent */
14109
14110     if (appData.icsActive) {
14111         /* Note: tournament rules require draw offers to be
14112            made after you make your move but before you punch
14113            your clock.  Currently ICS doesn't let you do that;
14114            instead, you immediately punch your clock after making
14115            a move, but you can offer a draw at any time. */
14116
14117         SendToICS(ics_prefix);
14118         SendToICS("draw\n");
14119         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14120     } else if (cmailMsgLoaded) {
14121         if (currentMove == cmailOldMove &&
14122             commentList[cmailOldMove] != NULL &&
14123             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14124                    "Black offers a draw" : "White offers a draw")) {
14125             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14126             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14127         } else if (currentMove == cmailOldMove + 1) {
14128             char *offer = WhiteOnMove(cmailOldMove) ?
14129               "White offers a draw" : "Black offers a draw";
14130             AppendComment(currentMove, offer, TRUE);
14131             DisplayComment(currentMove - 1, offer);
14132             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14133         } else {
14134             DisplayError(_("You must make your move before offering a draw"), 0);
14135             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14136         }
14137     } else if (first.offeredDraw) {
14138         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14139     } else {
14140         if (first.sendDrawOffers) {
14141             SendToProgram("draw\n", &first);
14142             userOfferedDraw = TRUE;
14143         }
14144     }
14145 }
14146
14147 void
14148 AdjournEvent ()
14149 {
14150     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14151
14152     if (appData.icsActive) {
14153         SendToICS(ics_prefix);
14154         SendToICS("adjourn\n");
14155     } else {
14156         /* Currently GNU Chess doesn't offer or accept Adjourns */
14157     }
14158 }
14159
14160
14161 void
14162 AbortEvent ()
14163 {
14164     /* Offer Abort or accept pending Abort offer from opponent */
14165
14166     if (appData.icsActive) {
14167         SendToICS(ics_prefix);
14168         SendToICS("abort\n");
14169     } else {
14170         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14171     }
14172 }
14173
14174 void
14175 ResignEvent ()
14176 {
14177     /* Resign.  You can do this even if it's not your turn. */
14178
14179     if (appData.icsActive) {
14180         SendToICS(ics_prefix);
14181         SendToICS("resign\n");
14182     } else {
14183         switch (gameMode) {
14184           case MachinePlaysWhite:
14185             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14186             break;
14187           case MachinePlaysBlack:
14188             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14189             break;
14190           case EditGame:
14191             if (cmailMsgLoaded) {
14192                 TruncateGame();
14193                 if (WhiteOnMove(cmailOldMove)) {
14194                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14195                 } else {
14196                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14197                 }
14198                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14199             }
14200             break;
14201           default:
14202             break;
14203         }
14204     }
14205 }
14206
14207
14208 void
14209 StopObservingEvent ()
14210 {
14211     /* Stop observing current games */
14212     SendToICS(ics_prefix);
14213     SendToICS("unobserve\n");
14214 }
14215
14216 void
14217 StopExaminingEvent ()
14218 {
14219     /* Stop observing current game */
14220     SendToICS(ics_prefix);
14221     SendToICS("unexamine\n");
14222 }
14223
14224 void
14225 ForwardInner (int target)
14226 {
14227     int limit; int oldSeekGraphUp = seekGraphUp;
14228
14229     if (appData.debugMode)
14230         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14231                 target, currentMove, forwardMostMove);
14232
14233     if (gameMode == EditPosition)
14234       return;
14235
14236     seekGraphUp = FALSE;
14237     MarkTargetSquares(1);
14238
14239     if (gameMode == PlayFromGameFile && !pausing)
14240       PauseEvent();
14241
14242     if (gameMode == IcsExamining && pausing)
14243       limit = pauseExamForwardMostMove;
14244     else
14245       limit = forwardMostMove;
14246
14247     if (target > limit) target = limit;
14248
14249     if (target > 0 && moveList[target - 1][0]) {
14250         int fromX, fromY, toX, toY;
14251         toX = moveList[target - 1][2] - AAA;
14252         toY = moveList[target - 1][3] - ONE;
14253         if (moveList[target - 1][1] == '@') {
14254             if (appData.highlightLastMove) {
14255                 SetHighlights(-1, -1, toX, toY);
14256             }
14257         } else {
14258             fromX = moveList[target - 1][0] - AAA;
14259             fromY = moveList[target - 1][1] - ONE;
14260             if (target == currentMove + 1) {
14261                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14262             }
14263             if (appData.highlightLastMove) {
14264                 SetHighlights(fromX, fromY, toX, toY);
14265             }
14266         }
14267     }
14268     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14269         gameMode == Training || gameMode == PlayFromGameFile ||
14270         gameMode == AnalyzeFile) {
14271         while (currentMove < target) {
14272             SendMoveToProgram(currentMove++, &first);
14273         }
14274     } else {
14275         currentMove = target;
14276     }
14277
14278     if (gameMode == EditGame || gameMode == EndOfGame) {
14279         whiteTimeRemaining = timeRemaining[0][currentMove];
14280         blackTimeRemaining = timeRemaining[1][currentMove];
14281     }
14282     DisplayBothClocks();
14283     DisplayMove(currentMove - 1);
14284     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14285     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14286     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14287         DisplayComment(currentMove - 1, commentList[currentMove]);
14288     }
14289 }
14290
14291
14292 void
14293 ForwardEvent ()
14294 {
14295     if (gameMode == IcsExamining && !pausing) {
14296         SendToICS(ics_prefix);
14297         SendToICS("forward\n");
14298     } else {
14299         ForwardInner(currentMove + 1);
14300     }
14301 }
14302
14303 void
14304 ToEndEvent ()
14305 {
14306     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14307         /* to optimze, we temporarily turn off analysis mode while we feed
14308          * the remaining moves to the engine. Otherwise we get analysis output
14309          * after each move.
14310          */
14311         if (first.analysisSupport) {
14312           SendToProgram("exit\nforce\n", &first);
14313           first.analyzing = FALSE;
14314         }
14315     }
14316
14317     if (gameMode == IcsExamining && !pausing) {
14318         SendToICS(ics_prefix);
14319         SendToICS("forward 999999\n");
14320     } else {
14321         ForwardInner(forwardMostMove);
14322     }
14323
14324     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14325         /* we have fed all the moves, so reactivate analysis mode */
14326         SendToProgram("analyze\n", &first);
14327         first.analyzing = TRUE;
14328         /*first.maybeThinking = TRUE;*/
14329         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14330     }
14331 }
14332
14333 void
14334 BackwardInner (int target)
14335 {
14336     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14337
14338     if (appData.debugMode)
14339         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14340                 target, currentMove, forwardMostMove);
14341
14342     if (gameMode == EditPosition) return;
14343     seekGraphUp = FALSE;
14344     MarkTargetSquares(1);
14345     if (currentMove <= backwardMostMove) {
14346         ClearHighlights();
14347         DrawPosition(full_redraw, boards[currentMove]);
14348         return;
14349     }
14350     if (gameMode == PlayFromGameFile && !pausing)
14351       PauseEvent();
14352
14353     if (moveList[target][0]) {
14354         int fromX, fromY, toX, toY;
14355         toX = moveList[target][2] - AAA;
14356         toY = moveList[target][3] - ONE;
14357         if (moveList[target][1] == '@') {
14358             if (appData.highlightLastMove) {
14359                 SetHighlights(-1, -1, toX, toY);
14360             }
14361         } else {
14362             fromX = moveList[target][0] - AAA;
14363             fromY = moveList[target][1] - ONE;
14364             if (target == currentMove - 1) {
14365                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14366             }
14367             if (appData.highlightLastMove) {
14368                 SetHighlights(fromX, fromY, toX, toY);
14369             }
14370         }
14371     }
14372     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14373         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14374         while (currentMove > target) {
14375             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14376                 // null move cannot be undone. Reload program with move history before it.
14377                 int i;
14378                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14379                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14380                 }
14381                 SendBoard(&first, i); 
14382                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14383                 break;
14384             }
14385             SendToProgram("undo\n", &first);
14386             currentMove--;
14387         }
14388     } else {
14389         currentMove = target;
14390     }
14391
14392     if (gameMode == EditGame || gameMode == EndOfGame) {
14393         whiteTimeRemaining = timeRemaining[0][currentMove];
14394         blackTimeRemaining = timeRemaining[1][currentMove];
14395     }
14396     DisplayBothClocks();
14397     DisplayMove(currentMove - 1);
14398     DrawPosition(full_redraw, boards[currentMove]);
14399     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14400     // [HGM] PV info: routine tests if comment empty
14401     DisplayComment(currentMove - 1, commentList[currentMove]);
14402 }
14403
14404 void
14405 BackwardEvent ()
14406 {
14407     if (gameMode == IcsExamining && !pausing) {
14408         SendToICS(ics_prefix);
14409         SendToICS("backward\n");
14410     } else {
14411         BackwardInner(currentMove - 1);
14412     }
14413 }
14414
14415 void
14416 ToStartEvent ()
14417 {
14418     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14419         /* to optimize, we temporarily turn off analysis mode while we undo
14420          * all the moves. Otherwise we get analysis output after each undo.
14421          */
14422         if (first.analysisSupport) {
14423           SendToProgram("exit\nforce\n", &first);
14424           first.analyzing = FALSE;
14425         }
14426     }
14427
14428     if (gameMode == IcsExamining && !pausing) {
14429         SendToICS(ics_prefix);
14430         SendToICS("backward 999999\n");
14431     } else {
14432         BackwardInner(backwardMostMove);
14433     }
14434
14435     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14436         /* we have fed all the moves, so reactivate analysis mode */
14437         SendToProgram("analyze\n", &first);
14438         first.analyzing = TRUE;
14439         /*first.maybeThinking = TRUE;*/
14440         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14441     }
14442 }
14443
14444 void
14445 ToNrEvent (int to)
14446 {
14447   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14448   if (to >= forwardMostMove) to = forwardMostMove;
14449   if (to <= backwardMostMove) to = backwardMostMove;
14450   if (to < currentMove) {
14451     BackwardInner(to);
14452   } else {
14453     ForwardInner(to);
14454   }
14455 }
14456
14457 void
14458 RevertEvent (Boolean annotate)
14459 {
14460     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14461         return;
14462     }
14463     if (gameMode != IcsExamining) {
14464         DisplayError(_("You are not examining a game"), 0);
14465         return;
14466     }
14467     if (pausing) {
14468         DisplayError(_("You can't revert while pausing"), 0);
14469         return;
14470     }
14471     SendToICS(ics_prefix);
14472     SendToICS("revert\n");
14473 }
14474
14475 void
14476 RetractMoveEvent ()
14477 {
14478     switch (gameMode) {
14479       case MachinePlaysWhite:
14480       case MachinePlaysBlack:
14481         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14482             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14483             return;
14484         }
14485         if (forwardMostMove < 2) return;
14486         currentMove = forwardMostMove = forwardMostMove - 2;
14487         whiteTimeRemaining = timeRemaining[0][currentMove];
14488         blackTimeRemaining = timeRemaining[1][currentMove];
14489         DisplayBothClocks();
14490         DisplayMove(currentMove - 1);
14491         ClearHighlights();/*!! could figure this out*/
14492         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14493         SendToProgram("remove\n", &first);
14494         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14495         break;
14496
14497       case BeginningOfGame:
14498       default:
14499         break;
14500
14501       case IcsPlayingWhite:
14502       case IcsPlayingBlack:
14503         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14504             SendToICS(ics_prefix);
14505             SendToICS("takeback 2\n");
14506         } else {
14507             SendToICS(ics_prefix);
14508             SendToICS("takeback 1\n");
14509         }
14510         break;
14511     }
14512 }
14513
14514 void
14515 MoveNowEvent ()
14516 {
14517     ChessProgramState *cps;
14518
14519     switch (gameMode) {
14520       case MachinePlaysWhite:
14521         if (!WhiteOnMove(forwardMostMove)) {
14522             DisplayError(_("It is your turn"), 0);
14523             return;
14524         }
14525         cps = &first;
14526         break;
14527       case MachinePlaysBlack:
14528         if (WhiteOnMove(forwardMostMove)) {
14529             DisplayError(_("It is your turn"), 0);
14530             return;
14531         }
14532         cps = &first;
14533         break;
14534       case TwoMachinesPlay:
14535         if (WhiteOnMove(forwardMostMove) ==
14536             (first.twoMachinesColor[0] == 'w')) {
14537             cps = &first;
14538         } else {
14539             cps = &second;
14540         }
14541         break;
14542       case BeginningOfGame:
14543       default:
14544         return;
14545     }
14546     SendToProgram("?\n", cps);
14547 }
14548
14549 void
14550 TruncateGameEvent ()
14551 {
14552     EditGameEvent();
14553     if (gameMode != EditGame) return;
14554     TruncateGame();
14555 }
14556
14557 void
14558 TruncateGame ()
14559 {
14560     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14561     if (forwardMostMove > currentMove) {
14562         if (gameInfo.resultDetails != NULL) {
14563             free(gameInfo.resultDetails);
14564             gameInfo.resultDetails = NULL;
14565             gameInfo.result = GameUnfinished;
14566         }
14567         forwardMostMove = currentMove;
14568         HistorySet(parseList, backwardMostMove, forwardMostMove,
14569                    currentMove-1);
14570     }
14571 }
14572
14573 void
14574 HintEvent ()
14575 {
14576     if (appData.noChessProgram) return;
14577     switch (gameMode) {
14578       case MachinePlaysWhite:
14579         if (WhiteOnMove(forwardMostMove)) {
14580             DisplayError(_("Wait until your turn"), 0);
14581             return;
14582         }
14583         break;
14584       case BeginningOfGame:
14585       case MachinePlaysBlack:
14586         if (!WhiteOnMove(forwardMostMove)) {
14587             DisplayError(_("Wait until your turn"), 0);
14588             return;
14589         }
14590         break;
14591       default:
14592         DisplayError(_("No hint available"), 0);
14593         return;
14594     }
14595     SendToProgram("hint\n", &first);
14596     hintRequested = TRUE;
14597 }
14598
14599 void
14600 BookEvent ()
14601 {
14602     if (appData.noChessProgram) return;
14603     switch (gameMode) {
14604       case MachinePlaysWhite:
14605         if (WhiteOnMove(forwardMostMove)) {
14606             DisplayError(_("Wait until your turn"), 0);
14607             return;
14608         }
14609         break;
14610       case BeginningOfGame:
14611       case MachinePlaysBlack:
14612         if (!WhiteOnMove(forwardMostMove)) {
14613             DisplayError(_("Wait until your turn"), 0);
14614             return;
14615         }
14616         break;
14617       case EditPosition:
14618         EditPositionDone(TRUE);
14619         break;
14620       case TwoMachinesPlay:
14621         return;
14622       default:
14623         break;
14624     }
14625     SendToProgram("bk\n", &first);
14626     bookOutput[0] = NULLCHAR;
14627     bookRequested = TRUE;
14628 }
14629
14630 void
14631 AboutGameEvent ()
14632 {
14633     char *tags = PGNTags(&gameInfo);
14634     TagsPopUp(tags, CmailMsg());
14635     free(tags);
14636 }
14637
14638 /* end button procedures */
14639
14640 void
14641 PrintPosition (FILE *fp, int move)
14642 {
14643     int i, j;
14644
14645     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14646         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14647             char c = PieceToChar(boards[move][i][j]);
14648             fputc(c == 'x' ? '.' : c, fp);
14649             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14650         }
14651     }
14652     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14653       fprintf(fp, "white to play\n");
14654     else
14655       fprintf(fp, "black to play\n");
14656 }
14657
14658 void
14659 PrintOpponents (FILE *fp)
14660 {
14661     if (gameInfo.white != NULL) {
14662         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14663     } else {
14664         fprintf(fp, "\n");
14665     }
14666 }
14667
14668 /* Find last component of program's own name, using some heuristics */
14669 void
14670 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14671 {
14672     char *p, *q, c;
14673     int local = (strcmp(host, "localhost") == 0);
14674     while (!local && (p = strchr(prog, ';')) != NULL) {
14675         p++;
14676         while (*p == ' ') p++;
14677         prog = p;
14678     }
14679     if (*prog == '"' || *prog == '\'') {
14680         q = strchr(prog + 1, *prog);
14681     } else {
14682         q = strchr(prog, ' ');
14683     }
14684     if (q == NULL) q = prog + strlen(prog);
14685     p = q;
14686     while (p >= prog && *p != '/' && *p != '\\') p--;
14687     p++;
14688     if(p == prog && *p == '"') p++;
14689     c = *q; *q = 0;
14690     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14691     memcpy(buf, p, q - p);
14692     buf[q - p] = NULLCHAR;
14693     if (!local) {
14694         strcat(buf, "@");
14695         strcat(buf, host);
14696     }
14697 }
14698
14699 char *
14700 TimeControlTagValue ()
14701 {
14702     char buf[MSG_SIZ];
14703     if (!appData.clockMode) {
14704       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14705     } else if (movesPerSession > 0) {
14706       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14707     } else if (timeIncrement == 0) {
14708       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14709     } else {
14710       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14711     }
14712     return StrSave(buf);
14713 }
14714
14715 void
14716 SetGameInfo ()
14717 {
14718     /* This routine is used only for certain modes */
14719     VariantClass v = gameInfo.variant;
14720     ChessMove r = GameUnfinished;
14721     char *p = NULL;
14722
14723     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14724         r = gameInfo.result;
14725         p = gameInfo.resultDetails;
14726         gameInfo.resultDetails = NULL;
14727     }
14728     ClearGameInfo(&gameInfo);
14729     gameInfo.variant = v;
14730
14731     switch (gameMode) {
14732       case MachinePlaysWhite:
14733         gameInfo.event = StrSave( appData.pgnEventHeader );
14734         gameInfo.site = StrSave(HostName());
14735         gameInfo.date = PGNDate();
14736         gameInfo.round = StrSave("-");
14737         gameInfo.white = StrSave(first.tidy);
14738         gameInfo.black = StrSave(UserName());
14739         gameInfo.timeControl = TimeControlTagValue();
14740         break;
14741
14742       case MachinePlaysBlack:
14743         gameInfo.event = StrSave( appData.pgnEventHeader );
14744         gameInfo.site = StrSave(HostName());
14745         gameInfo.date = PGNDate();
14746         gameInfo.round = StrSave("-");
14747         gameInfo.white = StrSave(UserName());
14748         gameInfo.black = StrSave(first.tidy);
14749         gameInfo.timeControl = TimeControlTagValue();
14750         break;
14751
14752       case TwoMachinesPlay:
14753         gameInfo.event = StrSave( appData.pgnEventHeader );
14754         gameInfo.site = StrSave(HostName());
14755         gameInfo.date = PGNDate();
14756         if (roundNr > 0) {
14757             char buf[MSG_SIZ];
14758             snprintf(buf, MSG_SIZ, "%d", roundNr);
14759             gameInfo.round = StrSave(buf);
14760         } else {
14761             gameInfo.round = StrSave("-");
14762         }
14763         if (first.twoMachinesColor[0] == 'w') {
14764             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14765             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14766         } else {
14767             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14768             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14769         }
14770         gameInfo.timeControl = TimeControlTagValue();
14771         break;
14772
14773       case EditGame:
14774         gameInfo.event = StrSave("Edited game");
14775         gameInfo.site = StrSave(HostName());
14776         gameInfo.date = PGNDate();
14777         gameInfo.round = StrSave("-");
14778         gameInfo.white = StrSave("-");
14779         gameInfo.black = StrSave("-");
14780         gameInfo.result = r;
14781         gameInfo.resultDetails = p;
14782         break;
14783
14784       case EditPosition:
14785         gameInfo.event = StrSave("Edited position");
14786         gameInfo.site = StrSave(HostName());
14787         gameInfo.date = PGNDate();
14788         gameInfo.round = StrSave("-");
14789         gameInfo.white = StrSave("-");
14790         gameInfo.black = StrSave("-");
14791         break;
14792
14793       case IcsPlayingWhite:
14794       case IcsPlayingBlack:
14795       case IcsObserving:
14796       case IcsExamining:
14797         break;
14798
14799       case PlayFromGameFile:
14800         gameInfo.event = StrSave("Game from non-PGN file");
14801         gameInfo.site = StrSave(HostName());
14802         gameInfo.date = PGNDate();
14803         gameInfo.round = StrSave("-");
14804         gameInfo.white = StrSave("?");
14805         gameInfo.black = StrSave("?");
14806         break;
14807
14808       default:
14809         break;
14810     }
14811 }
14812
14813 void
14814 ReplaceComment (int index, char *text)
14815 {
14816     int len;
14817     char *p;
14818     float score;
14819
14820     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14821        pvInfoList[index-1].depth == len &&
14822        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14823        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14824     while (*text == '\n') text++;
14825     len = strlen(text);
14826     while (len > 0 && text[len - 1] == '\n') len--;
14827
14828     if (commentList[index] != NULL)
14829       free(commentList[index]);
14830
14831     if (len == 0) {
14832         commentList[index] = NULL;
14833         return;
14834     }
14835   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14836       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14837       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14838     commentList[index] = (char *) malloc(len + 2);
14839     strncpy(commentList[index], text, len);
14840     commentList[index][len] = '\n';
14841     commentList[index][len + 1] = NULLCHAR;
14842   } else {
14843     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14844     char *p;
14845     commentList[index] = (char *) malloc(len + 7);
14846     safeStrCpy(commentList[index], "{\n", 3);
14847     safeStrCpy(commentList[index]+2, text, len+1);
14848     commentList[index][len+2] = NULLCHAR;
14849     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14850     strcat(commentList[index], "\n}\n");
14851   }
14852 }
14853
14854 void
14855 CrushCRs (char *text)
14856 {
14857   char *p = text;
14858   char *q = text;
14859   char ch;
14860
14861   do {
14862     ch = *p++;
14863     if (ch == '\r') continue;
14864     *q++ = ch;
14865   } while (ch != '\0');
14866 }
14867
14868 void
14869 AppendComment (int index, char *text, Boolean addBraces)
14870 /* addBraces  tells if we should add {} */
14871 {
14872     int oldlen, len;
14873     char *old;
14874
14875 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14876     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14877
14878     CrushCRs(text);
14879     while (*text == '\n') text++;
14880     len = strlen(text);
14881     while (len > 0 && text[len - 1] == '\n') len--;
14882     text[len] = NULLCHAR;
14883
14884     if (len == 0) return;
14885
14886     if (commentList[index] != NULL) {
14887       Boolean addClosingBrace = addBraces;
14888         old = commentList[index];
14889         oldlen = strlen(old);
14890         while(commentList[index][oldlen-1] ==  '\n')
14891           commentList[index][--oldlen] = NULLCHAR;
14892         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14893         safeStrCpy(commentList[index], old, oldlen + len + 6);
14894         free(old);
14895         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14896         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14897           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14898           while (*text == '\n') { text++; len--; }
14899           commentList[index][--oldlen] = NULLCHAR;
14900       }
14901         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14902         else          strcat(commentList[index], "\n");
14903         strcat(commentList[index], text);
14904         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14905         else          strcat(commentList[index], "\n");
14906     } else {
14907         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14908         if(addBraces)
14909           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14910         else commentList[index][0] = NULLCHAR;
14911         strcat(commentList[index], text);
14912         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14913         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14914     }
14915 }
14916
14917 static char *
14918 FindStr (char * text, char * sub_text)
14919 {
14920     char * result = strstr( text, sub_text );
14921
14922     if( result != NULL ) {
14923         result += strlen( sub_text );
14924     }
14925
14926     return result;
14927 }
14928
14929 /* [AS] Try to extract PV info from PGN comment */
14930 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14931 char *
14932 GetInfoFromComment (int index, char * text)
14933 {
14934     char * sep = text, *p;
14935
14936     if( text != NULL && index > 0 ) {
14937         int score = 0;
14938         int depth = 0;
14939         int time = -1, sec = 0, deci;
14940         char * s_eval = FindStr( text, "[%eval " );
14941         char * s_emt = FindStr( text, "[%emt " );
14942
14943         if( s_eval != NULL || s_emt != NULL ) {
14944             /* New style */
14945             char delim;
14946
14947             if( s_eval != NULL ) {
14948                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14949                     return text;
14950                 }
14951
14952                 if( delim != ']' ) {
14953                     return text;
14954                 }
14955             }
14956
14957             if( s_emt != NULL ) {
14958             }
14959                 return text;
14960         }
14961         else {
14962             /* We expect something like: [+|-]nnn.nn/dd */
14963             int score_lo = 0;
14964
14965             if(*text != '{') return text; // [HGM] braces: must be normal comment
14966
14967             sep = strchr( text, '/' );
14968             if( sep == NULL || sep < (text+4) ) {
14969                 return text;
14970             }
14971
14972             p = text;
14973             if(p[1] == '(') { // comment starts with PV
14974                p = strchr(p, ')'); // locate end of PV
14975                if(p == NULL || sep < p+5) return text;
14976                // at this point we have something like "{(.*) +0.23/6 ..."
14977                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14978                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14979                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14980             }
14981             time = -1; sec = -1; deci = -1;
14982             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14983                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14984                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14985                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14986                 return text;
14987             }
14988
14989             if( score_lo < 0 || score_lo >= 100 ) {
14990                 return text;
14991             }
14992
14993             if(sec >= 0) time = 600*time + 10*sec; else
14994             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14995
14996             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14997
14998             /* [HGM] PV time: now locate end of PV info */
14999             while( *++sep >= '0' && *sep <= '9'); // strip depth
15000             if(time >= 0)
15001             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15002             if(sec >= 0)
15003             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15004             if(deci >= 0)
15005             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15006             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15007         }
15008
15009         if( depth <= 0 ) {
15010             return text;
15011         }
15012
15013         if( time < 0 ) {
15014             time = -1;
15015         }
15016
15017         pvInfoList[index-1].depth = depth;
15018         pvInfoList[index-1].score = score;
15019         pvInfoList[index-1].time  = 10*time; // centi-sec
15020         if(*sep == '}') *sep = 0; else *--sep = '{';
15021         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15022     }
15023     return sep;
15024 }
15025
15026 void
15027 SendToProgram (char *message, ChessProgramState *cps)
15028 {
15029     int count, outCount, error;
15030     char buf[MSG_SIZ];
15031
15032     if (cps->pr == NoProc) return;
15033     Attention(cps);
15034
15035     if (appData.debugMode) {
15036         TimeMark now;
15037         GetTimeMark(&now);
15038         fprintf(debugFP, "%ld >%-6s: %s",
15039                 SubtractTimeMarks(&now, &programStartTime),
15040                 cps->which, message);
15041         if(serverFP)
15042             fprintf(serverFP, "%ld >%-6s: %s",
15043                 SubtractTimeMarks(&now, &programStartTime),
15044                 cps->which, message), fflush(serverFP);
15045     }
15046
15047     count = strlen(message);
15048     outCount = OutputToProcess(cps->pr, message, count, &error);
15049     if (outCount < count && !exiting
15050                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15051       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15052       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15053         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15054             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15055                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15056                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15057                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15058             } else {
15059                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15060                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15061                 gameInfo.result = res;
15062             }
15063             gameInfo.resultDetails = StrSave(buf);
15064         }
15065         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15066         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15067     }
15068 }
15069
15070 void
15071 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15072 {
15073     char *end_str;
15074     char buf[MSG_SIZ];
15075     ChessProgramState *cps = (ChessProgramState *)closure;
15076
15077     if (isr != cps->isr) return; /* Killed intentionally */
15078     if (count <= 0) {
15079         if (count == 0) {
15080             RemoveInputSource(cps->isr);
15081             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15082             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15083                     _(cps->which), cps->program);
15084         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15085                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15086                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15087                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15088                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15089                 } else {
15090                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15091                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15092                     gameInfo.result = res;
15093                 }
15094                 gameInfo.resultDetails = StrSave(buf);
15095             }
15096             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15097             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15098         } else {
15099             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15100                     _(cps->which), cps->program);
15101             RemoveInputSource(cps->isr);
15102
15103             /* [AS] Program is misbehaving badly... kill it */
15104             if( count == -2 ) {
15105                 DestroyChildProcess( cps->pr, 9 );
15106                 cps->pr = NoProc;
15107             }
15108
15109             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15110         }
15111         return;
15112     }
15113
15114     if ((end_str = strchr(message, '\r')) != NULL)
15115       *end_str = NULLCHAR;
15116     if ((end_str = strchr(message, '\n')) != NULL)
15117       *end_str = NULLCHAR;
15118
15119     if (appData.debugMode) {
15120         TimeMark now; int print = 1;
15121         char *quote = ""; char c; int i;
15122
15123         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15124                 char start = message[0];
15125                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15126                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15127                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15128                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15129                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15130                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15131                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15132                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15133                    sscanf(message, "hint: %c", &c)!=1 && 
15134                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15135                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15136                     print = (appData.engineComments >= 2);
15137                 }
15138                 message[0] = start; // restore original message
15139         }
15140         if(print) {
15141                 GetTimeMark(&now);
15142                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15143                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15144                         quote,
15145                         message);
15146                 if(serverFP)
15147                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15148                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15149                         quote,
15150                         message), fflush(serverFP);
15151         }
15152     }
15153
15154     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15155     if (appData.icsEngineAnalyze) {
15156         if (strstr(message, "whisper") != NULL ||
15157              strstr(message, "kibitz") != NULL ||
15158             strstr(message, "tellics") != NULL) return;
15159     }
15160
15161     HandleMachineMove(message, cps);
15162 }
15163
15164
15165 void
15166 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15167 {
15168     char buf[MSG_SIZ];
15169     int seconds;
15170
15171     if( timeControl_2 > 0 ) {
15172         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15173             tc = timeControl_2;
15174         }
15175     }
15176     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15177     inc /= cps->timeOdds;
15178     st  /= cps->timeOdds;
15179
15180     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15181
15182     if (st > 0) {
15183       /* Set exact time per move, normally using st command */
15184       if (cps->stKludge) {
15185         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15186         seconds = st % 60;
15187         if (seconds == 0) {
15188           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15189         } else {
15190           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15191         }
15192       } else {
15193         snprintf(buf, MSG_SIZ, "st %d\n", st);
15194       }
15195     } else {
15196       /* Set conventional or incremental time control, using level command */
15197       if (seconds == 0) {
15198         /* Note old gnuchess bug -- minutes:seconds used to not work.
15199            Fixed in later versions, but still avoid :seconds
15200            when seconds is 0. */
15201         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15202       } else {
15203         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15204                  seconds, inc/1000.);
15205       }
15206     }
15207     SendToProgram(buf, cps);
15208
15209     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15210     /* Orthogonally, limit search to given depth */
15211     if (sd > 0) {
15212       if (cps->sdKludge) {
15213         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15214       } else {
15215         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15216       }
15217       SendToProgram(buf, cps);
15218     }
15219
15220     if(cps->nps >= 0) { /* [HGM] nps */
15221         if(cps->supportsNPS == FALSE)
15222           cps->nps = -1; // don't use if engine explicitly says not supported!
15223         else {
15224           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15225           SendToProgram(buf, cps);
15226         }
15227     }
15228 }
15229
15230 ChessProgramState *
15231 WhitePlayer ()
15232 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15233 {
15234     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15235        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15236         return &second;
15237     return &first;
15238 }
15239
15240 void
15241 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15242 {
15243     char message[MSG_SIZ];
15244     long time, otime;
15245
15246     /* Note: this routine must be called when the clocks are stopped
15247        or when they have *just* been set or switched; otherwise
15248        it will be off by the time since the current tick started.
15249     */
15250     if (machineWhite) {
15251         time = whiteTimeRemaining / 10;
15252         otime = blackTimeRemaining / 10;
15253     } else {
15254         time = blackTimeRemaining / 10;
15255         otime = whiteTimeRemaining / 10;
15256     }
15257     /* [HGM] translate opponent's time by time-odds factor */
15258     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15259
15260     if (time <= 0) time = 1;
15261     if (otime <= 0) otime = 1;
15262
15263     snprintf(message, MSG_SIZ, "time %ld\n", time);
15264     SendToProgram(message, cps);
15265
15266     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15267     SendToProgram(message, cps);
15268 }
15269
15270 int
15271 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15272 {
15273   char buf[MSG_SIZ];
15274   int len = strlen(name);
15275   int val;
15276
15277   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15278     (*p) += len + 1;
15279     sscanf(*p, "%d", &val);
15280     *loc = (val != 0);
15281     while (**p && **p != ' ')
15282       (*p)++;
15283     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15284     SendToProgram(buf, cps);
15285     return TRUE;
15286   }
15287   return FALSE;
15288 }
15289
15290 int
15291 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15292 {
15293   char buf[MSG_SIZ];
15294   int len = strlen(name);
15295   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15296     (*p) += len + 1;
15297     sscanf(*p, "%d", loc);
15298     while (**p && **p != ' ') (*p)++;
15299     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15300     SendToProgram(buf, cps);
15301     return TRUE;
15302   }
15303   return FALSE;
15304 }
15305
15306 int
15307 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15308 {
15309   char buf[MSG_SIZ];
15310   int len = strlen(name);
15311   if (strncmp((*p), name, len) == 0
15312       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15313     (*p) += len + 2;
15314     sscanf(*p, "%[^\"]", loc);
15315     while (**p && **p != '\"') (*p)++;
15316     if (**p == '\"') (*p)++;
15317     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15318     SendToProgram(buf, cps);
15319     return TRUE;
15320   }
15321   return FALSE;
15322 }
15323
15324 int
15325 ParseOption (Option *opt, ChessProgramState *cps)
15326 // [HGM] options: process the string that defines an engine option, and determine
15327 // name, type, default value, and allowed value range
15328 {
15329         char *p, *q, buf[MSG_SIZ];
15330         int n, min = (-1)<<31, max = 1<<31, def;
15331
15332         if(p = strstr(opt->name, " -spin ")) {
15333             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15334             if(max < min) max = min; // enforce consistency
15335             if(def < min) def = min;
15336             if(def > max) def = max;
15337             opt->value = def;
15338             opt->min = min;
15339             opt->max = max;
15340             opt->type = Spin;
15341         } else if((p = strstr(opt->name, " -slider "))) {
15342             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15343             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15344             if(max < min) max = min; // enforce consistency
15345             if(def < min) def = min;
15346             if(def > max) def = max;
15347             opt->value = def;
15348             opt->min = min;
15349             opt->max = max;
15350             opt->type = Spin; // Slider;
15351         } else if((p = strstr(opt->name, " -string "))) {
15352             opt->textValue = p+9;
15353             opt->type = TextBox;
15354         } else if((p = strstr(opt->name, " -file "))) {
15355             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15356             opt->textValue = p+7;
15357             opt->type = FileName; // FileName;
15358         } else if((p = strstr(opt->name, " -path "))) {
15359             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15360             opt->textValue = p+7;
15361             opt->type = PathName; // PathName;
15362         } else if(p = strstr(opt->name, " -check ")) {
15363             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15364             opt->value = (def != 0);
15365             opt->type = CheckBox;
15366         } else if(p = strstr(opt->name, " -combo ")) {
15367             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15368             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15369             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15370             opt->value = n = 0;
15371             while(q = StrStr(q, " /// ")) {
15372                 n++; *q = 0;    // count choices, and null-terminate each of them
15373                 q += 5;
15374                 if(*q == '*') { // remember default, which is marked with * prefix
15375                     q++;
15376                     opt->value = n;
15377                 }
15378                 cps->comboList[cps->comboCnt++] = q;
15379             }
15380             cps->comboList[cps->comboCnt++] = NULL;
15381             opt->max = n + 1;
15382             opt->type = ComboBox;
15383         } else if(p = strstr(opt->name, " -button")) {
15384             opt->type = Button;
15385         } else if(p = strstr(opt->name, " -save")) {
15386             opt->type = SaveButton;
15387         } else return FALSE;
15388         *p = 0; // terminate option name
15389         // now look if the command-line options define a setting for this engine option.
15390         if(cps->optionSettings && cps->optionSettings[0])
15391             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15392         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15393           snprintf(buf, MSG_SIZ, "option %s", p);
15394                 if(p = strstr(buf, ",")) *p = 0;
15395                 if(q = strchr(buf, '=')) switch(opt->type) {
15396                     case ComboBox:
15397                         for(n=0; n<opt->max; n++)
15398                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15399                         break;
15400                     case TextBox:
15401                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15402                         break;
15403                     case Spin:
15404                     case CheckBox:
15405                         opt->value = atoi(q+1);
15406                     default:
15407                         break;
15408                 }
15409                 strcat(buf, "\n");
15410                 SendToProgram(buf, cps);
15411         }
15412         return TRUE;
15413 }
15414
15415 void
15416 FeatureDone (ChessProgramState *cps, int val)
15417 {
15418   DelayedEventCallback cb = GetDelayedEvent();
15419   if ((cb == InitBackEnd3 && cps == &first) ||
15420       (cb == SettingsMenuIfReady && cps == &second) ||
15421       (cb == LoadEngine) ||
15422       (cb == TwoMachinesEventIfReady)) {
15423     CancelDelayedEvent();
15424     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15425   }
15426   cps->initDone = val;
15427 }
15428
15429 /* Parse feature command from engine */
15430 void
15431 ParseFeatures (char *args, ChessProgramState *cps)
15432 {
15433   char *p = args;
15434   char *q;
15435   int val;
15436   char buf[MSG_SIZ];
15437
15438   for (;;) {
15439     while (*p == ' ') p++;
15440     if (*p == NULLCHAR) return;
15441
15442     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15443     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15444     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15445     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15446     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15447     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15448     if (BoolFeature(&p, "reuse", &val, cps)) {
15449       /* Engine can disable reuse, but can't enable it if user said no */
15450       if (!val) cps->reuse = FALSE;
15451       continue;
15452     }
15453     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15454     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15455       if (gameMode == TwoMachinesPlay) {
15456         DisplayTwoMachinesTitle();
15457       } else {
15458         DisplayTitle("");
15459       }
15460       continue;
15461     }
15462     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15463     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15464     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15465     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15466     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15467     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15468     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15469     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15470     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15471     if (IntFeature(&p, "done", &val, cps)) {
15472       FeatureDone(cps, val);
15473       continue;
15474     }
15475     /* Added by Tord: */
15476     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15477     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15478     /* End of additions by Tord */
15479
15480     /* [HGM] added features: */
15481     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15482     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15483     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15484     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15485     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15486     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15487     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15488         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15489           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15490             SendToProgram(buf, cps);
15491             continue;
15492         }
15493         if(cps->nrOptions >= MAX_OPTIONS) {
15494             cps->nrOptions--;
15495             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15496             DisplayError(buf, 0);
15497         }
15498         continue;
15499     }
15500     /* End of additions by HGM */
15501
15502     /* unknown feature: complain and skip */
15503     q = p;
15504     while (*q && *q != '=') q++;
15505     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15506     SendToProgram(buf, cps);
15507     p = q;
15508     if (*p == '=') {
15509       p++;
15510       if (*p == '\"') {
15511         p++;
15512         while (*p && *p != '\"') p++;
15513         if (*p == '\"') p++;
15514       } else {
15515         while (*p && *p != ' ') p++;
15516       }
15517     }
15518   }
15519
15520 }
15521
15522 void
15523 PeriodicUpdatesEvent (int newState)
15524 {
15525     if (newState == appData.periodicUpdates)
15526       return;
15527
15528     appData.periodicUpdates=newState;
15529
15530     /* Display type changes, so update it now */
15531 //    DisplayAnalysis();
15532
15533     /* Get the ball rolling again... */
15534     if (newState) {
15535         AnalysisPeriodicEvent(1);
15536         StartAnalysisClock();
15537     }
15538 }
15539
15540 void
15541 PonderNextMoveEvent (int newState)
15542 {
15543     if (newState == appData.ponderNextMove) return;
15544     if (gameMode == EditPosition) EditPositionDone(TRUE);
15545     if (newState) {
15546         SendToProgram("hard\n", &first);
15547         if (gameMode == TwoMachinesPlay) {
15548             SendToProgram("hard\n", &second);
15549         }
15550     } else {
15551         SendToProgram("easy\n", &first);
15552         thinkOutput[0] = NULLCHAR;
15553         if (gameMode == TwoMachinesPlay) {
15554             SendToProgram("easy\n", &second);
15555         }
15556     }
15557     appData.ponderNextMove = newState;
15558 }
15559
15560 void
15561 NewSettingEvent (int option, int *feature, char *command, int value)
15562 {
15563     char buf[MSG_SIZ];
15564
15565     if (gameMode == EditPosition) EditPositionDone(TRUE);
15566     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15567     if(feature == NULL || *feature) SendToProgram(buf, &first);
15568     if (gameMode == TwoMachinesPlay) {
15569         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15570     }
15571 }
15572
15573 void
15574 ShowThinkingEvent ()
15575 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15576 {
15577     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15578     int newState = appData.showThinking
15579         // [HGM] thinking: other features now need thinking output as well
15580         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15581
15582     if (oldState == newState) return;
15583     oldState = newState;
15584     if (gameMode == EditPosition) EditPositionDone(TRUE);
15585     if (oldState) {
15586         SendToProgram("post\n", &first);
15587         if (gameMode == TwoMachinesPlay) {
15588             SendToProgram("post\n", &second);
15589         }
15590     } else {
15591         SendToProgram("nopost\n", &first);
15592         thinkOutput[0] = NULLCHAR;
15593         if (gameMode == TwoMachinesPlay) {
15594             SendToProgram("nopost\n", &second);
15595         }
15596     }
15597 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15598 }
15599
15600 void
15601 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15602 {
15603   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15604   if (pr == NoProc) return;
15605   AskQuestion(title, question, replyPrefix, pr);
15606 }
15607
15608 void
15609 TypeInEvent (char firstChar)
15610 {
15611     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15612         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15613         gameMode == AnalyzeMode || gameMode == EditGame || 
15614         gameMode == EditPosition || gameMode == IcsExamining ||
15615         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15616         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15617                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15618                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15619         gameMode == Training) PopUpMoveDialog(firstChar);
15620 }
15621
15622 void
15623 TypeInDoneEvent (char *move)
15624 {
15625         Board board;
15626         int n, fromX, fromY, toX, toY;
15627         char promoChar;
15628         ChessMove moveType;
15629
15630         // [HGM] FENedit
15631         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15632                 EditPositionPasteFEN(move);
15633                 return;
15634         }
15635         // [HGM] movenum: allow move number to be typed in any mode
15636         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15637           ToNrEvent(2*n-1);
15638           return;
15639         }
15640         // undocumented kludge: allow command-line option to be typed in!
15641         // (potentially fatal, and does not implement the effect of the option.)
15642         // should only be used for options that are values on which future decisions will be made,
15643         // and definitely not on options that would be used during initialization.
15644         if(strstr(move, "!!! -") == move) {
15645             ParseArgsFromString(move+4);
15646             return;
15647         }
15648
15649       if (gameMode != EditGame && currentMove != forwardMostMove && 
15650         gameMode != Training) {
15651         DisplayMoveError(_("Displayed move is not current"));
15652       } else {
15653         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15654           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15655         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15656         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15657           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15658           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15659         } else {
15660           DisplayMoveError(_("Could not parse move"));
15661         }
15662       }
15663 }
15664
15665 void
15666 DisplayMove (int moveNumber)
15667 {
15668     char message[MSG_SIZ];
15669     char res[MSG_SIZ];
15670     char cpThinkOutput[MSG_SIZ];
15671
15672     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15673
15674     if (moveNumber == forwardMostMove - 1 ||
15675         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15676
15677         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15678
15679         if (strchr(cpThinkOutput, '\n')) {
15680             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15681         }
15682     } else {
15683         *cpThinkOutput = NULLCHAR;
15684     }
15685
15686     /* [AS] Hide thinking from human user */
15687     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15688         *cpThinkOutput = NULLCHAR;
15689         if( thinkOutput[0] != NULLCHAR ) {
15690             int i;
15691
15692             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15693                 cpThinkOutput[i] = '.';
15694             }
15695             cpThinkOutput[i] = NULLCHAR;
15696             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15697         }
15698     }
15699
15700     if (moveNumber == forwardMostMove - 1 &&
15701         gameInfo.resultDetails != NULL) {
15702         if (gameInfo.resultDetails[0] == NULLCHAR) {
15703           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15704         } else {
15705           snprintf(res, MSG_SIZ, " {%s} %s",
15706                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15707         }
15708     } else {
15709         res[0] = NULLCHAR;
15710     }
15711
15712     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15713         DisplayMessage(res, cpThinkOutput);
15714     } else {
15715       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15716                 WhiteOnMove(moveNumber) ? " " : ".. ",
15717                 parseList[moveNumber], res);
15718         DisplayMessage(message, cpThinkOutput);
15719     }
15720 }
15721
15722 void
15723 DisplayComment (int moveNumber, char *text)
15724 {
15725     char title[MSG_SIZ];
15726
15727     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15728       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15729     } else {
15730       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15731               WhiteOnMove(moveNumber) ? " " : ".. ",
15732               parseList[moveNumber]);
15733     }
15734     if (text != NULL && (appData.autoDisplayComment || commentUp))
15735         CommentPopUp(title, text);
15736 }
15737
15738 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15739  * might be busy thinking or pondering.  It can be omitted if your
15740  * gnuchess is configured to stop thinking immediately on any user
15741  * input.  However, that gnuchess feature depends on the FIONREAD
15742  * ioctl, which does not work properly on some flavors of Unix.
15743  */
15744 void
15745 Attention (ChessProgramState *cps)
15746 {
15747 #if ATTENTION
15748     if (!cps->useSigint) return;
15749     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15750     switch (gameMode) {
15751       case MachinePlaysWhite:
15752       case MachinePlaysBlack:
15753       case TwoMachinesPlay:
15754       case IcsPlayingWhite:
15755       case IcsPlayingBlack:
15756       case AnalyzeMode:
15757       case AnalyzeFile:
15758         /* Skip if we know it isn't thinking */
15759         if (!cps->maybeThinking) return;
15760         if (appData.debugMode)
15761           fprintf(debugFP, "Interrupting %s\n", cps->which);
15762         InterruptChildProcess(cps->pr);
15763         cps->maybeThinking = FALSE;
15764         break;
15765       default:
15766         break;
15767     }
15768 #endif /*ATTENTION*/
15769 }
15770
15771 int
15772 CheckFlags ()
15773 {
15774     if (whiteTimeRemaining <= 0) {
15775         if (!whiteFlag) {
15776             whiteFlag = TRUE;
15777             if (appData.icsActive) {
15778                 if (appData.autoCallFlag &&
15779                     gameMode == IcsPlayingBlack && !blackFlag) {
15780                   SendToICS(ics_prefix);
15781                   SendToICS("flag\n");
15782                 }
15783             } else {
15784                 if (blackFlag) {
15785                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15786                 } else {
15787                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15788                     if (appData.autoCallFlag) {
15789                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15790                         return TRUE;
15791                     }
15792                 }
15793             }
15794         }
15795     }
15796     if (blackTimeRemaining <= 0) {
15797         if (!blackFlag) {
15798             blackFlag = TRUE;
15799             if (appData.icsActive) {
15800                 if (appData.autoCallFlag &&
15801                     gameMode == IcsPlayingWhite && !whiteFlag) {
15802                   SendToICS(ics_prefix);
15803                   SendToICS("flag\n");
15804                 }
15805             } else {
15806                 if (whiteFlag) {
15807                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15808                 } else {
15809                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15810                     if (appData.autoCallFlag) {
15811                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15812                         return TRUE;
15813                     }
15814                 }
15815             }
15816         }
15817     }
15818     return FALSE;
15819 }
15820
15821 void
15822 CheckTimeControl ()
15823 {
15824     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15825         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15826
15827     /*
15828      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15829      */
15830     if ( !WhiteOnMove(forwardMostMove) ) {
15831         /* White made time control */
15832         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15833         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15834         /* [HGM] time odds: correct new time quota for time odds! */
15835                                             / WhitePlayer()->timeOdds;
15836         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15837     } else {
15838         lastBlack -= blackTimeRemaining;
15839         /* Black made time control */
15840         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15841                                             / WhitePlayer()->other->timeOdds;
15842         lastWhite = whiteTimeRemaining;
15843     }
15844 }
15845
15846 void
15847 DisplayBothClocks ()
15848 {
15849     int wom = gameMode == EditPosition ?
15850       !blackPlaysFirst : WhiteOnMove(currentMove);
15851     DisplayWhiteClock(whiteTimeRemaining, wom);
15852     DisplayBlackClock(blackTimeRemaining, !wom);
15853 }
15854
15855
15856 /* Timekeeping seems to be a portability nightmare.  I think everyone
15857    has ftime(), but I'm really not sure, so I'm including some ifdefs
15858    to use other calls if you don't.  Clocks will be less accurate if
15859    you have neither ftime nor gettimeofday.
15860 */
15861
15862 /* VS 2008 requires the #include outside of the function */
15863 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15864 #include <sys/timeb.h>
15865 #endif
15866
15867 /* Get the current time as a TimeMark */
15868 void
15869 GetTimeMark (TimeMark *tm)
15870 {
15871 #if HAVE_GETTIMEOFDAY
15872
15873     struct timeval timeVal;
15874     struct timezone timeZone;
15875
15876     gettimeofday(&timeVal, &timeZone);
15877     tm->sec = (long) timeVal.tv_sec;
15878     tm->ms = (int) (timeVal.tv_usec / 1000L);
15879
15880 #else /*!HAVE_GETTIMEOFDAY*/
15881 #if HAVE_FTIME
15882
15883 // include <sys/timeb.h> / moved to just above start of function
15884     struct timeb timeB;
15885
15886     ftime(&timeB);
15887     tm->sec = (long) timeB.time;
15888     tm->ms = (int) timeB.millitm;
15889
15890 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15891     tm->sec = (long) time(NULL);
15892     tm->ms = 0;
15893 #endif
15894 #endif
15895 }
15896
15897 /* Return the difference in milliseconds between two
15898    time marks.  We assume the difference will fit in a long!
15899 */
15900 long
15901 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15902 {
15903     return 1000L*(tm2->sec - tm1->sec) +
15904            (long) (tm2->ms - tm1->ms);
15905 }
15906
15907
15908 /*
15909  * Code to manage the game clocks.
15910  *
15911  * In tournament play, black starts the clock and then white makes a move.
15912  * We give the human user a slight advantage if he is playing white---the
15913  * clocks don't run until he makes his first move, so it takes zero time.
15914  * Also, we don't account for network lag, so we could get out of sync
15915  * with GNU Chess's clock -- but then, referees are always right.
15916  */
15917
15918 static TimeMark tickStartTM;
15919 static long intendedTickLength;
15920
15921 long
15922 NextTickLength (long timeRemaining)
15923 {
15924     long nominalTickLength, nextTickLength;
15925
15926     if (timeRemaining > 0L && timeRemaining <= 10000L)
15927       nominalTickLength = 100L;
15928     else
15929       nominalTickLength = 1000L;
15930     nextTickLength = timeRemaining % nominalTickLength;
15931     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15932
15933     return nextTickLength;
15934 }
15935
15936 /* Adjust clock one minute up or down */
15937 void
15938 AdjustClock (Boolean which, int dir)
15939 {
15940     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15941     if(which) blackTimeRemaining += 60000*dir;
15942     else      whiteTimeRemaining += 60000*dir;
15943     DisplayBothClocks();
15944     adjustedClock = TRUE;
15945 }
15946
15947 /* Stop clocks and reset to a fresh time control */
15948 void
15949 ResetClocks ()
15950 {
15951     (void) StopClockTimer();
15952     if (appData.icsActive) {
15953         whiteTimeRemaining = blackTimeRemaining = 0;
15954     } else if (searchTime) {
15955         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15956         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15957     } else { /* [HGM] correct new time quote for time odds */
15958         whiteTC = blackTC = fullTimeControlString;
15959         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15960         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15961     }
15962     if (whiteFlag || blackFlag) {
15963         DisplayTitle("");
15964         whiteFlag = blackFlag = FALSE;
15965     }
15966     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15967     DisplayBothClocks();
15968     adjustedClock = FALSE;
15969 }
15970
15971 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15972
15973 /* Decrement running clock by amount of time that has passed */
15974 void
15975 DecrementClocks ()
15976 {
15977     long timeRemaining;
15978     long lastTickLength, fudge;
15979     TimeMark now;
15980
15981     if (!appData.clockMode) return;
15982     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15983
15984     GetTimeMark(&now);
15985
15986     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15987
15988     /* Fudge if we woke up a little too soon */
15989     fudge = intendedTickLength - lastTickLength;
15990     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15991
15992     if (WhiteOnMove(forwardMostMove)) {
15993         if(whiteNPS >= 0) lastTickLength = 0;
15994         timeRemaining = whiteTimeRemaining -= lastTickLength;
15995         if(timeRemaining < 0 && !appData.icsActive) {
15996             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15997             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15998                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15999                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16000             }
16001         }
16002         DisplayWhiteClock(whiteTimeRemaining - fudge,
16003                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16004     } else {
16005         if(blackNPS >= 0) lastTickLength = 0;
16006         timeRemaining = blackTimeRemaining -= lastTickLength;
16007         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16008             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16009             if(suddenDeath) {
16010                 blackStartMove = forwardMostMove;
16011                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16012             }
16013         }
16014         DisplayBlackClock(blackTimeRemaining - fudge,
16015                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16016     }
16017     if (CheckFlags()) return;
16018
16019     tickStartTM = now;
16020     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16021     StartClockTimer(intendedTickLength);
16022
16023     /* if the time remaining has fallen below the alarm threshold, sound the
16024      * alarm. if the alarm has sounded and (due to a takeback or time control
16025      * with increment) the time remaining has increased to a level above the
16026      * threshold, reset the alarm so it can sound again.
16027      */
16028
16029     if (appData.icsActive && appData.icsAlarm) {
16030
16031         /* make sure we are dealing with the user's clock */
16032         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16033                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16034            )) return;
16035
16036         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16037             alarmSounded = FALSE;
16038         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16039             PlayAlarmSound();
16040             alarmSounded = TRUE;
16041         }
16042     }
16043 }
16044
16045
16046 /* A player has just moved, so stop the previously running
16047    clock and (if in clock mode) start the other one.
16048    We redisplay both clocks in case we're in ICS mode, because
16049    ICS gives us an update to both clocks after every move.
16050    Note that this routine is called *after* forwardMostMove
16051    is updated, so the last fractional tick must be subtracted
16052    from the color that is *not* on move now.
16053 */
16054 void
16055 SwitchClocks (int newMoveNr)
16056 {
16057     long lastTickLength;
16058     TimeMark now;
16059     int flagged = FALSE;
16060
16061     GetTimeMark(&now);
16062
16063     if (StopClockTimer() && appData.clockMode) {
16064         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16065         if (!WhiteOnMove(forwardMostMove)) {
16066             if(blackNPS >= 0) lastTickLength = 0;
16067             blackTimeRemaining -= lastTickLength;
16068            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16069 //         if(pvInfoList[forwardMostMove].time == -1)
16070                  pvInfoList[forwardMostMove].time =               // use GUI time
16071                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16072         } else {
16073            if(whiteNPS >= 0) lastTickLength = 0;
16074            whiteTimeRemaining -= lastTickLength;
16075            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16076 //         if(pvInfoList[forwardMostMove].time == -1)
16077                  pvInfoList[forwardMostMove].time =
16078                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16079         }
16080         flagged = CheckFlags();
16081     }
16082     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16083     CheckTimeControl();
16084
16085     if (flagged || !appData.clockMode) return;
16086
16087     switch (gameMode) {
16088       case MachinePlaysBlack:
16089       case MachinePlaysWhite:
16090       case BeginningOfGame:
16091         if (pausing) return;
16092         break;
16093
16094       case EditGame:
16095       case PlayFromGameFile:
16096       case IcsExamining:
16097         return;
16098
16099       default:
16100         break;
16101     }
16102
16103     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16104         if(WhiteOnMove(forwardMostMove))
16105              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16106         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16107     }
16108
16109     tickStartTM = now;
16110     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16111       whiteTimeRemaining : blackTimeRemaining);
16112     StartClockTimer(intendedTickLength);
16113 }
16114
16115
16116 /* Stop both clocks */
16117 void
16118 StopClocks ()
16119 {
16120     long lastTickLength;
16121     TimeMark now;
16122
16123     if (!StopClockTimer()) return;
16124     if (!appData.clockMode) return;
16125
16126     GetTimeMark(&now);
16127
16128     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16129     if (WhiteOnMove(forwardMostMove)) {
16130         if(whiteNPS >= 0) lastTickLength = 0;
16131         whiteTimeRemaining -= lastTickLength;
16132         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16133     } else {
16134         if(blackNPS >= 0) lastTickLength = 0;
16135         blackTimeRemaining -= lastTickLength;
16136         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16137     }
16138     CheckFlags();
16139 }
16140
16141 /* Start clock of player on move.  Time may have been reset, so
16142    if clock is already running, stop and restart it. */
16143 void
16144 StartClocks ()
16145 {
16146     (void) StopClockTimer(); /* in case it was running already */
16147     DisplayBothClocks();
16148     if (CheckFlags()) return;
16149
16150     if (!appData.clockMode) return;
16151     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16152
16153     GetTimeMark(&tickStartTM);
16154     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16155       whiteTimeRemaining : blackTimeRemaining);
16156
16157    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16158     whiteNPS = blackNPS = -1;
16159     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16160        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16161         whiteNPS = first.nps;
16162     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16163        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16164         blackNPS = first.nps;
16165     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16166         whiteNPS = second.nps;
16167     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16168         blackNPS = second.nps;
16169     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16170
16171     StartClockTimer(intendedTickLength);
16172 }
16173
16174 char *
16175 TimeString (long ms)
16176 {
16177     long second, minute, hour, day;
16178     char *sign = "";
16179     static char buf[32];
16180
16181     if (ms > 0 && ms <= 9900) {
16182       /* convert milliseconds to tenths, rounding up */
16183       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16184
16185       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16186       return buf;
16187     }
16188
16189     /* convert milliseconds to seconds, rounding up */
16190     /* use floating point to avoid strangeness of integer division
16191        with negative dividends on many machines */
16192     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16193
16194     if (second < 0) {
16195         sign = "-";
16196         second = -second;
16197     }
16198
16199     day = second / (60 * 60 * 24);
16200     second = second % (60 * 60 * 24);
16201     hour = second / (60 * 60);
16202     second = second % (60 * 60);
16203     minute = second / 60;
16204     second = second % 60;
16205
16206     if (day > 0)
16207       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16208               sign, day, hour, minute, second);
16209     else if (hour > 0)
16210       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16211     else
16212       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16213
16214     return buf;
16215 }
16216
16217
16218 /*
16219  * This is necessary because some C libraries aren't ANSI C compliant yet.
16220  */
16221 char *
16222 StrStr (char *string, char *match)
16223 {
16224     int i, length;
16225
16226     length = strlen(match);
16227
16228     for (i = strlen(string) - length; i >= 0; i--, string++)
16229       if (!strncmp(match, string, length))
16230         return string;
16231
16232     return NULL;
16233 }
16234
16235 char *
16236 StrCaseStr (char *string, char *match)
16237 {
16238     int i, j, length;
16239
16240     length = strlen(match);
16241
16242     for (i = strlen(string) - length; i >= 0; i--, string++) {
16243         for (j = 0; j < length; j++) {
16244             if (ToLower(match[j]) != ToLower(string[j]))
16245               break;
16246         }
16247         if (j == length) return string;
16248     }
16249
16250     return NULL;
16251 }
16252
16253 #ifndef _amigados
16254 int
16255 StrCaseCmp (char *s1, char *s2)
16256 {
16257     char c1, c2;
16258
16259     for (;;) {
16260         c1 = ToLower(*s1++);
16261         c2 = ToLower(*s2++);
16262         if (c1 > c2) return 1;
16263         if (c1 < c2) return -1;
16264         if (c1 == NULLCHAR) return 0;
16265     }
16266 }
16267
16268
16269 int
16270 ToLower (int c)
16271 {
16272     return isupper(c) ? tolower(c) : c;
16273 }
16274
16275
16276 int
16277 ToUpper (int c)
16278 {
16279     return islower(c) ? toupper(c) : c;
16280 }
16281 #endif /* !_amigados    */
16282
16283 char *
16284 StrSave (char *s)
16285 {
16286   char *ret;
16287
16288   if ((ret = (char *) malloc(strlen(s) + 1)))
16289     {
16290       safeStrCpy(ret, s, strlen(s)+1);
16291     }
16292   return ret;
16293 }
16294
16295 char *
16296 StrSavePtr (char *s, char **savePtr)
16297 {
16298     if (*savePtr) {
16299         free(*savePtr);
16300     }
16301     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16302       safeStrCpy(*savePtr, s, strlen(s)+1);
16303     }
16304     return(*savePtr);
16305 }
16306
16307 char *
16308 PGNDate ()
16309 {
16310     time_t clock;
16311     struct tm *tm;
16312     char buf[MSG_SIZ];
16313
16314     clock = time((time_t *)NULL);
16315     tm = localtime(&clock);
16316     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16317             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16318     return StrSave(buf);
16319 }
16320
16321
16322 char *
16323 PositionToFEN (int move, char *overrideCastling)
16324 {
16325     int i, j, fromX, fromY, toX, toY;
16326     int whiteToPlay;
16327     char buf[MSG_SIZ];
16328     char *p, *q;
16329     int emptycount;
16330     ChessSquare piece;
16331
16332     whiteToPlay = (gameMode == EditPosition) ?
16333       !blackPlaysFirst : (move % 2 == 0);
16334     p = buf;
16335
16336     /* Piece placement data */
16337     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16338         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16339         emptycount = 0;
16340         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16341             if (boards[move][i][j] == EmptySquare) {
16342                 emptycount++;
16343             } else { ChessSquare piece = boards[move][i][j];
16344                 if (emptycount > 0) {
16345                     if(emptycount<10) /* [HGM] can be >= 10 */
16346                         *p++ = '0' + emptycount;
16347                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16348                     emptycount = 0;
16349                 }
16350                 if(PieceToChar(piece) == '+') {
16351                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16352                     *p++ = '+';
16353                     piece = (ChessSquare)(DEMOTED piece);
16354                 }
16355                 *p++ = PieceToChar(piece);
16356                 if(p[-1] == '~') {
16357                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16358                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16359                     *p++ = '~';
16360                 }
16361             }
16362         }
16363         if (emptycount > 0) {
16364             if(emptycount<10) /* [HGM] can be >= 10 */
16365                 *p++ = '0' + emptycount;
16366             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16367             emptycount = 0;
16368         }
16369         *p++ = '/';
16370     }
16371     *(p - 1) = ' ';
16372
16373     /* [HGM] print Crazyhouse or Shogi holdings */
16374     if( gameInfo.holdingsWidth ) {
16375         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16376         q = p;
16377         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16378             piece = boards[move][i][BOARD_WIDTH-1];
16379             if( piece != EmptySquare )
16380               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16381                   *p++ = PieceToChar(piece);
16382         }
16383         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16384             piece = boards[move][BOARD_HEIGHT-i-1][0];
16385             if( piece != EmptySquare )
16386               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16387                   *p++ = PieceToChar(piece);
16388         }
16389
16390         if( q == p ) *p++ = '-';
16391         *p++ = ']';
16392         *p++ = ' ';
16393     }
16394
16395     /* Active color */
16396     *p++ = whiteToPlay ? 'w' : 'b';
16397     *p++ = ' ';
16398
16399   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16400     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16401   } else {
16402   if(nrCastlingRights) {
16403      q = p;
16404      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16405        /* [HGM] write directly from rights */
16406            if(boards[move][CASTLING][2] != NoRights &&
16407               boards[move][CASTLING][0] != NoRights   )
16408                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16409            if(boards[move][CASTLING][2] != NoRights &&
16410               boards[move][CASTLING][1] != NoRights   )
16411                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16412            if(boards[move][CASTLING][5] != NoRights &&
16413               boards[move][CASTLING][3] != NoRights   )
16414                 *p++ = boards[move][CASTLING][3] + AAA;
16415            if(boards[move][CASTLING][5] != NoRights &&
16416               boards[move][CASTLING][4] != NoRights   )
16417                 *p++ = boards[move][CASTLING][4] + AAA;
16418      } else {
16419
16420         /* [HGM] write true castling rights */
16421         if( nrCastlingRights == 6 ) {
16422             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16423                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16424             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16425                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16426             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16427                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16428             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16429                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16430         }
16431      }
16432      if (q == p) *p++ = '-'; /* No castling rights */
16433      *p++ = ' ';
16434   }
16435
16436   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16437      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16438     /* En passant target square */
16439     if (move > backwardMostMove) {
16440         fromX = moveList[move - 1][0] - AAA;
16441         fromY = moveList[move - 1][1] - ONE;
16442         toX = moveList[move - 1][2] - AAA;
16443         toY = moveList[move - 1][3] - ONE;
16444         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16445             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16446             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16447             fromX == toX) {
16448             /* 2-square pawn move just happened */
16449             *p++ = toX + AAA;
16450             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16451         } else {
16452             *p++ = '-';
16453         }
16454     } else if(move == backwardMostMove) {
16455         // [HGM] perhaps we should always do it like this, and forget the above?
16456         if((signed char)boards[move][EP_STATUS] >= 0) {
16457             *p++ = boards[move][EP_STATUS] + AAA;
16458             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16459         } else {
16460             *p++ = '-';
16461         }
16462     } else {
16463         *p++ = '-';
16464     }
16465     *p++ = ' ';
16466   }
16467   }
16468
16469     /* [HGM] find reversible plies */
16470     {   int i = 0, j=move;
16471
16472         if (appData.debugMode) { int k;
16473             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16474             for(k=backwardMostMove; k<=forwardMostMove; k++)
16475                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16476
16477         }
16478
16479         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16480         if( j == backwardMostMove ) i += initialRulePlies;
16481         sprintf(p, "%d ", i);
16482         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16483     }
16484     /* Fullmove number */
16485     sprintf(p, "%d", (move / 2) + 1);
16486
16487     return StrSave(buf);
16488 }
16489
16490 Boolean
16491 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16492 {
16493     int i, j;
16494     char *p, c;
16495     int emptycount;
16496     ChessSquare piece;
16497
16498     p = fen;
16499
16500     /* [HGM] by default clear Crazyhouse holdings, if present */
16501     if(gameInfo.holdingsWidth) {
16502        for(i=0; i<BOARD_HEIGHT; i++) {
16503            board[i][0]             = EmptySquare; /* black holdings */
16504            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16505            board[i][1]             = (ChessSquare) 0; /* black counts */
16506            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16507        }
16508     }
16509
16510     /* Piece placement data */
16511     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16512         j = 0;
16513         for (;;) {
16514             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16515                 if (*p == '/') p++;
16516                 emptycount = gameInfo.boardWidth - j;
16517                 while (emptycount--)
16518                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16519                 break;
16520 #if(BOARD_FILES >= 10)
16521             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16522                 p++; emptycount=10;
16523                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16524                 while (emptycount--)
16525                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16526 #endif
16527             } else if (isdigit(*p)) {
16528                 emptycount = *p++ - '0';
16529                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16530                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16531                 while (emptycount--)
16532                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16533             } else if (*p == '+' || isalpha(*p)) {
16534                 if (j >= gameInfo.boardWidth) return FALSE;
16535                 if(*p=='+') {
16536                     piece = CharToPiece(*++p);
16537                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16538                     piece = (ChessSquare) (PROMOTED piece ); p++;
16539                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16540                 } else piece = CharToPiece(*p++);
16541
16542                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16543                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16544                     piece = (ChessSquare) (PROMOTED piece);
16545                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16546                     p++;
16547                 }
16548                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16549             } else {
16550                 return FALSE;
16551             }
16552         }
16553     }
16554     while (*p == '/' || *p == ' ') p++;
16555
16556     /* [HGM] look for Crazyhouse holdings here */
16557     while(*p==' ') p++;
16558     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16559         if(*p == '[') p++;
16560         if(*p == '-' ) p++; /* empty holdings */ else {
16561             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16562             /* if we would allow FEN reading to set board size, we would   */
16563             /* have to add holdings and shift the board read so far here   */
16564             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16565                 p++;
16566                 if((int) piece >= (int) BlackPawn ) {
16567                     i = (int)piece - (int)BlackPawn;
16568                     i = PieceToNumber((ChessSquare)i);
16569                     if( i >= gameInfo.holdingsSize ) return FALSE;
16570                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16571                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16572                 } else {
16573                     i = (int)piece - (int)WhitePawn;
16574                     i = PieceToNumber((ChessSquare)i);
16575                     if( i >= gameInfo.holdingsSize ) return FALSE;
16576                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16577                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16578                 }
16579             }
16580         }
16581         if(*p == ']') p++;
16582     }
16583
16584     while(*p == ' ') p++;
16585
16586     /* Active color */
16587     c = *p++;
16588     if(appData.colorNickNames) {
16589       if( c == appData.colorNickNames[0] ) c = 'w'; else
16590       if( c == appData.colorNickNames[1] ) c = 'b';
16591     }
16592     switch (c) {
16593       case 'w':
16594         *blackPlaysFirst = FALSE;
16595         break;
16596       case 'b':
16597         *blackPlaysFirst = TRUE;
16598         break;
16599       default:
16600         return FALSE;
16601     }
16602
16603     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16604     /* return the extra info in global variiables             */
16605
16606     /* set defaults in case FEN is incomplete */
16607     board[EP_STATUS] = EP_UNKNOWN;
16608     for(i=0; i<nrCastlingRights; i++ ) {
16609         board[CASTLING][i] =
16610             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16611     }   /* assume possible unless obviously impossible */
16612     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16613     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16614     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16615                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16616     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16617     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16618     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16619                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16620     FENrulePlies = 0;
16621
16622     while(*p==' ') p++;
16623     if(nrCastlingRights) {
16624       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16625           /* castling indicator present, so default becomes no castlings */
16626           for(i=0; i<nrCastlingRights; i++ ) {
16627                  board[CASTLING][i] = NoRights;
16628           }
16629       }
16630       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16631              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16632              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16633              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16634         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16635
16636         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16637             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16638             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16639         }
16640         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16641             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16642         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16643                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16644         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16645                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16646         switch(c) {
16647           case'K':
16648               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16649               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16650               board[CASTLING][2] = whiteKingFile;
16651               break;
16652           case'Q':
16653               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16654               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16655               board[CASTLING][2] = whiteKingFile;
16656               break;
16657           case'k':
16658               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16659               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16660               board[CASTLING][5] = blackKingFile;
16661               break;
16662           case'q':
16663               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16664               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16665               board[CASTLING][5] = blackKingFile;
16666           case '-':
16667               break;
16668           default: /* FRC castlings */
16669               if(c >= 'a') { /* black rights */
16670                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16671                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16672                   if(i == BOARD_RGHT) break;
16673                   board[CASTLING][5] = i;
16674                   c -= AAA;
16675                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16676                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16677                   if(c > i)
16678                       board[CASTLING][3] = c;
16679                   else
16680                       board[CASTLING][4] = c;
16681               } else { /* white rights */
16682                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16683                     if(board[0][i] == WhiteKing) break;
16684                   if(i == BOARD_RGHT) break;
16685                   board[CASTLING][2] = i;
16686                   c -= AAA - 'a' + 'A';
16687                   if(board[0][c] >= WhiteKing) break;
16688                   if(c > i)
16689                       board[CASTLING][0] = c;
16690                   else
16691                       board[CASTLING][1] = c;
16692               }
16693         }
16694       }
16695       for(i=0; i<nrCastlingRights; i++)
16696         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16697     if (appData.debugMode) {
16698         fprintf(debugFP, "FEN castling rights:");
16699         for(i=0; i<nrCastlingRights; i++)
16700         fprintf(debugFP, " %d", board[CASTLING][i]);
16701         fprintf(debugFP, "\n");
16702     }
16703
16704       while(*p==' ') p++;
16705     }
16706
16707     /* read e.p. field in games that know e.p. capture */
16708     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16709        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16710       if(*p=='-') {
16711         p++; board[EP_STATUS] = EP_NONE;
16712       } else {
16713          char c = *p++ - AAA;
16714
16715          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16716          if(*p >= '0' && *p <='9') p++;
16717          board[EP_STATUS] = c;
16718       }
16719     }
16720
16721
16722     if(sscanf(p, "%d", &i) == 1) {
16723         FENrulePlies = i; /* 50-move ply counter */
16724         /* (The move number is still ignored)    */
16725     }
16726
16727     return TRUE;
16728 }
16729
16730 void
16731 EditPositionPasteFEN (char *fen)
16732 {
16733   if (fen != NULL) {
16734     Board initial_position;
16735
16736     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16737       DisplayError(_("Bad FEN position in clipboard"), 0);
16738       return ;
16739     } else {
16740       int savedBlackPlaysFirst = blackPlaysFirst;
16741       EditPositionEvent();
16742       blackPlaysFirst = savedBlackPlaysFirst;
16743       CopyBoard(boards[0], initial_position);
16744       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16745       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16746       DisplayBothClocks();
16747       DrawPosition(FALSE, boards[currentMove]);
16748     }
16749   }
16750 }
16751
16752 static char cseq[12] = "\\   ";
16753
16754 Boolean
16755 set_cont_sequence (char *new_seq)
16756 {
16757     int len;
16758     Boolean ret;
16759
16760     // handle bad attempts to set the sequence
16761         if (!new_seq)
16762                 return 0; // acceptable error - no debug
16763
16764     len = strlen(new_seq);
16765     ret = (len > 0) && (len < sizeof(cseq));
16766     if (ret)
16767       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16768     else if (appData.debugMode)
16769       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16770     return ret;
16771 }
16772
16773 /*
16774     reformat a source message so words don't cross the width boundary.  internal
16775     newlines are not removed.  returns the wrapped size (no null character unless
16776     included in source message).  If dest is NULL, only calculate the size required
16777     for the dest buffer.  lp argument indicats line position upon entry, and it's
16778     passed back upon exit.
16779 */
16780 int
16781 wrap (char *dest, char *src, int count, int width, int *lp)
16782 {
16783     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16784
16785     cseq_len = strlen(cseq);
16786     old_line = line = *lp;
16787     ansi = len = clen = 0;
16788
16789     for (i=0; i < count; i++)
16790     {
16791         if (src[i] == '\033')
16792             ansi = 1;
16793
16794         // if we hit the width, back up
16795         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16796         {
16797             // store i & len in case the word is too long
16798             old_i = i, old_len = len;
16799
16800             // find the end of the last word
16801             while (i && src[i] != ' ' && src[i] != '\n')
16802             {
16803                 i--;
16804                 len--;
16805             }
16806
16807             // word too long?  restore i & len before splitting it
16808             if ((old_i-i+clen) >= width)
16809             {
16810                 i = old_i;
16811                 len = old_len;
16812             }
16813
16814             // extra space?
16815             if (i && src[i-1] == ' ')
16816                 len--;
16817
16818             if (src[i] != ' ' && src[i] != '\n')
16819             {
16820                 i--;
16821                 if (len)
16822                     len--;
16823             }
16824
16825             // now append the newline and continuation sequence
16826             if (dest)
16827                 dest[len] = '\n';
16828             len++;
16829             if (dest)
16830                 strncpy(dest+len, cseq, cseq_len);
16831             len += cseq_len;
16832             line = cseq_len;
16833             clen = cseq_len;
16834             continue;
16835         }
16836
16837         if (dest)
16838             dest[len] = src[i];
16839         len++;
16840         if (!ansi)
16841             line++;
16842         if (src[i] == '\n')
16843             line = 0;
16844         if (src[i] == 'm')
16845             ansi = 0;
16846     }
16847     if (dest && appData.debugMode)
16848     {
16849         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16850             count, width, line, len, *lp);
16851         show_bytes(debugFP, src, count);
16852         fprintf(debugFP, "\ndest: ");
16853         show_bytes(debugFP, dest, len);
16854         fprintf(debugFP, "\n");
16855     }
16856     *lp = dest ? line : old_line;
16857
16858     return len;
16859 }
16860
16861 // [HGM] vari: routines for shelving variations
16862 Boolean modeRestore = FALSE;
16863
16864 void
16865 PushInner (int firstMove, int lastMove)
16866 {
16867         int i, j, nrMoves = lastMove - firstMove;
16868
16869         // push current tail of game on stack
16870         savedResult[storedGames] = gameInfo.result;
16871         savedDetails[storedGames] = gameInfo.resultDetails;
16872         gameInfo.resultDetails = NULL;
16873         savedFirst[storedGames] = firstMove;
16874         savedLast [storedGames] = lastMove;
16875         savedFramePtr[storedGames] = framePtr;
16876         framePtr -= nrMoves; // reserve space for the boards
16877         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16878             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16879             for(j=0; j<MOVE_LEN; j++)
16880                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16881             for(j=0; j<2*MOVE_LEN; j++)
16882                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16883             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16884             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16885             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16886             pvInfoList[firstMove+i-1].depth = 0;
16887             commentList[framePtr+i] = commentList[firstMove+i];
16888             commentList[firstMove+i] = NULL;
16889         }
16890
16891         storedGames++;
16892         forwardMostMove = firstMove; // truncate game so we can start variation
16893 }
16894
16895 void
16896 PushTail (int firstMove, int lastMove)
16897 {
16898         if(appData.icsActive) { // only in local mode
16899                 forwardMostMove = currentMove; // mimic old ICS behavior
16900                 return;
16901         }
16902         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16903
16904         PushInner(firstMove, lastMove);
16905         if(storedGames == 1) GreyRevert(FALSE);
16906         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16907 }
16908
16909 void
16910 PopInner (Boolean annotate)
16911 {
16912         int i, j, nrMoves;
16913         char buf[8000], moveBuf[20];
16914
16915         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16916         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16917         nrMoves = savedLast[storedGames] - currentMove;
16918         if(annotate) {
16919                 int cnt = 10;
16920                 if(!WhiteOnMove(currentMove))
16921                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16922                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16923                 for(i=currentMove; i<forwardMostMove; i++) {
16924                         if(WhiteOnMove(i))
16925                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16926                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16927                         strcat(buf, moveBuf);
16928                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16929                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16930                 }
16931                 strcat(buf, ")");
16932         }
16933         for(i=1; i<=nrMoves; i++) { // copy last variation back
16934             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16935             for(j=0; j<MOVE_LEN; j++)
16936                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16937             for(j=0; j<2*MOVE_LEN; j++)
16938                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16939             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16940             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16941             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16942             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16943             commentList[currentMove+i] = commentList[framePtr+i];
16944             commentList[framePtr+i] = NULL;
16945         }
16946         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16947         framePtr = savedFramePtr[storedGames];
16948         gameInfo.result = savedResult[storedGames];
16949         if(gameInfo.resultDetails != NULL) {
16950             free(gameInfo.resultDetails);
16951       }
16952         gameInfo.resultDetails = savedDetails[storedGames];
16953         forwardMostMove = currentMove + nrMoves;
16954 }
16955
16956 Boolean
16957 PopTail (Boolean annotate)
16958 {
16959         if(appData.icsActive) return FALSE; // only in local mode
16960         if(!storedGames) return FALSE; // sanity
16961         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16962
16963         PopInner(annotate);
16964         if(currentMove < forwardMostMove) ForwardEvent(); else
16965         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16966
16967         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16968         return TRUE;
16969 }
16970
16971 void
16972 CleanupTail ()
16973 {       // remove all shelved variations
16974         int i;
16975         for(i=0; i<storedGames; i++) {
16976             if(savedDetails[i])
16977                 free(savedDetails[i]);
16978             savedDetails[i] = NULL;
16979         }
16980         for(i=framePtr; i<MAX_MOVES; i++) {
16981                 if(commentList[i]) free(commentList[i]);
16982                 commentList[i] = NULL;
16983         }
16984         framePtr = MAX_MOVES-1;
16985         storedGames = 0;
16986 }
16987
16988 void
16989 LoadVariation (int index, char *text)
16990 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16991         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16992         int level = 0, move;
16993
16994         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16995         // first find outermost bracketing variation
16996         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16997             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16998                 if(*p == '{') wait = '}'; else
16999                 if(*p == '[') wait = ']'; else
17000                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17001                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17002             }
17003             if(*p == wait) wait = NULLCHAR; // closing ]} found
17004             p++;
17005         }
17006         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17007         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17008         end[1] = NULLCHAR; // clip off comment beyond variation
17009         ToNrEvent(currentMove-1);
17010         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17011         // kludge: use ParsePV() to append variation to game
17012         move = currentMove;
17013         ParsePV(start, TRUE, TRUE);
17014         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17015         ClearPremoveHighlights();
17016         CommentPopDown();
17017         ToNrEvent(currentMove+1);
17018 }
17019