Inform user in EditPosition mode how to clear board
[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(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
6890         // could be static click on premove from-square: abort premove
6891         gotPremove = 0;
6892         ClearPremoveHighlights();
6893     }
6894
6895     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6896         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6897
6898     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6899         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6900                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6901         defaultPromoChoice = DefaultPromoChoice(side);
6902     }
6903
6904     autoQueen = appData.alwaysPromoteToQueen;
6905
6906     if (fromX == -1) {
6907       int originalY = y;
6908       gatingPiece = EmptySquare;
6909       if (clickType != Press) {
6910         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6911             DragPieceEnd(xPix, yPix); dragging = 0;
6912             DrawPosition(FALSE, NULL);
6913         }
6914         return;
6915       }
6916       fromX = x; fromY = y; toX = toY = -1;
6917       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6918          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6919          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6920             /* First square */
6921             if (OKToStartUserMove(fromX, fromY)) {
6922                 second = 0;
6923                 MarkTargetSquares(0);
6924                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6925                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6926                     promoSweep = defaultPromoChoice;
6927                     selectFlag = 0; lastX = xPix; lastY = yPix;
6928                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6929                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6930                 }
6931                 if (appData.highlightDragging) {
6932                     SetHighlights(fromX, fromY, -1, -1);
6933                 }
6934             } else fromX = fromY = -1;
6935             return;
6936         }
6937     }
6938
6939     /* fromX != -1 */
6940     if (clickType == Press && gameMode != EditPosition) {
6941         ChessSquare fromP;
6942         ChessSquare toP;
6943         int frc;
6944
6945         // ignore off-board to clicks
6946         if(y < 0 || x < 0) return;
6947
6948         /* Check if clicking again on the same color piece */
6949         fromP = boards[currentMove][fromY][fromX];
6950         toP = boards[currentMove][y][x];
6951         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6952         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6953              WhitePawn <= toP && toP <= WhiteKing &&
6954              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6955              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6956             (BlackPawn <= fromP && fromP <= BlackKing &&
6957              BlackPawn <= toP && toP <= BlackKing &&
6958              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6959              !(fromP == BlackKing && toP == BlackRook && frc))) {
6960             /* Clicked again on same color piece -- changed his mind */
6961             second = (x == fromX && y == fromY);
6962             promoDefaultAltered = FALSE;
6963             MarkTargetSquares(1);
6964            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6965             if (appData.highlightDragging) {
6966                 SetHighlights(x, y, -1, -1);
6967             } else {
6968                 ClearHighlights();
6969             }
6970             if (OKToStartUserMove(x, y)) {
6971                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6972                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6973                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6974                  gatingPiece = boards[currentMove][fromY][fromX];
6975                 else gatingPiece = EmptySquare;
6976                 fromX = x;
6977                 fromY = y; dragging = 1;
6978                 MarkTargetSquares(0);
6979                 DragPieceBegin(xPix, yPix, FALSE);
6980                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6981                     promoSweep = defaultPromoChoice;
6982                     selectFlag = 0; lastX = xPix; lastY = yPix;
6983                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6984                 }
6985             }
6986            }
6987            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6988            second = FALSE; 
6989         }
6990         // ignore clicks on holdings
6991         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6992     }
6993
6994     if (clickType == Release && x == fromX && y == fromY) {
6995         DragPieceEnd(xPix, yPix); dragging = 0;
6996         if(clearFlag) {
6997             // a deferred attempt to click-click move an empty square on top of a piece
6998             boards[currentMove][y][x] = EmptySquare;
6999             ClearHighlights();
7000             DrawPosition(FALSE, boards[currentMove]);
7001             fromX = fromY = -1; clearFlag = 0;
7002             return;
7003         }
7004         if (appData.animateDragging) {
7005             /* Undo animation damage if any */
7006             DrawPosition(FALSE, NULL);
7007         }
7008         if (second) {
7009             /* Second up/down in same square; just abort move */
7010             second = 0;
7011             fromX = fromY = -1;
7012             gatingPiece = EmptySquare;
7013             ClearHighlights();
7014             gotPremove = 0;
7015             ClearPremoveHighlights();
7016         } else {
7017             /* First upclick in same square; start click-click mode */
7018             SetHighlights(x, y, -1, -1);
7019         }
7020         return;
7021     }
7022
7023     clearFlag = 0;
7024
7025     /* we now have a different from- and (possibly off-board) to-square */
7026     /* Completed move */
7027     toX = x;
7028     toY = y;
7029     saveAnimate = appData.animate;
7030     MarkTargetSquares(1);
7031     if (clickType == Press) {
7032         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7033             // must be Edit Position mode with empty-square selected
7034             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7035             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7036             return;
7037         }
7038         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7039             ChessSquare piece = boards[currentMove][fromY][fromX];
7040             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7041             promoSweep = defaultPromoChoice;
7042             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7043             selectFlag = 0; lastX = xPix; lastY = yPix;
7044             Sweep(0); // Pawn that is going to promote: preview promotion piece
7045             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7046             DrawPosition(FALSE, boards[currentMove]);
7047             return;
7048         }
7049         /* Finish clickclick move */
7050         if (appData.animate || appData.highlightLastMove) {
7051             SetHighlights(fromX, fromY, toX, toY);
7052         } else {
7053             ClearHighlights();
7054         }
7055     } else {
7056         /* Finish drag move */
7057         if (appData.highlightLastMove) {
7058             SetHighlights(fromX, fromY, toX, toY);
7059         } else {
7060             ClearHighlights();
7061         }
7062         DragPieceEnd(xPix, yPix); dragging = 0;
7063         /* Don't animate move and drag both */
7064         appData.animate = FALSE;
7065     }
7066
7067     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7068     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7069         ChessSquare piece = boards[currentMove][fromY][fromX];
7070         if(gameMode == EditPosition && piece != EmptySquare &&
7071            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7072             int n;
7073
7074             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7075                 n = PieceToNumber(piece - (int)BlackPawn);
7076                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7077                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7078                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7079             } else
7080             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7081                 n = PieceToNumber(piece);
7082                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7083                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7084                 boards[currentMove][n][BOARD_WIDTH-2]++;
7085             }
7086             boards[currentMove][fromY][fromX] = EmptySquare;
7087         }
7088         ClearHighlights();
7089         fromX = fromY = -1;
7090         DrawPosition(TRUE, boards[currentMove]);
7091         return;
7092     }
7093
7094     // off-board moves should not be highlighted
7095     if(x < 0 || y < 0) ClearHighlights();
7096
7097     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7098
7099     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7100         SetHighlights(fromX, fromY, toX, toY);
7101         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7102             // [HGM] super: promotion to captured piece selected from holdings
7103             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7104             promotionChoice = TRUE;
7105             // kludge follows to temporarily execute move on display, without promoting yet
7106             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7107             boards[currentMove][toY][toX] = p;
7108             DrawPosition(FALSE, boards[currentMove]);
7109             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7110             boards[currentMove][toY][toX] = q;
7111             DisplayMessage("Click in holdings to choose piece", "");
7112             return;
7113         }
7114         PromotionPopUp();
7115     } else {
7116         int oldMove = currentMove;
7117         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7118         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7119         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7120         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7121            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7122             DrawPosition(TRUE, boards[currentMove]);
7123         fromX = fromY = -1;
7124     }
7125     appData.animate = saveAnimate;
7126     if (appData.animate || appData.animateDragging) {
7127         /* Undo animation damage if needed */
7128         DrawPosition(FALSE, NULL);
7129     }
7130 }
7131
7132 int
7133 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7134 {   // front-end-free part taken out of PieceMenuPopup
7135     int whichMenu; int xSqr, ySqr;
7136
7137     if(seekGraphUp) { // [HGM] seekgraph
7138         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7139         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7140         return -2;
7141     }
7142
7143     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7144          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7145         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7146         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7147         if(action == Press)   {
7148             originalFlip = flipView;
7149             flipView = !flipView; // temporarily flip board to see game from partners perspective
7150             DrawPosition(TRUE, partnerBoard);
7151             DisplayMessage(partnerStatus, "");
7152             partnerUp = TRUE;
7153         } else if(action == Release) {
7154             flipView = originalFlip;
7155             DrawPosition(TRUE, boards[currentMove]);
7156             partnerUp = FALSE;
7157         }
7158         return -2;
7159     }
7160
7161     xSqr = EventToSquare(x, BOARD_WIDTH);
7162     ySqr = EventToSquare(y, BOARD_HEIGHT);
7163     if (action == Release) {
7164         if(pieceSweep != EmptySquare) {
7165             EditPositionMenuEvent(pieceSweep, toX, toY);
7166             pieceSweep = EmptySquare;
7167         } else UnLoadPV(); // [HGM] pv
7168     }
7169     if (action != Press) return -2; // return code to be ignored
7170     switch (gameMode) {
7171       case IcsExamining:
7172         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7173       case EditPosition:
7174         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7175         if (xSqr < 0 || ySqr < 0) return -1;
7176         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7177         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7178         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7179         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7180         NextPiece(0);
7181         return 2; // grab
7182       case IcsObserving:
7183         if(!appData.icsEngineAnalyze) return -1;
7184       case IcsPlayingWhite:
7185       case IcsPlayingBlack:
7186         if(!appData.zippyPlay) goto noZip;
7187       case AnalyzeMode:
7188       case AnalyzeFile:
7189       case MachinePlaysWhite:
7190       case MachinePlaysBlack:
7191       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7192         if (!appData.dropMenu) {
7193           LoadPV(x, y);
7194           return 2; // flag front-end to grab mouse events
7195         }
7196         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7197            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7198       case EditGame:
7199       noZip:
7200         if (xSqr < 0 || ySqr < 0) return -1;
7201         if (!appData.dropMenu || appData.testLegality &&
7202             gameInfo.variant != VariantBughouse &&
7203             gameInfo.variant != VariantCrazyhouse) return -1;
7204         whichMenu = 1; // drop menu
7205         break;
7206       default:
7207         return -1;
7208     }
7209
7210     if (((*fromX = xSqr) < 0) ||
7211         ((*fromY = ySqr) < 0)) {
7212         *fromX = *fromY = -1;
7213         return -1;
7214     }
7215     if (flipView)
7216       *fromX = BOARD_WIDTH - 1 - *fromX;
7217     else
7218       *fromY = BOARD_HEIGHT - 1 - *fromY;
7219
7220     return whichMenu;
7221 }
7222
7223 void
7224 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7225 {
7226 //    char * hint = lastHint;
7227     FrontEndProgramStats stats;
7228
7229     stats.which = cps == &first ? 0 : 1;
7230     stats.depth = cpstats->depth;
7231     stats.nodes = cpstats->nodes;
7232     stats.score = cpstats->score;
7233     stats.time = cpstats->time;
7234     stats.pv = cpstats->movelist;
7235     stats.hint = lastHint;
7236     stats.an_move_index = 0;
7237     stats.an_move_count = 0;
7238
7239     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7240         stats.hint = cpstats->move_name;
7241         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7242         stats.an_move_count = cpstats->nr_moves;
7243     }
7244
7245     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
7246
7247     SetProgramStats( &stats );
7248 }
7249
7250 void
7251 ClearEngineOutputPane (int which)
7252 {
7253     static FrontEndProgramStats dummyStats;
7254     dummyStats.which = which;
7255     dummyStats.pv = "#";
7256     SetProgramStats( &dummyStats );
7257 }
7258
7259 #define MAXPLAYERS 500
7260
7261 char *
7262 TourneyStandings (int display)
7263 {
7264     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7265     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7266     char result, *p, *names[MAXPLAYERS];
7267
7268     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7269         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7270     names[0] = p = strdup(appData.participants);
7271     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7272
7273     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7274
7275     while(result = appData.results[nr]) {
7276         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7277         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7278         wScore = bScore = 0;
7279         switch(result) {
7280           case '+': wScore = 2; break;
7281           case '-': bScore = 2; break;
7282           case '=': wScore = bScore = 1; break;
7283           case ' ':
7284           case '*': return strdup("busy"); // tourney not finished
7285         }
7286         score[w] += wScore;
7287         score[b] += bScore;
7288         games[w]++;
7289         games[b]++;
7290         nr++;
7291     }
7292     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7293     for(w=0; w<nPlayers; w++) {
7294         bScore = -1;
7295         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7296         ranking[w] = b; points[w] = bScore; score[b] = -2;
7297     }
7298     p = malloc(nPlayers*34+1);
7299     for(w=0; w<nPlayers && w<display; w++)
7300         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7301     free(names[0]);
7302     return p;
7303 }
7304
7305 void
7306 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7307 {       // count all piece types
7308         int p, f, r;
7309         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7310         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7311         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7312                 p = board[r][f];
7313                 pCnt[p]++;
7314                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7315                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7316                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7317                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7318                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7319                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7320         }
7321 }
7322
7323 int
7324 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7325 {
7326         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7327         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7328
7329         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7330         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7331         if(myPawns == 2 && nMine == 3) // KPP
7332             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7333         if(myPawns == 1 && nMine == 2) // KP
7334             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7335         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7336             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7337         if(myPawns) return FALSE;
7338         if(pCnt[WhiteRook+side])
7339             return pCnt[BlackRook-side] ||
7340                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7341                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7342                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7343         if(pCnt[WhiteCannon+side]) {
7344             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7345             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7346         }
7347         if(pCnt[WhiteKnight+side])
7348             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7349         return FALSE;
7350 }
7351
7352 int
7353 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7354 {
7355         VariantClass v = gameInfo.variant;
7356
7357         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7358         if(v == VariantShatranj) return TRUE; // always winnable through baring
7359         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7360         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7361
7362         if(v == VariantXiangqi) {
7363                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7364
7365                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7366                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7367                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7368                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7369                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7370                 if(stale) // we have at least one last-rank P plus perhaps C
7371                     return majors // KPKX
7372                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7373                 else // KCA*E*
7374                     return pCnt[WhiteFerz+side] // KCAK
7375                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7376                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7377                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7378
7379         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7380                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7381
7382                 if(nMine == 1) return FALSE; // bare King
7383                 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
7384                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7385                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7386                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7387                 if(pCnt[WhiteKnight+side])
7388                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7389                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7390                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7391                 if(nBishops)
7392                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7393                 if(pCnt[WhiteAlfil+side])
7394                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7395                 if(pCnt[WhiteWazir+side])
7396                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7397         }
7398
7399         return TRUE;
7400 }
7401
7402 int
7403 CompareWithRights (Board b1, Board b2)
7404 {
7405     int rights = 0;
7406     if(!CompareBoards(b1, b2)) return FALSE;
7407     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7408     /* compare castling rights */
7409     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7410            rights++; /* King lost rights, while rook still had them */
7411     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7412         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7413            rights++; /* but at least one rook lost them */
7414     }
7415     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7416            rights++;
7417     if( b1[CASTLING][5] != NoRights ) {
7418         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7419            rights++;
7420     }
7421     return rights == 0;
7422 }
7423
7424 int
7425 Adjudicate (ChessProgramState *cps)
7426 {       // [HGM] some adjudications useful with buggy engines
7427         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7428         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7429         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7430         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7431         int k, count = 0; static int bare = 1;
7432         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7433         Boolean canAdjudicate = !appData.icsActive;
7434
7435         // most tests only when we understand the game, i.e. legality-checking on
7436             if( appData.testLegality )
7437             {   /* [HGM] Some more adjudications for obstinate engines */
7438                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7439                 static int moveCount = 6;
7440                 ChessMove result;
7441                 char *reason = NULL;
7442
7443                 /* Count what is on board. */
7444                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7445
7446                 /* Some material-based adjudications that have to be made before stalemate test */
7447                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7448                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7449                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7450                      if(canAdjudicate && appData.checkMates) {
7451                          if(engineOpponent)
7452                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7453                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7454                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7455                          return 1;
7456                      }
7457                 }
7458
7459                 /* Bare King in Shatranj (loses) or Losers (wins) */
7460                 if( nrW == 1 || nrB == 1) {
7461                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7462                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7463                      if(canAdjudicate && appData.checkMates) {
7464                          if(engineOpponent)
7465                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7466                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7467                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7468                          return 1;
7469                      }
7470                   } else
7471                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7472                   {    /* bare King */
7473                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7474                         if(canAdjudicate && appData.checkMates) {
7475                             /* but only adjudicate if adjudication enabled */
7476                             if(engineOpponent)
7477                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7478                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7479                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7480                             return 1;
7481                         }
7482                   }
7483                 } else bare = 1;
7484
7485
7486             // don't wait for engine to announce game end if we can judge ourselves
7487             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7488               case MT_CHECK:
7489                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7490                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7491                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7492                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7493                             checkCnt++;
7494                         if(checkCnt >= 2) {
7495                             reason = "Xboard adjudication: 3rd check";
7496                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7497                             break;
7498                         }
7499                     }
7500                 }
7501               case MT_NONE:
7502               default:
7503                 break;
7504               case MT_STALEMATE:
7505               case MT_STAINMATE:
7506                 reason = "Xboard adjudication: Stalemate";
7507                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7508                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7509                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7510                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7511                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7512                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7513                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7514                                                                         EP_CHECKMATE : EP_WINS);
7515                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7516                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7517                 }
7518                 break;
7519               case MT_CHECKMATE:
7520                 reason = "Xboard adjudication: Checkmate";
7521                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7522                 break;
7523             }
7524
7525                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7526                     case EP_STALEMATE:
7527                         result = GameIsDrawn; break;
7528                     case EP_CHECKMATE:
7529                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7530                     case EP_WINS:
7531                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7532                     default:
7533                         result = EndOfFile;
7534                 }
7535                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7536                     if(engineOpponent)
7537                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7538                     GameEnds( result, reason, GE_XBOARD );
7539                     return 1;
7540                 }
7541
7542                 /* Next absolutely insufficient mating material. */
7543                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7544                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7545                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7546
7547                      /* always flag draws, for judging claims */
7548                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7549
7550                      if(canAdjudicate && appData.materialDraws) {
7551                          /* but only adjudicate them if adjudication enabled */
7552                          if(engineOpponent) {
7553                            SendToProgram("force\n", engineOpponent); // suppress reply
7554                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7555                          }
7556                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7557                          return 1;
7558                      }
7559                 }
7560
7561                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7562                 if(gameInfo.variant == VariantXiangqi ?
7563                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7564                  : nrW + nrB == 4 &&
7565                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7566                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7567                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7568                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7569                    ) ) {
7570                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7571                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7572                           if(engineOpponent) {
7573                             SendToProgram("force\n", engineOpponent); // suppress reply
7574                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7575                           }
7576                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7577                           return 1;
7578                      }
7579                 } else moveCount = 6;
7580             }
7581
7582         // Repetition draws and 50-move rule can be applied independently of legality testing
7583
7584                 /* Check for rep-draws */
7585                 count = 0;
7586                 for(k = forwardMostMove-2;
7587                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7588                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7589                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7590                     k-=2)
7591                 {   int rights=0;
7592                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7593                         /* compare castling rights */
7594                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7595                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7596                                 rights++; /* King lost rights, while rook still had them */
7597                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7598                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7599                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7600                                    rights++; /* but at least one rook lost them */
7601                         }
7602                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7603                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7604                                 rights++;
7605                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7606                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7607                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7608                                    rights++;
7609                         }
7610                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7611                             && appData.drawRepeats > 1) {
7612                              /* adjudicate after user-specified nr of repeats */
7613                              int result = GameIsDrawn;
7614                              char *details = "XBoard adjudication: repetition draw";
7615                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7616                                 // [HGM] xiangqi: check for forbidden perpetuals
7617                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7618                                 for(m=forwardMostMove; m>k; m-=2) {
7619                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7620                                         ourPerpetual = 0; // the current mover did not always check
7621                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7622                                         hisPerpetual = 0; // the opponent did not always check
7623                                 }
7624                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7625                                                                         ourPerpetual, hisPerpetual);
7626                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7627                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7628                                     details = "Xboard adjudication: perpetual checking";
7629                                 } else
7630                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7631                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7632                                 } else
7633                                 // Now check for perpetual chases
7634                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7635                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7636                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7637                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7638                                         static char resdet[MSG_SIZ];
7639                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7640                                         details = resdet;
7641                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7642                                     } else
7643                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7644                                         break; // Abort repetition-checking loop.
7645                                 }
7646                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7647                              }
7648                              if(engineOpponent) {
7649                                SendToProgram("force\n", engineOpponent); // suppress reply
7650                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7651                              }
7652                              GameEnds( result, details, GE_XBOARD );
7653                              return 1;
7654                         }
7655                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7656                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7657                     }
7658                 }
7659
7660                 /* Now we test for 50-move draws. Determine ply count */
7661                 count = forwardMostMove;
7662                 /* look for last irreversble move */
7663                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7664                     count--;
7665                 /* if we hit starting position, add initial plies */
7666                 if( count == backwardMostMove )
7667                     count -= initialRulePlies;
7668                 count = forwardMostMove - count;
7669                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7670                         // adjust reversible move counter for checks in Xiangqi
7671                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7672                         if(i < backwardMostMove) i = backwardMostMove;
7673                         while(i <= forwardMostMove) {
7674                                 lastCheck = inCheck; // check evasion does not count
7675                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7676                                 if(inCheck || lastCheck) count--; // check does not count
7677                                 i++;
7678                         }
7679                 }
7680                 if( count >= 100)
7681                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7682                          /* this is used to judge if draw claims are legal */
7683                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7684                          if(engineOpponent) {
7685                            SendToProgram("force\n", engineOpponent); // suppress reply
7686                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7687                          }
7688                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7689                          return 1;
7690                 }
7691
7692                 /* if draw offer is pending, treat it as a draw claim
7693                  * when draw condition present, to allow engines a way to
7694                  * claim draws before making their move to avoid a race
7695                  * condition occurring after their move
7696                  */
7697                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7698                          char *p = NULL;
7699                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7700                              p = "Draw claim: 50-move rule";
7701                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7702                              p = "Draw claim: 3-fold repetition";
7703                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7704                              p = "Draw claim: insufficient mating material";
7705                          if( p != NULL && canAdjudicate) {
7706                              if(engineOpponent) {
7707                                SendToProgram("force\n", engineOpponent); // suppress reply
7708                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7709                              }
7710                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7711                              return 1;
7712                          }
7713                 }
7714
7715                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7716                     if(engineOpponent) {
7717                       SendToProgram("force\n", engineOpponent); // suppress reply
7718                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7719                     }
7720                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7721                     return 1;
7722                 }
7723         return 0;
7724 }
7725
7726 char *
7727 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7728 {   // [HGM] book: this routine intercepts moves to simulate book replies
7729     char *bookHit = NULL;
7730
7731     //first determine if the incoming move brings opponent into his book
7732     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7733         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7734     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7735     if(bookHit != NULL && !cps->bookSuspend) {
7736         // make sure opponent is not going to reply after receiving move to book position
7737         SendToProgram("force\n", cps);
7738         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7739     }
7740     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7741     // now arrange restart after book miss
7742     if(bookHit) {
7743         // after a book hit we never send 'go', and the code after the call to this routine
7744         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7745         char buf[MSG_SIZ], *move = bookHit;
7746         if(cps->useSAN) {
7747             int fromX, fromY, toX, toY;
7748             char promoChar;
7749             ChessMove moveType;
7750             move = buf + 30;
7751             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7752                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7753                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7754                                     PosFlags(forwardMostMove),
7755                                     fromY, fromX, toY, toX, promoChar, move);
7756             } else {
7757                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7758                 bookHit = NULL;
7759             }
7760         }
7761         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7762         SendToProgram(buf, cps);
7763         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7764     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7765         SendToProgram("go\n", cps);
7766         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7767     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7768         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7769             SendToProgram("go\n", cps);
7770         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7771     }
7772     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7773 }
7774
7775 char *savedMessage;
7776 ChessProgramState *savedState;
7777 void
7778 DeferredBookMove (void)
7779 {
7780         if(savedState->lastPing != savedState->lastPong)
7781                     ScheduleDelayedEvent(DeferredBookMove, 10);
7782         else
7783         HandleMachineMove(savedMessage, savedState);
7784 }
7785
7786 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7787
7788 void
7789 HandleMachineMove (char *message, ChessProgramState *cps)
7790 {
7791     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7792     char realname[MSG_SIZ];
7793     int fromX, fromY, toX, toY;
7794     ChessMove moveType;
7795     char promoChar;
7796     char *p, *pv=buf1;
7797     int machineWhite;
7798     char *bookHit;
7799
7800     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7801         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7802         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7803             DisplayError(_("Invalid pairing from pairing engine"), 0);
7804             return;
7805         }
7806         pairingReceived = 1;
7807         NextMatchGame();
7808         return; // Skim the pairing messages here.
7809     }
7810
7811     cps->userError = 0;
7812
7813 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7814     /*
7815      * Kludge to ignore BEL characters
7816      */
7817     while (*message == '\007') message++;
7818
7819     /*
7820      * [HGM] engine debug message: ignore lines starting with '#' character
7821      */
7822     if(cps->debug && *message == '#') return;
7823
7824     /*
7825      * Look for book output
7826      */
7827     if (cps == &first && bookRequested) {
7828         if (message[0] == '\t' || message[0] == ' ') {
7829             /* Part of the book output is here; append it */
7830             strcat(bookOutput, message);
7831             strcat(bookOutput, "  \n");
7832             return;
7833         } else if (bookOutput[0] != NULLCHAR) {
7834             /* All of book output has arrived; display it */
7835             char *p = bookOutput;
7836             while (*p != NULLCHAR) {
7837                 if (*p == '\t') *p = ' ';
7838                 p++;
7839             }
7840             DisplayInformation(bookOutput);
7841             bookRequested = FALSE;
7842             /* Fall through to parse the current output */
7843         }
7844     }
7845
7846     /*
7847      * Look for machine move.
7848      */
7849     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7850         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7851     {
7852         /* This method is only useful on engines that support ping */
7853         if (cps->lastPing != cps->lastPong) {
7854           if (gameMode == BeginningOfGame) {
7855             /* Extra move from before last new; ignore */
7856             if (appData.debugMode) {
7857                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7858             }
7859           } else {
7860             if (appData.debugMode) {
7861                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7862                         cps->which, gameMode);
7863             }
7864
7865             SendToProgram("undo\n", cps);
7866           }
7867           return;
7868         }
7869
7870         switch (gameMode) {
7871           case BeginningOfGame:
7872             /* Extra move from before last reset; ignore */
7873             if (appData.debugMode) {
7874                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7875             }
7876             return;
7877
7878           case EndOfGame:
7879           case IcsIdle:
7880           default:
7881             /* Extra move after we tried to stop.  The mode test is
7882                not a reliable way of detecting this problem, but it's
7883                the best we can do on engines that don't support ping.
7884             */
7885             if (appData.debugMode) {
7886                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7887                         cps->which, gameMode);
7888             }
7889             SendToProgram("undo\n", cps);
7890             return;
7891
7892           case MachinePlaysWhite:
7893           case IcsPlayingWhite:
7894             machineWhite = TRUE;
7895             break;
7896
7897           case MachinePlaysBlack:
7898           case IcsPlayingBlack:
7899             machineWhite = FALSE;
7900             break;
7901
7902           case TwoMachinesPlay:
7903             machineWhite = (cps->twoMachinesColor[0] == 'w');
7904             break;
7905         }
7906         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7907             if (appData.debugMode) {
7908                 fprintf(debugFP,
7909                         "Ignoring move out of turn by %s, gameMode %d"
7910                         ", forwardMost %d\n",
7911                         cps->which, gameMode, forwardMostMove);
7912             }
7913             return;
7914         }
7915
7916         if(cps->alphaRank) AlphaRank(machineMove, 4);
7917         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7918                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7919             /* Machine move could not be parsed; ignore it. */
7920           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7921                     machineMove, _(cps->which));
7922             DisplayError(buf1, 0);
7923             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7924                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7925             if (gameMode == TwoMachinesPlay) {
7926               GameEnds(machineWhite ? BlackWins : WhiteWins,
7927                        buf1, GE_XBOARD);
7928             }
7929             return;
7930         }
7931
7932         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7933         /* So we have to redo legality test with true e.p. status here,  */
7934         /* to make sure an illegal e.p. capture does not slip through,   */
7935         /* to cause a forfeit on a justified illegal-move complaint      */
7936         /* of the opponent.                                              */
7937         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7938            ChessMove moveType;
7939            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7940                              fromY, fromX, toY, toX, promoChar);
7941             if(moveType == IllegalMove) {
7942               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7943                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7944                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7945                            buf1, GE_XBOARD);
7946                 return;
7947            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7948            /* [HGM] Kludge to handle engines that send FRC-style castling
7949               when they shouldn't (like TSCP-Gothic) */
7950            switch(moveType) {
7951              case WhiteASideCastleFR:
7952              case BlackASideCastleFR:
7953                toX+=2;
7954                currentMoveString[2]++;
7955                break;
7956              case WhiteHSideCastleFR:
7957              case BlackHSideCastleFR:
7958                toX--;
7959                currentMoveString[2]--;
7960                break;
7961              default: ; // nothing to do, but suppresses warning of pedantic compilers
7962            }
7963         }
7964         hintRequested = FALSE;
7965         lastHint[0] = NULLCHAR;
7966         bookRequested = FALSE;
7967         /* Program may be pondering now */
7968         cps->maybeThinking = TRUE;
7969         if (cps->sendTime == 2) cps->sendTime = 1;
7970         if (cps->offeredDraw) cps->offeredDraw--;
7971
7972         /* [AS] Save move info*/
7973         pvInfoList[ forwardMostMove ].score = programStats.score;
7974         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7975         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7976
7977         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7978
7979         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7980         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7981             int count = 0;
7982
7983             while( count < adjudicateLossPlies ) {
7984                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7985
7986                 if( count & 1 ) {
7987                     score = -score; /* Flip score for winning side */
7988                 }
7989
7990                 if( score > adjudicateLossThreshold ) {
7991                     break;
7992                 }
7993
7994                 count++;
7995             }
7996
7997             if( count >= adjudicateLossPlies ) {
7998                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7999
8000                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8001                     "Xboard adjudication",
8002                     GE_XBOARD );
8003
8004                 return;
8005             }
8006         }
8007
8008         if(Adjudicate(cps)) {
8009             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8010             return; // [HGM] adjudicate: for all automatic game ends
8011         }
8012
8013 #if ZIPPY
8014         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8015             first.initDone) {
8016           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8017                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8018                 SendToICS("draw ");
8019                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8020           }
8021           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8022           ics_user_moved = 1;
8023           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8024                 char buf[3*MSG_SIZ];
8025
8026                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8027                         programStats.score / 100.,
8028                         programStats.depth,
8029                         programStats.time / 100.,
8030                         (unsigned int)programStats.nodes,
8031                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8032                         programStats.movelist);
8033                 SendToICS(buf);
8034 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8035           }
8036         }
8037 #endif
8038
8039         /* [AS] Clear stats for next move */
8040         ClearProgramStats();
8041         thinkOutput[0] = NULLCHAR;
8042         hiddenThinkOutputState = 0;
8043
8044         bookHit = NULL;
8045         if (gameMode == TwoMachinesPlay) {
8046             /* [HGM] relaying draw offers moved to after reception of move */
8047             /* and interpreting offer as claim if it brings draw condition */
8048             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8049                 SendToProgram("draw\n", cps->other);
8050             }
8051             if (cps->other->sendTime) {
8052                 SendTimeRemaining(cps->other,
8053                                   cps->other->twoMachinesColor[0] == 'w');
8054             }
8055             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8056             if (firstMove && !bookHit) {
8057                 firstMove = FALSE;
8058                 if (cps->other->useColors) {
8059                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8060                 }
8061                 SendToProgram("go\n", cps->other);
8062             }
8063             cps->other->maybeThinking = TRUE;
8064         }
8065
8066         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8067
8068         if (!pausing && appData.ringBellAfterMoves) {
8069             RingBell();
8070         }
8071
8072         /*
8073          * Reenable menu items that were disabled while
8074          * machine was thinking
8075          */
8076         if (gameMode != TwoMachinesPlay)
8077             SetUserThinkingEnables();
8078
8079         // [HGM] book: after book hit opponent has received move and is now in force mode
8080         // force the book reply into it, and then fake that it outputted this move by jumping
8081         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8082         if(bookHit) {
8083                 static char bookMove[MSG_SIZ]; // a bit generous?
8084
8085                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8086                 strcat(bookMove, bookHit);
8087                 message = bookMove;
8088                 cps = cps->other;
8089                 programStats.nodes = programStats.depth = programStats.time =
8090                 programStats.score = programStats.got_only_move = 0;
8091                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8092
8093                 if(cps->lastPing != cps->lastPong) {
8094                     savedMessage = message; // args for deferred call
8095                     savedState = cps;
8096                     ScheduleDelayedEvent(DeferredBookMove, 10);
8097                     return;
8098                 }
8099                 goto FakeBookMove;
8100         }
8101
8102         return;
8103     }
8104
8105     /* Set special modes for chess engines.  Later something general
8106      *  could be added here; for now there is just one kludge feature,
8107      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8108      *  when "xboard" is given as an interactive command.
8109      */
8110     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8111         cps->useSigint = FALSE;
8112         cps->useSigterm = FALSE;
8113     }
8114     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8115       ParseFeatures(message+8, cps);
8116       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8117     }
8118
8119     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8120                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8121       int dummy, s=6; char buf[MSG_SIZ];
8122       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8123       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8124       if(startedFromSetupPosition) return;
8125       ParseFEN(boards[0], &dummy, message+s);
8126       DrawPosition(TRUE, boards[0]);
8127       startedFromSetupPosition = TRUE;
8128       return;
8129     }
8130     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8131      * want this, I was asked to put it in, and obliged.
8132      */
8133     if (!strncmp(message, "setboard ", 9)) {
8134         Board initial_position;
8135
8136         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8137
8138         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8139             DisplayError(_("Bad FEN received from engine"), 0);
8140             return ;
8141         } else {
8142            Reset(TRUE, FALSE);
8143            CopyBoard(boards[0], initial_position);
8144            initialRulePlies = FENrulePlies;
8145            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8146            else gameMode = MachinePlaysBlack;
8147            DrawPosition(FALSE, boards[currentMove]);
8148         }
8149         return;
8150     }
8151
8152     /*
8153      * Look for communication commands
8154      */
8155     if (!strncmp(message, "telluser ", 9)) {
8156         if(message[9] == '\\' && message[10] == '\\')
8157             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8158         PlayTellSound();
8159         DisplayNote(message + 9);
8160         return;
8161     }
8162     if (!strncmp(message, "tellusererror ", 14)) {
8163         cps->userError = 1;
8164         if(message[14] == '\\' && message[15] == '\\')
8165             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8166         PlayTellSound();
8167         DisplayError(message + 14, 0);
8168         return;
8169     }
8170     if (!strncmp(message, "tellopponent ", 13)) {
8171       if (appData.icsActive) {
8172         if (loggedOn) {
8173           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8174           SendToICS(buf1);
8175         }
8176       } else {
8177         DisplayNote(message + 13);
8178       }
8179       return;
8180     }
8181     if (!strncmp(message, "tellothers ", 11)) {
8182       if (appData.icsActive) {
8183         if (loggedOn) {
8184           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8185           SendToICS(buf1);
8186         }
8187       }
8188       return;
8189     }
8190     if (!strncmp(message, "tellall ", 8)) {
8191       if (appData.icsActive) {
8192         if (loggedOn) {
8193           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8194           SendToICS(buf1);
8195         }
8196       } else {
8197         DisplayNote(message + 8);
8198       }
8199       return;
8200     }
8201     if (strncmp(message, "warning", 7) == 0) {
8202         /* Undocumented feature, use tellusererror in new code */
8203         DisplayError(message, 0);
8204         return;
8205     }
8206     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8207         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8208         strcat(realname, " query");
8209         AskQuestion(realname, buf2, buf1, cps->pr);
8210         return;
8211     }
8212     /* Commands from the engine directly to ICS.  We don't allow these to be
8213      *  sent until we are logged on. Crafty kibitzes have been known to
8214      *  interfere with the login process.
8215      */
8216     if (loggedOn) {
8217         if (!strncmp(message, "tellics ", 8)) {
8218             SendToICS(message + 8);
8219             SendToICS("\n");
8220             return;
8221         }
8222         if (!strncmp(message, "tellicsnoalias ", 15)) {
8223             SendToICS(ics_prefix);
8224             SendToICS(message + 15);
8225             SendToICS("\n");
8226             return;
8227         }
8228         /* The following are for backward compatibility only */
8229         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8230             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8231             SendToICS(ics_prefix);
8232             SendToICS(message);
8233             SendToICS("\n");
8234             return;
8235         }
8236     }
8237     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8238         return;
8239     }
8240     /*
8241      * If the move is illegal, cancel it and redraw the board.
8242      * Also deal with other error cases.  Matching is rather loose
8243      * here to accommodate engines written before the spec.
8244      */
8245     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8246         strncmp(message, "Error", 5) == 0) {
8247         if (StrStr(message, "name") ||
8248             StrStr(message, "rating") || StrStr(message, "?") ||
8249             StrStr(message, "result") || StrStr(message, "board") ||
8250             StrStr(message, "bk") || StrStr(message, "computer") ||
8251             StrStr(message, "variant") || StrStr(message, "hint") ||
8252             StrStr(message, "random") || StrStr(message, "depth") ||
8253             StrStr(message, "accepted")) {
8254             return;
8255         }
8256         if (StrStr(message, "protover")) {
8257           /* Program is responding to input, so it's apparently done
8258              initializing, and this error message indicates it is
8259              protocol version 1.  So we don't need to wait any longer
8260              for it to initialize and send feature commands. */
8261           FeatureDone(cps, 1);
8262           cps->protocolVersion = 1;
8263           return;
8264         }
8265         cps->maybeThinking = FALSE;
8266
8267         if (StrStr(message, "draw")) {
8268             /* Program doesn't have "draw" command */
8269             cps->sendDrawOffers = 0;
8270             return;
8271         }
8272         if (cps->sendTime != 1 &&
8273             (StrStr(message, "time") || StrStr(message, "otim"))) {
8274           /* Program apparently doesn't have "time" or "otim" command */
8275           cps->sendTime = 0;
8276           return;
8277         }
8278         if (StrStr(message, "analyze")) {
8279             cps->analysisSupport = FALSE;
8280             cps->analyzing = FALSE;
8281 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8282             EditGameEvent(); // [HGM] try to preserve loaded game
8283             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8284             DisplayError(buf2, 0);
8285             return;
8286         }
8287         if (StrStr(message, "(no matching move)st")) {
8288           /* Special kludge for GNU Chess 4 only */
8289           cps->stKludge = TRUE;
8290           SendTimeControl(cps, movesPerSession, timeControl,
8291                           timeIncrement, appData.searchDepth,
8292                           searchTime);
8293           return;
8294         }
8295         if (StrStr(message, "(no matching move)sd")) {
8296           /* Special kludge for GNU Chess 4 only */
8297           cps->sdKludge = TRUE;
8298           SendTimeControl(cps, movesPerSession, timeControl,
8299                           timeIncrement, appData.searchDepth,
8300                           searchTime);
8301           return;
8302         }
8303         if (!StrStr(message, "llegal")) {
8304             return;
8305         }
8306         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8307             gameMode == IcsIdle) return;
8308         if (forwardMostMove <= backwardMostMove) return;
8309         if (pausing) PauseEvent();
8310       if(appData.forceIllegal) {
8311             // [HGM] illegal: machine refused move; force position after move into it
8312           SendToProgram("force\n", cps);
8313           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8314                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8315                 // when black is to move, while there might be nothing on a2 or black
8316                 // might already have the move. So send the board as if white has the move.
8317                 // But first we must change the stm of the engine, as it refused the last move
8318                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8319                 if(WhiteOnMove(forwardMostMove)) {
8320                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8321                     SendBoard(cps, forwardMostMove); // kludgeless board
8322                 } else {
8323                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8324                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8325                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8326                 }
8327           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8328             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8329                  gameMode == TwoMachinesPlay)
8330               SendToProgram("go\n", cps);
8331             return;
8332       } else
8333         if (gameMode == PlayFromGameFile) {
8334             /* Stop reading this game file */
8335             gameMode = EditGame;
8336             ModeHighlight();
8337         }
8338         /* [HGM] illegal-move claim should forfeit game when Xboard */
8339         /* only passes fully legal moves                            */
8340         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8341             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8342                                 "False illegal-move claim", GE_XBOARD );
8343             return; // do not take back move we tested as valid
8344         }
8345         currentMove = forwardMostMove-1;
8346         DisplayMove(currentMove-1); /* before DisplayMoveError */
8347         SwitchClocks(forwardMostMove-1); // [HGM] race
8348         DisplayBothClocks();
8349         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8350                 parseList[currentMove], _(cps->which));
8351         DisplayMoveError(buf1);
8352         DrawPosition(FALSE, boards[currentMove]);
8353
8354         SetUserThinkingEnables();
8355         return;
8356     }
8357     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8358         /* Program has a broken "time" command that
8359            outputs a string not ending in newline.
8360            Don't use it. */
8361         cps->sendTime = 0;
8362     }
8363
8364     /*
8365      * If chess program startup fails, exit with an error message.
8366      * Attempts to recover here are futile. [HGM] Well, we try anyway
8367      */
8368     if ((StrStr(message, "unknown host") != NULL)
8369         || (StrStr(message, "No remote directory") != NULL)
8370         || (StrStr(message, "not found") != NULL)
8371         || (StrStr(message, "No such file") != NULL)
8372         || (StrStr(message, "can't alloc") != NULL)
8373         || (StrStr(message, "Permission denied") != NULL)) {
8374
8375         cps->maybeThinking = FALSE;
8376         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8377                 _(cps->which), cps->program, cps->host, message);
8378         RemoveInputSource(cps->isr);
8379         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8380             if(cps == &first) {
8381                 appData.noChessProgram = TRUE;
8382                 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8383                 gameMode = BeginningOfGame; ModeHighlight();
8384                 SetNCPMode();
8385             }
8386             if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8387             DisplayMessage("", ""); // erase waiting message
8388             DisplayError(buf1, 0);
8389         }
8390         return;
8391     }
8392
8393     /*
8394      * Look for hint output
8395      */
8396     if (sscanf(message, "Hint: %s", buf1) == 1) {
8397         if (cps == &first && hintRequested) {
8398             hintRequested = FALSE;
8399             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8400                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8401                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8402                                     PosFlags(forwardMostMove),
8403                                     fromY, fromX, toY, toX, promoChar, buf1);
8404                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8405                 DisplayInformation(buf2);
8406             } else {
8407                 /* Hint move could not be parsed!? */
8408               snprintf(buf2, sizeof(buf2),
8409                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8410                         buf1, _(cps->which));
8411                 DisplayError(buf2, 0);
8412             }
8413         } else {
8414           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8415         }
8416         return;
8417     }
8418
8419     /*
8420      * Ignore other messages if game is not in progress
8421      */
8422     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8423         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8424
8425     /*
8426      * look for win, lose, draw, or draw offer
8427      */
8428     if (strncmp(message, "1-0", 3) == 0) {
8429         char *p, *q, *r = "";
8430         p = strchr(message, '{');
8431         if (p) {
8432             q = strchr(p, '}');
8433             if (q) {
8434                 *q = NULLCHAR;
8435                 r = p + 1;
8436             }
8437         }
8438         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8439         return;
8440     } else if (strncmp(message, "0-1", 3) == 0) {
8441         char *p, *q, *r = "";
8442         p = strchr(message, '{');
8443         if (p) {
8444             q = strchr(p, '}');
8445             if (q) {
8446                 *q = NULLCHAR;
8447                 r = p + 1;
8448             }
8449         }
8450         /* Kludge for Arasan 4.1 bug */
8451         if (strcmp(r, "Black resigns") == 0) {
8452             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8453             return;
8454         }
8455         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8456         return;
8457     } else if (strncmp(message, "1/2", 3) == 0) {
8458         char *p, *q, *r = "";
8459         p = strchr(message, '{');
8460         if (p) {
8461             q = strchr(p, '}');
8462             if (q) {
8463                 *q = NULLCHAR;
8464                 r = p + 1;
8465             }
8466         }
8467
8468         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8469         return;
8470
8471     } else if (strncmp(message, "White resign", 12) == 0) {
8472         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8473         return;
8474     } else if (strncmp(message, "Black resign", 12) == 0) {
8475         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8476         return;
8477     } else if (strncmp(message, "White matches", 13) == 0 ||
8478                strncmp(message, "Black matches", 13) == 0   ) {
8479         /* [HGM] ignore GNUShogi noises */
8480         return;
8481     } else if (strncmp(message, "White", 5) == 0 &&
8482                message[5] != '(' &&
8483                StrStr(message, "Black") == NULL) {
8484         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8485         return;
8486     } else if (strncmp(message, "Black", 5) == 0 &&
8487                message[5] != '(') {
8488         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8489         return;
8490     } else if (strcmp(message, "resign") == 0 ||
8491                strcmp(message, "computer resigns") == 0) {
8492         switch (gameMode) {
8493           case MachinePlaysBlack:
8494           case IcsPlayingBlack:
8495             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8496             break;
8497           case MachinePlaysWhite:
8498           case IcsPlayingWhite:
8499             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8500             break;
8501           case TwoMachinesPlay:
8502             if (cps->twoMachinesColor[0] == 'w')
8503               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8504             else
8505               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8506             break;
8507           default:
8508             /* can't happen */
8509             break;
8510         }
8511         return;
8512     } else if (strncmp(message, "opponent mates", 14) == 0) {
8513         switch (gameMode) {
8514           case MachinePlaysBlack:
8515           case IcsPlayingBlack:
8516             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8517             break;
8518           case MachinePlaysWhite:
8519           case IcsPlayingWhite:
8520             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8521             break;
8522           case TwoMachinesPlay:
8523             if (cps->twoMachinesColor[0] == 'w')
8524               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8525             else
8526               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8527             break;
8528           default:
8529             /* can't happen */
8530             break;
8531         }
8532         return;
8533     } else if (strncmp(message, "computer mates", 14) == 0) {
8534         switch (gameMode) {
8535           case MachinePlaysBlack:
8536           case IcsPlayingBlack:
8537             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8538             break;
8539           case MachinePlaysWhite:
8540           case IcsPlayingWhite:
8541             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8542             break;
8543           case TwoMachinesPlay:
8544             if (cps->twoMachinesColor[0] == 'w')
8545               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8546             else
8547               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8548             break;
8549           default:
8550             /* can't happen */
8551             break;
8552         }
8553         return;
8554     } else if (strncmp(message, "checkmate", 9) == 0) {
8555         if (WhiteOnMove(forwardMostMove)) {
8556             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8557         } else {
8558             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8559         }
8560         return;
8561     } else if (strstr(message, "Draw") != NULL ||
8562                strstr(message, "game is a draw") != NULL) {
8563         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8564         return;
8565     } else if (strstr(message, "offer") != NULL &&
8566                strstr(message, "draw") != NULL) {
8567 #if ZIPPY
8568         if (appData.zippyPlay && first.initDone) {
8569             /* Relay offer to ICS */
8570             SendToICS(ics_prefix);
8571             SendToICS("draw\n");
8572         }
8573 #endif
8574         cps->offeredDraw = 2; /* valid until this engine moves twice */
8575         if (gameMode == TwoMachinesPlay) {
8576             if (cps->other->offeredDraw) {
8577                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8578             /* [HGM] in two-machine mode we delay relaying draw offer      */
8579             /* until after we also have move, to see if it is really claim */
8580             }
8581         } else if (gameMode == MachinePlaysWhite ||
8582                    gameMode == MachinePlaysBlack) {
8583           if (userOfferedDraw) {
8584             DisplayInformation(_("Machine accepts your draw offer"));
8585             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8586           } else {
8587             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8588           }
8589         }
8590     }
8591
8592
8593     /*
8594      * Look for thinking output
8595      */
8596     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8597           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8598                                 ) {
8599         int plylev, mvleft, mvtot, curscore, time;
8600         char mvname[MOVE_LEN];
8601         u64 nodes; // [DM]
8602         char plyext;
8603         int ignore = FALSE;
8604         int prefixHint = FALSE;
8605         mvname[0] = NULLCHAR;
8606
8607         switch (gameMode) {
8608           case MachinePlaysBlack:
8609           case IcsPlayingBlack:
8610             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8611             break;
8612           case MachinePlaysWhite:
8613           case IcsPlayingWhite:
8614             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8615             break;
8616           case AnalyzeMode:
8617           case AnalyzeFile:
8618             break;
8619           case IcsObserving: /* [DM] icsEngineAnalyze */
8620             if (!appData.icsEngineAnalyze) ignore = TRUE;
8621             break;
8622           case TwoMachinesPlay:
8623             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8624                 ignore = TRUE;
8625             }
8626             break;
8627           default:
8628             ignore = TRUE;
8629             break;
8630         }
8631
8632         if (!ignore) {
8633             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8634             buf1[0] = NULLCHAR;
8635             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8636                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8637
8638                 if (plyext != ' ' && plyext != '\t') {
8639                     time *= 100;
8640                 }
8641
8642                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8643                 if( cps->scoreIsAbsolute &&
8644                     ( gameMode == MachinePlaysBlack ||
8645                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8646                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8647                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8648                      !WhiteOnMove(currentMove)
8649                     ) )
8650                 {
8651                     curscore = -curscore;
8652                 }
8653
8654                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8655
8656                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8657                         char buf[MSG_SIZ];
8658                         FILE *f;
8659                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8660                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8661                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8662                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8663                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8664                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8665                                 fclose(f);
8666                         } else DisplayError(_("failed writing PV"), 0);
8667                 }
8668
8669                 tempStats.depth = plylev;
8670                 tempStats.nodes = nodes;
8671                 tempStats.time = time;
8672                 tempStats.score = curscore;
8673                 tempStats.got_only_move = 0;
8674
8675                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8676                         int ticklen;
8677
8678                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8679                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8680                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8681                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8682                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8683                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8684                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8685                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8686                 }
8687
8688                 /* Buffer overflow protection */
8689                 if (pv[0] != NULLCHAR) {
8690                     if (strlen(pv) >= sizeof(tempStats.movelist)
8691                         && appData.debugMode) {
8692                         fprintf(debugFP,
8693                                 "PV is too long; using the first %u bytes.\n",
8694                                 (unsigned) sizeof(tempStats.movelist) - 1);
8695                     }
8696
8697                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8698                 } else {
8699                     sprintf(tempStats.movelist, " no PV\n");
8700                 }
8701
8702                 if (tempStats.seen_stat) {
8703                     tempStats.ok_to_send = 1;
8704                 }
8705
8706                 if (strchr(tempStats.movelist, '(') != NULL) {
8707                     tempStats.line_is_book = 1;
8708                     tempStats.nr_moves = 0;
8709                     tempStats.moves_left = 0;
8710                 } else {
8711                     tempStats.line_is_book = 0;
8712                 }
8713
8714                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8715                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8716
8717                 SendProgramStatsToFrontend( cps, &tempStats );
8718
8719                 /*
8720                     [AS] Protect the thinkOutput buffer from overflow... this
8721                     is only useful if buf1 hasn't overflowed first!
8722                 */
8723                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8724                          plylev,
8725                          (gameMode == TwoMachinesPlay ?
8726                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8727                          ((double) curscore) / 100.0,
8728                          prefixHint ? lastHint : "",
8729                          prefixHint ? " " : "" );
8730
8731                 if( buf1[0] != NULLCHAR ) {
8732                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8733
8734                     if( strlen(pv) > max_len ) {
8735                         if( appData.debugMode) {
8736                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8737                         }
8738                         pv[max_len+1] = '\0';
8739                     }
8740
8741                     strcat( thinkOutput, pv);
8742                 }
8743
8744                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8745                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8746                     DisplayMove(currentMove - 1);
8747                 }
8748                 return;
8749
8750             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8751                 /* crafty (9.25+) says "(only move) <move>"
8752                  * if there is only 1 legal move
8753                  */
8754                 sscanf(p, "(only move) %s", buf1);
8755                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8756                 sprintf(programStats.movelist, "%s (only move)", buf1);
8757                 programStats.depth = 1;
8758                 programStats.nr_moves = 1;
8759                 programStats.moves_left = 1;
8760                 programStats.nodes = 1;
8761                 programStats.time = 1;
8762                 programStats.got_only_move = 1;
8763
8764                 /* Not really, but we also use this member to
8765                    mean "line isn't going to change" (Crafty
8766                    isn't searching, so stats won't change) */
8767                 programStats.line_is_book = 1;
8768
8769                 SendProgramStatsToFrontend( cps, &programStats );
8770
8771                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8772                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8773                     DisplayMove(currentMove - 1);
8774                 }
8775                 return;
8776             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8777                               &time, &nodes, &plylev, &mvleft,
8778                               &mvtot, mvname) >= 5) {
8779                 /* The stat01: line is from Crafty (9.29+) in response
8780                    to the "." command */
8781                 programStats.seen_stat = 1;
8782                 cps->maybeThinking = TRUE;
8783
8784                 if (programStats.got_only_move || !appData.periodicUpdates)
8785                   return;
8786
8787                 programStats.depth = plylev;
8788                 programStats.time = time;
8789                 programStats.nodes = nodes;
8790                 programStats.moves_left = mvleft;
8791                 programStats.nr_moves = mvtot;
8792                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8793                 programStats.ok_to_send = 1;
8794                 programStats.movelist[0] = '\0';
8795
8796                 SendProgramStatsToFrontend( cps, &programStats );
8797
8798                 return;
8799
8800             } else if (strncmp(message,"++",2) == 0) {
8801                 /* Crafty 9.29+ outputs this */
8802                 programStats.got_fail = 2;
8803                 return;
8804
8805             } else if (strncmp(message,"--",2) == 0) {
8806                 /* Crafty 9.29+ outputs this */
8807                 programStats.got_fail = 1;
8808                 return;
8809
8810             } else if (thinkOutput[0] != NULLCHAR &&
8811                        strncmp(message, "    ", 4) == 0) {
8812                 unsigned message_len;
8813
8814                 p = message;
8815                 while (*p && *p == ' ') p++;
8816
8817                 message_len = strlen( p );
8818
8819                 /* [AS] Avoid buffer overflow */
8820                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8821                     strcat(thinkOutput, " ");
8822                     strcat(thinkOutput, p);
8823                 }
8824
8825                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8826                     strcat(programStats.movelist, " ");
8827                     strcat(programStats.movelist, p);
8828                 }
8829
8830                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8831                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8832                     DisplayMove(currentMove - 1);
8833                 }
8834                 return;
8835             }
8836         }
8837         else {
8838             buf1[0] = NULLCHAR;
8839
8840             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8841                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8842             {
8843                 ChessProgramStats cpstats;
8844
8845                 if (plyext != ' ' && plyext != '\t') {
8846                     time *= 100;
8847                 }
8848
8849                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8850                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8851                     curscore = -curscore;
8852                 }
8853
8854                 cpstats.depth = plylev;
8855                 cpstats.nodes = nodes;
8856                 cpstats.time = time;
8857                 cpstats.score = curscore;
8858                 cpstats.got_only_move = 0;
8859                 cpstats.movelist[0] = '\0';
8860
8861                 if (buf1[0] != NULLCHAR) {
8862                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8863                 }
8864
8865                 cpstats.ok_to_send = 0;
8866                 cpstats.line_is_book = 0;
8867                 cpstats.nr_moves = 0;
8868                 cpstats.moves_left = 0;
8869
8870                 SendProgramStatsToFrontend( cps, &cpstats );
8871             }
8872         }
8873     }
8874 }
8875
8876
8877 /* Parse a game score from the character string "game", and
8878    record it as the history of the current game.  The game
8879    score is NOT assumed to start from the standard position.
8880    The display is not updated in any way.
8881    */
8882 void
8883 ParseGameHistory (char *game)
8884 {
8885     ChessMove moveType;
8886     int fromX, fromY, toX, toY, boardIndex;
8887     char promoChar;
8888     char *p, *q;
8889     char buf[MSG_SIZ];
8890
8891     if (appData.debugMode)
8892       fprintf(debugFP, "Parsing game history: %s\n", game);
8893
8894     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8895     gameInfo.site = StrSave(appData.icsHost);
8896     gameInfo.date = PGNDate();
8897     gameInfo.round = StrSave("-");
8898
8899     /* Parse out names of players */
8900     while (*game == ' ') game++;
8901     p = buf;
8902     while (*game != ' ') *p++ = *game++;
8903     *p = NULLCHAR;
8904     gameInfo.white = StrSave(buf);
8905     while (*game == ' ') game++;
8906     p = buf;
8907     while (*game != ' ' && *game != '\n') *p++ = *game++;
8908     *p = NULLCHAR;
8909     gameInfo.black = StrSave(buf);
8910
8911     /* Parse moves */
8912     boardIndex = blackPlaysFirst ? 1 : 0;
8913     yynewstr(game);
8914     for (;;) {
8915         yyboardindex = boardIndex;
8916         moveType = (ChessMove) Myylex();
8917         switch (moveType) {
8918           case IllegalMove:             /* maybe suicide chess, etc. */
8919   if (appData.debugMode) {
8920     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8921     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8922     setbuf(debugFP, NULL);
8923   }
8924           case WhitePromotion:
8925           case BlackPromotion:
8926           case WhiteNonPromotion:
8927           case BlackNonPromotion:
8928           case NormalMove:
8929           case WhiteCapturesEnPassant:
8930           case BlackCapturesEnPassant:
8931           case WhiteKingSideCastle:
8932           case WhiteQueenSideCastle:
8933           case BlackKingSideCastle:
8934           case BlackQueenSideCastle:
8935           case WhiteKingSideCastleWild:
8936           case WhiteQueenSideCastleWild:
8937           case BlackKingSideCastleWild:
8938           case BlackQueenSideCastleWild:
8939           /* PUSH Fabien */
8940           case WhiteHSideCastleFR:
8941           case WhiteASideCastleFR:
8942           case BlackHSideCastleFR:
8943           case BlackASideCastleFR:
8944           /* POP Fabien */
8945             fromX = currentMoveString[0] - AAA;
8946             fromY = currentMoveString[1] - ONE;
8947             toX = currentMoveString[2] - AAA;
8948             toY = currentMoveString[3] - ONE;
8949             promoChar = currentMoveString[4];
8950             break;
8951           case WhiteDrop:
8952           case BlackDrop:
8953             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8954             fromX = moveType == WhiteDrop ?
8955               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8956             (int) CharToPiece(ToLower(currentMoveString[0]));
8957             fromY = DROP_RANK;
8958             toX = currentMoveString[2] - AAA;
8959             toY = currentMoveString[3] - ONE;
8960             promoChar = NULLCHAR;
8961             break;
8962           case AmbiguousMove:
8963             /* bug? */
8964             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8965   if (appData.debugMode) {
8966     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8967     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8968     setbuf(debugFP, NULL);
8969   }
8970             DisplayError(buf, 0);
8971             return;
8972           case ImpossibleMove:
8973             /* bug? */
8974             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8975   if (appData.debugMode) {
8976     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8977     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8978     setbuf(debugFP, NULL);
8979   }
8980             DisplayError(buf, 0);
8981             return;
8982           case EndOfFile:
8983             if (boardIndex < backwardMostMove) {
8984                 /* Oops, gap.  How did that happen? */
8985                 DisplayError(_("Gap in move list"), 0);
8986                 return;
8987             }
8988             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8989             if (boardIndex > forwardMostMove) {
8990                 forwardMostMove = boardIndex;
8991             }
8992             return;
8993           case ElapsedTime:
8994             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8995                 strcat(parseList[boardIndex-1], " ");
8996                 strcat(parseList[boardIndex-1], yy_text);
8997             }
8998             continue;
8999           case Comment:
9000           case PGNTag:
9001           case NAG:
9002           default:
9003             /* ignore */
9004             continue;
9005           case WhiteWins:
9006           case BlackWins:
9007           case GameIsDrawn:
9008           case GameUnfinished:
9009             if (gameMode == IcsExamining) {
9010                 if (boardIndex < backwardMostMove) {
9011                     /* Oops, gap.  How did that happen? */
9012                     return;
9013                 }
9014                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9015                 return;
9016             }
9017             gameInfo.result = moveType;
9018             p = strchr(yy_text, '{');
9019             if (p == NULL) p = strchr(yy_text, '(');
9020             if (p == NULL) {
9021                 p = yy_text;
9022                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9023             } else {
9024                 q = strchr(p, *p == '{' ? '}' : ')');
9025                 if (q != NULL) *q = NULLCHAR;
9026                 p++;
9027             }
9028             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9029             gameInfo.resultDetails = StrSave(p);
9030             continue;
9031         }
9032         if (boardIndex >= forwardMostMove &&
9033             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9034             backwardMostMove = blackPlaysFirst ? 1 : 0;
9035             return;
9036         }
9037         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9038                                  fromY, fromX, toY, toX, promoChar,
9039                                  parseList[boardIndex]);
9040         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9041         /* currentMoveString is set as a side-effect of yylex */
9042         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9043         strcat(moveList[boardIndex], "\n");
9044         boardIndex++;
9045         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9046         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9047           case MT_NONE:
9048           case MT_STALEMATE:
9049           default:
9050             break;
9051           case MT_CHECK:
9052             if(gameInfo.variant != VariantShogi)
9053                 strcat(parseList[boardIndex - 1], "+");
9054             break;
9055           case MT_CHECKMATE:
9056           case MT_STAINMATE:
9057             strcat(parseList[boardIndex - 1], "#");
9058             break;
9059         }
9060     }
9061 }
9062
9063
9064 /* Apply a move to the given board  */
9065 void
9066 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9067 {
9068   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9069   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9070
9071     /* [HGM] compute & store e.p. status and castling rights for new position */
9072     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9073
9074       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9075       oldEP = (signed char)board[EP_STATUS];
9076       board[EP_STATUS] = EP_NONE;
9077
9078   if (fromY == DROP_RANK) {
9079         /* must be first */
9080         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9081             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9082             return;
9083         }
9084         piece = board[toY][toX] = (ChessSquare) fromX;
9085   } else {
9086       int i;
9087
9088       if( board[toY][toX] != EmptySquare )
9089            board[EP_STATUS] = EP_CAPTURE;
9090
9091       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9092            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9093                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9094       } else
9095       if( board[fromY][fromX] == WhitePawn ) {
9096            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9097                board[EP_STATUS] = EP_PAWN_MOVE;
9098            if( toY-fromY==2) {
9099                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9100                         gameInfo.variant != VariantBerolina || toX < fromX)
9101                       board[EP_STATUS] = toX | berolina;
9102                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9103                         gameInfo.variant != VariantBerolina || toX > fromX)
9104                       board[EP_STATUS] = toX;
9105            }
9106       } else
9107       if( board[fromY][fromX] == BlackPawn ) {
9108            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9109                board[EP_STATUS] = EP_PAWN_MOVE;
9110            if( toY-fromY== -2) {
9111                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9112                         gameInfo.variant != VariantBerolina || toX < fromX)
9113                       board[EP_STATUS] = toX | berolina;
9114                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9115                         gameInfo.variant != VariantBerolina || toX > fromX)
9116                       board[EP_STATUS] = toX;
9117            }
9118        }
9119
9120        for(i=0; i<nrCastlingRights; i++) {
9121            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9122               board[CASTLING][i] == toX   && castlingRank[i] == toY
9123              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9124        }
9125
9126      if (fromX == toX && fromY == toY) return;
9127
9128      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9129      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9130      if(gameInfo.variant == VariantKnightmate)
9131          king += (int) WhiteUnicorn - (int) WhiteKing;
9132
9133     /* Code added by Tord: */
9134     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9135     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9136         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9137       board[fromY][fromX] = EmptySquare;
9138       board[toY][toX] = EmptySquare;
9139       if((toX > fromX) != (piece == WhiteRook)) {
9140         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9141       } else {
9142         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9143       }
9144     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9145                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9146       board[fromY][fromX] = EmptySquare;
9147       board[toY][toX] = EmptySquare;
9148       if((toX > fromX) != (piece == BlackRook)) {
9149         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9150       } else {
9151         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9152       }
9153     /* End of code added by Tord */
9154
9155     } else if (board[fromY][fromX] == king
9156         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9157         && toY == fromY && toX > fromX+1) {
9158         board[fromY][fromX] = EmptySquare;
9159         board[toY][toX] = king;
9160         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9161         board[fromY][BOARD_RGHT-1] = EmptySquare;
9162     } else if (board[fromY][fromX] == king
9163         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9164                && toY == fromY && toX < fromX-1) {
9165         board[fromY][fromX] = EmptySquare;
9166         board[toY][toX] = king;
9167         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9168         board[fromY][BOARD_LEFT] = EmptySquare;
9169     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9170                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9171                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9172                ) {
9173         /* white pawn promotion */
9174         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9175         if(gameInfo.variant==VariantBughouse ||
9176            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9177             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9178         board[fromY][fromX] = EmptySquare;
9179     } else if ((fromY >= BOARD_HEIGHT>>1)
9180                && (toX != fromX)
9181                && gameInfo.variant != VariantXiangqi
9182                && gameInfo.variant != VariantBerolina
9183                && (board[fromY][fromX] == WhitePawn)
9184                && (board[toY][toX] == EmptySquare)) {
9185         board[fromY][fromX] = EmptySquare;
9186         board[toY][toX] = WhitePawn;
9187         captured = board[toY - 1][toX];
9188         board[toY - 1][toX] = EmptySquare;
9189     } else if ((fromY == BOARD_HEIGHT-4)
9190                && (toX == fromX)
9191                && gameInfo.variant == VariantBerolina
9192                && (board[fromY][fromX] == WhitePawn)
9193                && (board[toY][toX] == EmptySquare)) {
9194         board[fromY][fromX] = EmptySquare;
9195         board[toY][toX] = WhitePawn;
9196         if(oldEP & EP_BEROLIN_A) {
9197                 captured = board[fromY][fromX-1];
9198                 board[fromY][fromX-1] = EmptySquare;
9199         }else{  captured = board[fromY][fromX+1];
9200                 board[fromY][fromX+1] = EmptySquare;
9201         }
9202     } else if (board[fromY][fromX] == king
9203         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9204                && toY == fromY && toX > fromX+1) {
9205         board[fromY][fromX] = EmptySquare;
9206         board[toY][toX] = king;
9207         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9208         board[fromY][BOARD_RGHT-1] = EmptySquare;
9209     } else if (board[fromY][fromX] == king
9210         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9211                && toY == fromY && toX < fromX-1) {
9212         board[fromY][fromX] = EmptySquare;
9213         board[toY][toX] = king;
9214         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9215         board[fromY][BOARD_LEFT] = EmptySquare;
9216     } else if (fromY == 7 && fromX == 3
9217                && board[fromY][fromX] == BlackKing
9218                && toY == 7 && toX == 5) {
9219         board[fromY][fromX] = EmptySquare;
9220         board[toY][toX] = BlackKing;
9221         board[fromY][7] = EmptySquare;
9222         board[toY][4] = BlackRook;
9223     } else if (fromY == 7 && fromX == 3
9224                && board[fromY][fromX] == BlackKing
9225                && toY == 7 && toX == 1) {
9226         board[fromY][fromX] = EmptySquare;
9227         board[toY][toX] = BlackKing;
9228         board[fromY][0] = EmptySquare;
9229         board[toY][2] = BlackRook;
9230     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9231                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9232                && toY < promoRank && promoChar
9233                ) {
9234         /* black pawn promotion */
9235         board[toY][toX] = CharToPiece(ToLower(promoChar));
9236         if(gameInfo.variant==VariantBughouse ||
9237            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9238             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9239         board[fromY][fromX] = EmptySquare;
9240     } else if ((fromY < BOARD_HEIGHT>>1)
9241                && (toX != fromX)
9242                && gameInfo.variant != VariantXiangqi
9243                && gameInfo.variant != VariantBerolina
9244                && (board[fromY][fromX] == BlackPawn)
9245                && (board[toY][toX] == EmptySquare)) {
9246         board[fromY][fromX] = EmptySquare;
9247         board[toY][toX] = BlackPawn;
9248         captured = board[toY + 1][toX];
9249         board[toY + 1][toX] = EmptySquare;
9250     } else if ((fromY == 3)
9251                && (toX == fromX)
9252                && gameInfo.variant == VariantBerolina
9253                && (board[fromY][fromX] == BlackPawn)
9254                && (board[toY][toX] == EmptySquare)) {
9255         board[fromY][fromX] = EmptySquare;
9256         board[toY][toX] = BlackPawn;
9257         if(oldEP & EP_BEROLIN_A) {
9258                 captured = board[fromY][fromX-1];
9259                 board[fromY][fromX-1] = EmptySquare;
9260         }else{  captured = board[fromY][fromX+1];
9261                 board[fromY][fromX+1] = EmptySquare;
9262         }
9263     } else {
9264         board[toY][toX] = board[fromY][fromX];
9265         board[fromY][fromX] = EmptySquare;
9266     }
9267   }
9268
9269     if (gameInfo.holdingsWidth != 0) {
9270
9271       /* !!A lot more code needs to be written to support holdings  */
9272       /* [HGM] OK, so I have written it. Holdings are stored in the */
9273       /* penultimate board files, so they are automaticlly stored   */
9274       /* in the game history.                                       */
9275       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9276                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9277         /* Delete from holdings, by decreasing count */
9278         /* and erasing image if necessary            */
9279         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9280         if(p < (int) BlackPawn) { /* white drop */
9281              p -= (int)WhitePawn;
9282                  p = PieceToNumber((ChessSquare)p);
9283              if(p >= gameInfo.holdingsSize) p = 0;
9284              if(--board[p][BOARD_WIDTH-2] <= 0)
9285                   board[p][BOARD_WIDTH-1] = EmptySquare;
9286              if((int)board[p][BOARD_WIDTH-2] < 0)
9287                         board[p][BOARD_WIDTH-2] = 0;
9288         } else {                  /* black drop */
9289              p -= (int)BlackPawn;
9290                  p = PieceToNumber((ChessSquare)p);
9291              if(p >= gameInfo.holdingsSize) p = 0;
9292              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9293                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9294              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9295                         board[BOARD_HEIGHT-1-p][1] = 0;
9296         }
9297       }
9298       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9299           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9300         /* [HGM] holdings: Add to holdings, if holdings exist */
9301         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9302                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9303                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9304         }
9305         p = (int) captured;
9306         if (p >= (int) BlackPawn) {
9307           p -= (int)BlackPawn;
9308           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9309                   /* in Shogi restore piece to its original  first */
9310                   captured = (ChessSquare) (DEMOTED captured);
9311                   p = DEMOTED p;
9312           }
9313           p = PieceToNumber((ChessSquare)p);
9314           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9315           board[p][BOARD_WIDTH-2]++;
9316           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9317         } else {
9318           p -= (int)WhitePawn;
9319           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9320                   captured = (ChessSquare) (DEMOTED captured);
9321                   p = DEMOTED p;
9322           }
9323           p = PieceToNumber((ChessSquare)p);
9324           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9325           board[BOARD_HEIGHT-1-p][1]++;
9326           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9327         }
9328       }
9329     } else if (gameInfo.variant == VariantAtomic) {
9330       if (captured != EmptySquare) {
9331         int y, x;
9332         for (y = toY-1; y <= toY+1; y++) {
9333           for (x = toX-1; x <= toX+1; x++) {
9334             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9335                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9336               board[y][x] = EmptySquare;
9337             }
9338           }
9339         }
9340         board[toY][toX] = EmptySquare;
9341       }
9342     }
9343     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9344         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9345     } else
9346     if(promoChar == '+') {
9347         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9348         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9349     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9350         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9351     }
9352     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9353                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9354         // [HGM] superchess: take promotion piece out of holdings
9355         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9356         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9357             if(!--board[k][BOARD_WIDTH-2])
9358                 board[k][BOARD_WIDTH-1] = EmptySquare;
9359         } else {
9360             if(!--board[BOARD_HEIGHT-1-k][1])
9361                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9362         }
9363     }
9364
9365 }
9366
9367 /* Updates forwardMostMove */
9368 void
9369 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9370 {
9371 //    forwardMostMove++; // [HGM] bare: moved downstream
9372
9373     (void) CoordsToAlgebraic(boards[forwardMostMove],
9374                              PosFlags(forwardMostMove),
9375                              fromY, fromX, toY, toX, promoChar,
9376                              parseList[forwardMostMove]);
9377
9378     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9379         int timeLeft; static int lastLoadFlag=0; int king, piece;
9380         piece = boards[forwardMostMove][fromY][fromX];
9381         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9382         if(gameInfo.variant == VariantKnightmate)
9383             king += (int) WhiteUnicorn - (int) WhiteKing;
9384         if(forwardMostMove == 0) {
9385             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9386                 fprintf(serverMoves, "%s;", UserName());
9387             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9388                 fprintf(serverMoves, "%s;", second.tidy);
9389             fprintf(serverMoves, "%s;", first.tidy);
9390             if(gameMode == MachinePlaysWhite)
9391                 fprintf(serverMoves, "%s;", UserName());
9392             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9393                 fprintf(serverMoves, "%s;", second.tidy);
9394         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9395         lastLoadFlag = loadFlag;
9396         // print base move
9397         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9398         // print castling suffix
9399         if( toY == fromY && piece == king ) {
9400             if(toX-fromX > 1)
9401                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9402             if(fromX-toX >1)
9403                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9404         }
9405         // e.p. suffix
9406         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9407              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9408              boards[forwardMostMove][toY][toX] == EmptySquare
9409              && fromX != toX && fromY != toY)
9410                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9411         // promotion suffix
9412         if(promoChar != NULLCHAR)
9413                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9414         if(!loadFlag) {
9415                 char buf[MOVE_LEN*2], *p; int len;
9416             fprintf(serverMoves, "/%d/%d",
9417                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9418             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9419             else                      timeLeft = blackTimeRemaining/1000;
9420             fprintf(serverMoves, "/%d", timeLeft);
9421                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9422                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9423                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9424             fprintf(serverMoves, "/%s", buf);
9425         }
9426         fflush(serverMoves);
9427     }
9428
9429     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9430         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9431       return;
9432     }
9433     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9434     if (commentList[forwardMostMove+1] != NULL) {
9435         free(commentList[forwardMostMove+1]);
9436         commentList[forwardMostMove+1] = NULL;
9437     }
9438     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9439     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9440     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9441     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9442     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9443     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9444     adjustedClock = FALSE;
9445     gameInfo.result = GameUnfinished;
9446     if (gameInfo.resultDetails != NULL) {
9447         free(gameInfo.resultDetails);
9448         gameInfo.resultDetails = NULL;
9449     }
9450     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9451                               moveList[forwardMostMove - 1]);
9452     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9453       case MT_NONE:
9454       case MT_STALEMATE:
9455       default:
9456         break;
9457       case MT_CHECK:
9458         if(gameInfo.variant != VariantShogi)
9459             strcat(parseList[forwardMostMove - 1], "+");
9460         break;
9461       case MT_CHECKMATE:
9462       case MT_STAINMATE:
9463         strcat(parseList[forwardMostMove - 1], "#");
9464         break;
9465     }
9466
9467 }
9468
9469 /* Updates currentMove if not pausing */
9470 void
9471 ShowMove (int fromX, int fromY, int toX, int toY)
9472 {
9473     int instant = (gameMode == PlayFromGameFile) ?
9474         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9475     if(appData.noGUI) return;
9476     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9477         if (!instant) {
9478             if (forwardMostMove == currentMove + 1) {
9479                 AnimateMove(boards[forwardMostMove - 1],
9480                             fromX, fromY, toX, toY);
9481             }
9482             if (appData.highlightLastMove) {
9483                 SetHighlights(fromX, fromY, toX, toY);
9484             }
9485         }
9486         currentMove = forwardMostMove;
9487     }
9488
9489     if (instant) return;
9490
9491     DisplayMove(currentMove - 1);
9492     DrawPosition(FALSE, boards[currentMove]);
9493     DisplayBothClocks();
9494     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9495 }
9496
9497 void
9498 SendEgtPath (ChessProgramState *cps)
9499 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9500         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9501
9502         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9503
9504         while(*p) {
9505             char c, *q = name+1, *r, *s;
9506
9507             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9508             while(*p && *p != ',') *q++ = *p++;
9509             *q++ = ':'; *q = 0;
9510             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9511                 strcmp(name, ",nalimov:") == 0 ) {
9512                 // take nalimov path from the menu-changeable option first, if it is defined
9513               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9514                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9515             } else
9516             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9517                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9518                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9519                 s = r = StrStr(s, ":") + 1; // beginning of path info
9520                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9521                 c = *r; *r = 0;             // temporarily null-terminate path info
9522                     *--q = 0;               // strip of trailig ':' from name
9523                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9524                 *r = c;
9525                 SendToProgram(buf,cps);     // send egtbpath command for this format
9526             }
9527             if(*p == ',') p++; // read away comma to position for next format name
9528         }
9529 }
9530
9531 void
9532 InitChessProgram (ChessProgramState *cps, int setup)
9533 /* setup needed to setup FRC opening position */
9534 {
9535     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9536     if (appData.noChessProgram) return;
9537     hintRequested = FALSE;
9538     bookRequested = FALSE;
9539
9540     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9541     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9542     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9543     if(cps->memSize) { /* [HGM] memory */
9544       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9545         SendToProgram(buf, cps);
9546     }
9547     SendEgtPath(cps); /* [HGM] EGT */
9548     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9549       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9550         SendToProgram(buf, cps);
9551     }
9552
9553     SendToProgram(cps->initString, cps);
9554     if (gameInfo.variant != VariantNormal &&
9555         gameInfo.variant != VariantLoadable
9556         /* [HGM] also send variant if board size non-standard */
9557         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9558                                             ) {
9559       char *v = VariantName(gameInfo.variant);
9560       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9561         /* [HGM] in protocol 1 we have to assume all variants valid */
9562         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9563         DisplayFatalError(buf, 0, 1);
9564         return;
9565       }
9566
9567       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9568       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9569       if( gameInfo.variant == VariantXiangqi )
9570            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9571       if( gameInfo.variant == VariantShogi )
9572            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9573       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9574            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9575       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9576           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9577            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9578       if( gameInfo.variant == VariantCourier )
9579            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9580       if( gameInfo.variant == VariantSuper )
9581            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9582       if( gameInfo.variant == VariantGreat )
9583            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9584       if( gameInfo.variant == VariantSChess )
9585            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9586       if( gameInfo.variant == VariantGrand )
9587            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9588
9589       if(overruled) {
9590         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9591                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9592            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9593            if(StrStr(cps->variants, b) == NULL) {
9594                // specific sized variant not known, check if general sizing allowed
9595                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9596                    if(StrStr(cps->variants, "boardsize") == NULL) {
9597                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9598                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9599                        DisplayFatalError(buf, 0, 1);
9600                        return;
9601                    }
9602                    /* [HGM] here we really should compare with the maximum supported board size */
9603                }
9604            }
9605       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9606       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9607       SendToProgram(buf, cps);
9608     }
9609     currentlyInitializedVariant = gameInfo.variant;
9610
9611     /* [HGM] send opening position in FRC to first engine */
9612     if(setup) {
9613           SendToProgram("force\n", cps);
9614           SendBoard(cps, 0);
9615           /* engine is now in force mode! Set flag to wake it up after first move. */
9616           setboardSpoiledMachineBlack = 1;
9617     }
9618
9619     if (cps->sendICS) {
9620       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9621       SendToProgram(buf, cps);
9622     }
9623     cps->maybeThinking = FALSE;
9624     cps->offeredDraw = 0;
9625     if (!appData.icsActive) {
9626         SendTimeControl(cps, movesPerSession, timeControl,
9627                         timeIncrement, appData.searchDepth,
9628                         searchTime);
9629     }
9630     if (appData.showThinking
9631         // [HGM] thinking: four options require thinking output to be sent
9632         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9633                                 ) {
9634         SendToProgram("post\n", cps);
9635     }
9636     SendToProgram("hard\n", cps);
9637     if (!appData.ponderNextMove) {
9638         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9639            it without being sure what state we are in first.  "hard"
9640            is not a toggle, so that one is OK.
9641          */
9642         SendToProgram("easy\n", cps);
9643     }
9644     if (cps->usePing) {
9645       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9646       SendToProgram(buf, cps);
9647     }
9648     cps->initDone = TRUE;
9649     ClearEngineOutputPane(cps == &second);
9650 }
9651
9652
9653 void
9654 StartChessProgram (ChessProgramState *cps)
9655 {
9656     char buf[MSG_SIZ];
9657     int err;
9658
9659     if (appData.noChessProgram) return;
9660     cps->initDone = FALSE;
9661
9662     if (strcmp(cps->host, "localhost") == 0) {
9663         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9664     } else if (*appData.remoteShell == NULLCHAR) {
9665         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9666     } else {
9667         if (*appData.remoteUser == NULLCHAR) {
9668           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9669                     cps->program);
9670         } else {
9671           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9672                     cps->host, appData.remoteUser, cps->program);
9673         }
9674         err = StartChildProcess(buf, "", &cps->pr);
9675     }
9676
9677     if (err != 0) {
9678       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9679         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9680         if(cps != &first) return;
9681         appData.noChessProgram = TRUE;
9682         ThawUI();
9683         SetNCPMode();
9684 //      DisplayFatalError(buf, err, 1);
9685 //      cps->pr = NoProc;
9686 //      cps->isr = NULL;
9687         return;
9688     }
9689
9690     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9691     if (cps->protocolVersion > 1) {
9692       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9693       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9694       cps->comboCnt = 0;  //                and values of combo boxes
9695       SendToProgram(buf, cps);
9696     } else {
9697       SendToProgram("xboard\n", cps);
9698     }
9699 }
9700
9701 void
9702 TwoMachinesEventIfReady P((void))
9703 {
9704   static int curMess = 0;
9705   if (first.lastPing != first.lastPong) {
9706     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9707     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9708     return;
9709   }
9710   if (second.lastPing != second.lastPong) {
9711     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9712     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9713     return;
9714   }
9715   DisplayMessage("", ""); curMess = 0;
9716   ThawUI();
9717   TwoMachinesEvent();
9718 }
9719
9720 char *
9721 MakeName (char *template)
9722 {
9723     time_t clock;
9724     struct tm *tm;
9725     static char buf[MSG_SIZ];
9726     char *p = buf;
9727     int i;
9728
9729     clock = time((time_t *)NULL);
9730     tm = localtime(&clock);
9731
9732     while(*p++ = *template++) if(p[-1] == '%') {
9733         switch(*template++) {
9734           case 0:   *p = 0; return buf;
9735           case 'Y': i = tm->tm_year+1900; break;
9736           case 'y': i = tm->tm_year-100; break;
9737           case 'M': i = tm->tm_mon+1; break;
9738           case 'd': i = tm->tm_mday; break;
9739           case 'h': i = tm->tm_hour; break;
9740           case 'm': i = tm->tm_min; break;
9741           case 's': i = tm->tm_sec; break;
9742           default:  i = 0;
9743         }
9744         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9745     }
9746     return buf;
9747 }
9748
9749 int
9750 CountPlayers (char *p)
9751 {
9752     int n = 0;
9753     while(p = strchr(p, '\n')) p++, n++; // count participants
9754     return n;
9755 }
9756
9757 FILE *
9758 WriteTourneyFile (char *results, FILE *f)
9759 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9760     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9761     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9762         // create a file with tournament description
9763         fprintf(f, "-participants {%s}\n", appData.participants);
9764         fprintf(f, "-seedBase %d\n", appData.seedBase);
9765         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9766         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9767         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9768         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9769         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9770         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9771         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9772         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9773         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9774         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9775         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9776         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9777         if(searchTime > 0)
9778                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9779         else {
9780                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9781                 fprintf(f, "-tc %s\n", appData.timeControl);
9782                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9783         }
9784         fprintf(f, "-results \"%s\"\n", results);
9785     }
9786     return f;
9787 }
9788
9789 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9790
9791 void
9792 Substitute (char *participants, int expunge)
9793 {
9794     int i, changed, changes=0, nPlayers=0;
9795     char *p, *q, *r, buf[MSG_SIZ];
9796     if(participants == NULL) return;
9797     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9798     r = p = participants; q = appData.participants;
9799     while(*p && *p == *q) {
9800         if(*p == '\n') r = p+1, nPlayers++;
9801         p++; q++;
9802     }
9803     if(*p) { // difference
9804         while(*p && *p++ != '\n');
9805         while(*q && *q++ != '\n');
9806       changed = nPlayers;
9807         changes = 1 + (strcmp(p, q) != 0);
9808     }
9809     if(changes == 1) { // a single engine mnemonic was changed
9810         q = r; while(*q) nPlayers += (*q++ == '\n');
9811         p = buf; while(*r && (*p = *r++) != '\n') p++;
9812         *p = NULLCHAR;
9813         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9814         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9815         if(mnemonic[i]) { // The substitute is valid
9816             FILE *f;
9817             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9818                 flock(fileno(f), LOCK_EX);
9819                 ParseArgsFromFile(f);
9820                 fseek(f, 0, SEEK_SET);
9821                 FREE(appData.participants); appData.participants = participants;
9822                 if(expunge) { // erase results of replaced engine
9823                     int len = strlen(appData.results), w, b, dummy;
9824                     for(i=0; i<len; i++) {
9825                         Pairing(i, nPlayers, &w, &b, &dummy);
9826                         if((w == changed || b == changed) && appData.results[i] == '*') {
9827                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9828                             fclose(f);
9829                             return;
9830                         }
9831                     }
9832                     for(i=0; i<len; i++) {
9833                         Pairing(i, nPlayers, &w, &b, &dummy);
9834                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9835                     }
9836                 }
9837                 WriteTourneyFile(appData.results, f);
9838                 fclose(f); // release lock
9839                 return;
9840             }
9841         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9842     }
9843     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9844     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9845     free(participants);
9846     return;
9847 }
9848
9849 int
9850 CreateTourney (char *name)
9851 {
9852         FILE *f;
9853         if(matchMode && strcmp(name, appData.tourneyFile)) {
9854              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9855         }
9856         if(name[0] == NULLCHAR) {
9857             if(appData.participants[0])
9858                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9859             return 0;
9860         }
9861         f = fopen(name, "r");
9862         if(f) { // file exists
9863             ASSIGN(appData.tourneyFile, name);
9864             ParseArgsFromFile(f); // parse it
9865         } else {
9866             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9867             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9868                 DisplayError(_("Not enough participants"), 0);
9869                 return 0;
9870             }
9871             ASSIGN(appData.tourneyFile, name);
9872             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9873             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9874         }
9875         fclose(f);
9876         appData.noChessProgram = FALSE;
9877         appData.clockMode = TRUE;
9878         SetGNUMode();
9879         return 1;
9880 }
9881
9882 int
9883 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9884 {
9885     char buf[MSG_SIZ], *p, *q;
9886     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9887     skip = !all && group[0]; // if group requested, we start in skip mode
9888     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9889         p = names; q = buf; header = 0;
9890         while(*p && *p != '\n') *q++ = *p++;
9891         *q = 0;
9892         if(*p == '\n') p++;
9893         if(buf[0] == '#') {
9894             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9895             depth++; // we must be entering a new group
9896             if(all) continue; // suppress printing group headers when complete list requested
9897             header = 1;
9898             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9899         }
9900         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9901         if(engineList[i]) free(engineList[i]);
9902         engineList[i] = strdup(buf);
9903         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9904         if(engineMnemonic[i]) free(engineMnemonic[i]);
9905         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9906             strcat(buf, " (");
9907             sscanf(q + 8, "%s", buf + strlen(buf));
9908             strcat(buf, ")");
9909         }
9910         engineMnemonic[i] = strdup(buf);
9911         i++;
9912     }
9913     engineList[i] = engineMnemonic[i] = NULL;
9914     return i;
9915 }
9916
9917 // following implemented as macro to avoid type limitations
9918 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9919
9920 void
9921 SwapEngines (int n)
9922 {   // swap settings for first engine and other engine (so far only some selected options)
9923     int h;
9924     char *p;
9925     if(n == 0) return;
9926     SWAP(directory, p)
9927     SWAP(chessProgram, p)
9928     SWAP(isUCI, h)
9929     SWAP(hasOwnBookUCI, h)
9930     SWAP(protocolVersion, h)
9931     SWAP(reuse, h)
9932     SWAP(scoreIsAbsolute, h)
9933     SWAP(timeOdds, h)
9934     SWAP(logo, p)
9935     SWAP(pgnName, p)
9936     SWAP(pvSAN, h)
9937     SWAP(engOptions, p)
9938 }
9939
9940 int
9941 SetPlayer (int player, char *p)
9942 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9943     int i;
9944     char buf[MSG_SIZ], *engineName;
9945     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9946     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9947     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9948     if(mnemonic[i]) {
9949         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9950         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9951         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9952         ParseArgsFromString(buf);
9953     }
9954     free(engineName);
9955     return i;
9956 }
9957
9958 char *recentEngines;
9959
9960 void
9961 RecentEngineEvent (int nr)
9962 {
9963     int n;
9964 //    SwapEngines(1); // bump first to second
9965 //    ReplaceEngine(&second, 1); // and load it there
9966     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9967     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9968     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9969         ReplaceEngine(&first, 0);
9970         FloatToFront(&appData.recentEngineList, command[n]);
9971     }
9972 }
9973
9974 int
9975 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9976 {   // determine players from game number
9977     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9978
9979     if(appData.tourneyType == 0) {
9980         roundsPerCycle = (nPlayers - 1) | 1;
9981         pairingsPerRound = nPlayers / 2;
9982     } else if(appData.tourneyType > 0) {
9983         roundsPerCycle = nPlayers - appData.tourneyType;
9984         pairingsPerRound = appData.tourneyType;
9985     }
9986     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9987     gamesPerCycle = gamesPerRound * roundsPerCycle;
9988     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9989     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9990     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9991     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9992     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9993     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9994
9995     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9996     if(appData.roundSync) *syncInterval = gamesPerRound;
9997
9998     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9999
10000     if(appData.tourneyType == 0) {
10001         if(curPairing == (nPlayers-1)/2 ) {
10002             *whitePlayer = curRound;
10003             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10004         } else {
10005             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10006             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10007             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10008             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10009         }
10010     } else if(appData.tourneyType > 1) {
10011         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10012         *whitePlayer = curRound + appData.tourneyType;
10013     } else if(appData.tourneyType > 0) {
10014         *whitePlayer = curPairing;
10015         *blackPlayer = curRound + appData.tourneyType;
10016     }
10017
10018     // take care of white/black alternation per round. 
10019     // For cycles and games this is already taken care of by default, derived from matchGame!
10020     return curRound & 1;
10021 }
10022
10023 int
10024 NextTourneyGame (int nr, int *swapColors)
10025 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10026     char *p, *q;
10027     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10028     FILE *tf;
10029     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10030     tf = fopen(appData.tourneyFile, "r");
10031     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10032     ParseArgsFromFile(tf); fclose(tf);
10033     InitTimeControls(); // TC might be altered from tourney file
10034
10035     nPlayers = CountPlayers(appData.participants); // count participants
10036     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10037     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10038
10039     if(syncInterval) {
10040         p = q = appData.results;
10041         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10042         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10043             DisplayMessage(_("Waiting for other game(s)"),"");
10044             waitingForGame = TRUE;
10045             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10046             return 0;
10047         }
10048         waitingForGame = FALSE;
10049     }
10050
10051     if(appData.tourneyType < 0) {
10052         if(nr>=0 && !pairingReceived) {
10053             char buf[1<<16];
10054             if(pairing.pr == NoProc) {
10055                 if(!appData.pairingEngine[0]) {
10056                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10057                     return 0;
10058                 }
10059                 StartChessProgram(&pairing); // starts the pairing engine
10060             }
10061             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10062             SendToProgram(buf, &pairing);
10063             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10064             SendToProgram(buf, &pairing);
10065             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10066         }
10067         pairingReceived = 0;                              // ... so we continue here 
10068         *swapColors = 0;
10069         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10070         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10071         matchGame = 1; roundNr = nr / syncInterval + 1;
10072     }
10073
10074     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10075
10076     // redefine engines, engine dir, etc.
10077     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10078     if(first.pr == NoProc) {
10079       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10080       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10081     }
10082     if(second.pr == NoProc) {
10083       SwapEngines(1);
10084       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10085       SwapEngines(1);         // and make that valid for second engine by swapping
10086       InitEngine(&second, 1);
10087     }
10088     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10089     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10090     return 1;
10091 }
10092
10093 void
10094 NextMatchGame ()
10095 {   // performs game initialization that does not invoke engines, and then tries to start the game
10096     int res, firstWhite, swapColors = 0;
10097     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10098     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
10099         char buf[MSG_SIZ];
10100         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10101         if(strcmp(buf, currentDebugFile)) { // name has changed
10102             FILE *f = fopen(buf, "w");
10103             if(f) { // if opening the new file failed, just keep using the old one
10104                 ASSIGN(currentDebugFile, buf);
10105                 fclose(debugFP);
10106                 debugFP = f;
10107             }
10108             if(appData.serverFileName) {
10109                 if(serverFP) fclose(serverFP);
10110                 serverFP = fopen(appData.serverFileName, "w");
10111                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10112                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10113             }
10114         }
10115     }
10116     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10117     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10118     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10119     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10120     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10121     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10122     Reset(FALSE, first.pr != NoProc);
10123     res = LoadGameOrPosition(matchGame); // setup game
10124     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10125     if(!res) return; // abort when bad game/pos file
10126     TwoMachinesEvent();
10127 }
10128
10129 void
10130 UserAdjudicationEvent (int result)
10131 {
10132     ChessMove gameResult = GameIsDrawn;
10133
10134     if( result > 0 ) {
10135         gameResult = WhiteWins;
10136     }
10137     else if( result < 0 ) {
10138         gameResult = BlackWins;
10139     }
10140
10141     if( gameMode == TwoMachinesPlay ) {
10142         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10143     }
10144 }
10145
10146
10147 // [HGM] save: calculate checksum of game to make games easily identifiable
10148 int
10149 StringCheckSum (char *s)
10150 {
10151         int i = 0;
10152         if(s==NULL) return 0;
10153         while(*s) i = i*259 + *s++;
10154         return i;
10155 }
10156
10157 int
10158 GameCheckSum ()
10159 {
10160         int i, sum=0;
10161         for(i=backwardMostMove; i<forwardMostMove; i++) {
10162                 sum += pvInfoList[i].depth;
10163                 sum += StringCheckSum(parseList[i]);
10164                 sum += StringCheckSum(commentList[i]);
10165                 sum *= 261;
10166         }
10167         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10168         return sum + StringCheckSum(commentList[i]);
10169 } // end of save patch
10170
10171 void
10172 GameEnds (ChessMove result, char *resultDetails, int whosays)
10173 {
10174     GameMode nextGameMode;
10175     int isIcsGame;
10176     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10177
10178     if(endingGame) return; /* [HGM] crash: forbid recursion */
10179     endingGame = 1;
10180     if(twoBoards) { // [HGM] dual: switch back to one board
10181         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10182         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10183     }
10184     if (appData.debugMode) {
10185       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10186               result, resultDetails ? resultDetails : "(null)", whosays);
10187     }
10188
10189     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10190
10191     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10192         /* If we are playing on ICS, the server decides when the
10193            game is over, but the engine can offer to draw, claim
10194            a draw, or resign.
10195          */
10196 #if ZIPPY
10197         if (appData.zippyPlay && first.initDone) {
10198             if (result == GameIsDrawn) {
10199                 /* In case draw still needs to be claimed */
10200                 SendToICS(ics_prefix);
10201                 SendToICS("draw\n");
10202             } else if (StrCaseStr(resultDetails, "resign")) {
10203                 SendToICS(ics_prefix);
10204                 SendToICS("resign\n");
10205             }
10206         }
10207 #endif
10208         endingGame = 0; /* [HGM] crash */
10209         return;
10210     }
10211
10212     /* If we're loading the game from a file, stop */
10213     if (whosays == GE_FILE) {
10214       (void) StopLoadGameTimer();
10215       gameFileFP = NULL;
10216     }
10217
10218     /* Cancel draw offers */
10219     first.offeredDraw = second.offeredDraw = 0;
10220
10221     /* If this is an ICS game, only ICS can really say it's done;
10222        if not, anyone can. */
10223     isIcsGame = (gameMode == IcsPlayingWhite ||
10224                  gameMode == IcsPlayingBlack ||
10225                  gameMode == IcsObserving    ||
10226                  gameMode == IcsExamining);
10227
10228     if (!isIcsGame || whosays == GE_ICS) {
10229         /* OK -- not an ICS game, or ICS said it was done */
10230         StopClocks();
10231         if (!isIcsGame && !appData.noChessProgram)
10232           SetUserThinkingEnables();
10233
10234         /* [HGM] if a machine claims the game end we verify this claim */
10235         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10236             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10237                 char claimer;
10238                 ChessMove trueResult = (ChessMove) -1;
10239
10240                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10241                                             first.twoMachinesColor[0] :
10242                                             second.twoMachinesColor[0] ;
10243
10244                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10245                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10246                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10247                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10248                 } else
10249                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10250                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10251                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10252                 } else
10253                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10254                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10255                 }
10256
10257                 // now verify win claims, but not in drop games, as we don't understand those yet
10258                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10259                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10260                     (result == WhiteWins && claimer == 'w' ||
10261                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10262                       if (appData.debugMode) {
10263                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10264                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10265                       }
10266                       if(result != trueResult) {
10267                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10268                               result = claimer == 'w' ? BlackWins : WhiteWins;
10269                               resultDetails = buf;
10270                       }
10271                 } else
10272                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10273                     && (forwardMostMove <= backwardMostMove ||
10274                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10275                         (claimer=='b')==(forwardMostMove&1))
10276                                                                                   ) {
10277                       /* [HGM] verify: draws that were not flagged are false claims */
10278                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10279                       result = claimer == 'w' ? BlackWins : WhiteWins;
10280                       resultDetails = buf;
10281                 }
10282                 /* (Claiming a loss is accepted no questions asked!) */
10283             }
10284             /* [HGM] bare: don't allow bare King to win */
10285             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10286                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10287                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10288                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10289                && result != GameIsDrawn)
10290             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10291                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10292                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10293                         if(p >= 0 && p <= (int)WhiteKing) k++;
10294                 }
10295                 if (appData.debugMode) {
10296                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10297                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10298                 }
10299                 if(k <= 1) {
10300                         result = GameIsDrawn;
10301                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10302                         resultDetails = buf;
10303                 }
10304             }
10305         }
10306
10307
10308         if(serverMoves != NULL && !loadFlag) { char c = '=';
10309             if(result==WhiteWins) c = '+';
10310             if(result==BlackWins) c = '-';
10311             if(resultDetails != NULL)
10312                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10313         }
10314         if (resultDetails != NULL) {
10315             gameInfo.result = result;
10316             gameInfo.resultDetails = StrSave(resultDetails);
10317
10318             /* display last move only if game was not loaded from file */
10319             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10320                 DisplayMove(currentMove - 1);
10321
10322             if (forwardMostMove != 0) {
10323                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10324                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10325                                                                 ) {
10326                     if (*appData.saveGameFile != NULLCHAR) {
10327                         SaveGameToFile(appData.saveGameFile, TRUE);
10328                     } else if (appData.autoSaveGames) {
10329                         AutoSaveGame();
10330                     }
10331                     if (*appData.savePositionFile != NULLCHAR) {
10332                         SavePositionToFile(appData.savePositionFile);
10333                     }
10334                 }
10335             }
10336
10337             /* Tell program how game ended in case it is learning */
10338             /* [HGM] Moved this to after saving the PGN, just in case */
10339             /* engine died and we got here through time loss. In that */
10340             /* case we will get a fatal error writing the pipe, which */
10341             /* would otherwise lose us the PGN.                       */
10342             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10343             /* output during GameEnds should never be fatal anymore   */
10344             if (gameMode == MachinePlaysWhite ||
10345                 gameMode == MachinePlaysBlack ||
10346                 gameMode == TwoMachinesPlay ||
10347                 gameMode == IcsPlayingWhite ||
10348                 gameMode == IcsPlayingBlack ||
10349                 gameMode == BeginningOfGame) {
10350                 char buf[MSG_SIZ];
10351                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10352                         resultDetails);
10353                 if (first.pr != NoProc) {
10354                     SendToProgram(buf, &first);
10355                 }
10356                 if (second.pr != NoProc &&
10357                     gameMode == TwoMachinesPlay) {
10358                     SendToProgram(buf, &second);
10359                 }
10360             }
10361         }
10362
10363         if (appData.icsActive) {
10364             if (appData.quietPlay &&
10365                 (gameMode == IcsPlayingWhite ||
10366                  gameMode == IcsPlayingBlack)) {
10367                 SendToICS(ics_prefix);
10368                 SendToICS("set shout 1\n");
10369             }
10370             nextGameMode = IcsIdle;
10371             ics_user_moved = FALSE;
10372             /* clean up premove.  It's ugly when the game has ended and the
10373              * premove highlights are still on the board.
10374              */
10375             if (gotPremove) {
10376               gotPremove = FALSE;
10377               ClearPremoveHighlights();
10378               DrawPosition(FALSE, boards[currentMove]);
10379             }
10380             if (whosays == GE_ICS) {
10381                 switch (result) {
10382                 case WhiteWins:
10383                     if (gameMode == IcsPlayingWhite)
10384                         PlayIcsWinSound();
10385                     else if(gameMode == IcsPlayingBlack)
10386                         PlayIcsLossSound();
10387                     break;
10388                 case BlackWins:
10389                     if (gameMode == IcsPlayingBlack)
10390                         PlayIcsWinSound();
10391                     else if(gameMode == IcsPlayingWhite)
10392                         PlayIcsLossSound();
10393                     break;
10394                 case GameIsDrawn:
10395                     PlayIcsDrawSound();
10396                     break;
10397                 default:
10398                     PlayIcsUnfinishedSound();
10399                 }
10400             }
10401         } else if (gameMode == EditGame ||
10402                    gameMode == PlayFromGameFile ||
10403                    gameMode == AnalyzeMode ||
10404                    gameMode == AnalyzeFile) {
10405             nextGameMode = gameMode;
10406         } else {
10407             nextGameMode = EndOfGame;
10408         }
10409         pausing = FALSE;
10410         ModeHighlight();
10411     } else {
10412         nextGameMode = gameMode;
10413     }
10414
10415     if (appData.noChessProgram) {
10416         gameMode = nextGameMode;
10417         ModeHighlight();
10418         endingGame = 0; /* [HGM] crash */
10419         return;
10420     }
10421
10422     if (first.reuse) {
10423         /* Put first chess program into idle state */
10424         if (first.pr != NoProc &&
10425             (gameMode == MachinePlaysWhite ||
10426              gameMode == MachinePlaysBlack ||
10427              gameMode == TwoMachinesPlay ||
10428              gameMode == IcsPlayingWhite ||
10429              gameMode == IcsPlayingBlack ||
10430              gameMode == BeginningOfGame)) {
10431             SendToProgram("force\n", &first);
10432             if (first.usePing) {
10433               char buf[MSG_SIZ];
10434               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10435               SendToProgram(buf, &first);
10436             }
10437         }
10438     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10439         /* Kill off first chess program */
10440         if (first.isr != NULL)
10441           RemoveInputSource(first.isr);
10442         first.isr = NULL;
10443
10444         if (first.pr != NoProc) {
10445             ExitAnalyzeMode();
10446             DoSleep( appData.delayBeforeQuit );
10447             SendToProgram("quit\n", &first);
10448             DoSleep( appData.delayAfterQuit );
10449             DestroyChildProcess(first.pr, first.useSigterm);
10450         }
10451         first.pr = NoProc;
10452     }
10453     if (second.reuse) {
10454         /* Put second chess program into idle state */
10455         if (second.pr != NoProc &&
10456             gameMode == TwoMachinesPlay) {
10457             SendToProgram("force\n", &second);
10458             if (second.usePing) {
10459               char buf[MSG_SIZ];
10460               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10461               SendToProgram(buf, &second);
10462             }
10463         }
10464     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10465         /* Kill off second chess program */
10466         if (second.isr != NULL)
10467           RemoveInputSource(second.isr);
10468         second.isr = NULL;
10469
10470         if (second.pr != NoProc) {
10471             DoSleep( appData.delayBeforeQuit );
10472             SendToProgram("quit\n", &second);
10473             DoSleep( appData.delayAfterQuit );
10474             DestroyChildProcess(second.pr, second.useSigterm);
10475         }
10476         second.pr = NoProc;
10477     }
10478
10479     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10480         char resChar = '=';
10481         switch (result) {
10482         case WhiteWins:
10483           resChar = '+';
10484           if (first.twoMachinesColor[0] == 'w') {
10485             first.matchWins++;
10486           } else {
10487             second.matchWins++;
10488           }
10489           break;
10490         case BlackWins:
10491           resChar = '-';
10492           if (first.twoMachinesColor[0] == 'b') {
10493             first.matchWins++;
10494           } else {
10495             second.matchWins++;
10496           }
10497           break;
10498         case GameUnfinished:
10499           resChar = ' ';
10500         default:
10501           break;
10502         }
10503
10504         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10505         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10506             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10507             ReserveGame(nextGame, resChar); // sets nextGame
10508             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10509             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10510         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10511
10512         if (nextGame <= appData.matchGames && !abortMatch) {
10513             gameMode = nextGameMode;
10514             matchGame = nextGame; // this will be overruled in tourney mode!
10515             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10516             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10517             endingGame = 0; /* [HGM] crash */
10518             return;
10519         } else {
10520             gameMode = nextGameMode;
10521             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10522                      first.tidy, second.tidy,
10523                      first.matchWins, second.matchWins,
10524                      appData.matchGames - (first.matchWins + second.matchWins));
10525             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10526             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10527             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10528             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10529                 first.twoMachinesColor = "black\n";
10530                 second.twoMachinesColor = "white\n";
10531             } else {
10532                 first.twoMachinesColor = "white\n";
10533                 second.twoMachinesColor = "black\n";
10534             }
10535         }
10536     }
10537     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10538         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10539       ExitAnalyzeMode();
10540     gameMode = nextGameMode;
10541     ModeHighlight();
10542     endingGame = 0;  /* [HGM] crash */
10543     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10544         if(matchMode == TRUE) { // match through command line: exit with or without popup
10545             if(ranking) {
10546                 ToNrEvent(forwardMostMove);
10547                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10548                 else ExitEvent(0);
10549             } else DisplayFatalError(buf, 0, 0);
10550         } else { // match through menu; just stop, with or without popup
10551             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10552             ModeHighlight();
10553             if(ranking){
10554                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10555             } else DisplayNote(buf);
10556       }
10557       if(ranking) free(ranking);
10558     }
10559 }
10560
10561 /* Assumes program was just initialized (initString sent).
10562    Leaves program in force mode. */
10563 void
10564 FeedMovesToProgram (ChessProgramState *cps, int upto)
10565 {
10566     int i;
10567
10568     if (appData.debugMode)
10569       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10570               startedFromSetupPosition ? "position and " : "",
10571               backwardMostMove, upto, cps->which);
10572     if(currentlyInitializedVariant != gameInfo.variant) {
10573       char buf[MSG_SIZ];
10574         // [HGM] variantswitch: make engine aware of new variant
10575         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10576                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10577         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10578         SendToProgram(buf, cps);
10579         currentlyInitializedVariant = gameInfo.variant;
10580     }
10581     SendToProgram("force\n", cps);
10582     if (startedFromSetupPosition) {
10583         SendBoard(cps, backwardMostMove);
10584     if (appData.debugMode) {
10585         fprintf(debugFP, "feedMoves\n");
10586     }
10587     }
10588     for (i = backwardMostMove; i < upto; i++) {
10589         SendMoveToProgram(i, cps);
10590     }
10591 }
10592
10593
10594 int
10595 ResurrectChessProgram ()
10596 {
10597      /* The chess program may have exited.
10598         If so, restart it and feed it all the moves made so far. */
10599     static int doInit = 0;
10600
10601     if (appData.noChessProgram) return 1;
10602
10603     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10604         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10605         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10606         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10607     } else {
10608         if (first.pr != NoProc) return 1;
10609         StartChessProgram(&first);
10610     }
10611     InitChessProgram(&first, FALSE);
10612     FeedMovesToProgram(&first, currentMove);
10613
10614     if (!first.sendTime) {
10615         /* can't tell gnuchess what its clock should read,
10616            so we bow to its notion. */
10617         ResetClocks();
10618         timeRemaining[0][currentMove] = whiteTimeRemaining;
10619         timeRemaining[1][currentMove] = blackTimeRemaining;
10620     }
10621
10622     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10623                 appData.icsEngineAnalyze) && first.analysisSupport) {
10624       SendToProgram("analyze\n", &first);
10625       first.analyzing = TRUE;
10626     }
10627     return 1;
10628 }
10629
10630 /*
10631  * Button procedures
10632  */
10633 void
10634 Reset (int redraw, int init)
10635 {
10636     int i;
10637
10638     if (appData.debugMode) {
10639         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10640                 redraw, init, gameMode);
10641     }
10642     CleanupTail(); // [HGM] vari: delete any stored variations
10643     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10644     pausing = pauseExamInvalid = FALSE;
10645     startedFromSetupPosition = blackPlaysFirst = FALSE;
10646     firstMove = TRUE;
10647     whiteFlag = blackFlag = FALSE;
10648     userOfferedDraw = FALSE;
10649     hintRequested = bookRequested = FALSE;
10650     first.maybeThinking = FALSE;
10651     second.maybeThinking = FALSE;
10652     first.bookSuspend = FALSE; // [HGM] book
10653     second.bookSuspend = FALSE;
10654     thinkOutput[0] = NULLCHAR;
10655     lastHint[0] = NULLCHAR;
10656     ClearGameInfo(&gameInfo);
10657     gameInfo.variant = StringToVariant(appData.variant);
10658     ics_user_moved = ics_clock_paused = FALSE;
10659     ics_getting_history = H_FALSE;
10660     ics_gamenum = -1;
10661     white_holding[0] = black_holding[0] = NULLCHAR;
10662     ClearProgramStats();
10663     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10664
10665     ResetFrontEnd();
10666     ClearHighlights();
10667     flipView = appData.flipView;
10668     ClearPremoveHighlights();
10669     gotPremove = FALSE;
10670     alarmSounded = FALSE;
10671
10672     GameEnds(EndOfFile, NULL, GE_PLAYER);
10673     if(appData.serverMovesName != NULL) {
10674         /* [HGM] prepare to make moves file for broadcasting */
10675         clock_t t = clock();
10676         if(serverMoves != NULL) fclose(serverMoves);
10677         serverMoves = fopen(appData.serverMovesName, "r");
10678         if(serverMoves != NULL) {
10679             fclose(serverMoves);
10680             /* delay 15 sec before overwriting, so all clients can see end */
10681             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10682         }
10683         serverMoves = fopen(appData.serverMovesName, "w");
10684     }
10685
10686     ExitAnalyzeMode();
10687     gameMode = BeginningOfGame;
10688     ModeHighlight();
10689     if(appData.icsActive) gameInfo.variant = VariantNormal;
10690     currentMove = forwardMostMove = backwardMostMove = 0;
10691     MarkTargetSquares(1);
10692     InitPosition(redraw);
10693     for (i = 0; i < MAX_MOVES; i++) {
10694         if (commentList[i] != NULL) {
10695             free(commentList[i]);
10696             commentList[i] = NULL;
10697         }
10698     }
10699     ResetClocks();
10700     timeRemaining[0][0] = whiteTimeRemaining;
10701     timeRemaining[1][0] = blackTimeRemaining;
10702
10703     if (first.pr == NoProc) {
10704         StartChessProgram(&first);
10705     }
10706     if (init) {
10707             InitChessProgram(&first, startedFromSetupPosition);
10708     }
10709     DisplayTitle("");
10710     DisplayMessage("", "");
10711     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10712     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10713 }
10714
10715 void
10716 AutoPlayGameLoop ()
10717 {
10718     for (;;) {
10719         if (!AutoPlayOneMove())
10720           return;
10721         if (matchMode || appData.timeDelay == 0)
10722           continue;
10723         if (appData.timeDelay < 0)
10724           return;
10725         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10726         break;
10727     }
10728 }
10729
10730
10731 int
10732 AutoPlayOneMove ()
10733 {
10734     int fromX, fromY, toX, toY;
10735
10736     if (appData.debugMode) {
10737       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10738     }
10739
10740     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10741       return FALSE;
10742
10743     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10744       pvInfoList[currentMove].depth = programStats.depth;
10745       pvInfoList[currentMove].score = programStats.score;
10746       pvInfoList[currentMove].time  = 0;
10747       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10748     }
10749
10750     if (currentMove >= forwardMostMove) {
10751       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10752 //      gameMode = EndOfGame;
10753 //      ModeHighlight();
10754
10755       /* [AS] Clear current move marker at the end of a game */
10756       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10757
10758       return FALSE;
10759     }
10760
10761     toX = moveList[currentMove][2] - AAA;
10762     toY = moveList[currentMove][3] - ONE;
10763
10764     if (moveList[currentMove][1] == '@') {
10765         if (appData.highlightLastMove) {
10766             SetHighlights(-1, -1, toX, toY);
10767         }
10768     } else {
10769         fromX = moveList[currentMove][0] - AAA;
10770         fromY = moveList[currentMove][1] - ONE;
10771
10772         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10773
10774         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10775
10776         if (appData.highlightLastMove) {
10777             SetHighlights(fromX, fromY, toX, toY);
10778         }
10779     }
10780     DisplayMove(currentMove);
10781     SendMoveToProgram(currentMove++, &first);
10782     DisplayBothClocks();
10783     DrawPosition(FALSE, boards[currentMove]);
10784     // [HGM] PV info: always display, routine tests if empty
10785     DisplayComment(currentMove - 1, commentList[currentMove]);
10786     return TRUE;
10787 }
10788
10789
10790 int
10791 LoadGameOneMove (ChessMove readAhead)
10792 {
10793     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10794     char promoChar = NULLCHAR;
10795     ChessMove moveType;
10796     char move[MSG_SIZ];
10797     char *p, *q;
10798
10799     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10800         gameMode != AnalyzeMode && gameMode != Training) {
10801         gameFileFP = NULL;
10802         return FALSE;
10803     }
10804
10805     yyboardindex = forwardMostMove;
10806     if (readAhead != EndOfFile) {
10807       moveType = readAhead;
10808     } else {
10809       if (gameFileFP == NULL)
10810           return FALSE;
10811       moveType = (ChessMove) Myylex();
10812     }
10813
10814     done = FALSE;
10815     switch (moveType) {
10816       case Comment:
10817         if (appData.debugMode)
10818           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10819         p = yy_text;
10820
10821         /* append the comment but don't display it */
10822         AppendComment(currentMove, p, FALSE);
10823         return TRUE;
10824
10825       case WhiteCapturesEnPassant:
10826       case BlackCapturesEnPassant:
10827       case WhitePromotion:
10828       case BlackPromotion:
10829       case WhiteNonPromotion:
10830       case BlackNonPromotion:
10831       case NormalMove:
10832       case WhiteKingSideCastle:
10833       case WhiteQueenSideCastle:
10834       case BlackKingSideCastle:
10835       case BlackQueenSideCastle:
10836       case WhiteKingSideCastleWild:
10837       case WhiteQueenSideCastleWild:
10838       case BlackKingSideCastleWild:
10839       case BlackQueenSideCastleWild:
10840       /* PUSH Fabien */
10841       case WhiteHSideCastleFR:
10842       case WhiteASideCastleFR:
10843       case BlackHSideCastleFR:
10844       case BlackASideCastleFR:
10845       /* POP Fabien */
10846         if (appData.debugMode)
10847           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10848         fromX = currentMoveString[0] - AAA;
10849         fromY = currentMoveString[1] - ONE;
10850         toX = currentMoveString[2] - AAA;
10851         toY = currentMoveString[3] - ONE;
10852         promoChar = currentMoveString[4];
10853         break;
10854
10855       case WhiteDrop:
10856       case BlackDrop:
10857         if (appData.debugMode)
10858           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10859         fromX = moveType == WhiteDrop ?
10860           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10861         (int) CharToPiece(ToLower(currentMoveString[0]));
10862         fromY = DROP_RANK;
10863         toX = currentMoveString[2] - AAA;
10864         toY = currentMoveString[3] - ONE;
10865         break;
10866
10867       case WhiteWins:
10868       case BlackWins:
10869       case GameIsDrawn:
10870       case GameUnfinished:
10871         if (appData.debugMode)
10872           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10873         p = strchr(yy_text, '{');
10874         if (p == NULL) p = strchr(yy_text, '(');
10875         if (p == NULL) {
10876             p = yy_text;
10877             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10878         } else {
10879             q = strchr(p, *p == '{' ? '}' : ')');
10880             if (q != NULL) *q = NULLCHAR;
10881             p++;
10882         }
10883         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10884         GameEnds(moveType, p, GE_FILE);
10885         done = TRUE;
10886         if (cmailMsgLoaded) {
10887             ClearHighlights();
10888             flipView = WhiteOnMove(currentMove);
10889             if (moveType == GameUnfinished) flipView = !flipView;
10890             if (appData.debugMode)
10891               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10892         }
10893         break;
10894
10895       case EndOfFile:
10896         if (appData.debugMode)
10897           fprintf(debugFP, "Parser hit end of file\n");
10898         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10899           case MT_NONE:
10900           case MT_CHECK:
10901             break;
10902           case MT_CHECKMATE:
10903           case MT_STAINMATE:
10904             if (WhiteOnMove(currentMove)) {
10905                 GameEnds(BlackWins, "Black mates", GE_FILE);
10906             } else {
10907                 GameEnds(WhiteWins, "White mates", GE_FILE);
10908             }
10909             break;
10910           case MT_STALEMATE:
10911             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10912             break;
10913         }
10914         done = TRUE;
10915         break;
10916
10917       case MoveNumberOne:
10918         if (lastLoadGameStart == GNUChessGame) {
10919             /* GNUChessGames have numbers, but they aren't move numbers */
10920             if (appData.debugMode)
10921               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10922                       yy_text, (int) moveType);
10923             return LoadGameOneMove(EndOfFile); /* tail recursion */
10924         }
10925         /* else fall thru */
10926
10927       case XBoardGame:
10928       case GNUChessGame:
10929       case PGNTag:
10930         /* Reached start of next game in file */
10931         if (appData.debugMode)
10932           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10933         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10934           case MT_NONE:
10935           case MT_CHECK:
10936             break;
10937           case MT_CHECKMATE:
10938           case MT_STAINMATE:
10939             if (WhiteOnMove(currentMove)) {
10940                 GameEnds(BlackWins, "Black mates", GE_FILE);
10941             } else {
10942                 GameEnds(WhiteWins, "White mates", GE_FILE);
10943             }
10944             break;
10945           case MT_STALEMATE:
10946             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10947             break;
10948         }
10949         done = TRUE;
10950         break;
10951
10952       case PositionDiagram:     /* should not happen; ignore */
10953       case ElapsedTime:         /* ignore */
10954       case NAG:                 /* ignore */
10955         if (appData.debugMode)
10956           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10957                   yy_text, (int) moveType);
10958         return LoadGameOneMove(EndOfFile); /* tail recursion */
10959
10960       case IllegalMove:
10961         if (appData.testLegality) {
10962             if (appData.debugMode)
10963               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10964             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10965                     (forwardMostMove / 2) + 1,
10966                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10967             DisplayError(move, 0);
10968             done = TRUE;
10969         } else {
10970             if (appData.debugMode)
10971               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10972                       yy_text, currentMoveString);
10973             fromX = currentMoveString[0] - AAA;
10974             fromY = currentMoveString[1] - ONE;
10975             toX = currentMoveString[2] - AAA;
10976             toY = currentMoveString[3] - ONE;
10977             promoChar = currentMoveString[4];
10978         }
10979         break;
10980
10981       case AmbiguousMove:
10982         if (appData.debugMode)
10983           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10984         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10985                 (forwardMostMove / 2) + 1,
10986                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10987         DisplayError(move, 0);
10988         done = TRUE;
10989         break;
10990
10991       default:
10992       case ImpossibleMove:
10993         if (appData.debugMode)
10994           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10995         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10996                 (forwardMostMove / 2) + 1,
10997                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10998         DisplayError(move, 0);
10999         done = TRUE;
11000         break;
11001     }
11002
11003     if (done) {
11004         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11005             DrawPosition(FALSE, boards[currentMove]);
11006             DisplayBothClocks();
11007             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11008               DisplayComment(currentMove - 1, commentList[currentMove]);
11009         }
11010         (void) StopLoadGameTimer();
11011         gameFileFP = NULL;
11012         cmailOldMove = forwardMostMove;
11013         return FALSE;
11014     } else {
11015         /* currentMoveString is set as a side-effect of yylex */
11016
11017         thinkOutput[0] = NULLCHAR;
11018         MakeMove(fromX, fromY, toX, toY, promoChar);
11019         currentMove = forwardMostMove;
11020         return TRUE;
11021     }
11022 }
11023
11024 /* Load the nth game from the given file */
11025 int
11026 LoadGameFromFile (char *filename, int n, char *title, int useList)
11027 {
11028     FILE *f;
11029     char buf[MSG_SIZ];
11030
11031     if (strcmp(filename, "-") == 0) {
11032         f = stdin;
11033         title = "stdin";
11034     } else {
11035         f = fopen(filename, "rb");
11036         if (f == NULL) {
11037           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11038             DisplayError(buf, errno);
11039             return FALSE;
11040         }
11041     }
11042     if (fseek(f, 0, 0) == -1) {
11043         /* f is not seekable; probably a pipe */
11044         useList = FALSE;
11045     }
11046     if (useList && n == 0) {
11047         int error = GameListBuild(f);
11048         if (error) {
11049             DisplayError(_("Cannot build game list"), error);
11050         } else if (!ListEmpty(&gameList) &&
11051                    ((ListGame *) gameList.tailPred)->number > 1) {
11052             GameListPopUp(f, title);
11053             return TRUE;
11054         }
11055         GameListDestroy();
11056         n = 1;
11057     }
11058     if (n == 0) n = 1;
11059     return LoadGame(f, n, title, FALSE);
11060 }
11061
11062
11063 void
11064 MakeRegisteredMove ()
11065 {
11066     int fromX, fromY, toX, toY;
11067     char promoChar;
11068     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11069         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11070           case CMAIL_MOVE:
11071           case CMAIL_DRAW:
11072             if (appData.debugMode)
11073               fprintf(debugFP, "Restoring %s for game %d\n",
11074                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11075
11076             thinkOutput[0] = NULLCHAR;
11077             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11078             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11079             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11080             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11081             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11082             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11083             MakeMove(fromX, fromY, toX, toY, promoChar);
11084             ShowMove(fromX, fromY, toX, toY);
11085
11086             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11087               case MT_NONE:
11088               case MT_CHECK:
11089                 break;
11090
11091               case MT_CHECKMATE:
11092               case MT_STAINMATE:
11093                 if (WhiteOnMove(currentMove)) {
11094                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11095                 } else {
11096                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11097                 }
11098                 break;
11099
11100               case MT_STALEMATE:
11101                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11102                 break;
11103             }
11104
11105             break;
11106
11107           case CMAIL_RESIGN:
11108             if (WhiteOnMove(currentMove)) {
11109                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11110             } else {
11111                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11112             }
11113             break;
11114
11115           case CMAIL_ACCEPT:
11116             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11117             break;
11118
11119           default:
11120             break;
11121         }
11122     }
11123
11124     return;
11125 }
11126
11127 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11128 int
11129 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11130 {
11131     int retVal;
11132
11133     if (gameNumber > nCmailGames) {
11134         DisplayError(_("No more games in this message"), 0);
11135         return FALSE;
11136     }
11137     if (f == lastLoadGameFP) {
11138         int offset = gameNumber - lastLoadGameNumber;
11139         if (offset == 0) {
11140             cmailMsg[0] = NULLCHAR;
11141             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11142                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11143                 nCmailMovesRegistered--;
11144             }
11145             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11146             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11147                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11148             }
11149         } else {
11150             if (! RegisterMove()) return FALSE;
11151         }
11152     }
11153
11154     retVal = LoadGame(f, gameNumber, title, useList);
11155
11156     /* Make move registered during previous look at this game, if any */
11157     MakeRegisteredMove();
11158
11159     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11160         commentList[currentMove]
11161           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11162         DisplayComment(currentMove - 1, commentList[currentMove]);
11163     }
11164
11165     return retVal;
11166 }
11167
11168 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11169 int
11170 ReloadGame (int offset)
11171 {
11172     int gameNumber = lastLoadGameNumber + offset;
11173     if (lastLoadGameFP == NULL) {
11174         DisplayError(_("No game has been loaded yet"), 0);
11175         return FALSE;
11176     }
11177     if (gameNumber <= 0) {
11178         DisplayError(_("Can't back up any further"), 0);
11179         return FALSE;
11180     }
11181     if (cmailMsgLoaded) {
11182         return CmailLoadGame(lastLoadGameFP, gameNumber,
11183                              lastLoadGameTitle, lastLoadGameUseList);
11184     } else {
11185         return LoadGame(lastLoadGameFP, gameNumber,
11186                         lastLoadGameTitle, lastLoadGameUseList);
11187     }
11188 }
11189
11190 int keys[EmptySquare+1];
11191
11192 int
11193 PositionMatches (Board b1, Board b2)
11194 {
11195     int r, f, sum=0;
11196     switch(appData.searchMode) {
11197         case 1: return CompareWithRights(b1, b2);
11198         case 2:
11199             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11200                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11201             }
11202             return TRUE;
11203         case 3:
11204             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11205               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11206                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11207             }
11208             return sum==0;
11209         case 4:
11210             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11211                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11212             }
11213             return sum==0;
11214     }
11215     return TRUE;
11216 }
11217
11218 #define Q_PROMO  4
11219 #define Q_EP     3
11220 #define Q_BCASTL 2
11221 #define Q_WCASTL 1
11222
11223 int pieceList[256], quickBoard[256];
11224 ChessSquare pieceType[256] = { EmptySquare };
11225 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11226 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11227 int soughtTotal, turn;
11228 Boolean epOK, flipSearch;
11229
11230 typedef struct {
11231     unsigned char piece, to;
11232 } Move;
11233
11234 #define DSIZE (250000)
11235
11236 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11237 Move *moveDatabase = initialSpace;
11238 unsigned int movePtr, dataSize = DSIZE;
11239
11240 int
11241 MakePieceList (Board board, int *counts)
11242 {
11243     int r, f, n=Q_PROMO, total=0;
11244     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11245     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11246         int sq = f + (r<<4);
11247         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11248             quickBoard[sq] = ++n;
11249             pieceList[n] = sq;
11250             pieceType[n] = board[r][f];
11251             counts[board[r][f]]++;
11252             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11253             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11254             total++;
11255         }
11256     }
11257     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11258     return total;
11259 }
11260
11261 void
11262 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11263 {
11264     int sq = fromX + (fromY<<4);
11265     int piece = quickBoard[sq];
11266     quickBoard[sq] = 0;
11267     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11268     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11269         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11270         moveDatabase[movePtr++].piece = Q_WCASTL;
11271         quickBoard[sq] = piece;
11272         piece = quickBoard[from]; quickBoard[from] = 0;
11273         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11274     } else
11275     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11276         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11277         moveDatabase[movePtr++].piece = Q_BCASTL;
11278         quickBoard[sq] = piece;
11279         piece = quickBoard[from]; quickBoard[from] = 0;
11280         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11281     } else
11282     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11283         quickBoard[(fromY<<4)+toX] = 0;
11284         moveDatabase[movePtr].piece = Q_EP;
11285         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11286         moveDatabase[movePtr].to = sq;
11287     } else
11288     if(promoPiece != pieceType[piece]) {
11289         moveDatabase[movePtr++].piece = Q_PROMO;
11290         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11291     }
11292     moveDatabase[movePtr].piece = piece;
11293     quickBoard[sq] = piece;
11294     movePtr++;
11295 }
11296
11297 int
11298 PackGame (Board board)
11299 {
11300     Move *newSpace = NULL;
11301     moveDatabase[movePtr].piece = 0; // terminate previous game
11302     if(movePtr > dataSize) {
11303         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11304         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11305         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11306         if(newSpace) {
11307             int i;
11308             Move *p = moveDatabase, *q = newSpace;
11309             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11310             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11311             moveDatabase = newSpace;
11312         } else { // calloc failed, we must be out of memory. Too bad...
11313             dataSize = 0; // prevent calloc events for all subsequent games
11314             return 0;     // and signal this one isn't cached
11315         }
11316     }
11317     movePtr++;
11318     MakePieceList(board, counts);
11319     return movePtr;
11320 }
11321
11322 int
11323 QuickCompare (Board board, int *minCounts, int *maxCounts)
11324 {   // compare according to search mode
11325     int r, f;
11326     switch(appData.searchMode)
11327     {
11328       case 1: // exact position match
11329         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11330         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11331             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11332         }
11333         break;
11334       case 2: // can have extra material on empty squares
11335         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11336             if(board[r][f] == EmptySquare) continue;
11337             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11338         }
11339         break;
11340       case 3: // material with exact Pawn structure
11341         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11342             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11343             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11344         } // fall through to material comparison
11345       case 4: // exact material
11346         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11347         break;
11348       case 6: // material range with given imbalance
11349         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11350         // fall through to range comparison
11351       case 5: // material range
11352         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11353     }
11354     return TRUE;
11355 }
11356
11357 int
11358 QuickScan (Board board, Move *move)
11359 {   // reconstruct game,and compare all positions in it
11360     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11361     do {
11362         int piece = move->piece;
11363         int to = move->to, from = pieceList[piece];
11364         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11365           if(!piece) return -1;
11366           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11367             piece = (++move)->piece;
11368             from = pieceList[piece];
11369             counts[pieceType[piece]]--;
11370             pieceType[piece] = (ChessSquare) move->to;
11371             counts[move->to]++;
11372           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11373             counts[pieceType[quickBoard[to]]]--;
11374             quickBoard[to] = 0; total--;
11375             move++;
11376             continue;
11377           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11378             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11379             from  = pieceList[piece]; // so this must be King
11380             quickBoard[from] = 0;
11381             quickBoard[to] = piece;
11382             pieceList[piece] = to;
11383             move++;
11384             continue;
11385           }
11386         }
11387         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11388         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11389         quickBoard[from] = 0;
11390         quickBoard[to] = piece;
11391         pieceList[piece] = to;
11392         cnt++; turn ^= 3;
11393         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11394            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11395            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11396                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11397           ) {
11398             static int lastCounts[EmptySquare+1];
11399             int i;
11400             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11401             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11402         } else stretch = 0;
11403         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11404         move++;
11405     } while(1);
11406 }
11407
11408 void
11409 InitSearch ()
11410 {
11411     int r, f;
11412     flipSearch = FALSE;
11413     CopyBoard(soughtBoard, boards[currentMove]);
11414     soughtTotal = MakePieceList(soughtBoard, maxSought);
11415     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11416     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11417     CopyBoard(reverseBoard, boards[currentMove]);
11418     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11419         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11420         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11421         reverseBoard[r][f] = piece;
11422     }
11423     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11424     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11425     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11426                  || (boards[currentMove][CASTLING][2] == NoRights || 
11427                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11428                  && (boards[currentMove][CASTLING][5] == NoRights || 
11429                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11430       ) {
11431         flipSearch = TRUE;
11432         CopyBoard(flipBoard, soughtBoard);
11433         CopyBoard(rotateBoard, reverseBoard);
11434         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11435             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11436             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11437         }
11438     }
11439     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11440     if(appData.searchMode >= 5) {
11441         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11442         MakePieceList(soughtBoard, minSought);
11443         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11444     }
11445     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11446         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11447 }
11448
11449 GameInfo dummyInfo;
11450
11451 int
11452 GameContainsPosition (FILE *f, ListGame *lg)
11453 {
11454     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11455     int fromX, fromY, toX, toY;
11456     char promoChar;
11457     static int initDone=FALSE;
11458
11459     // weed out games based on numerical tag comparison
11460     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11461     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11462     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11463     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11464     if(!initDone) {
11465         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11466         initDone = TRUE;
11467     }
11468     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11469     else CopyBoard(boards[scratch], initialPosition); // default start position
11470     if(lg->moves) {
11471         turn = btm + 1;
11472         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11473         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11474     }
11475     if(btm) plyNr++;
11476     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11477     fseek(f, lg->offset, 0);
11478     yynewfile(f);
11479     while(1) {
11480         yyboardindex = scratch;
11481         quickFlag = plyNr+1;
11482         next = Myylex();
11483         quickFlag = 0;
11484         switch(next) {
11485             case PGNTag:
11486                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11487             default:
11488                 continue;
11489
11490             case XBoardGame:
11491             case GNUChessGame:
11492                 if(plyNr) return -1; // after we have seen moves, this is for new game
11493               continue;
11494
11495             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11496             case ImpossibleMove:
11497             case WhiteWins: // game ends here with these four
11498             case BlackWins:
11499             case GameIsDrawn:
11500             case GameUnfinished:
11501                 return -1;
11502
11503             case IllegalMove:
11504                 if(appData.testLegality) return -1;
11505             case WhiteCapturesEnPassant:
11506             case BlackCapturesEnPassant:
11507             case WhitePromotion:
11508             case BlackPromotion:
11509             case WhiteNonPromotion:
11510             case BlackNonPromotion:
11511             case NormalMove:
11512             case WhiteKingSideCastle:
11513             case WhiteQueenSideCastle:
11514             case BlackKingSideCastle:
11515             case BlackQueenSideCastle:
11516             case WhiteKingSideCastleWild:
11517             case WhiteQueenSideCastleWild:
11518             case BlackKingSideCastleWild:
11519             case BlackQueenSideCastleWild:
11520             case WhiteHSideCastleFR:
11521             case WhiteASideCastleFR:
11522             case BlackHSideCastleFR:
11523             case BlackASideCastleFR:
11524                 fromX = currentMoveString[0] - AAA;
11525                 fromY = currentMoveString[1] - ONE;
11526                 toX = currentMoveString[2] - AAA;
11527                 toY = currentMoveString[3] - ONE;
11528                 promoChar = currentMoveString[4];
11529                 break;
11530             case WhiteDrop:
11531             case BlackDrop:
11532                 fromX = next == WhiteDrop ?
11533                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11534                   (int) CharToPiece(ToLower(currentMoveString[0]));
11535                 fromY = DROP_RANK;
11536                 toX = currentMoveString[2] - AAA;
11537                 toY = currentMoveString[3] - ONE;
11538                 promoChar = 0;
11539                 break;
11540         }
11541         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11542         plyNr++;
11543         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11544         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11545         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11546         if(appData.findMirror) {
11547             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11548             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11549         }
11550     }
11551 }
11552
11553 /* Load the nth game from open file f */
11554 int
11555 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11556 {
11557     ChessMove cm;
11558     char buf[MSG_SIZ];
11559     int gn = gameNumber;
11560     ListGame *lg = NULL;
11561     int numPGNTags = 0;
11562     int err, pos = -1;
11563     GameMode oldGameMode;
11564     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11565
11566     if (appData.debugMode)
11567         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11568
11569     if (gameMode == Training )
11570         SetTrainingModeOff();
11571
11572     oldGameMode = gameMode;
11573     if (gameMode != BeginningOfGame) {
11574       Reset(FALSE, TRUE);
11575     }
11576
11577     gameFileFP = f;
11578     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11579         fclose(lastLoadGameFP);
11580     }
11581
11582     if (useList) {
11583         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11584
11585         if (lg) {
11586             fseek(f, lg->offset, 0);
11587             GameListHighlight(gameNumber);
11588             pos = lg->position;
11589             gn = 1;
11590         }
11591         else {
11592             DisplayError(_("Game number out of range"), 0);
11593             return FALSE;
11594         }
11595     } else {
11596         GameListDestroy();
11597         if (fseek(f, 0, 0) == -1) {
11598             if (f == lastLoadGameFP ?
11599                 gameNumber == lastLoadGameNumber + 1 :
11600                 gameNumber == 1) {
11601                 gn = 1;
11602             } else {
11603                 DisplayError(_("Can't seek on game file"), 0);
11604                 return FALSE;
11605             }
11606         }
11607     }
11608     lastLoadGameFP = f;
11609     lastLoadGameNumber = gameNumber;
11610     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11611     lastLoadGameUseList = useList;
11612
11613     yynewfile(f);
11614
11615     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11616       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11617                 lg->gameInfo.black);
11618             DisplayTitle(buf);
11619     } else if (*title != NULLCHAR) {
11620         if (gameNumber > 1) {
11621           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11622             DisplayTitle(buf);
11623         } else {
11624             DisplayTitle(title);
11625         }
11626     }
11627
11628     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11629         gameMode = PlayFromGameFile;
11630         ModeHighlight();
11631     }
11632
11633     currentMove = forwardMostMove = backwardMostMove = 0;
11634     CopyBoard(boards[0], initialPosition);
11635     StopClocks();
11636
11637     /*
11638      * Skip the first gn-1 games in the file.
11639      * Also skip over anything that precedes an identifiable
11640      * start of game marker, to avoid being confused by
11641      * garbage at the start of the file.  Currently
11642      * recognized start of game markers are the move number "1",
11643      * the pattern "gnuchess .* game", the pattern
11644      * "^[#;%] [^ ]* game file", and a PGN tag block.
11645      * A game that starts with one of the latter two patterns
11646      * will also have a move number 1, possibly
11647      * following a position diagram.
11648      * 5-4-02: Let's try being more lenient and allowing a game to
11649      * start with an unnumbered move.  Does that break anything?
11650      */
11651     cm = lastLoadGameStart = EndOfFile;
11652     while (gn > 0) {
11653         yyboardindex = forwardMostMove;
11654         cm = (ChessMove) Myylex();
11655         switch (cm) {
11656           case EndOfFile:
11657             if (cmailMsgLoaded) {
11658                 nCmailGames = CMAIL_MAX_GAMES - gn;
11659             } else {
11660                 Reset(TRUE, TRUE);
11661                 DisplayError(_("Game not found in file"), 0);
11662             }
11663             return FALSE;
11664
11665           case GNUChessGame:
11666           case XBoardGame:
11667             gn--;
11668             lastLoadGameStart = cm;
11669             break;
11670
11671           case MoveNumberOne:
11672             switch (lastLoadGameStart) {
11673               case GNUChessGame:
11674               case XBoardGame:
11675               case PGNTag:
11676                 break;
11677               case MoveNumberOne:
11678               case EndOfFile:
11679                 gn--;           /* count this game */
11680                 lastLoadGameStart = cm;
11681                 break;
11682               default:
11683                 /* impossible */
11684                 break;
11685             }
11686             break;
11687
11688           case PGNTag:
11689             switch (lastLoadGameStart) {
11690               case GNUChessGame:
11691               case PGNTag:
11692               case MoveNumberOne:
11693               case EndOfFile:
11694                 gn--;           /* count this game */
11695                 lastLoadGameStart = cm;
11696                 break;
11697               case XBoardGame:
11698                 lastLoadGameStart = cm; /* game counted already */
11699                 break;
11700               default:
11701                 /* impossible */
11702                 break;
11703             }
11704             if (gn > 0) {
11705                 do {
11706                     yyboardindex = forwardMostMove;
11707                     cm = (ChessMove) Myylex();
11708                 } while (cm == PGNTag || cm == Comment);
11709             }
11710             break;
11711
11712           case WhiteWins:
11713           case BlackWins:
11714           case GameIsDrawn:
11715             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11716                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11717                     != CMAIL_OLD_RESULT) {
11718                     nCmailResults ++ ;
11719                     cmailResult[  CMAIL_MAX_GAMES
11720                                 - gn - 1] = CMAIL_OLD_RESULT;
11721                 }
11722             }
11723             break;
11724
11725           case NormalMove:
11726             /* Only a NormalMove can be at the start of a game
11727              * without a position diagram. */
11728             if (lastLoadGameStart == EndOfFile ) {
11729               gn--;
11730               lastLoadGameStart = MoveNumberOne;
11731             }
11732             break;
11733
11734           default:
11735             break;
11736         }
11737     }
11738
11739     if (appData.debugMode)
11740       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11741
11742     if (cm == XBoardGame) {
11743         /* Skip any header junk before position diagram and/or move 1 */
11744         for (;;) {
11745             yyboardindex = forwardMostMove;
11746             cm = (ChessMove) Myylex();
11747
11748             if (cm == EndOfFile ||
11749                 cm == GNUChessGame || cm == XBoardGame) {
11750                 /* Empty game; pretend end-of-file and handle later */
11751                 cm = EndOfFile;
11752                 break;
11753             }
11754
11755             if (cm == MoveNumberOne || cm == PositionDiagram ||
11756                 cm == PGNTag || cm == Comment)
11757               break;
11758         }
11759     } else if (cm == GNUChessGame) {
11760         if (gameInfo.event != NULL) {
11761             free(gameInfo.event);
11762         }
11763         gameInfo.event = StrSave(yy_text);
11764     }
11765
11766     startedFromSetupPosition = FALSE;
11767     while (cm == PGNTag) {
11768         if (appData.debugMode)
11769           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11770         err = ParsePGNTag(yy_text, &gameInfo);
11771         if (!err) numPGNTags++;
11772
11773         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11774         if(gameInfo.variant != oldVariant) {
11775             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11776             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11777             InitPosition(TRUE);
11778             oldVariant = gameInfo.variant;
11779             if (appData.debugMode)
11780               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11781         }
11782
11783
11784         if (gameInfo.fen != NULL) {
11785           Board initial_position;
11786           startedFromSetupPosition = TRUE;
11787           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11788             Reset(TRUE, TRUE);
11789             DisplayError(_("Bad FEN position in file"), 0);
11790             return FALSE;
11791           }
11792           CopyBoard(boards[0], initial_position);
11793           if (blackPlaysFirst) {
11794             currentMove = forwardMostMove = backwardMostMove = 1;
11795             CopyBoard(boards[1], initial_position);
11796             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11797             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11798             timeRemaining[0][1] = whiteTimeRemaining;
11799             timeRemaining[1][1] = blackTimeRemaining;
11800             if (commentList[0] != NULL) {
11801               commentList[1] = commentList[0];
11802               commentList[0] = NULL;
11803             }
11804           } else {
11805             currentMove = forwardMostMove = backwardMostMove = 0;
11806           }
11807           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11808           {   int i;
11809               initialRulePlies = FENrulePlies;
11810               for( i=0; i< nrCastlingRights; i++ )
11811                   initialRights[i] = initial_position[CASTLING][i];
11812           }
11813           yyboardindex = forwardMostMove;
11814           free(gameInfo.fen);
11815           gameInfo.fen = NULL;
11816         }
11817
11818         yyboardindex = forwardMostMove;
11819         cm = (ChessMove) Myylex();
11820
11821         /* Handle comments interspersed among the tags */
11822         while (cm == Comment) {
11823             char *p;
11824             if (appData.debugMode)
11825               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11826             p = yy_text;
11827             AppendComment(currentMove, p, FALSE);
11828             yyboardindex = forwardMostMove;
11829             cm = (ChessMove) Myylex();
11830         }
11831     }
11832
11833     /* don't rely on existence of Event tag since if game was
11834      * pasted from clipboard the Event tag may not exist
11835      */
11836     if (numPGNTags > 0){
11837         char *tags;
11838         if (gameInfo.variant == VariantNormal) {
11839           VariantClass v = StringToVariant(gameInfo.event);
11840           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11841           if(v < VariantShogi) gameInfo.variant = v;
11842         }
11843         if (!matchMode) {
11844           if( appData.autoDisplayTags ) {
11845             tags = PGNTags(&gameInfo);
11846             TagsPopUp(tags, CmailMsg());
11847             free(tags);
11848           }
11849         }
11850     } else {
11851         /* Make something up, but don't display it now */
11852         SetGameInfo();
11853         TagsPopDown();
11854     }
11855
11856     if (cm == PositionDiagram) {
11857         int i, j;
11858         char *p;
11859         Board initial_position;
11860
11861         if (appData.debugMode)
11862           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11863
11864         if (!startedFromSetupPosition) {
11865             p = yy_text;
11866             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11867               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11868                 switch (*p) {
11869                   case '{':
11870                   case '[':
11871                   case '-':
11872                   case ' ':
11873                   case '\t':
11874                   case '\n':
11875                   case '\r':
11876                     break;
11877                   default:
11878                     initial_position[i][j++] = CharToPiece(*p);
11879                     break;
11880                 }
11881             while (*p == ' ' || *p == '\t' ||
11882                    *p == '\n' || *p == '\r') p++;
11883
11884             if (strncmp(p, "black", strlen("black"))==0)
11885               blackPlaysFirst = TRUE;
11886             else
11887               blackPlaysFirst = FALSE;
11888             startedFromSetupPosition = TRUE;
11889
11890             CopyBoard(boards[0], initial_position);
11891             if (blackPlaysFirst) {
11892                 currentMove = forwardMostMove = backwardMostMove = 1;
11893                 CopyBoard(boards[1], initial_position);
11894                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11895                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11896                 timeRemaining[0][1] = whiteTimeRemaining;
11897                 timeRemaining[1][1] = blackTimeRemaining;
11898                 if (commentList[0] != NULL) {
11899                     commentList[1] = commentList[0];
11900                     commentList[0] = NULL;
11901                 }
11902             } else {
11903                 currentMove = forwardMostMove = backwardMostMove = 0;
11904             }
11905         }
11906         yyboardindex = forwardMostMove;
11907         cm = (ChessMove) Myylex();
11908     }
11909
11910     if (first.pr == NoProc) {
11911         StartChessProgram(&first);
11912     }
11913     InitChessProgram(&first, FALSE);
11914     SendToProgram("force\n", &first);
11915     if (startedFromSetupPosition) {
11916         SendBoard(&first, forwardMostMove);
11917     if (appData.debugMode) {
11918         fprintf(debugFP, "Load Game\n");
11919     }
11920         DisplayBothClocks();
11921     }
11922
11923     /* [HGM] server: flag to write setup moves in broadcast file as one */
11924     loadFlag = appData.suppressLoadMoves;
11925
11926     while (cm == Comment) {
11927         char *p;
11928         if (appData.debugMode)
11929           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11930         p = yy_text;
11931         AppendComment(currentMove, p, FALSE);
11932         yyboardindex = forwardMostMove;
11933         cm = (ChessMove) Myylex();
11934     }
11935
11936     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11937         cm == WhiteWins || cm == BlackWins ||
11938         cm == GameIsDrawn || cm == GameUnfinished) {
11939         DisplayMessage("", _("No moves in game"));
11940         if (cmailMsgLoaded) {
11941             if (appData.debugMode)
11942               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11943             ClearHighlights();
11944             flipView = FALSE;
11945         }
11946         DrawPosition(FALSE, boards[currentMove]);
11947         DisplayBothClocks();
11948         gameMode = EditGame;
11949         ModeHighlight();
11950         gameFileFP = NULL;
11951         cmailOldMove = 0;
11952         return TRUE;
11953     }
11954
11955     // [HGM] PV info: routine tests if comment empty
11956     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11957         DisplayComment(currentMove - 1, commentList[currentMove]);
11958     }
11959     if (!matchMode && appData.timeDelay != 0)
11960       DrawPosition(FALSE, boards[currentMove]);
11961
11962     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11963       programStats.ok_to_send = 1;
11964     }
11965
11966     /* if the first token after the PGN tags is a move
11967      * and not move number 1, retrieve it from the parser
11968      */
11969     if (cm != MoveNumberOne)
11970         LoadGameOneMove(cm);
11971
11972     /* load the remaining moves from the file */
11973     while (LoadGameOneMove(EndOfFile)) {
11974       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11975       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11976     }
11977
11978     /* rewind to the start of the game */
11979     currentMove = backwardMostMove;
11980
11981     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11982
11983     if (oldGameMode == AnalyzeFile ||
11984         oldGameMode == AnalyzeMode) {
11985       AnalyzeFileEvent();
11986     }
11987
11988     if (!matchMode && pos >= 0) {
11989         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11990     } else
11991     if (matchMode || appData.timeDelay == 0) {
11992       ToEndEvent();
11993     } else if (appData.timeDelay > 0) {
11994       AutoPlayGameLoop();
11995     }
11996
11997     if (appData.debugMode)
11998         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11999
12000     loadFlag = 0; /* [HGM] true game starts */
12001     return TRUE;
12002 }
12003
12004 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12005 int
12006 ReloadPosition (int offset)
12007 {
12008     int positionNumber = lastLoadPositionNumber + offset;
12009     if (lastLoadPositionFP == NULL) {
12010         DisplayError(_("No position has been loaded yet"), 0);
12011         return FALSE;
12012     }
12013     if (positionNumber <= 0) {
12014         DisplayError(_("Can't back up any further"), 0);
12015         return FALSE;
12016     }
12017     return LoadPosition(lastLoadPositionFP, positionNumber,
12018                         lastLoadPositionTitle);
12019 }
12020
12021 /* Load the nth position from the given file */
12022 int
12023 LoadPositionFromFile (char *filename, int n, char *title)
12024 {
12025     FILE *f;
12026     char buf[MSG_SIZ];
12027
12028     if (strcmp(filename, "-") == 0) {
12029         return LoadPosition(stdin, n, "stdin");
12030     } else {
12031         f = fopen(filename, "rb");
12032         if (f == NULL) {
12033             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12034             DisplayError(buf, errno);
12035             return FALSE;
12036         } else {
12037             return LoadPosition(f, n, title);
12038         }
12039     }
12040 }
12041
12042 /* Load the nth position from the given open file, and close it */
12043 int
12044 LoadPosition (FILE *f, int positionNumber, char *title)
12045 {
12046     char *p, line[MSG_SIZ];
12047     Board initial_position;
12048     int i, j, fenMode, pn;
12049
12050     if (gameMode == Training )
12051         SetTrainingModeOff();
12052
12053     if (gameMode != BeginningOfGame) {
12054         Reset(FALSE, TRUE);
12055     }
12056     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12057         fclose(lastLoadPositionFP);
12058     }
12059     if (positionNumber == 0) positionNumber = 1;
12060     lastLoadPositionFP = f;
12061     lastLoadPositionNumber = positionNumber;
12062     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12063     if (first.pr == NoProc && !appData.noChessProgram) {
12064       StartChessProgram(&first);
12065       InitChessProgram(&first, FALSE);
12066     }
12067     pn = positionNumber;
12068     if (positionNumber < 0) {
12069         /* Negative position number means to seek to that byte offset */
12070         if (fseek(f, -positionNumber, 0) == -1) {
12071             DisplayError(_("Can't seek on position file"), 0);
12072             return FALSE;
12073         };
12074         pn = 1;
12075     } else {
12076         if (fseek(f, 0, 0) == -1) {
12077             if (f == lastLoadPositionFP ?
12078                 positionNumber == lastLoadPositionNumber + 1 :
12079                 positionNumber == 1) {
12080                 pn = 1;
12081             } else {
12082                 DisplayError(_("Can't seek on position file"), 0);
12083                 return FALSE;
12084             }
12085         }
12086     }
12087     /* See if this file is FEN or old-style xboard */
12088     if (fgets(line, MSG_SIZ, f) == NULL) {
12089         DisplayError(_("Position not found in file"), 0);
12090         return FALSE;
12091     }
12092     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12093     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12094
12095     if (pn >= 2) {
12096         if (fenMode || line[0] == '#') pn--;
12097         while (pn > 0) {
12098             /* skip positions before number pn */
12099             if (fgets(line, MSG_SIZ, f) == NULL) {
12100                 Reset(TRUE, TRUE);
12101                 DisplayError(_("Position not found in file"), 0);
12102                 return FALSE;
12103             }
12104             if (fenMode || line[0] == '#') pn--;
12105         }
12106     }
12107
12108     if (fenMode) {
12109         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12110             DisplayError(_("Bad FEN position in file"), 0);
12111             return FALSE;
12112         }
12113     } else {
12114         (void) fgets(line, MSG_SIZ, f);
12115         (void) fgets(line, MSG_SIZ, f);
12116
12117         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12118             (void) fgets(line, MSG_SIZ, f);
12119             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12120                 if (*p == ' ')
12121                   continue;
12122                 initial_position[i][j++] = CharToPiece(*p);
12123             }
12124         }
12125
12126         blackPlaysFirst = FALSE;
12127         if (!feof(f)) {
12128             (void) fgets(line, MSG_SIZ, f);
12129             if (strncmp(line, "black", strlen("black"))==0)
12130               blackPlaysFirst = TRUE;
12131         }
12132     }
12133     startedFromSetupPosition = TRUE;
12134
12135     CopyBoard(boards[0], initial_position);
12136     if (blackPlaysFirst) {
12137         currentMove = forwardMostMove = backwardMostMove = 1;
12138         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12139         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12140         CopyBoard(boards[1], initial_position);
12141         DisplayMessage("", _("Black to play"));
12142     } else {
12143         currentMove = forwardMostMove = backwardMostMove = 0;
12144         DisplayMessage("", _("White to play"));
12145     }
12146     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12147     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12148         SendToProgram("force\n", &first);
12149         SendBoard(&first, forwardMostMove);
12150     }
12151     if (appData.debugMode) {
12152 int i, j;
12153   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12154   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12155         fprintf(debugFP, "Load Position\n");
12156     }
12157
12158     if (positionNumber > 1) {
12159       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12160         DisplayTitle(line);
12161     } else {
12162         DisplayTitle(title);
12163     }
12164     gameMode = EditGame;
12165     ModeHighlight();
12166     ResetClocks();
12167     timeRemaining[0][1] = whiteTimeRemaining;
12168     timeRemaining[1][1] = blackTimeRemaining;
12169     DrawPosition(FALSE, boards[currentMove]);
12170
12171     return TRUE;
12172 }
12173
12174
12175 void
12176 CopyPlayerNameIntoFileName (char **dest, char *src)
12177 {
12178     while (*src != NULLCHAR && *src != ',') {
12179         if (*src == ' ') {
12180             *(*dest)++ = '_';
12181             src++;
12182         } else {
12183             *(*dest)++ = *src++;
12184         }
12185     }
12186 }
12187
12188 char *
12189 DefaultFileName (char *ext)
12190 {
12191     static char def[MSG_SIZ];
12192     char *p;
12193
12194     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12195         p = def;
12196         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12197         *p++ = '-';
12198         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12199         *p++ = '.';
12200         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12201     } else {
12202         def[0] = NULLCHAR;
12203     }
12204     return def;
12205 }
12206
12207 /* Save the current game to the given file */
12208 int
12209 SaveGameToFile (char *filename, int append)
12210 {
12211     FILE *f;
12212     char buf[MSG_SIZ];
12213     int result, i, t,tot=0;
12214
12215     if (strcmp(filename, "-") == 0) {
12216         return SaveGame(stdout, 0, NULL);
12217     } else {
12218         for(i=0; i<10; i++) { // upto 10 tries
12219              f = fopen(filename, append ? "a" : "w");
12220              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12221              if(f || errno != 13) break;
12222              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12223              tot += t;
12224         }
12225         if (f == NULL) {
12226             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12227             DisplayError(buf, errno);
12228             return FALSE;
12229         } else {
12230             safeStrCpy(buf, lastMsg, MSG_SIZ);
12231             DisplayMessage(_("Waiting for access to save file"), "");
12232             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12233             DisplayMessage(_("Saving game"), "");
12234             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12235             result = SaveGame(f, 0, NULL);
12236             DisplayMessage(buf, "");
12237             return result;
12238         }
12239     }
12240 }
12241
12242 char *
12243 SavePart (char *str)
12244 {
12245     static char buf[MSG_SIZ];
12246     char *p;
12247
12248     p = strchr(str, ' ');
12249     if (p == NULL) return str;
12250     strncpy(buf, str, p - str);
12251     buf[p - str] = NULLCHAR;
12252     return buf;
12253 }
12254
12255 #define PGN_MAX_LINE 75
12256
12257 #define PGN_SIDE_WHITE  0
12258 #define PGN_SIDE_BLACK  1
12259
12260 static int
12261 FindFirstMoveOutOfBook (int side)
12262 {
12263     int result = -1;
12264
12265     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12266         int index = backwardMostMove;
12267         int has_book_hit = 0;
12268
12269         if( (index % 2) != side ) {
12270             index++;
12271         }
12272
12273         while( index < forwardMostMove ) {
12274             /* Check to see if engine is in book */
12275             int depth = pvInfoList[index].depth;
12276             int score = pvInfoList[index].score;
12277             int in_book = 0;
12278
12279             if( depth <= 2 ) {
12280                 in_book = 1;
12281             }
12282             else if( score == 0 && depth == 63 ) {
12283                 in_book = 1; /* Zappa */
12284             }
12285             else if( score == 2 && depth == 99 ) {
12286                 in_book = 1; /* Abrok */
12287             }
12288
12289             has_book_hit += in_book;
12290
12291             if( ! in_book ) {
12292                 result = index;
12293
12294                 break;
12295             }
12296
12297             index += 2;
12298         }
12299     }
12300
12301     return result;
12302 }
12303
12304 void
12305 GetOutOfBookInfo (char * buf)
12306 {
12307     int oob[2];
12308     int i;
12309     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12310
12311     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12312     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12313
12314     *buf = '\0';
12315
12316     if( oob[0] >= 0 || oob[1] >= 0 ) {
12317         for( i=0; i<2; i++ ) {
12318             int idx = oob[i];
12319
12320             if( idx >= 0 ) {
12321                 if( i > 0 && oob[0] >= 0 ) {
12322                     strcat( buf, "   " );
12323                 }
12324
12325                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12326                 sprintf( buf+strlen(buf), "%s%.2f",
12327                     pvInfoList[idx].score >= 0 ? "+" : "",
12328                     pvInfoList[idx].score / 100.0 );
12329             }
12330         }
12331     }
12332 }
12333
12334 /* Save game in PGN style and close the file */
12335 int
12336 SaveGamePGN (FILE *f)
12337 {
12338     int i, offset, linelen, newblock;
12339     time_t tm;
12340 //    char *movetext;
12341     char numtext[32];
12342     int movelen, numlen, blank;
12343     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12344
12345     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12346
12347     tm = time((time_t *) NULL);
12348
12349     PrintPGNTags(f, &gameInfo);
12350
12351     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12352
12353     if (backwardMostMove > 0 || startedFromSetupPosition) {
12354         char *fen = PositionToFEN(backwardMostMove, NULL);
12355         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12356         fprintf(f, "\n{--------------\n");
12357         PrintPosition(f, backwardMostMove);
12358         fprintf(f, "--------------}\n");
12359         free(fen);
12360     }
12361     else {
12362         /* [AS] Out of book annotation */
12363         if( appData.saveOutOfBookInfo ) {
12364             char buf[64];
12365
12366             GetOutOfBookInfo( buf );
12367
12368             if( buf[0] != '\0' ) {
12369                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12370             }
12371         }
12372
12373         fprintf(f, "\n");
12374     }
12375
12376     i = backwardMostMove;
12377     linelen = 0;
12378     newblock = TRUE;
12379
12380     while (i < forwardMostMove) {
12381         /* Print comments preceding this move */
12382         if (commentList[i] != NULL) {
12383             if (linelen > 0) fprintf(f, "\n");
12384             fprintf(f, "%s", commentList[i]);
12385             linelen = 0;
12386             newblock = TRUE;
12387         }
12388
12389         /* Format move number */
12390         if ((i % 2) == 0)
12391           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12392         else
12393           if (newblock)
12394             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12395           else
12396             numtext[0] = NULLCHAR;
12397
12398         numlen = strlen(numtext);
12399         newblock = FALSE;
12400
12401         /* Print move number */
12402         blank = linelen > 0 && numlen > 0;
12403         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12404             fprintf(f, "\n");
12405             linelen = 0;
12406             blank = 0;
12407         }
12408         if (blank) {
12409             fprintf(f, " ");
12410             linelen++;
12411         }
12412         fprintf(f, "%s", numtext);
12413         linelen += numlen;
12414
12415         /* Get move */
12416         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12417         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12418
12419         /* Print move */
12420         blank = linelen > 0 && movelen > 0;
12421         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12422             fprintf(f, "\n");
12423             linelen = 0;
12424             blank = 0;
12425         }
12426         if (blank) {
12427             fprintf(f, " ");
12428             linelen++;
12429         }
12430         fprintf(f, "%s", move_buffer);
12431         linelen += movelen;
12432
12433         /* [AS] Add PV info if present */
12434         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12435             /* [HGM] add time */
12436             char buf[MSG_SIZ]; int seconds;
12437
12438             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12439
12440             if( seconds <= 0)
12441               buf[0] = 0;
12442             else
12443               if( seconds < 30 )
12444                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12445               else
12446                 {
12447                   seconds = (seconds + 4)/10; // round to full seconds
12448                   if( seconds < 60 )
12449                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12450                   else
12451                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12452                 }
12453
12454             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12455                       pvInfoList[i].score >= 0 ? "+" : "",
12456                       pvInfoList[i].score / 100.0,
12457                       pvInfoList[i].depth,
12458                       buf );
12459
12460             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12461
12462             /* Print score/depth */
12463             blank = linelen > 0 && movelen > 0;
12464             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12465                 fprintf(f, "\n");
12466                 linelen = 0;
12467                 blank = 0;
12468             }
12469             if (blank) {
12470                 fprintf(f, " ");
12471                 linelen++;
12472             }
12473             fprintf(f, "%s", move_buffer);
12474             linelen += movelen;
12475         }
12476
12477         i++;
12478     }
12479
12480     /* Start a new line */
12481     if (linelen > 0) fprintf(f, "\n");
12482
12483     /* Print comments after last move */
12484     if (commentList[i] != NULL) {
12485         fprintf(f, "%s\n", commentList[i]);
12486     }
12487
12488     /* Print result */
12489     if (gameInfo.resultDetails != NULL &&
12490         gameInfo.resultDetails[0] != NULLCHAR) {
12491         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12492                 PGNResult(gameInfo.result));
12493     } else {
12494         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12495     }
12496
12497     fclose(f);
12498     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12499     return TRUE;
12500 }
12501
12502 /* Save game in old style and close the file */
12503 int
12504 SaveGameOldStyle (FILE *f)
12505 {
12506     int i, offset;
12507     time_t tm;
12508
12509     tm = time((time_t *) NULL);
12510
12511     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12512     PrintOpponents(f);
12513
12514     if (backwardMostMove > 0 || startedFromSetupPosition) {
12515         fprintf(f, "\n[--------------\n");
12516         PrintPosition(f, backwardMostMove);
12517         fprintf(f, "--------------]\n");
12518     } else {
12519         fprintf(f, "\n");
12520     }
12521
12522     i = backwardMostMove;
12523     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12524
12525     while (i < forwardMostMove) {
12526         if (commentList[i] != NULL) {
12527             fprintf(f, "[%s]\n", commentList[i]);
12528         }
12529
12530         if ((i % 2) == 1) {
12531             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12532             i++;
12533         } else {
12534             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12535             i++;
12536             if (commentList[i] != NULL) {
12537                 fprintf(f, "\n");
12538                 continue;
12539             }
12540             if (i >= forwardMostMove) {
12541                 fprintf(f, "\n");
12542                 break;
12543             }
12544             fprintf(f, "%s\n", parseList[i]);
12545             i++;
12546         }
12547     }
12548
12549     if (commentList[i] != NULL) {
12550         fprintf(f, "[%s]\n", commentList[i]);
12551     }
12552
12553     /* This isn't really the old style, but it's close enough */
12554     if (gameInfo.resultDetails != NULL &&
12555         gameInfo.resultDetails[0] != NULLCHAR) {
12556         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12557                 gameInfo.resultDetails);
12558     } else {
12559         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12560     }
12561
12562     fclose(f);
12563     return TRUE;
12564 }
12565
12566 /* Save the current game to open file f and close the file */
12567 int
12568 SaveGame (FILE *f, int dummy, char *dummy2)
12569 {
12570     if (gameMode == EditPosition) EditPositionDone(TRUE);
12571     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12572     if (appData.oldSaveStyle)
12573       return SaveGameOldStyle(f);
12574     else
12575       return SaveGamePGN(f);
12576 }
12577
12578 /* Save the current position to the given file */
12579 int
12580 SavePositionToFile (char *filename)
12581 {
12582     FILE *f;
12583     char buf[MSG_SIZ];
12584
12585     if (strcmp(filename, "-") == 0) {
12586         return SavePosition(stdout, 0, NULL);
12587     } else {
12588         f = fopen(filename, "a");
12589         if (f == NULL) {
12590             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12591             DisplayError(buf, errno);
12592             return FALSE;
12593         } else {
12594             safeStrCpy(buf, lastMsg, MSG_SIZ);
12595             DisplayMessage(_("Waiting for access to save file"), "");
12596             flock(fileno(f), LOCK_EX); // [HGM] lock
12597             DisplayMessage(_("Saving position"), "");
12598             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12599             SavePosition(f, 0, NULL);
12600             DisplayMessage(buf, "");
12601             return TRUE;
12602         }
12603     }
12604 }
12605
12606 /* Save the current position to the given open file and close the file */
12607 int
12608 SavePosition (FILE *f, int dummy, char *dummy2)
12609 {
12610     time_t tm;
12611     char *fen;
12612
12613     if (gameMode == EditPosition) EditPositionDone(TRUE);
12614     if (appData.oldSaveStyle) {
12615         tm = time((time_t *) NULL);
12616
12617         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12618         PrintOpponents(f);
12619         fprintf(f, "[--------------\n");
12620         PrintPosition(f, currentMove);
12621         fprintf(f, "--------------]\n");
12622     } else {
12623         fen = PositionToFEN(currentMove, NULL);
12624         fprintf(f, "%s\n", fen);
12625         free(fen);
12626     }
12627     fclose(f);
12628     return TRUE;
12629 }
12630
12631 void
12632 ReloadCmailMsgEvent (int unregister)
12633 {
12634 #if !WIN32
12635     static char *inFilename = NULL;
12636     static char *outFilename;
12637     int i;
12638     struct stat inbuf, outbuf;
12639     int status;
12640
12641     /* Any registered moves are unregistered if unregister is set, */
12642     /* i.e. invoked by the signal handler */
12643     if (unregister) {
12644         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12645             cmailMoveRegistered[i] = FALSE;
12646             if (cmailCommentList[i] != NULL) {
12647                 free(cmailCommentList[i]);
12648                 cmailCommentList[i] = NULL;
12649             }
12650         }
12651         nCmailMovesRegistered = 0;
12652     }
12653
12654     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12655         cmailResult[i] = CMAIL_NOT_RESULT;
12656     }
12657     nCmailResults = 0;
12658
12659     if (inFilename == NULL) {
12660         /* Because the filenames are static they only get malloced once  */
12661         /* and they never get freed                                      */
12662         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12663         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12664
12665         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12666         sprintf(outFilename, "%s.out", appData.cmailGameName);
12667     }
12668
12669     status = stat(outFilename, &outbuf);
12670     if (status < 0) {
12671         cmailMailedMove = FALSE;
12672     } else {
12673         status = stat(inFilename, &inbuf);
12674         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12675     }
12676
12677     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12678        counts the games, notes how each one terminated, etc.
12679
12680        It would be nice to remove this kludge and instead gather all
12681        the information while building the game list.  (And to keep it
12682        in the game list nodes instead of having a bunch of fixed-size
12683        parallel arrays.)  Note this will require getting each game's
12684        termination from the PGN tags, as the game list builder does
12685        not process the game moves.  --mann
12686        */
12687     cmailMsgLoaded = TRUE;
12688     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12689
12690     /* Load first game in the file or popup game menu */
12691     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12692
12693 #endif /* !WIN32 */
12694     return;
12695 }
12696
12697 int
12698 RegisterMove ()
12699 {
12700     FILE *f;
12701     char string[MSG_SIZ];
12702
12703     if (   cmailMailedMove
12704         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12705         return TRUE;            /* Allow free viewing  */
12706     }
12707
12708     /* Unregister move to ensure that we don't leave RegisterMove        */
12709     /* with the move registered when the conditions for registering no   */
12710     /* longer hold                                                       */
12711     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12712         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12713         nCmailMovesRegistered --;
12714
12715         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12716           {
12717               free(cmailCommentList[lastLoadGameNumber - 1]);
12718               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12719           }
12720     }
12721
12722     if (cmailOldMove == -1) {
12723         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12724         return FALSE;
12725     }
12726
12727     if (currentMove > cmailOldMove + 1) {
12728         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12729         return FALSE;
12730     }
12731
12732     if (currentMove < cmailOldMove) {
12733         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12734         return FALSE;
12735     }
12736
12737     if (forwardMostMove > currentMove) {
12738         /* Silently truncate extra moves */
12739         TruncateGame();
12740     }
12741
12742     if (   (currentMove == cmailOldMove + 1)
12743         || (   (currentMove == cmailOldMove)
12744             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12745                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12746         if (gameInfo.result != GameUnfinished) {
12747             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12748         }
12749
12750         if (commentList[currentMove] != NULL) {
12751             cmailCommentList[lastLoadGameNumber - 1]
12752               = StrSave(commentList[currentMove]);
12753         }
12754         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12755
12756         if (appData.debugMode)
12757           fprintf(debugFP, "Saving %s for game %d\n",
12758                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12759
12760         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12761
12762         f = fopen(string, "w");
12763         if (appData.oldSaveStyle) {
12764             SaveGameOldStyle(f); /* also closes the file */
12765
12766             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12767             f = fopen(string, "w");
12768             SavePosition(f, 0, NULL); /* also closes the file */
12769         } else {
12770             fprintf(f, "{--------------\n");
12771             PrintPosition(f, currentMove);
12772             fprintf(f, "--------------}\n\n");
12773
12774             SaveGame(f, 0, NULL); /* also closes the file*/
12775         }
12776
12777         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12778         nCmailMovesRegistered ++;
12779     } else if (nCmailGames == 1) {
12780         DisplayError(_("You have not made a move yet"), 0);
12781         return FALSE;
12782     }
12783
12784     return TRUE;
12785 }
12786
12787 void
12788 MailMoveEvent ()
12789 {
12790 #if !WIN32
12791     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12792     FILE *commandOutput;
12793     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12794     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12795     int nBuffers;
12796     int i;
12797     int archived;
12798     char *arcDir;
12799
12800     if (! cmailMsgLoaded) {
12801         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12802         return;
12803     }
12804
12805     if (nCmailGames == nCmailResults) {
12806         DisplayError(_("No unfinished games"), 0);
12807         return;
12808     }
12809
12810 #if CMAIL_PROHIBIT_REMAIL
12811     if (cmailMailedMove) {
12812       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);
12813         DisplayError(msg, 0);
12814         return;
12815     }
12816 #endif
12817
12818     if (! (cmailMailedMove || RegisterMove())) return;
12819
12820     if (   cmailMailedMove
12821         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12822       snprintf(string, MSG_SIZ, partCommandString,
12823                appData.debugMode ? " -v" : "", appData.cmailGameName);
12824         commandOutput = popen(string, "r");
12825
12826         if (commandOutput == NULL) {
12827             DisplayError(_("Failed to invoke cmail"), 0);
12828         } else {
12829             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12830                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12831             }
12832             if (nBuffers > 1) {
12833                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12834                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12835                 nBytes = MSG_SIZ - 1;
12836             } else {
12837                 (void) memcpy(msg, buffer, nBytes);
12838             }
12839             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12840
12841             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12842                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12843
12844                 archived = TRUE;
12845                 for (i = 0; i < nCmailGames; i ++) {
12846                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12847                         archived = FALSE;
12848                     }
12849                 }
12850                 if (   archived
12851                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12852                         != NULL)) {
12853                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12854                            arcDir,
12855                            appData.cmailGameName,
12856                            gameInfo.date);
12857                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12858                     cmailMsgLoaded = FALSE;
12859                 }
12860             }
12861
12862             DisplayInformation(msg);
12863             pclose(commandOutput);
12864         }
12865     } else {
12866         if ((*cmailMsg) != '\0') {
12867             DisplayInformation(cmailMsg);
12868         }
12869     }
12870
12871     return;
12872 #endif /* !WIN32 */
12873 }
12874
12875 char *
12876 CmailMsg ()
12877 {
12878 #if WIN32
12879     return NULL;
12880 #else
12881     int  prependComma = 0;
12882     char number[5];
12883     char string[MSG_SIZ];       /* Space for game-list */
12884     int  i;
12885
12886     if (!cmailMsgLoaded) return "";
12887
12888     if (cmailMailedMove) {
12889       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12890     } else {
12891         /* Create a list of games left */
12892       snprintf(string, MSG_SIZ, "[");
12893         for (i = 0; i < nCmailGames; i ++) {
12894             if (! (   cmailMoveRegistered[i]
12895                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12896                 if (prependComma) {
12897                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12898                 } else {
12899                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12900                     prependComma = 1;
12901                 }
12902
12903                 strcat(string, number);
12904             }
12905         }
12906         strcat(string, "]");
12907
12908         if (nCmailMovesRegistered + nCmailResults == 0) {
12909             switch (nCmailGames) {
12910               case 1:
12911                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12912                 break;
12913
12914               case 2:
12915                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12916                 break;
12917
12918               default:
12919                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12920                          nCmailGames);
12921                 break;
12922             }
12923         } else {
12924             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12925               case 1:
12926                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12927                          string);
12928                 break;
12929
12930               case 0:
12931                 if (nCmailResults == nCmailGames) {
12932                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12933                 } else {
12934                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12935                 }
12936                 break;
12937
12938               default:
12939                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12940                          string);
12941             }
12942         }
12943     }
12944     return cmailMsg;
12945 #endif /* WIN32 */
12946 }
12947
12948 void
12949 ResetGameEvent ()
12950 {
12951     if (gameMode == Training)
12952       SetTrainingModeOff();
12953
12954     Reset(TRUE, TRUE);
12955     cmailMsgLoaded = FALSE;
12956     if (appData.icsActive) {
12957       SendToICS(ics_prefix);
12958       SendToICS("refresh\n");
12959     }
12960 }
12961
12962 void
12963 ExitEvent (int status)
12964 {
12965     exiting++;
12966     if (exiting > 2) {
12967       /* Give up on clean exit */
12968       exit(status);
12969     }
12970     if (exiting > 1) {
12971       /* Keep trying for clean exit */
12972       return;
12973     }
12974
12975     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12976
12977     if (telnetISR != NULL) {
12978       RemoveInputSource(telnetISR);
12979     }
12980     if (icsPR != NoProc) {
12981       DestroyChildProcess(icsPR, TRUE);
12982     }
12983
12984     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12985     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12986
12987     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12988     /* make sure this other one finishes before killing it!                  */
12989     if(endingGame) { int count = 0;
12990         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12991         while(endingGame && count++ < 10) DoSleep(1);
12992         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12993     }
12994
12995     /* Kill off chess programs */
12996     if (first.pr != NoProc) {
12997         ExitAnalyzeMode();
12998
12999         DoSleep( appData.delayBeforeQuit );
13000         SendToProgram("quit\n", &first);
13001         DoSleep( appData.delayAfterQuit );
13002         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13003     }
13004     if (second.pr != NoProc) {
13005         DoSleep( appData.delayBeforeQuit );
13006         SendToProgram("quit\n", &second);
13007         DoSleep( appData.delayAfterQuit );
13008         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13009     }
13010     if (first.isr != NULL) {
13011         RemoveInputSource(first.isr);
13012     }
13013     if (second.isr != NULL) {
13014         RemoveInputSource(second.isr);
13015     }
13016
13017     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13018     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13019
13020     ShutDownFrontEnd();
13021     exit(status);
13022 }
13023
13024 void
13025 PauseEvent ()
13026 {
13027     if (appData.debugMode)
13028         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13029     if (pausing) {
13030         pausing = FALSE;
13031         ModeHighlight();
13032         if (gameMode == MachinePlaysWhite ||
13033             gameMode == MachinePlaysBlack) {
13034             StartClocks();
13035         } else {
13036             DisplayBothClocks();
13037         }
13038         if (gameMode == PlayFromGameFile) {
13039             if (appData.timeDelay >= 0)
13040                 AutoPlayGameLoop();
13041         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13042             Reset(FALSE, TRUE);
13043             SendToICS(ics_prefix);
13044             SendToICS("refresh\n");
13045         } else if (currentMove < forwardMostMove) {
13046             ForwardInner(forwardMostMove);
13047         }
13048         pauseExamInvalid = FALSE;
13049     } else {
13050         switch (gameMode) {
13051           default:
13052             return;
13053           case IcsExamining:
13054             pauseExamForwardMostMove = forwardMostMove;
13055             pauseExamInvalid = FALSE;
13056             /* fall through */
13057           case IcsObserving:
13058           case IcsPlayingWhite:
13059           case IcsPlayingBlack:
13060             pausing = TRUE;
13061             ModeHighlight();
13062             return;
13063           case PlayFromGameFile:
13064             (void) StopLoadGameTimer();
13065             pausing = TRUE;
13066             ModeHighlight();
13067             break;
13068           case BeginningOfGame:
13069             if (appData.icsActive) return;
13070             /* else fall through */
13071           case MachinePlaysWhite:
13072           case MachinePlaysBlack:
13073           case TwoMachinesPlay:
13074             if (forwardMostMove == 0)
13075               return;           /* don't pause if no one has moved */
13076             if ((gameMode == MachinePlaysWhite &&
13077                  !WhiteOnMove(forwardMostMove)) ||
13078                 (gameMode == MachinePlaysBlack &&
13079                  WhiteOnMove(forwardMostMove))) {
13080                 StopClocks();
13081             }
13082             pausing = TRUE;
13083             ModeHighlight();
13084             break;
13085         }
13086     }
13087 }
13088
13089 void
13090 EditCommentEvent ()
13091 {
13092     char title[MSG_SIZ];
13093
13094     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13095       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13096     } else {
13097       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13098                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13099                parseList[currentMove - 1]);
13100     }
13101
13102     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13103 }
13104
13105
13106 void
13107 EditTagsEvent ()
13108 {
13109     char *tags = PGNTags(&gameInfo);
13110     bookUp = FALSE;
13111     EditTagsPopUp(tags, NULL);
13112     free(tags);
13113 }
13114
13115 void
13116 AnalyzeModeEvent ()
13117 {
13118     if (appData.noChessProgram || gameMode == AnalyzeMode)
13119       return;
13120
13121     if (gameMode != AnalyzeFile) {
13122         if (!appData.icsEngineAnalyze) {
13123                EditGameEvent();
13124                if (gameMode != EditGame) return;
13125         }
13126         ResurrectChessProgram();
13127         SendToProgram("analyze\n", &first);
13128         first.analyzing = TRUE;
13129         /*first.maybeThinking = TRUE;*/
13130         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13131         EngineOutputPopUp();
13132     }
13133     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13134     pausing = FALSE;
13135     ModeHighlight();
13136     SetGameInfo();
13137
13138     StartAnalysisClock();
13139     GetTimeMark(&lastNodeCountTime);
13140     lastNodeCount = 0;
13141 }
13142
13143 void
13144 AnalyzeFileEvent ()
13145 {
13146     if (appData.noChessProgram || gameMode == AnalyzeFile)
13147       return;
13148
13149     if (gameMode != AnalyzeMode) {
13150         EditGameEvent();
13151         if (gameMode != EditGame) return;
13152         ResurrectChessProgram();
13153         SendToProgram("analyze\n", &first);
13154         first.analyzing = TRUE;
13155         /*first.maybeThinking = TRUE;*/
13156         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13157         EngineOutputPopUp();
13158     }
13159     gameMode = AnalyzeFile;
13160     pausing = FALSE;
13161     ModeHighlight();
13162     SetGameInfo();
13163
13164     StartAnalysisClock();
13165     GetTimeMark(&lastNodeCountTime);
13166     lastNodeCount = 0;
13167     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13168 }
13169
13170 void
13171 MachineWhiteEvent ()
13172 {
13173     char buf[MSG_SIZ];
13174     char *bookHit = NULL;
13175
13176     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13177       return;
13178
13179
13180     if (gameMode == PlayFromGameFile ||
13181         gameMode == TwoMachinesPlay  ||
13182         gameMode == Training         ||
13183         gameMode == AnalyzeMode      ||
13184         gameMode == EndOfGame)
13185         EditGameEvent();
13186
13187     if (gameMode == EditPosition)
13188         EditPositionDone(TRUE);
13189
13190     if (!WhiteOnMove(currentMove)) {
13191         DisplayError(_("It is not White's turn"), 0);
13192         return;
13193     }
13194
13195     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13196       ExitAnalyzeMode();
13197
13198     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13199         gameMode == AnalyzeFile)
13200         TruncateGame();
13201
13202     ResurrectChessProgram();    /* in case it isn't running */
13203     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13204         gameMode = MachinePlaysWhite;
13205         ResetClocks();
13206     } else
13207     gameMode = MachinePlaysWhite;
13208     pausing = FALSE;
13209     ModeHighlight();
13210     SetGameInfo();
13211     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13212     DisplayTitle(buf);
13213     if (first.sendName) {
13214       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13215       SendToProgram(buf, &first);
13216     }
13217     if (first.sendTime) {
13218       if (first.useColors) {
13219         SendToProgram("black\n", &first); /*gnu kludge*/
13220       }
13221       SendTimeRemaining(&first, TRUE);
13222     }
13223     if (first.useColors) {
13224       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13225     }
13226     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13227     SetMachineThinkingEnables();
13228     first.maybeThinking = TRUE;
13229     StartClocks();
13230     firstMove = FALSE;
13231
13232     if (appData.autoFlipView && !flipView) {
13233       flipView = !flipView;
13234       DrawPosition(FALSE, NULL);
13235       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13236     }
13237
13238     if(bookHit) { // [HGM] book: simulate book reply
13239         static char bookMove[MSG_SIZ]; // a bit generous?
13240
13241         programStats.nodes = programStats.depth = programStats.time =
13242         programStats.score = programStats.got_only_move = 0;
13243         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13244
13245         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13246         strcat(bookMove, bookHit);
13247         HandleMachineMove(bookMove, &first);
13248     }
13249 }
13250
13251 void
13252 MachineBlackEvent ()
13253 {
13254   char buf[MSG_SIZ];
13255   char *bookHit = NULL;
13256
13257     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13258         return;
13259
13260
13261     if (gameMode == PlayFromGameFile ||
13262         gameMode == TwoMachinesPlay  ||
13263         gameMode == Training         ||
13264         gameMode == AnalyzeMode      ||
13265         gameMode == EndOfGame)
13266         EditGameEvent();
13267
13268     if (gameMode == EditPosition)
13269         EditPositionDone(TRUE);
13270
13271     if (WhiteOnMove(currentMove)) {
13272         DisplayError(_("It is not Black's turn"), 0);
13273         return;
13274     }
13275
13276     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13277       ExitAnalyzeMode();
13278
13279     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13280         gameMode == AnalyzeFile)
13281         TruncateGame();
13282
13283     ResurrectChessProgram();    /* in case it isn't running */
13284     gameMode = MachinePlaysBlack;
13285     pausing = FALSE;
13286     ModeHighlight();
13287     SetGameInfo();
13288     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13289     DisplayTitle(buf);
13290     if (first.sendName) {
13291       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13292       SendToProgram(buf, &first);
13293     }
13294     if (first.sendTime) {
13295       if (first.useColors) {
13296         SendToProgram("white\n", &first); /*gnu kludge*/
13297       }
13298       SendTimeRemaining(&first, FALSE);
13299     }
13300     if (first.useColors) {
13301       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13302     }
13303     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13304     SetMachineThinkingEnables();
13305     first.maybeThinking = TRUE;
13306     StartClocks();
13307
13308     if (appData.autoFlipView && flipView) {
13309       flipView = !flipView;
13310       DrawPosition(FALSE, NULL);
13311       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13312     }
13313     if(bookHit) { // [HGM] book: simulate book reply
13314         static char bookMove[MSG_SIZ]; // a bit generous?
13315
13316         programStats.nodes = programStats.depth = programStats.time =
13317         programStats.score = programStats.got_only_move = 0;
13318         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13319
13320         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13321         strcat(bookMove, bookHit);
13322         HandleMachineMove(bookMove, &first);
13323     }
13324 }
13325
13326
13327 void
13328 DisplayTwoMachinesTitle ()
13329 {
13330     char buf[MSG_SIZ];
13331     if (appData.matchGames > 0) {
13332         if(appData.tourneyFile[0]) {
13333           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13334                    gameInfo.white, _("vs."), gameInfo.black,
13335                    nextGame+1, appData.matchGames+1,
13336                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13337         } else 
13338         if (first.twoMachinesColor[0] == 'w') {
13339           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13340                    gameInfo.white, _("vs."),  gameInfo.black,
13341                    first.matchWins, second.matchWins,
13342                    matchGame - 1 - (first.matchWins + second.matchWins));
13343         } else {
13344           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13345                    gameInfo.white, _("vs."), gameInfo.black,
13346                    second.matchWins, first.matchWins,
13347                    matchGame - 1 - (first.matchWins + second.matchWins));
13348         }
13349     } else {
13350       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13351     }
13352     DisplayTitle(buf);
13353 }
13354
13355 void
13356 SettingsMenuIfReady ()
13357 {
13358   if (second.lastPing != second.lastPong) {
13359     DisplayMessage("", _("Waiting for second chess program"));
13360     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13361     return;
13362   }
13363   ThawUI();
13364   DisplayMessage("", "");
13365   SettingsPopUp(&second);
13366 }
13367
13368 int
13369 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13370 {
13371     char buf[MSG_SIZ];
13372     if (cps->pr == NoProc) {
13373         StartChessProgram(cps);
13374         if (cps->protocolVersion == 1) {
13375           retry();
13376         } else {
13377           /* kludge: allow timeout for initial "feature" command */
13378           FreezeUI();
13379           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13380           DisplayMessage("", buf);
13381           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13382         }
13383         return 1;
13384     }
13385     return 0;
13386 }
13387
13388 void
13389 TwoMachinesEvent P((void))
13390 {
13391     int i;
13392     char buf[MSG_SIZ];
13393     ChessProgramState *onmove;
13394     char *bookHit = NULL;
13395     static int stalling = 0;
13396     TimeMark now;
13397     long wait;
13398
13399     if (appData.noChessProgram) return;
13400
13401     switch (gameMode) {
13402       case TwoMachinesPlay:
13403         return;
13404       case MachinePlaysWhite:
13405       case MachinePlaysBlack:
13406         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13407             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13408             return;
13409         }
13410         /* fall through */
13411       case BeginningOfGame:
13412       case PlayFromGameFile:
13413       case EndOfGame:
13414         EditGameEvent();
13415         if (gameMode != EditGame) return;
13416         break;
13417       case EditPosition:
13418         EditPositionDone(TRUE);
13419         break;
13420       case AnalyzeMode:
13421       case AnalyzeFile:
13422         ExitAnalyzeMode();
13423         break;
13424       case EditGame:
13425       default:
13426         break;
13427     }
13428
13429 //    forwardMostMove = currentMove;
13430     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13431
13432     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13433
13434     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13435     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13436       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13437       return;
13438     }
13439     if(!stalling) {
13440       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13441       SendToProgram("force\n", &second);
13442       stalling = 1;
13443       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13444       return;
13445     }
13446     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13447     if(appData.matchPause>10000 || appData.matchPause<10)
13448                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13449     wait = SubtractTimeMarks(&now, &pauseStart);
13450     if(wait < appData.matchPause) {
13451         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13452         return;
13453     }
13454     // we are now committed to starting the game
13455     stalling = 0;
13456     DisplayMessage("", "");
13457     if (startedFromSetupPosition) {
13458         SendBoard(&second, backwardMostMove);
13459     if (appData.debugMode) {
13460         fprintf(debugFP, "Two Machines\n");
13461     }
13462     }
13463     for (i = backwardMostMove; i < forwardMostMove; i++) {
13464         SendMoveToProgram(i, &second);
13465     }
13466
13467     gameMode = TwoMachinesPlay;
13468     pausing = FALSE;
13469     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13470     SetGameInfo();
13471     DisplayTwoMachinesTitle();
13472     firstMove = TRUE;
13473     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13474         onmove = &first;
13475     } else {
13476         onmove = &second;
13477     }
13478     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13479     SendToProgram(first.computerString, &first);
13480     if (first.sendName) {
13481       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13482       SendToProgram(buf, &first);
13483     }
13484     SendToProgram(second.computerString, &second);
13485     if (second.sendName) {
13486       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13487       SendToProgram(buf, &second);
13488     }
13489
13490     ResetClocks();
13491     if (!first.sendTime || !second.sendTime) {
13492         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13493         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13494     }
13495     if (onmove->sendTime) {
13496       if (onmove->useColors) {
13497         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13498       }
13499       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13500     }
13501     if (onmove->useColors) {
13502       SendToProgram(onmove->twoMachinesColor, onmove);
13503     }
13504     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13505 //    SendToProgram("go\n", onmove);
13506     onmove->maybeThinking = TRUE;
13507     SetMachineThinkingEnables();
13508
13509     StartClocks();
13510
13511     if(bookHit) { // [HGM] book: simulate book reply
13512         static char bookMove[MSG_SIZ]; // a bit generous?
13513
13514         programStats.nodes = programStats.depth = programStats.time =
13515         programStats.score = programStats.got_only_move = 0;
13516         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13517
13518         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13519         strcat(bookMove, bookHit);
13520         savedMessage = bookMove; // args for deferred call
13521         savedState = onmove;
13522         ScheduleDelayedEvent(DeferredBookMove, 1);
13523     }
13524 }
13525
13526 void
13527 TrainingEvent ()
13528 {
13529     if (gameMode == Training) {
13530       SetTrainingModeOff();
13531       gameMode = PlayFromGameFile;
13532       DisplayMessage("", _("Training mode off"));
13533     } else {
13534       gameMode = Training;
13535       animateTraining = appData.animate;
13536
13537       /* make sure we are not already at the end of the game */
13538       if (currentMove < forwardMostMove) {
13539         SetTrainingModeOn();
13540         DisplayMessage("", _("Training mode on"));
13541       } else {
13542         gameMode = PlayFromGameFile;
13543         DisplayError(_("Already at end of game"), 0);
13544       }
13545     }
13546     ModeHighlight();
13547 }
13548
13549 void
13550 IcsClientEvent ()
13551 {
13552     if (!appData.icsActive) return;
13553     switch (gameMode) {
13554       case IcsPlayingWhite:
13555       case IcsPlayingBlack:
13556       case IcsObserving:
13557       case IcsIdle:
13558       case BeginningOfGame:
13559       case IcsExamining:
13560         return;
13561
13562       case EditGame:
13563         break;
13564
13565       case EditPosition:
13566         EditPositionDone(TRUE);
13567         break;
13568
13569       case AnalyzeMode:
13570       case AnalyzeFile:
13571         ExitAnalyzeMode();
13572         break;
13573
13574       default:
13575         EditGameEvent();
13576         break;
13577     }
13578
13579     gameMode = IcsIdle;
13580     ModeHighlight();
13581     return;
13582 }
13583
13584 void
13585 EditGameEvent ()
13586 {
13587     int i;
13588
13589     switch (gameMode) {
13590       case Training:
13591         SetTrainingModeOff();
13592         break;
13593       case MachinePlaysWhite:
13594       case MachinePlaysBlack:
13595       case BeginningOfGame:
13596         SendToProgram("force\n", &first);
13597         SetUserThinkingEnables();
13598         break;
13599       case PlayFromGameFile:
13600         (void) StopLoadGameTimer();
13601         if (gameFileFP != NULL) {
13602             gameFileFP = NULL;
13603         }
13604         break;
13605       case EditPosition:
13606         EditPositionDone(TRUE);
13607         break;
13608       case AnalyzeMode:
13609       case AnalyzeFile:
13610         ExitAnalyzeMode();
13611         SendToProgram("force\n", &first);
13612         break;
13613       case TwoMachinesPlay:
13614         GameEnds(EndOfFile, NULL, GE_PLAYER);
13615         ResurrectChessProgram();
13616         SetUserThinkingEnables();
13617         break;
13618       case EndOfGame:
13619         ResurrectChessProgram();
13620         break;
13621       case IcsPlayingBlack:
13622       case IcsPlayingWhite:
13623         DisplayError(_("Warning: You are still playing a game"), 0);
13624         break;
13625       case IcsObserving:
13626         DisplayError(_("Warning: You are still observing a game"), 0);
13627         break;
13628       case IcsExamining:
13629         DisplayError(_("Warning: You are still examining a game"), 0);
13630         break;
13631       case IcsIdle:
13632         break;
13633       case EditGame:
13634       default:
13635         return;
13636     }
13637
13638     pausing = FALSE;
13639     StopClocks();
13640     first.offeredDraw = second.offeredDraw = 0;
13641
13642     if (gameMode == PlayFromGameFile) {
13643         whiteTimeRemaining = timeRemaining[0][currentMove];
13644         blackTimeRemaining = timeRemaining[1][currentMove];
13645         DisplayTitle("");
13646     }
13647
13648     if (gameMode == MachinePlaysWhite ||
13649         gameMode == MachinePlaysBlack ||
13650         gameMode == TwoMachinesPlay ||
13651         gameMode == EndOfGame) {
13652         i = forwardMostMove;
13653         while (i > currentMove) {
13654             SendToProgram("undo\n", &first);
13655             i--;
13656         }
13657         if(!adjustedClock) {
13658         whiteTimeRemaining = timeRemaining[0][currentMove];
13659         blackTimeRemaining = timeRemaining[1][currentMove];
13660         DisplayBothClocks();
13661         }
13662         if (whiteFlag || blackFlag) {
13663             whiteFlag = blackFlag = 0;
13664         }
13665         DisplayTitle("");
13666     }
13667
13668     gameMode = EditGame;
13669     ModeHighlight();
13670     SetGameInfo();
13671 }
13672
13673
13674 void
13675 EditPositionEvent ()
13676 {
13677     if (gameMode == EditPosition) {
13678         EditGameEvent();
13679         return;
13680     }
13681
13682     EditGameEvent();
13683     if (gameMode != EditGame) return;
13684
13685     gameMode = EditPosition;
13686     ModeHighlight();
13687     SetGameInfo();
13688     if (currentMove > 0)
13689       CopyBoard(boards[0], boards[currentMove]);
13690
13691     blackPlaysFirst = !WhiteOnMove(currentMove);
13692     ResetClocks();
13693     currentMove = forwardMostMove = backwardMostMove = 0;
13694     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13695     DisplayMove(-1);
13696     if(!appData.pieceMenu) DisplayMessage("Click clock to clear board", "");
13697 }
13698
13699 void
13700 ExitAnalyzeMode ()
13701 {
13702     /* [DM] icsEngineAnalyze - possible call from other functions */
13703     if (appData.icsEngineAnalyze) {
13704         appData.icsEngineAnalyze = FALSE;
13705
13706         DisplayMessage("",_("Close ICS engine analyze..."));
13707     }
13708     if (first.analysisSupport && first.analyzing) {
13709       SendToProgram("exit\n", &first);
13710       first.analyzing = FALSE;
13711     }
13712     thinkOutput[0] = NULLCHAR;
13713 }
13714
13715 void
13716 EditPositionDone (Boolean fakeRights)
13717 {
13718     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13719
13720     startedFromSetupPosition = TRUE;
13721     InitChessProgram(&first, FALSE);
13722     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13723       boards[0][EP_STATUS] = EP_NONE;
13724       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13725     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13726         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13727         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13728       } else boards[0][CASTLING][2] = NoRights;
13729     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13730         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13731         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13732       } else boards[0][CASTLING][5] = NoRights;
13733     }
13734     SendToProgram("force\n", &first);
13735     if (blackPlaysFirst) {
13736         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13737         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13738         currentMove = forwardMostMove = backwardMostMove = 1;
13739         CopyBoard(boards[1], boards[0]);
13740     } else {
13741         currentMove = forwardMostMove = backwardMostMove = 0;
13742     }
13743     SendBoard(&first, forwardMostMove);
13744     if (appData.debugMode) {
13745         fprintf(debugFP, "EditPosDone\n");
13746     }
13747     DisplayTitle("");
13748     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13749     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13750     gameMode = EditGame;
13751     ModeHighlight();
13752     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13753     ClearHighlights(); /* [AS] */
13754 }
13755
13756 /* Pause for `ms' milliseconds */
13757 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13758 void
13759 TimeDelay (long ms)
13760 {
13761     TimeMark m1, m2;
13762
13763     GetTimeMark(&m1);
13764     do {
13765         GetTimeMark(&m2);
13766     } while (SubtractTimeMarks(&m2, &m1) < ms);
13767 }
13768
13769 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13770 void
13771 SendMultiLineToICS (char *buf)
13772 {
13773     char temp[MSG_SIZ+1], *p;
13774     int len;
13775
13776     len = strlen(buf);
13777     if (len > MSG_SIZ)
13778       len = MSG_SIZ;
13779
13780     strncpy(temp, buf, len);
13781     temp[len] = 0;
13782
13783     p = temp;
13784     while (*p) {
13785         if (*p == '\n' || *p == '\r')
13786           *p = ' ';
13787         ++p;
13788     }
13789
13790     strcat(temp, "\n");
13791     SendToICS(temp);
13792     SendToPlayer(temp, strlen(temp));
13793 }
13794
13795 void
13796 SetWhiteToPlayEvent ()
13797 {
13798     if (gameMode == EditPosition) {
13799         blackPlaysFirst = FALSE;
13800         DisplayBothClocks();    /* works because currentMove is 0 */
13801     } else if (gameMode == IcsExamining) {
13802         SendToICS(ics_prefix);
13803         SendToICS("tomove white\n");
13804     }
13805 }
13806
13807 void
13808 SetBlackToPlayEvent ()
13809 {
13810     if (gameMode == EditPosition) {
13811         blackPlaysFirst = TRUE;
13812         currentMove = 1;        /* kludge */
13813         DisplayBothClocks();
13814         currentMove = 0;
13815     } else if (gameMode == IcsExamining) {
13816         SendToICS(ics_prefix);
13817         SendToICS("tomove black\n");
13818     }
13819 }
13820
13821 void
13822 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13823 {
13824     char buf[MSG_SIZ];
13825     ChessSquare piece = boards[0][y][x];
13826
13827     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13828
13829     switch (selection) {
13830       case ClearBoard:
13831         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13832             SendToICS(ics_prefix);
13833             SendToICS("bsetup clear\n");
13834         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13835             SendToICS(ics_prefix);
13836             SendToICS("clearboard\n");
13837         } else {
13838             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13839                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13840                 for (y = 0; y < BOARD_HEIGHT; y++) {
13841                     if (gameMode == IcsExamining) {
13842                         if (boards[currentMove][y][x] != EmptySquare) {
13843                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13844                                     AAA + x, ONE + y);
13845                             SendToICS(buf);
13846                         }
13847                     } else {
13848                         boards[0][y][x] = p;
13849                     }
13850                 }
13851             }
13852         }
13853         if (gameMode == EditPosition) {
13854             DrawPosition(FALSE, boards[0]);
13855         }
13856         break;
13857
13858       case WhitePlay:
13859         SetWhiteToPlayEvent();
13860         break;
13861
13862       case BlackPlay:
13863         SetBlackToPlayEvent();
13864         break;
13865
13866       case EmptySquare:
13867         if (gameMode == IcsExamining) {
13868             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13869             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13870             SendToICS(buf);
13871         } else {
13872             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13873                 if(x == BOARD_LEFT-2) {
13874                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13875                     boards[0][y][1] = 0;
13876                 } else
13877                 if(x == BOARD_RGHT+1) {
13878                     if(y >= gameInfo.holdingsSize) break;
13879                     boards[0][y][BOARD_WIDTH-2] = 0;
13880                 } else break;
13881             }
13882             boards[0][y][x] = EmptySquare;
13883             DrawPosition(FALSE, boards[0]);
13884         }
13885         break;
13886
13887       case PromotePiece:
13888         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13889            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13890             selection = (ChessSquare) (PROMOTED piece);
13891         } else if(piece == EmptySquare) selection = WhiteSilver;
13892         else selection = (ChessSquare)((int)piece - 1);
13893         goto defaultlabel;
13894
13895       case DemotePiece:
13896         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13897            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13898             selection = (ChessSquare) (DEMOTED piece);
13899         } else if(piece == EmptySquare) selection = BlackSilver;
13900         else selection = (ChessSquare)((int)piece + 1);
13901         goto defaultlabel;
13902
13903       case WhiteQueen:
13904       case BlackQueen:
13905         if(gameInfo.variant == VariantShatranj ||
13906            gameInfo.variant == VariantXiangqi  ||
13907            gameInfo.variant == VariantCourier  ||
13908            gameInfo.variant == VariantMakruk     )
13909             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13910         goto defaultlabel;
13911
13912       case WhiteKing:
13913       case BlackKing:
13914         if(gameInfo.variant == VariantXiangqi)
13915             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13916         if(gameInfo.variant == VariantKnightmate)
13917             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13918       default:
13919         defaultlabel:
13920         if (gameMode == IcsExamining) {
13921             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13922             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13923                      PieceToChar(selection), AAA + x, ONE + y);
13924             SendToICS(buf);
13925         } else {
13926             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13927                 int n;
13928                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13929                     n = PieceToNumber(selection - BlackPawn);
13930                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13931                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13932                     boards[0][BOARD_HEIGHT-1-n][1]++;
13933                 } else
13934                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13935                     n = PieceToNumber(selection);
13936                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13937                     boards[0][n][BOARD_WIDTH-1] = selection;
13938                     boards[0][n][BOARD_WIDTH-2]++;
13939                 }
13940             } else
13941             boards[0][y][x] = selection;
13942             DrawPosition(TRUE, boards[0]);
13943             ClearHighlights();
13944             fromX = fromY = -1;
13945         }
13946         break;
13947     }
13948 }
13949
13950
13951 void
13952 DropMenuEvent (ChessSquare selection, int x, int y)
13953 {
13954     ChessMove moveType;
13955
13956     switch (gameMode) {
13957       case IcsPlayingWhite:
13958       case MachinePlaysBlack:
13959         if (!WhiteOnMove(currentMove)) {
13960             DisplayMoveError(_("It is Black's turn"));
13961             return;
13962         }
13963         moveType = WhiteDrop;
13964         break;
13965       case IcsPlayingBlack:
13966       case MachinePlaysWhite:
13967         if (WhiteOnMove(currentMove)) {
13968             DisplayMoveError(_("It is White's turn"));
13969             return;
13970         }
13971         moveType = BlackDrop;
13972         break;
13973       case EditGame:
13974         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13975         break;
13976       default:
13977         return;
13978     }
13979
13980     if (moveType == BlackDrop && selection < BlackPawn) {
13981       selection = (ChessSquare) ((int) selection
13982                                  + (int) BlackPawn - (int) WhitePawn);
13983     }
13984     if (boards[currentMove][y][x] != EmptySquare) {
13985         DisplayMoveError(_("That square is occupied"));
13986         return;
13987     }
13988
13989     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13990 }
13991
13992 void
13993 AcceptEvent ()
13994 {
13995     /* Accept a pending offer of any kind from opponent */
13996
13997     if (appData.icsActive) {
13998         SendToICS(ics_prefix);
13999         SendToICS("accept\n");
14000     } else if (cmailMsgLoaded) {
14001         if (currentMove == cmailOldMove &&
14002             commentList[cmailOldMove] != NULL &&
14003             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14004                    "Black offers a draw" : "White offers a draw")) {
14005             TruncateGame();
14006             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14007             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14008         } else {
14009             DisplayError(_("There is no pending offer on this move"), 0);
14010             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14011         }
14012     } else {
14013         /* Not used for offers from chess program */
14014     }
14015 }
14016
14017 void
14018 DeclineEvent ()
14019 {
14020     /* Decline a pending offer of any kind from opponent */
14021
14022     if (appData.icsActive) {
14023         SendToICS(ics_prefix);
14024         SendToICS("decline\n");
14025     } else if (cmailMsgLoaded) {
14026         if (currentMove == cmailOldMove &&
14027             commentList[cmailOldMove] != NULL &&
14028             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14029                    "Black offers a draw" : "White offers a draw")) {
14030 #ifdef NOTDEF
14031             AppendComment(cmailOldMove, "Draw declined", TRUE);
14032             DisplayComment(cmailOldMove - 1, "Draw declined");
14033 #endif /*NOTDEF*/
14034         } else {
14035             DisplayError(_("There is no pending offer on this move"), 0);
14036         }
14037     } else {
14038         /* Not used for offers from chess program */
14039     }
14040 }
14041
14042 void
14043 RematchEvent ()
14044 {
14045     /* Issue ICS rematch command */
14046     if (appData.icsActive) {
14047         SendToICS(ics_prefix);
14048         SendToICS("rematch\n");
14049     }
14050 }
14051
14052 void
14053 CallFlagEvent ()
14054 {
14055     /* Call your opponent's flag (claim a win on time) */
14056     if (appData.icsActive) {
14057         SendToICS(ics_prefix);
14058         SendToICS("flag\n");
14059     } else {
14060         switch (gameMode) {
14061           default:
14062             return;
14063           case MachinePlaysWhite:
14064             if (whiteFlag) {
14065                 if (blackFlag)
14066                   GameEnds(GameIsDrawn, "Both players ran out of time",
14067                            GE_PLAYER);
14068                 else
14069                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14070             } else {
14071                 DisplayError(_("Your opponent is not out of time"), 0);
14072             }
14073             break;
14074           case MachinePlaysBlack:
14075             if (blackFlag) {
14076                 if (whiteFlag)
14077                   GameEnds(GameIsDrawn, "Both players ran out of time",
14078                            GE_PLAYER);
14079                 else
14080                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14081             } else {
14082                 DisplayError(_("Your opponent is not out of time"), 0);
14083             }
14084             break;
14085         }
14086     }
14087 }
14088
14089 void
14090 ClockClick (int which)
14091 {       // [HGM] code moved to back-end from winboard.c
14092         if(which) { // black clock
14093           if (gameMode == EditPosition || gameMode == IcsExamining) {
14094             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14095             SetBlackToPlayEvent();
14096           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14097           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14098           } else if (shiftKey) {
14099             AdjustClock(which, -1);
14100           } else if (gameMode == IcsPlayingWhite ||
14101                      gameMode == MachinePlaysBlack) {
14102             CallFlagEvent();
14103           }
14104         } else { // white clock
14105           if (gameMode == EditPosition || gameMode == IcsExamining) {
14106             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14107             SetWhiteToPlayEvent();
14108           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14109           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14110           } else if (shiftKey) {
14111             AdjustClock(which, -1);
14112           } else if (gameMode == IcsPlayingBlack ||
14113                    gameMode == MachinePlaysWhite) {
14114             CallFlagEvent();
14115           }
14116         }
14117 }
14118
14119 void
14120 DrawEvent ()
14121 {
14122     /* Offer draw or accept pending draw offer from opponent */
14123
14124     if (appData.icsActive) {
14125         /* Note: tournament rules require draw offers to be
14126            made after you make your move but before you punch
14127            your clock.  Currently ICS doesn't let you do that;
14128            instead, you immediately punch your clock after making
14129            a move, but you can offer a draw at any time. */
14130
14131         SendToICS(ics_prefix);
14132         SendToICS("draw\n");
14133         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14134     } else if (cmailMsgLoaded) {
14135         if (currentMove == cmailOldMove &&
14136             commentList[cmailOldMove] != NULL &&
14137             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14138                    "Black offers a draw" : "White offers a draw")) {
14139             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14140             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14141         } else if (currentMove == cmailOldMove + 1) {
14142             char *offer = WhiteOnMove(cmailOldMove) ?
14143               "White offers a draw" : "Black offers a draw";
14144             AppendComment(currentMove, offer, TRUE);
14145             DisplayComment(currentMove - 1, offer);
14146             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14147         } else {
14148             DisplayError(_("You must make your move before offering a draw"), 0);
14149             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14150         }
14151     } else if (first.offeredDraw) {
14152         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14153     } else {
14154         if (first.sendDrawOffers) {
14155             SendToProgram("draw\n", &first);
14156             userOfferedDraw = TRUE;
14157         }
14158     }
14159 }
14160
14161 void
14162 AdjournEvent ()
14163 {
14164     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14165
14166     if (appData.icsActive) {
14167         SendToICS(ics_prefix);
14168         SendToICS("adjourn\n");
14169     } else {
14170         /* Currently GNU Chess doesn't offer or accept Adjourns */
14171     }
14172 }
14173
14174
14175 void
14176 AbortEvent ()
14177 {
14178     /* Offer Abort or accept pending Abort offer from opponent */
14179
14180     if (appData.icsActive) {
14181         SendToICS(ics_prefix);
14182         SendToICS("abort\n");
14183     } else {
14184         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14185     }
14186 }
14187
14188 void
14189 ResignEvent ()
14190 {
14191     /* Resign.  You can do this even if it's not your turn. */
14192
14193     if (appData.icsActive) {
14194         SendToICS(ics_prefix);
14195         SendToICS("resign\n");
14196     } else {
14197         switch (gameMode) {
14198           case MachinePlaysWhite:
14199             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14200             break;
14201           case MachinePlaysBlack:
14202             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14203             break;
14204           case EditGame:
14205             if (cmailMsgLoaded) {
14206                 TruncateGame();
14207                 if (WhiteOnMove(cmailOldMove)) {
14208                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14209                 } else {
14210                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14211                 }
14212                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14213             }
14214             break;
14215           default:
14216             break;
14217         }
14218     }
14219 }
14220
14221
14222 void
14223 StopObservingEvent ()
14224 {
14225     /* Stop observing current games */
14226     SendToICS(ics_prefix);
14227     SendToICS("unobserve\n");
14228 }
14229
14230 void
14231 StopExaminingEvent ()
14232 {
14233     /* Stop observing current game */
14234     SendToICS(ics_prefix);
14235     SendToICS("unexamine\n");
14236 }
14237
14238 void
14239 ForwardInner (int target)
14240 {
14241     int limit; int oldSeekGraphUp = seekGraphUp;
14242
14243     if (appData.debugMode)
14244         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14245                 target, currentMove, forwardMostMove);
14246
14247     if (gameMode == EditPosition)
14248       return;
14249
14250     seekGraphUp = FALSE;
14251     MarkTargetSquares(1);
14252
14253     if (gameMode == PlayFromGameFile && !pausing)
14254       PauseEvent();
14255
14256     if (gameMode == IcsExamining && pausing)
14257       limit = pauseExamForwardMostMove;
14258     else
14259       limit = forwardMostMove;
14260
14261     if (target > limit) target = limit;
14262
14263     if (target > 0 && moveList[target - 1][0]) {
14264         int fromX, fromY, toX, toY;
14265         toX = moveList[target - 1][2] - AAA;
14266         toY = moveList[target - 1][3] - ONE;
14267         if (moveList[target - 1][1] == '@') {
14268             if (appData.highlightLastMove) {
14269                 SetHighlights(-1, -1, toX, toY);
14270             }
14271         } else {
14272             fromX = moveList[target - 1][0] - AAA;
14273             fromY = moveList[target - 1][1] - ONE;
14274             if (target == currentMove + 1) {
14275                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14276             }
14277             if (appData.highlightLastMove) {
14278                 SetHighlights(fromX, fromY, toX, toY);
14279             }
14280         }
14281     }
14282     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14283         gameMode == Training || gameMode == PlayFromGameFile ||
14284         gameMode == AnalyzeFile) {
14285         while (currentMove < target) {
14286             SendMoveToProgram(currentMove++, &first);
14287         }
14288     } else {
14289         currentMove = target;
14290     }
14291
14292     if (gameMode == EditGame || gameMode == EndOfGame) {
14293         whiteTimeRemaining = timeRemaining[0][currentMove];
14294         blackTimeRemaining = timeRemaining[1][currentMove];
14295     }
14296     DisplayBothClocks();
14297     DisplayMove(currentMove - 1);
14298     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14299     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14300     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14301         DisplayComment(currentMove - 1, commentList[currentMove]);
14302     }
14303 }
14304
14305
14306 void
14307 ForwardEvent ()
14308 {
14309     if (gameMode == IcsExamining && !pausing) {
14310         SendToICS(ics_prefix);
14311         SendToICS("forward\n");
14312     } else {
14313         ForwardInner(currentMove + 1);
14314     }
14315 }
14316
14317 void
14318 ToEndEvent ()
14319 {
14320     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14321         /* to optimze, we temporarily turn off analysis mode while we feed
14322          * the remaining moves to the engine. Otherwise we get analysis output
14323          * after each move.
14324          */
14325         if (first.analysisSupport) {
14326           SendToProgram("exit\nforce\n", &first);
14327           first.analyzing = FALSE;
14328         }
14329     }
14330
14331     if (gameMode == IcsExamining && !pausing) {
14332         SendToICS(ics_prefix);
14333         SendToICS("forward 999999\n");
14334     } else {
14335         ForwardInner(forwardMostMove);
14336     }
14337
14338     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14339         /* we have fed all the moves, so reactivate analysis mode */
14340         SendToProgram("analyze\n", &first);
14341         first.analyzing = TRUE;
14342         /*first.maybeThinking = TRUE;*/
14343         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14344     }
14345 }
14346
14347 void
14348 BackwardInner (int target)
14349 {
14350     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14351
14352     if (appData.debugMode)
14353         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14354                 target, currentMove, forwardMostMove);
14355
14356     if (gameMode == EditPosition) return;
14357     seekGraphUp = FALSE;
14358     MarkTargetSquares(1);
14359     if (currentMove <= backwardMostMove) {
14360         ClearHighlights();
14361         DrawPosition(full_redraw, boards[currentMove]);
14362         return;
14363     }
14364     if (gameMode == PlayFromGameFile && !pausing)
14365       PauseEvent();
14366
14367     if (moveList[target][0]) {
14368         int fromX, fromY, toX, toY;
14369         toX = moveList[target][2] - AAA;
14370         toY = moveList[target][3] - ONE;
14371         if (moveList[target][1] == '@') {
14372             if (appData.highlightLastMove) {
14373                 SetHighlights(-1, -1, toX, toY);
14374             }
14375         } else {
14376             fromX = moveList[target][0] - AAA;
14377             fromY = moveList[target][1] - ONE;
14378             if (target == currentMove - 1) {
14379                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14380             }
14381             if (appData.highlightLastMove) {
14382                 SetHighlights(fromX, fromY, toX, toY);
14383             }
14384         }
14385     }
14386     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14387         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14388         while (currentMove > target) {
14389             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14390                 // null move cannot be undone. Reload program with move history before it.
14391                 int i;
14392                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14393                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14394                 }
14395                 SendBoard(&first, i); 
14396                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14397                 break;
14398             }
14399             SendToProgram("undo\n", &first);
14400             currentMove--;
14401         }
14402     } else {
14403         currentMove = target;
14404     }
14405
14406     if (gameMode == EditGame || gameMode == EndOfGame) {
14407         whiteTimeRemaining = timeRemaining[0][currentMove];
14408         blackTimeRemaining = timeRemaining[1][currentMove];
14409     }
14410     DisplayBothClocks();
14411     DisplayMove(currentMove - 1);
14412     DrawPosition(full_redraw, boards[currentMove]);
14413     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14414     // [HGM] PV info: routine tests if comment empty
14415     DisplayComment(currentMove - 1, commentList[currentMove]);
14416 }
14417
14418 void
14419 BackwardEvent ()
14420 {
14421     if (gameMode == IcsExamining && !pausing) {
14422         SendToICS(ics_prefix);
14423         SendToICS("backward\n");
14424     } else {
14425         BackwardInner(currentMove - 1);
14426     }
14427 }
14428
14429 void
14430 ToStartEvent ()
14431 {
14432     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14433         /* to optimize, we temporarily turn off analysis mode while we undo
14434          * all the moves. Otherwise we get analysis output after each undo.
14435          */
14436         if (first.analysisSupport) {
14437           SendToProgram("exit\nforce\n", &first);
14438           first.analyzing = FALSE;
14439         }
14440     }
14441
14442     if (gameMode == IcsExamining && !pausing) {
14443         SendToICS(ics_prefix);
14444         SendToICS("backward 999999\n");
14445     } else {
14446         BackwardInner(backwardMostMove);
14447     }
14448
14449     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14450         /* we have fed all the moves, so reactivate analysis mode */
14451         SendToProgram("analyze\n", &first);
14452         first.analyzing = TRUE;
14453         /*first.maybeThinking = TRUE;*/
14454         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14455     }
14456 }
14457
14458 void
14459 ToNrEvent (int to)
14460 {
14461   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14462   if (to >= forwardMostMove) to = forwardMostMove;
14463   if (to <= backwardMostMove) to = backwardMostMove;
14464   if (to < currentMove) {
14465     BackwardInner(to);
14466   } else {
14467     ForwardInner(to);
14468   }
14469 }
14470
14471 void
14472 RevertEvent (Boolean annotate)
14473 {
14474     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14475         return;
14476     }
14477     if (gameMode != IcsExamining) {
14478         DisplayError(_("You are not examining a game"), 0);
14479         return;
14480     }
14481     if (pausing) {
14482         DisplayError(_("You can't revert while pausing"), 0);
14483         return;
14484     }
14485     SendToICS(ics_prefix);
14486     SendToICS("revert\n");
14487 }
14488
14489 void
14490 RetractMoveEvent ()
14491 {
14492     switch (gameMode) {
14493       case MachinePlaysWhite:
14494       case MachinePlaysBlack:
14495         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14496             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14497             return;
14498         }
14499         if (forwardMostMove < 2) return;
14500         currentMove = forwardMostMove = forwardMostMove - 2;
14501         whiteTimeRemaining = timeRemaining[0][currentMove];
14502         blackTimeRemaining = timeRemaining[1][currentMove];
14503         DisplayBothClocks();
14504         DisplayMove(currentMove - 1);
14505         ClearHighlights();/*!! could figure this out*/
14506         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14507         SendToProgram("remove\n", &first);
14508         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14509         break;
14510
14511       case BeginningOfGame:
14512       default:
14513         break;
14514
14515       case IcsPlayingWhite:
14516       case IcsPlayingBlack:
14517         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14518             SendToICS(ics_prefix);
14519             SendToICS("takeback 2\n");
14520         } else {
14521             SendToICS(ics_prefix);
14522             SendToICS("takeback 1\n");
14523         }
14524         break;
14525     }
14526 }
14527
14528 void
14529 MoveNowEvent ()
14530 {
14531     ChessProgramState *cps;
14532
14533     switch (gameMode) {
14534       case MachinePlaysWhite:
14535         if (!WhiteOnMove(forwardMostMove)) {
14536             DisplayError(_("It is your turn"), 0);
14537             return;
14538         }
14539         cps = &first;
14540         break;
14541       case MachinePlaysBlack:
14542         if (WhiteOnMove(forwardMostMove)) {
14543             DisplayError(_("It is your turn"), 0);
14544             return;
14545         }
14546         cps = &first;
14547         break;
14548       case TwoMachinesPlay:
14549         if (WhiteOnMove(forwardMostMove) ==
14550             (first.twoMachinesColor[0] == 'w')) {
14551             cps = &first;
14552         } else {
14553             cps = &second;
14554         }
14555         break;
14556       case BeginningOfGame:
14557       default:
14558         return;
14559     }
14560     SendToProgram("?\n", cps);
14561 }
14562
14563 void
14564 TruncateGameEvent ()
14565 {
14566     EditGameEvent();
14567     if (gameMode != EditGame) return;
14568     TruncateGame();
14569 }
14570
14571 void
14572 TruncateGame ()
14573 {
14574     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14575     if (forwardMostMove > currentMove) {
14576         if (gameInfo.resultDetails != NULL) {
14577             free(gameInfo.resultDetails);
14578             gameInfo.resultDetails = NULL;
14579             gameInfo.result = GameUnfinished;
14580         }
14581         forwardMostMove = currentMove;
14582         HistorySet(parseList, backwardMostMove, forwardMostMove,
14583                    currentMove-1);
14584     }
14585 }
14586
14587 void
14588 HintEvent ()
14589 {
14590     if (appData.noChessProgram) return;
14591     switch (gameMode) {
14592       case MachinePlaysWhite:
14593         if (WhiteOnMove(forwardMostMove)) {
14594             DisplayError(_("Wait until your turn"), 0);
14595             return;
14596         }
14597         break;
14598       case BeginningOfGame:
14599       case MachinePlaysBlack:
14600         if (!WhiteOnMove(forwardMostMove)) {
14601             DisplayError(_("Wait until your turn"), 0);
14602             return;
14603         }
14604         break;
14605       default:
14606         DisplayError(_("No hint available"), 0);
14607         return;
14608     }
14609     SendToProgram("hint\n", &first);
14610     hintRequested = TRUE;
14611 }
14612
14613 void
14614 BookEvent ()
14615 {
14616     if (appData.noChessProgram) return;
14617     switch (gameMode) {
14618       case MachinePlaysWhite:
14619         if (WhiteOnMove(forwardMostMove)) {
14620             DisplayError(_("Wait until your turn"), 0);
14621             return;
14622         }
14623         break;
14624       case BeginningOfGame:
14625       case MachinePlaysBlack:
14626         if (!WhiteOnMove(forwardMostMove)) {
14627             DisplayError(_("Wait until your turn"), 0);
14628             return;
14629         }
14630         break;
14631       case EditPosition:
14632         EditPositionDone(TRUE);
14633         break;
14634       case TwoMachinesPlay:
14635         return;
14636       default:
14637         break;
14638     }
14639     SendToProgram("bk\n", &first);
14640     bookOutput[0] = NULLCHAR;
14641     bookRequested = TRUE;
14642 }
14643
14644 void
14645 AboutGameEvent ()
14646 {
14647     char *tags = PGNTags(&gameInfo);
14648     TagsPopUp(tags, CmailMsg());
14649     free(tags);
14650 }
14651
14652 /* end button procedures */
14653
14654 void
14655 PrintPosition (FILE *fp, int move)
14656 {
14657     int i, j;
14658
14659     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14660         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14661             char c = PieceToChar(boards[move][i][j]);
14662             fputc(c == 'x' ? '.' : c, fp);
14663             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14664         }
14665     }
14666     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14667       fprintf(fp, "white to play\n");
14668     else
14669       fprintf(fp, "black to play\n");
14670 }
14671
14672 void
14673 PrintOpponents (FILE *fp)
14674 {
14675     if (gameInfo.white != NULL) {
14676         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14677     } else {
14678         fprintf(fp, "\n");
14679     }
14680 }
14681
14682 /* Find last component of program's own name, using some heuristics */
14683 void
14684 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14685 {
14686     char *p, *q, c;
14687     int local = (strcmp(host, "localhost") == 0);
14688     while (!local && (p = strchr(prog, ';')) != NULL) {
14689         p++;
14690         while (*p == ' ') p++;
14691         prog = p;
14692     }
14693     if (*prog == '"' || *prog == '\'') {
14694         q = strchr(prog + 1, *prog);
14695     } else {
14696         q = strchr(prog, ' ');
14697     }
14698     if (q == NULL) q = prog + strlen(prog);
14699     p = q;
14700     while (p >= prog && *p != '/' && *p != '\\') p--;
14701     p++;
14702     if(p == prog && *p == '"') p++;
14703     c = *q; *q = 0;
14704     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14705     memcpy(buf, p, q - p);
14706     buf[q - p] = NULLCHAR;
14707     if (!local) {
14708         strcat(buf, "@");
14709         strcat(buf, host);
14710     }
14711 }
14712
14713 char *
14714 TimeControlTagValue ()
14715 {
14716     char buf[MSG_SIZ];
14717     if (!appData.clockMode) {
14718       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14719     } else if (movesPerSession > 0) {
14720       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14721     } else if (timeIncrement == 0) {
14722       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14723     } else {
14724       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14725     }
14726     return StrSave(buf);
14727 }
14728
14729 void
14730 SetGameInfo ()
14731 {
14732     /* This routine is used only for certain modes */
14733     VariantClass v = gameInfo.variant;
14734     ChessMove r = GameUnfinished;
14735     char *p = NULL;
14736
14737     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14738         r = gameInfo.result;
14739         p = gameInfo.resultDetails;
14740         gameInfo.resultDetails = NULL;
14741     }
14742     ClearGameInfo(&gameInfo);
14743     gameInfo.variant = v;
14744
14745     switch (gameMode) {
14746       case MachinePlaysWhite:
14747         gameInfo.event = StrSave( appData.pgnEventHeader );
14748         gameInfo.site = StrSave(HostName());
14749         gameInfo.date = PGNDate();
14750         gameInfo.round = StrSave("-");
14751         gameInfo.white = StrSave(first.tidy);
14752         gameInfo.black = StrSave(UserName());
14753         gameInfo.timeControl = TimeControlTagValue();
14754         break;
14755
14756       case MachinePlaysBlack:
14757         gameInfo.event = StrSave( appData.pgnEventHeader );
14758         gameInfo.site = StrSave(HostName());
14759         gameInfo.date = PGNDate();
14760         gameInfo.round = StrSave("-");
14761         gameInfo.white = StrSave(UserName());
14762         gameInfo.black = StrSave(first.tidy);
14763         gameInfo.timeControl = TimeControlTagValue();
14764         break;
14765
14766       case TwoMachinesPlay:
14767         gameInfo.event = StrSave( appData.pgnEventHeader );
14768         gameInfo.site = StrSave(HostName());
14769         gameInfo.date = PGNDate();
14770         if (roundNr > 0) {
14771             char buf[MSG_SIZ];
14772             snprintf(buf, MSG_SIZ, "%d", roundNr);
14773             gameInfo.round = StrSave(buf);
14774         } else {
14775             gameInfo.round = StrSave("-");
14776         }
14777         if (first.twoMachinesColor[0] == 'w') {
14778             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14779             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14780         } else {
14781             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14782             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14783         }
14784         gameInfo.timeControl = TimeControlTagValue();
14785         break;
14786
14787       case EditGame:
14788         gameInfo.event = StrSave("Edited game");
14789         gameInfo.site = StrSave(HostName());
14790         gameInfo.date = PGNDate();
14791         gameInfo.round = StrSave("-");
14792         gameInfo.white = StrSave("-");
14793         gameInfo.black = StrSave("-");
14794         gameInfo.result = r;
14795         gameInfo.resultDetails = p;
14796         break;
14797
14798       case EditPosition:
14799         gameInfo.event = StrSave("Edited position");
14800         gameInfo.site = StrSave(HostName());
14801         gameInfo.date = PGNDate();
14802         gameInfo.round = StrSave("-");
14803         gameInfo.white = StrSave("-");
14804         gameInfo.black = StrSave("-");
14805         break;
14806
14807       case IcsPlayingWhite:
14808       case IcsPlayingBlack:
14809       case IcsObserving:
14810       case IcsExamining:
14811         break;
14812
14813       case PlayFromGameFile:
14814         gameInfo.event = StrSave("Game from non-PGN file");
14815         gameInfo.site = StrSave(HostName());
14816         gameInfo.date = PGNDate();
14817         gameInfo.round = StrSave("-");
14818         gameInfo.white = StrSave("?");
14819         gameInfo.black = StrSave("?");
14820         break;
14821
14822       default:
14823         break;
14824     }
14825 }
14826
14827 void
14828 ReplaceComment (int index, char *text)
14829 {
14830     int len;
14831     char *p;
14832     float score;
14833
14834     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14835        pvInfoList[index-1].depth == len &&
14836        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14837        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14838     while (*text == '\n') text++;
14839     len = strlen(text);
14840     while (len > 0 && text[len - 1] == '\n') len--;
14841
14842     if (commentList[index] != NULL)
14843       free(commentList[index]);
14844
14845     if (len == 0) {
14846         commentList[index] = NULL;
14847         return;
14848     }
14849   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14850       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14851       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14852     commentList[index] = (char *) malloc(len + 2);
14853     strncpy(commentList[index], text, len);
14854     commentList[index][len] = '\n';
14855     commentList[index][len + 1] = NULLCHAR;
14856   } else {
14857     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14858     char *p;
14859     commentList[index] = (char *) malloc(len + 7);
14860     safeStrCpy(commentList[index], "{\n", 3);
14861     safeStrCpy(commentList[index]+2, text, len+1);
14862     commentList[index][len+2] = NULLCHAR;
14863     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14864     strcat(commentList[index], "\n}\n");
14865   }
14866 }
14867
14868 void
14869 CrushCRs (char *text)
14870 {
14871   char *p = text;
14872   char *q = text;
14873   char ch;
14874
14875   do {
14876     ch = *p++;
14877     if (ch == '\r') continue;
14878     *q++ = ch;
14879   } while (ch != '\0');
14880 }
14881
14882 void
14883 AppendComment (int index, char *text, Boolean addBraces)
14884 /* addBraces  tells if we should add {} */
14885 {
14886     int oldlen, len;
14887     char *old;
14888
14889 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14890     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14891
14892     CrushCRs(text);
14893     while (*text == '\n') text++;
14894     len = strlen(text);
14895     while (len > 0 && text[len - 1] == '\n') len--;
14896     text[len] = NULLCHAR;
14897
14898     if (len == 0) return;
14899
14900     if (commentList[index] != NULL) {
14901       Boolean addClosingBrace = addBraces;
14902         old = commentList[index];
14903         oldlen = strlen(old);
14904         while(commentList[index][oldlen-1] ==  '\n')
14905           commentList[index][--oldlen] = NULLCHAR;
14906         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14907         safeStrCpy(commentList[index], old, oldlen + len + 6);
14908         free(old);
14909         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14910         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14911           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14912           while (*text == '\n') { text++; len--; }
14913           commentList[index][--oldlen] = NULLCHAR;
14914       }
14915         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14916         else          strcat(commentList[index], "\n");
14917         strcat(commentList[index], text);
14918         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14919         else          strcat(commentList[index], "\n");
14920     } else {
14921         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14922         if(addBraces)
14923           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14924         else commentList[index][0] = NULLCHAR;
14925         strcat(commentList[index], text);
14926         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14927         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14928     }
14929 }
14930
14931 static char *
14932 FindStr (char * text, char * sub_text)
14933 {
14934     char * result = strstr( text, sub_text );
14935
14936     if( result != NULL ) {
14937         result += strlen( sub_text );
14938     }
14939
14940     return result;
14941 }
14942
14943 /* [AS] Try to extract PV info from PGN comment */
14944 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14945 char *
14946 GetInfoFromComment (int index, char * text)
14947 {
14948     char * sep = text, *p;
14949
14950     if( text != NULL && index > 0 ) {
14951         int score = 0;
14952         int depth = 0;
14953         int time = -1, sec = 0, deci;
14954         char * s_eval = FindStr( text, "[%eval " );
14955         char * s_emt = FindStr( text, "[%emt " );
14956
14957         if( s_eval != NULL || s_emt != NULL ) {
14958             /* New style */
14959             char delim;
14960
14961             if( s_eval != NULL ) {
14962                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14963                     return text;
14964                 }
14965
14966                 if( delim != ']' ) {
14967                     return text;
14968                 }
14969             }
14970
14971             if( s_emt != NULL ) {
14972             }
14973                 return text;
14974         }
14975         else {
14976             /* We expect something like: [+|-]nnn.nn/dd */
14977             int score_lo = 0;
14978
14979             if(*text != '{') return text; // [HGM] braces: must be normal comment
14980
14981             sep = strchr( text, '/' );
14982             if( sep == NULL || sep < (text+4) ) {
14983                 return text;
14984             }
14985
14986             p = text;
14987             if(p[1] == '(') { // comment starts with PV
14988                p = strchr(p, ')'); // locate end of PV
14989                if(p == NULL || sep < p+5) return text;
14990                // at this point we have something like "{(.*) +0.23/6 ..."
14991                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14992                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14993                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14994             }
14995             time = -1; sec = -1; deci = -1;
14996             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14997                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14998                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14999                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15000                 return text;
15001             }
15002
15003             if( score_lo < 0 || score_lo >= 100 ) {
15004                 return text;
15005             }
15006
15007             if(sec >= 0) time = 600*time + 10*sec; else
15008             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15009
15010             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15011
15012             /* [HGM] PV time: now locate end of PV info */
15013             while( *++sep >= '0' && *sep <= '9'); // strip depth
15014             if(time >= 0)
15015             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15016             if(sec >= 0)
15017             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15018             if(deci >= 0)
15019             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15020             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15021         }
15022
15023         if( depth <= 0 ) {
15024             return text;
15025         }
15026
15027         if( time < 0 ) {
15028             time = -1;
15029         }
15030
15031         pvInfoList[index-1].depth = depth;
15032         pvInfoList[index-1].score = score;
15033         pvInfoList[index-1].time  = 10*time; // centi-sec
15034         if(*sep == '}') *sep = 0; else *--sep = '{';
15035         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15036     }
15037     return sep;
15038 }
15039
15040 void
15041 SendToProgram (char *message, ChessProgramState *cps)
15042 {
15043     int count, outCount, error;
15044     char buf[MSG_SIZ];
15045
15046     if (cps->pr == NoProc) return;
15047     Attention(cps);
15048
15049     if (appData.debugMode) {
15050         TimeMark now;
15051         GetTimeMark(&now);
15052         fprintf(debugFP, "%ld >%-6s: %s",
15053                 SubtractTimeMarks(&now, &programStartTime),
15054                 cps->which, message);
15055         if(serverFP)
15056             fprintf(serverFP, "%ld >%-6s: %s",
15057                 SubtractTimeMarks(&now, &programStartTime),
15058                 cps->which, message), fflush(serverFP);
15059     }
15060
15061     count = strlen(message);
15062     outCount = OutputToProcess(cps->pr, message, count, &error);
15063     if (outCount < count && !exiting
15064                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15065       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15066       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15067         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15068             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15069                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15070                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15071                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15072             } else {
15073                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15074                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15075                 gameInfo.result = res;
15076             }
15077             gameInfo.resultDetails = StrSave(buf);
15078         }
15079         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15080         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15081     }
15082 }
15083
15084 void
15085 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15086 {
15087     char *end_str;
15088     char buf[MSG_SIZ];
15089     ChessProgramState *cps = (ChessProgramState *)closure;
15090
15091     if (isr != cps->isr) return; /* Killed intentionally */
15092     if (count <= 0) {
15093         if (count == 0) {
15094             RemoveInputSource(cps->isr);
15095             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15096             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15097                     _(cps->which), cps->program);
15098         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15099                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15100                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15101                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15102                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15103                 } else {
15104                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15105                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15106                     gameInfo.result = res;
15107                 }
15108                 gameInfo.resultDetails = StrSave(buf);
15109             }
15110             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15111             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15112         } else {
15113             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15114                     _(cps->which), cps->program);
15115             RemoveInputSource(cps->isr);
15116
15117             /* [AS] Program is misbehaving badly... kill it */
15118             if( count == -2 ) {
15119                 DestroyChildProcess( cps->pr, 9 );
15120                 cps->pr = NoProc;
15121             }
15122
15123             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15124         }
15125         return;
15126     }
15127
15128     if ((end_str = strchr(message, '\r')) != NULL)
15129       *end_str = NULLCHAR;
15130     if ((end_str = strchr(message, '\n')) != NULL)
15131       *end_str = NULLCHAR;
15132
15133     if (appData.debugMode) {
15134         TimeMark now; int print = 1;
15135         char *quote = ""; char c; int i;
15136
15137         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15138                 char start = message[0];
15139                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15140                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15141                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15142                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15143                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15144                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15145                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15146                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15147                    sscanf(message, "hint: %c", &c)!=1 && 
15148                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15149                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15150                     print = (appData.engineComments >= 2);
15151                 }
15152                 message[0] = start; // restore original message
15153         }
15154         if(print) {
15155                 GetTimeMark(&now);
15156                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15157                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15158                         quote,
15159                         message);
15160                 if(serverFP)
15161                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15162                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15163                         quote,
15164                         message), fflush(serverFP);
15165         }
15166     }
15167
15168     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15169     if (appData.icsEngineAnalyze) {
15170         if (strstr(message, "whisper") != NULL ||
15171              strstr(message, "kibitz") != NULL ||
15172             strstr(message, "tellics") != NULL) return;
15173     }
15174
15175     HandleMachineMove(message, cps);
15176 }
15177
15178
15179 void
15180 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15181 {
15182     char buf[MSG_SIZ];
15183     int seconds;
15184
15185     if( timeControl_2 > 0 ) {
15186         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15187             tc = timeControl_2;
15188         }
15189     }
15190     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15191     inc /= cps->timeOdds;
15192     st  /= cps->timeOdds;
15193
15194     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15195
15196     if (st > 0) {
15197       /* Set exact time per move, normally using st command */
15198       if (cps->stKludge) {
15199         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15200         seconds = st % 60;
15201         if (seconds == 0) {
15202           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15203         } else {
15204           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15205         }
15206       } else {
15207         snprintf(buf, MSG_SIZ, "st %d\n", st);
15208       }
15209     } else {
15210       /* Set conventional or incremental time control, using level command */
15211       if (seconds == 0) {
15212         /* Note old gnuchess bug -- minutes:seconds used to not work.
15213            Fixed in later versions, but still avoid :seconds
15214            when seconds is 0. */
15215         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15216       } else {
15217         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15218                  seconds, inc/1000.);
15219       }
15220     }
15221     SendToProgram(buf, cps);
15222
15223     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15224     /* Orthogonally, limit search to given depth */
15225     if (sd > 0) {
15226       if (cps->sdKludge) {
15227         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15228       } else {
15229         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15230       }
15231       SendToProgram(buf, cps);
15232     }
15233
15234     if(cps->nps >= 0) { /* [HGM] nps */
15235         if(cps->supportsNPS == FALSE)
15236           cps->nps = -1; // don't use if engine explicitly says not supported!
15237         else {
15238           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15239           SendToProgram(buf, cps);
15240         }
15241     }
15242 }
15243
15244 ChessProgramState *
15245 WhitePlayer ()
15246 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15247 {
15248     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15249        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15250         return &second;
15251     return &first;
15252 }
15253
15254 void
15255 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15256 {
15257     char message[MSG_SIZ];
15258     long time, otime;
15259
15260     /* Note: this routine must be called when the clocks are stopped
15261        or when they have *just* been set or switched; otherwise
15262        it will be off by the time since the current tick started.
15263     */
15264     if (machineWhite) {
15265         time = whiteTimeRemaining / 10;
15266         otime = blackTimeRemaining / 10;
15267     } else {
15268         time = blackTimeRemaining / 10;
15269         otime = whiteTimeRemaining / 10;
15270     }
15271     /* [HGM] translate opponent's time by time-odds factor */
15272     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15273
15274     if (time <= 0) time = 1;
15275     if (otime <= 0) otime = 1;
15276
15277     snprintf(message, MSG_SIZ, "time %ld\n", time);
15278     SendToProgram(message, cps);
15279
15280     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15281     SendToProgram(message, cps);
15282 }
15283
15284 int
15285 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15286 {
15287   char buf[MSG_SIZ];
15288   int len = strlen(name);
15289   int val;
15290
15291   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15292     (*p) += len + 1;
15293     sscanf(*p, "%d", &val);
15294     *loc = (val != 0);
15295     while (**p && **p != ' ')
15296       (*p)++;
15297     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15298     SendToProgram(buf, cps);
15299     return TRUE;
15300   }
15301   return FALSE;
15302 }
15303
15304 int
15305 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15306 {
15307   char buf[MSG_SIZ];
15308   int len = strlen(name);
15309   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15310     (*p) += len + 1;
15311     sscanf(*p, "%d", loc);
15312     while (**p && **p != ' ') (*p)++;
15313     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15314     SendToProgram(buf, cps);
15315     return TRUE;
15316   }
15317   return FALSE;
15318 }
15319
15320 int
15321 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15322 {
15323   char buf[MSG_SIZ];
15324   int len = strlen(name);
15325   if (strncmp((*p), name, len) == 0
15326       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15327     (*p) += len + 2;
15328     sscanf(*p, "%[^\"]", loc);
15329     while (**p && **p != '\"') (*p)++;
15330     if (**p == '\"') (*p)++;
15331     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15332     SendToProgram(buf, cps);
15333     return TRUE;
15334   }
15335   return FALSE;
15336 }
15337
15338 int
15339 ParseOption (Option *opt, ChessProgramState *cps)
15340 // [HGM] options: process the string that defines an engine option, and determine
15341 // name, type, default value, and allowed value range
15342 {
15343         char *p, *q, buf[MSG_SIZ];
15344         int n, min = (-1)<<31, max = 1<<31, def;
15345
15346         if(p = strstr(opt->name, " -spin ")) {
15347             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15348             if(max < min) max = min; // enforce consistency
15349             if(def < min) def = min;
15350             if(def > max) def = max;
15351             opt->value = def;
15352             opt->min = min;
15353             opt->max = max;
15354             opt->type = Spin;
15355         } else if((p = strstr(opt->name, " -slider "))) {
15356             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15357             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15358             if(max < min) max = min; // enforce consistency
15359             if(def < min) def = min;
15360             if(def > max) def = max;
15361             opt->value = def;
15362             opt->min = min;
15363             opt->max = max;
15364             opt->type = Spin; // Slider;
15365         } else if((p = strstr(opt->name, " -string "))) {
15366             opt->textValue = p+9;
15367             opt->type = TextBox;
15368         } else if((p = strstr(opt->name, " -file "))) {
15369             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15370             opt->textValue = p+7;
15371             opt->type = FileName; // FileName;
15372         } else if((p = strstr(opt->name, " -path "))) {
15373             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15374             opt->textValue = p+7;
15375             opt->type = PathName; // PathName;
15376         } else if(p = strstr(opt->name, " -check ")) {
15377             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15378             opt->value = (def != 0);
15379             opt->type = CheckBox;
15380         } else if(p = strstr(opt->name, " -combo ")) {
15381             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15382             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15383             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15384             opt->value = n = 0;
15385             while(q = StrStr(q, " /// ")) {
15386                 n++; *q = 0;    // count choices, and null-terminate each of them
15387                 q += 5;
15388                 if(*q == '*') { // remember default, which is marked with * prefix
15389                     q++;
15390                     opt->value = n;
15391                 }
15392                 cps->comboList[cps->comboCnt++] = q;
15393             }
15394             cps->comboList[cps->comboCnt++] = NULL;
15395             opt->max = n + 1;
15396             opt->type = ComboBox;
15397         } else if(p = strstr(opt->name, " -button")) {
15398             opt->type = Button;
15399         } else if(p = strstr(opt->name, " -save")) {
15400             opt->type = SaveButton;
15401         } else return FALSE;
15402         *p = 0; // terminate option name
15403         // now look if the command-line options define a setting for this engine option.
15404         if(cps->optionSettings && cps->optionSettings[0])
15405             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15406         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15407           snprintf(buf, MSG_SIZ, "option %s", p);
15408                 if(p = strstr(buf, ",")) *p = 0;
15409                 if(q = strchr(buf, '=')) switch(opt->type) {
15410                     case ComboBox:
15411                         for(n=0; n<opt->max; n++)
15412                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15413                         break;
15414                     case TextBox:
15415                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15416                         break;
15417                     case Spin:
15418                     case CheckBox:
15419                         opt->value = atoi(q+1);
15420                     default:
15421                         break;
15422                 }
15423                 strcat(buf, "\n");
15424                 SendToProgram(buf, cps);
15425         }
15426         return TRUE;
15427 }
15428
15429 void
15430 FeatureDone (ChessProgramState *cps, int val)
15431 {
15432   DelayedEventCallback cb = GetDelayedEvent();
15433   if ((cb == InitBackEnd3 && cps == &first) ||
15434       (cb == SettingsMenuIfReady && cps == &second) ||
15435       (cb == LoadEngine) ||
15436       (cb == TwoMachinesEventIfReady)) {
15437     CancelDelayedEvent();
15438     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15439   }
15440   cps->initDone = val;
15441 }
15442
15443 /* Parse feature command from engine */
15444 void
15445 ParseFeatures (char *args, ChessProgramState *cps)
15446 {
15447   char *p = args;
15448   char *q;
15449   int val;
15450   char buf[MSG_SIZ];
15451
15452   for (;;) {
15453     while (*p == ' ') p++;
15454     if (*p == NULLCHAR) return;
15455
15456     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15457     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15458     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15459     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15460     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15461     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15462     if (BoolFeature(&p, "reuse", &val, cps)) {
15463       /* Engine can disable reuse, but can't enable it if user said no */
15464       if (!val) cps->reuse = FALSE;
15465       continue;
15466     }
15467     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15468     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15469       if (gameMode == TwoMachinesPlay) {
15470         DisplayTwoMachinesTitle();
15471       } else {
15472         DisplayTitle("");
15473       }
15474       continue;
15475     }
15476     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15477     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15478     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15479     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15480     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15481     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15482     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15483     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15484     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15485     if (IntFeature(&p, "done", &val, cps)) {
15486       FeatureDone(cps, val);
15487       continue;
15488     }
15489     /* Added by Tord: */
15490     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15491     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15492     /* End of additions by Tord */
15493
15494     /* [HGM] added features: */
15495     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15496     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15497     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15498     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15499     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15500     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15501     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15502         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15503           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15504             SendToProgram(buf, cps);
15505             continue;
15506         }
15507         if(cps->nrOptions >= MAX_OPTIONS) {
15508             cps->nrOptions--;
15509             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15510             DisplayError(buf, 0);
15511         }
15512         continue;
15513     }
15514     /* End of additions by HGM */
15515
15516     /* unknown feature: complain and skip */
15517     q = p;
15518     while (*q && *q != '=') q++;
15519     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15520     SendToProgram(buf, cps);
15521     p = q;
15522     if (*p == '=') {
15523       p++;
15524       if (*p == '\"') {
15525         p++;
15526         while (*p && *p != '\"') p++;
15527         if (*p == '\"') p++;
15528       } else {
15529         while (*p && *p != ' ') p++;
15530       }
15531     }
15532   }
15533
15534 }
15535
15536 void
15537 PeriodicUpdatesEvent (int newState)
15538 {
15539     if (newState == appData.periodicUpdates)
15540       return;
15541
15542     appData.periodicUpdates=newState;
15543
15544     /* Display type changes, so update it now */
15545 //    DisplayAnalysis();
15546
15547     /* Get the ball rolling again... */
15548     if (newState) {
15549         AnalysisPeriodicEvent(1);
15550         StartAnalysisClock();
15551     }
15552 }
15553
15554 void
15555 PonderNextMoveEvent (int newState)
15556 {
15557     if (newState == appData.ponderNextMove) return;
15558     if (gameMode == EditPosition) EditPositionDone(TRUE);
15559     if (newState) {
15560         SendToProgram("hard\n", &first);
15561         if (gameMode == TwoMachinesPlay) {
15562             SendToProgram("hard\n", &second);
15563         }
15564     } else {
15565         SendToProgram("easy\n", &first);
15566         thinkOutput[0] = NULLCHAR;
15567         if (gameMode == TwoMachinesPlay) {
15568             SendToProgram("easy\n", &second);
15569         }
15570     }
15571     appData.ponderNextMove = newState;
15572 }
15573
15574 void
15575 NewSettingEvent (int option, int *feature, char *command, int value)
15576 {
15577     char buf[MSG_SIZ];
15578
15579     if (gameMode == EditPosition) EditPositionDone(TRUE);
15580     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15581     if(feature == NULL || *feature) SendToProgram(buf, &first);
15582     if (gameMode == TwoMachinesPlay) {
15583         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15584     }
15585 }
15586
15587 void
15588 ShowThinkingEvent ()
15589 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15590 {
15591     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15592     int newState = appData.showThinking
15593         // [HGM] thinking: other features now need thinking output as well
15594         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15595
15596     if (oldState == newState) return;
15597     oldState = newState;
15598     if (gameMode == EditPosition) EditPositionDone(TRUE);
15599     if (oldState) {
15600         SendToProgram("post\n", &first);
15601         if (gameMode == TwoMachinesPlay) {
15602             SendToProgram("post\n", &second);
15603         }
15604     } else {
15605         SendToProgram("nopost\n", &first);
15606         thinkOutput[0] = NULLCHAR;
15607         if (gameMode == TwoMachinesPlay) {
15608             SendToProgram("nopost\n", &second);
15609         }
15610     }
15611 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15612 }
15613
15614 void
15615 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15616 {
15617   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15618   if (pr == NoProc) return;
15619   AskQuestion(title, question, replyPrefix, pr);
15620 }
15621
15622 void
15623 TypeInEvent (char firstChar)
15624 {
15625     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15626         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15627         gameMode == AnalyzeMode || gameMode == EditGame || 
15628         gameMode == EditPosition || gameMode == IcsExamining ||
15629         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15630         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15631                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15632                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15633         gameMode == Training) PopUpMoveDialog(firstChar);
15634 }
15635
15636 void
15637 TypeInDoneEvent (char *move)
15638 {
15639         Board board;
15640         int n, fromX, fromY, toX, toY;
15641         char promoChar;
15642         ChessMove moveType;
15643
15644         // [HGM] FENedit
15645         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15646                 EditPositionPasteFEN(move);
15647                 return;
15648         }
15649         // [HGM] movenum: allow move number to be typed in any mode
15650         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15651           ToNrEvent(2*n-1);
15652           return;
15653         }
15654         // undocumented kludge: allow command-line option to be typed in!
15655         // (potentially fatal, and does not implement the effect of the option.)
15656         // should only be used for options that are values on which future decisions will be made,
15657         // and definitely not on options that would be used during initialization.
15658         if(strstr(move, "!!! -") == move) {
15659             ParseArgsFromString(move+4);
15660             return;
15661         }
15662
15663       if (gameMode != EditGame && currentMove != forwardMostMove && 
15664         gameMode != Training) {
15665         DisplayMoveError(_("Displayed move is not current"));
15666       } else {
15667         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15668           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15669         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15670         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15671           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15672           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15673         } else {
15674           DisplayMoveError(_("Could not parse move"));
15675         }
15676       }
15677 }
15678
15679 void
15680 DisplayMove (int moveNumber)
15681 {
15682     char message[MSG_SIZ];
15683     char res[MSG_SIZ];
15684     char cpThinkOutput[MSG_SIZ];
15685
15686     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15687
15688     if (moveNumber == forwardMostMove - 1 ||
15689         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15690
15691         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15692
15693         if (strchr(cpThinkOutput, '\n')) {
15694             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15695         }
15696     } else {
15697         *cpThinkOutput = NULLCHAR;
15698     }
15699
15700     /* [AS] Hide thinking from human user */
15701     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15702         *cpThinkOutput = NULLCHAR;
15703         if( thinkOutput[0] != NULLCHAR ) {
15704             int i;
15705
15706             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15707                 cpThinkOutput[i] = '.';
15708             }
15709             cpThinkOutput[i] = NULLCHAR;
15710             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15711         }
15712     }
15713
15714     if (moveNumber == forwardMostMove - 1 &&
15715         gameInfo.resultDetails != NULL) {
15716         if (gameInfo.resultDetails[0] == NULLCHAR) {
15717           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15718         } else {
15719           snprintf(res, MSG_SIZ, " {%s} %s",
15720                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15721         }
15722     } else {
15723         res[0] = NULLCHAR;
15724     }
15725
15726     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15727         DisplayMessage(res, cpThinkOutput);
15728     } else {
15729       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15730                 WhiteOnMove(moveNumber) ? " " : ".. ",
15731                 parseList[moveNumber], res);
15732         DisplayMessage(message, cpThinkOutput);
15733     }
15734 }
15735
15736 void
15737 DisplayComment (int moveNumber, char *text)
15738 {
15739     char title[MSG_SIZ];
15740
15741     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15742       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15743     } else {
15744       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15745               WhiteOnMove(moveNumber) ? " " : ".. ",
15746               parseList[moveNumber]);
15747     }
15748     if (text != NULL && (appData.autoDisplayComment || commentUp))
15749         CommentPopUp(title, text);
15750 }
15751
15752 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15753  * might be busy thinking or pondering.  It can be omitted if your
15754  * gnuchess is configured to stop thinking immediately on any user
15755  * input.  However, that gnuchess feature depends on the FIONREAD
15756  * ioctl, which does not work properly on some flavors of Unix.
15757  */
15758 void
15759 Attention (ChessProgramState *cps)
15760 {
15761 #if ATTENTION
15762     if (!cps->useSigint) return;
15763     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15764     switch (gameMode) {
15765       case MachinePlaysWhite:
15766       case MachinePlaysBlack:
15767       case TwoMachinesPlay:
15768       case IcsPlayingWhite:
15769       case IcsPlayingBlack:
15770       case AnalyzeMode:
15771       case AnalyzeFile:
15772         /* Skip if we know it isn't thinking */
15773         if (!cps->maybeThinking) return;
15774         if (appData.debugMode)
15775           fprintf(debugFP, "Interrupting %s\n", cps->which);
15776         InterruptChildProcess(cps->pr);
15777         cps->maybeThinking = FALSE;
15778         break;
15779       default:
15780         break;
15781     }
15782 #endif /*ATTENTION*/
15783 }
15784
15785 int
15786 CheckFlags ()
15787 {
15788     if (whiteTimeRemaining <= 0) {
15789         if (!whiteFlag) {
15790             whiteFlag = TRUE;
15791             if (appData.icsActive) {
15792                 if (appData.autoCallFlag &&
15793                     gameMode == IcsPlayingBlack && !blackFlag) {
15794                   SendToICS(ics_prefix);
15795                   SendToICS("flag\n");
15796                 }
15797             } else {
15798                 if (blackFlag) {
15799                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15800                 } else {
15801                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15802                     if (appData.autoCallFlag) {
15803                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15804                         return TRUE;
15805                     }
15806                 }
15807             }
15808         }
15809     }
15810     if (blackTimeRemaining <= 0) {
15811         if (!blackFlag) {
15812             blackFlag = TRUE;
15813             if (appData.icsActive) {
15814                 if (appData.autoCallFlag &&
15815                     gameMode == IcsPlayingWhite && !whiteFlag) {
15816                   SendToICS(ics_prefix);
15817                   SendToICS("flag\n");
15818                 }
15819             } else {
15820                 if (whiteFlag) {
15821                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15822                 } else {
15823                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15824                     if (appData.autoCallFlag) {
15825                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15826                         return TRUE;
15827                     }
15828                 }
15829             }
15830         }
15831     }
15832     return FALSE;
15833 }
15834
15835 void
15836 CheckTimeControl ()
15837 {
15838     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15839         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15840
15841     /*
15842      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15843      */
15844     if ( !WhiteOnMove(forwardMostMove) ) {
15845         /* White made time control */
15846         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15847         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15848         /* [HGM] time odds: correct new time quota for time odds! */
15849                                             / WhitePlayer()->timeOdds;
15850         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15851     } else {
15852         lastBlack -= blackTimeRemaining;
15853         /* Black made time control */
15854         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15855                                             / WhitePlayer()->other->timeOdds;
15856         lastWhite = whiteTimeRemaining;
15857     }
15858 }
15859
15860 void
15861 DisplayBothClocks ()
15862 {
15863     int wom = gameMode == EditPosition ?
15864       !blackPlaysFirst : WhiteOnMove(currentMove);
15865     DisplayWhiteClock(whiteTimeRemaining, wom);
15866     DisplayBlackClock(blackTimeRemaining, !wom);
15867 }
15868
15869
15870 /* Timekeeping seems to be a portability nightmare.  I think everyone
15871    has ftime(), but I'm really not sure, so I'm including some ifdefs
15872    to use other calls if you don't.  Clocks will be less accurate if
15873    you have neither ftime nor gettimeofday.
15874 */
15875
15876 /* VS 2008 requires the #include outside of the function */
15877 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15878 #include <sys/timeb.h>
15879 #endif
15880
15881 /* Get the current time as a TimeMark */
15882 void
15883 GetTimeMark (TimeMark *tm)
15884 {
15885 #if HAVE_GETTIMEOFDAY
15886
15887     struct timeval timeVal;
15888     struct timezone timeZone;
15889
15890     gettimeofday(&timeVal, &timeZone);
15891     tm->sec = (long) timeVal.tv_sec;
15892     tm->ms = (int) (timeVal.tv_usec / 1000L);
15893
15894 #else /*!HAVE_GETTIMEOFDAY*/
15895 #if HAVE_FTIME
15896
15897 // include <sys/timeb.h> / moved to just above start of function
15898     struct timeb timeB;
15899
15900     ftime(&timeB);
15901     tm->sec = (long) timeB.time;
15902     tm->ms = (int) timeB.millitm;
15903
15904 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15905     tm->sec = (long) time(NULL);
15906     tm->ms = 0;
15907 #endif
15908 #endif
15909 }
15910
15911 /* Return the difference in milliseconds between two
15912    time marks.  We assume the difference will fit in a long!
15913 */
15914 long
15915 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15916 {
15917     return 1000L*(tm2->sec - tm1->sec) +
15918            (long) (tm2->ms - tm1->ms);
15919 }
15920
15921
15922 /*
15923  * Code to manage the game clocks.
15924  *
15925  * In tournament play, black starts the clock and then white makes a move.
15926  * We give the human user a slight advantage if he is playing white---the
15927  * clocks don't run until he makes his first move, so it takes zero time.
15928  * Also, we don't account for network lag, so we could get out of sync
15929  * with GNU Chess's clock -- but then, referees are always right.
15930  */
15931
15932 static TimeMark tickStartTM;
15933 static long intendedTickLength;
15934
15935 long
15936 NextTickLength (long timeRemaining)
15937 {
15938     long nominalTickLength, nextTickLength;
15939
15940     if (timeRemaining > 0L && timeRemaining <= 10000L)
15941       nominalTickLength = 100L;
15942     else
15943       nominalTickLength = 1000L;
15944     nextTickLength = timeRemaining % nominalTickLength;
15945     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15946
15947     return nextTickLength;
15948 }
15949
15950 /* Adjust clock one minute up or down */
15951 void
15952 AdjustClock (Boolean which, int dir)
15953 {
15954     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15955     if(which) blackTimeRemaining += 60000*dir;
15956     else      whiteTimeRemaining += 60000*dir;
15957     DisplayBothClocks();
15958     adjustedClock = TRUE;
15959 }
15960
15961 /* Stop clocks and reset to a fresh time control */
15962 void
15963 ResetClocks ()
15964 {
15965     (void) StopClockTimer();
15966     if (appData.icsActive) {
15967         whiteTimeRemaining = blackTimeRemaining = 0;
15968     } else if (searchTime) {
15969         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15970         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15971     } else { /* [HGM] correct new time quote for time odds */
15972         whiteTC = blackTC = fullTimeControlString;
15973         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15974         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15975     }
15976     if (whiteFlag || blackFlag) {
15977         DisplayTitle("");
15978         whiteFlag = blackFlag = FALSE;
15979     }
15980     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15981     DisplayBothClocks();
15982     adjustedClock = FALSE;
15983 }
15984
15985 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15986
15987 /* Decrement running clock by amount of time that has passed */
15988 void
15989 DecrementClocks ()
15990 {
15991     long timeRemaining;
15992     long lastTickLength, fudge;
15993     TimeMark now;
15994
15995     if (!appData.clockMode) return;
15996     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15997
15998     GetTimeMark(&now);
15999
16000     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16001
16002     /* Fudge if we woke up a little too soon */
16003     fudge = intendedTickLength - lastTickLength;
16004     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16005
16006     if (WhiteOnMove(forwardMostMove)) {
16007         if(whiteNPS >= 0) lastTickLength = 0;
16008         timeRemaining = whiteTimeRemaining -= lastTickLength;
16009         if(timeRemaining < 0 && !appData.icsActive) {
16010             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16011             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16012                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16013                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16014             }
16015         }
16016         DisplayWhiteClock(whiteTimeRemaining - fudge,
16017                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16018     } else {
16019         if(blackNPS >= 0) lastTickLength = 0;
16020         timeRemaining = blackTimeRemaining -= lastTickLength;
16021         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16022             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16023             if(suddenDeath) {
16024                 blackStartMove = forwardMostMove;
16025                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16026             }
16027         }
16028         DisplayBlackClock(blackTimeRemaining - fudge,
16029                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16030     }
16031     if (CheckFlags()) return;
16032
16033     tickStartTM = now;
16034     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16035     StartClockTimer(intendedTickLength);
16036
16037     /* if the time remaining has fallen below the alarm threshold, sound the
16038      * alarm. if the alarm has sounded and (due to a takeback or time control
16039      * with increment) the time remaining has increased to a level above the
16040      * threshold, reset the alarm so it can sound again.
16041      */
16042
16043     if (appData.icsActive && appData.icsAlarm) {
16044
16045         /* make sure we are dealing with the user's clock */
16046         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16047                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16048            )) return;
16049
16050         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16051             alarmSounded = FALSE;
16052         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16053             PlayAlarmSound();
16054             alarmSounded = TRUE;
16055         }
16056     }
16057 }
16058
16059
16060 /* A player has just moved, so stop the previously running
16061    clock and (if in clock mode) start the other one.
16062    We redisplay both clocks in case we're in ICS mode, because
16063    ICS gives us an update to both clocks after every move.
16064    Note that this routine is called *after* forwardMostMove
16065    is updated, so the last fractional tick must be subtracted
16066    from the color that is *not* on move now.
16067 */
16068 void
16069 SwitchClocks (int newMoveNr)
16070 {
16071     long lastTickLength;
16072     TimeMark now;
16073     int flagged = FALSE;
16074
16075     GetTimeMark(&now);
16076
16077     if (StopClockTimer() && appData.clockMode) {
16078         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16079         if (!WhiteOnMove(forwardMostMove)) {
16080             if(blackNPS >= 0) lastTickLength = 0;
16081             blackTimeRemaining -= lastTickLength;
16082            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16083 //         if(pvInfoList[forwardMostMove].time == -1)
16084                  pvInfoList[forwardMostMove].time =               // use GUI time
16085                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16086         } else {
16087            if(whiteNPS >= 0) lastTickLength = 0;
16088            whiteTimeRemaining -= lastTickLength;
16089            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16090 //         if(pvInfoList[forwardMostMove].time == -1)
16091                  pvInfoList[forwardMostMove].time =
16092                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16093         }
16094         flagged = CheckFlags();
16095     }
16096     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16097     CheckTimeControl();
16098
16099     if (flagged || !appData.clockMode) return;
16100
16101     switch (gameMode) {
16102       case MachinePlaysBlack:
16103       case MachinePlaysWhite:
16104       case BeginningOfGame:
16105         if (pausing) return;
16106         break;
16107
16108       case EditGame:
16109       case PlayFromGameFile:
16110       case IcsExamining:
16111         return;
16112
16113       default:
16114         break;
16115     }
16116
16117     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16118         if(WhiteOnMove(forwardMostMove))
16119              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16120         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16121     }
16122
16123     tickStartTM = now;
16124     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16125       whiteTimeRemaining : blackTimeRemaining);
16126     StartClockTimer(intendedTickLength);
16127 }
16128
16129
16130 /* Stop both clocks */
16131 void
16132 StopClocks ()
16133 {
16134     long lastTickLength;
16135     TimeMark now;
16136
16137     if (!StopClockTimer()) return;
16138     if (!appData.clockMode) return;
16139
16140     GetTimeMark(&now);
16141
16142     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16143     if (WhiteOnMove(forwardMostMove)) {
16144         if(whiteNPS >= 0) lastTickLength = 0;
16145         whiteTimeRemaining -= lastTickLength;
16146         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16147     } else {
16148         if(blackNPS >= 0) lastTickLength = 0;
16149         blackTimeRemaining -= lastTickLength;
16150         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16151     }
16152     CheckFlags();
16153 }
16154
16155 /* Start clock of player on move.  Time may have been reset, so
16156    if clock is already running, stop and restart it. */
16157 void
16158 StartClocks ()
16159 {
16160     (void) StopClockTimer(); /* in case it was running already */
16161     DisplayBothClocks();
16162     if (CheckFlags()) return;
16163
16164     if (!appData.clockMode) return;
16165     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16166
16167     GetTimeMark(&tickStartTM);
16168     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16169       whiteTimeRemaining : blackTimeRemaining);
16170
16171    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16172     whiteNPS = blackNPS = -1;
16173     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16174        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16175         whiteNPS = first.nps;
16176     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16177        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16178         blackNPS = first.nps;
16179     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16180         whiteNPS = second.nps;
16181     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16182         blackNPS = second.nps;
16183     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16184
16185     StartClockTimer(intendedTickLength);
16186 }
16187
16188 char *
16189 TimeString (long ms)
16190 {
16191     long second, minute, hour, day;
16192     char *sign = "";
16193     static char buf[32];
16194
16195     if (ms > 0 && ms <= 9900) {
16196       /* convert milliseconds to tenths, rounding up */
16197       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16198
16199       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16200       return buf;
16201     }
16202
16203     /* convert milliseconds to seconds, rounding up */
16204     /* use floating point to avoid strangeness of integer division
16205        with negative dividends on many machines */
16206     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16207
16208     if (second < 0) {
16209         sign = "-";
16210         second = -second;
16211     }
16212
16213     day = second / (60 * 60 * 24);
16214     second = second % (60 * 60 * 24);
16215     hour = second / (60 * 60);
16216     second = second % (60 * 60);
16217     minute = second / 60;
16218     second = second % 60;
16219
16220     if (day > 0)
16221       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16222               sign, day, hour, minute, second);
16223     else if (hour > 0)
16224       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16225     else
16226       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16227
16228     return buf;
16229 }
16230
16231
16232 /*
16233  * This is necessary because some C libraries aren't ANSI C compliant yet.
16234  */
16235 char *
16236 StrStr (char *string, char *match)
16237 {
16238     int i, length;
16239
16240     length = strlen(match);
16241
16242     for (i = strlen(string) - length; i >= 0; i--, string++)
16243       if (!strncmp(match, string, length))
16244         return string;
16245
16246     return NULL;
16247 }
16248
16249 char *
16250 StrCaseStr (char *string, char *match)
16251 {
16252     int i, j, length;
16253
16254     length = strlen(match);
16255
16256     for (i = strlen(string) - length; i >= 0; i--, string++) {
16257         for (j = 0; j < length; j++) {
16258             if (ToLower(match[j]) != ToLower(string[j]))
16259               break;
16260         }
16261         if (j == length) return string;
16262     }
16263
16264     return NULL;
16265 }
16266
16267 #ifndef _amigados
16268 int
16269 StrCaseCmp (char *s1, char *s2)
16270 {
16271     char c1, c2;
16272
16273     for (;;) {
16274         c1 = ToLower(*s1++);
16275         c2 = ToLower(*s2++);
16276         if (c1 > c2) return 1;
16277         if (c1 < c2) return -1;
16278         if (c1 == NULLCHAR) return 0;
16279     }
16280 }
16281
16282
16283 int
16284 ToLower (int c)
16285 {
16286     return isupper(c) ? tolower(c) : c;
16287 }
16288
16289
16290 int
16291 ToUpper (int c)
16292 {
16293     return islower(c) ? toupper(c) : c;
16294 }
16295 #endif /* !_amigados    */
16296
16297 char *
16298 StrSave (char *s)
16299 {
16300   char *ret;
16301
16302   if ((ret = (char *) malloc(strlen(s) + 1)))
16303     {
16304       safeStrCpy(ret, s, strlen(s)+1);
16305     }
16306   return ret;
16307 }
16308
16309 char *
16310 StrSavePtr (char *s, char **savePtr)
16311 {
16312     if (*savePtr) {
16313         free(*savePtr);
16314     }
16315     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16316       safeStrCpy(*savePtr, s, strlen(s)+1);
16317     }
16318     return(*savePtr);
16319 }
16320
16321 char *
16322 PGNDate ()
16323 {
16324     time_t clock;
16325     struct tm *tm;
16326     char buf[MSG_SIZ];
16327
16328     clock = time((time_t *)NULL);
16329     tm = localtime(&clock);
16330     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16331             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16332     return StrSave(buf);
16333 }
16334
16335
16336 char *
16337 PositionToFEN (int move, char *overrideCastling)
16338 {
16339     int i, j, fromX, fromY, toX, toY;
16340     int whiteToPlay;
16341     char buf[MSG_SIZ];
16342     char *p, *q;
16343     int emptycount;
16344     ChessSquare piece;
16345
16346     whiteToPlay = (gameMode == EditPosition) ?
16347       !blackPlaysFirst : (move % 2 == 0);
16348     p = buf;
16349
16350     /* Piece placement data */
16351     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16352         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16353         emptycount = 0;
16354         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16355             if (boards[move][i][j] == EmptySquare) {
16356                 emptycount++;
16357             } else { ChessSquare piece = boards[move][i][j];
16358                 if (emptycount > 0) {
16359                     if(emptycount<10) /* [HGM] can be >= 10 */
16360                         *p++ = '0' + emptycount;
16361                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16362                     emptycount = 0;
16363                 }
16364                 if(PieceToChar(piece) == '+') {
16365                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16366                     *p++ = '+';
16367                     piece = (ChessSquare)(DEMOTED piece);
16368                 }
16369                 *p++ = PieceToChar(piece);
16370                 if(p[-1] == '~') {
16371                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16372                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16373                     *p++ = '~';
16374                 }
16375             }
16376         }
16377         if (emptycount > 0) {
16378             if(emptycount<10) /* [HGM] can be >= 10 */
16379                 *p++ = '0' + emptycount;
16380             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16381             emptycount = 0;
16382         }
16383         *p++ = '/';
16384     }
16385     *(p - 1) = ' ';
16386
16387     /* [HGM] print Crazyhouse or Shogi holdings */
16388     if( gameInfo.holdingsWidth ) {
16389         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16390         q = p;
16391         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16392             piece = boards[move][i][BOARD_WIDTH-1];
16393             if( piece != EmptySquare )
16394               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16395                   *p++ = PieceToChar(piece);
16396         }
16397         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16398             piece = boards[move][BOARD_HEIGHT-i-1][0];
16399             if( piece != EmptySquare )
16400               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16401                   *p++ = PieceToChar(piece);
16402         }
16403
16404         if( q == p ) *p++ = '-';
16405         *p++ = ']';
16406         *p++ = ' ';
16407     }
16408
16409     /* Active color */
16410     *p++ = whiteToPlay ? 'w' : 'b';
16411     *p++ = ' ';
16412
16413   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16414     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16415   } else {
16416   if(nrCastlingRights) {
16417      q = p;
16418      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16419        /* [HGM] write directly from rights */
16420            if(boards[move][CASTLING][2] != NoRights &&
16421               boards[move][CASTLING][0] != NoRights   )
16422                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16423            if(boards[move][CASTLING][2] != NoRights &&
16424               boards[move][CASTLING][1] != NoRights   )
16425                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16426            if(boards[move][CASTLING][5] != NoRights &&
16427               boards[move][CASTLING][3] != NoRights   )
16428                 *p++ = boards[move][CASTLING][3] + AAA;
16429            if(boards[move][CASTLING][5] != NoRights &&
16430               boards[move][CASTLING][4] != NoRights   )
16431                 *p++ = boards[move][CASTLING][4] + AAA;
16432      } else {
16433
16434         /* [HGM] write true castling rights */
16435         if( nrCastlingRights == 6 ) {
16436             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16437                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16438             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16439                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16440             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16441                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16442             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16443                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16444         }
16445      }
16446      if (q == p) *p++ = '-'; /* No castling rights */
16447      *p++ = ' ';
16448   }
16449
16450   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16451      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16452     /* En passant target square */
16453     if (move > backwardMostMove) {
16454         fromX = moveList[move - 1][0] - AAA;
16455         fromY = moveList[move - 1][1] - ONE;
16456         toX = moveList[move - 1][2] - AAA;
16457         toY = moveList[move - 1][3] - ONE;
16458         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16459             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16460             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16461             fromX == toX) {
16462             /* 2-square pawn move just happened */
16463             *p++ = toX + AAA;
16464             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16465         } else {
16466             *p++ = '-';
16467         }
16468     } else if(move == backwardMostMove) {
16469         // [HGM] perhaps we should always do it like this, and forget the above?
16470         if((signed char)boards[move][EP_STATUS] >= 0) {
16471             *p++ = boards[move][EP_STATUS] + AAA;
16472             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16473         } else {
16474             *p++ = '-';
16475         }
16476     } else {
16477         *p++ = '-';
16478     }
16479     *p++ = ' ';
16480   }
16481   }
16482
16483     /* [HGM] find reversible plies */
16484     {   int i = 0, j=move;
16485
16486         if (appData.debugMode) { int k;
16487             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16488             for(k=backwardMostMove; k<=forwardMostMove; k++)
16489                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16490
16491         }
16492
16493         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16494         if( j == backwardMostMove ) i += initialRulePlies;
16495         sprintf(p, "%d ", i);
16496         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16497     }
16498     /* Fullmove number */
16499     sprintf(p, "%d", (move / 2) + 1);
16500
16501     return StrSave(buf);
16502 }
16503
16504 Boolean
16505 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16506 {
16507     int i, j;
16508     char *p, c;
16509     int emptycount;
16510     ChessSquare piece;
16511
16512     p = fen;
16513
16514     /* [HGM] by default clear Crazyhouse holdings, if present */
16515     if(gameInfo.holdingsWidth) {
16516        for(i=0; i<BOARD_HEIGHT; i++) {
16517            board[i][0]             = EmptySquare; /* black holdings */
16518            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16519            board[i][1]             = (ChessSquare) 0; /* black counts */
16520            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16521        }
16522     }
16523
16524     /* Piece placement data */
16525     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16526         j = 0;
16527         for (;;) {
16528             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16529                 if (*p == '/') p++;
16530                 emptycount = gameInfo.boardWidth - j;
16531                 while (emptycount--)
16532                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16533                 break;
16534 #if(BOARD_FILES >= 10)
16535             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16536                 p++; emptycount=10;
16537                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16538                 while (emptycount--)
16539                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16540 #endif
16541             } else if (isdigit(*p)) {
16542                 emptycount = *p++ - '0';
16543                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16544                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16545                 while (emptycount--)
16546                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16547             } else if (*p == '+' || isalpha(*p)) {
16548                 if (j >= gameInfo.boardWidth) return FALSE;
16549                 if(*p=='+') {
16550                     piece = CharToPiece(*++p);
16551                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16552                     piece = (ChessSquare) (PROMOTED piece ); p++;
16553                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16554                 } else piece = CharToPiece(*p++);
16555
16556                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16557                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16558                     piece = (ChessSquare) (PROMOTED piece);
16559                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16560                     p++;
16561                 }
16562                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16563             } else {
16564                 return FALSE;
16565             }
16566         }
16567     }
16568     while (*p == '/' || *p == ' ') p++;
16569
16570     /* [HGM] look for Crazyhouse holdings here */
16571     while(*p==' ') p++;
16572     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16573         if(*p == '[') p++;
16574         if(*p == '-' ) p++; /* empty holdings */ else {
16575             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16576             /* if we would allow FEN reading to set board size, we would   */
16577             /* have to add holdings and shift the board read so far here   */
16578             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16579                 p++;
16580                 if((int) piece >= (int) BlackPawn ) {
16581                     i = (int)piece - (int)BlackPawn;
16582                     i = PieceToNumber((ChessSquare)i);
16583                     if( i >= gameInfo.holdingsSize ) return FALSE;
16584                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16585                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16586                 } else {
16587                     i = (int)piece - (int)WhitePawn;
16588                     i = PieceToNumber((ChessSquare)i);
16589                     if( i >= gameInfo.holdingsSize ) return FALSE;
16590                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16591                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16592                 }
16593             }
16594         }
16595         if(*p == ']') p++;
16596     }
16597
16598     while(*p == ' ') p++;
16599
16600     /* Active color */
16601     c = *p++;
16602     if(appData.colorNickNames) {
16603       if( c == appData.colorNickNames[0] ) c = 'w'; else
16604       if( c == appData.colorNickNames[1] ) c = 'b';
16605     }
16606     switch (c) {
16607       case 'w':
16608         *blackPlaysFirst = FALSE;
16609         break;
16610       case 'b':
16611         *blackPlaysFirst = TRUE;
16612         break;
16613       default:
16614         return FALSE;
16615     }
16616
16617     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16618     /* return the extra info in global variiables             */
16619
16620     /* set defaults in case FEN is incomplete */
16621     board[EP_STATUS] = EP_UNKNOWN;
16622     for(i=0; i<nrCastlingRights; i++ ) {
16623         board[CASTLING][i] =
16624             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16625     }   /* assume possible unless obviously impossible */
16626     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16627     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16628     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16629                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16630     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16631     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16632     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16633                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16634     FENrulePlies = 0;
16635
16636     while(*p==' ') p++;
16637     if(nrCastlingRights) {
16638       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16639           /* castling indicator present, so default becomes no castlings */
16640           for(i=0; i<nrCastlingRights; i++ ) {
16641                  board[CASTLING][i] = NoRights;
16642           }
16643       }
16644       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16645              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16646              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16647              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16648         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16649
16650         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16651             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16652             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16653         }
16654         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16655             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16656         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16657                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16658         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16659                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16660         switch(c) {
16661           case'K':
16662               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16663               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16664               board[CASTLING][2] = whiteKingFile;
16665               break;
16666           case'Q':
16667               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16668               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16669               board[CASTLING][2] = whiteKingFile;
16670               break;
16671           case'k':
16672               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16673               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16674               board[CASTLING][5] = blackKingFile;
16675               break;
16676           case'q':
16677               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16678               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16679               board[CASTLING][5] = blackKingFile;
16680           case '-':
16681               break;
16682           default: /* FRC castlings */
16683               if(c >= 'a') { /* black rights */
16684                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16685                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16686                   if(i == BOARD_RGHT) break;
16687                   board[CASTLING][5] = i;
16688                   c -= AAA;
16689                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16690                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16691                   if(c > i)
16692                       board[CASTLING][3] = c;
16693                   else
16694                       board[CASTLING][4] = c;
16695               } else { /* white rights */
16696                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16697                     if(board[0][i] == WhiteKing) break;
16698                   if(i == BOARD_RGHT) break;
16699                   board[CASTLING][2] = i;
16700                   c -= AAA - 'a' + 'A';
16701                   if(board[0][c] >= WhiteKing) break;
16702                   if(c > i)
16703                       board[CASTLING][0] = c;
16704                   else
16705                       board[CASTLING][1] = c;
16706               }
16707         }
16708       }
16709       for(i=0; i<nrCastlingRights; i++)
16710         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16711     if (appData.debugMode) {
16712         fprintf(debugFP, "FEN castling rights:");
16713         for(i=0; i<nrCastlingRights; i++)
16714         fprintf(debugFP, " %d", board[CASTLING][i]);
16715         fprintf(debugFP, "\n");
16716     }
16717
16718       while(*p==' ') p++;
16719     }
16720
16721     /* read e.p. field in games that know e.p. capture */
16722     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16723        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16724       if(*p=='-') {
16725         p++; board[EP_STATUS] = EP_NONE;
16726       } else {
16727          char c = *p++ - AAA;
16728
16729          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16730          if(*p >= '0' && *p <='9') p++;
16731          board[EP_STATUS] = c;
16732       }
16733     }
16734
16735
16736     if(sscanf(p, "%d", &i) == 1) {
16737         FENrulePlies = i; /* 50-move ply counter */
16738         /* (The move number is still ignored)    */
16739     }
16740
16741     return TRUE;
16742 }
16743
16744 void
16745 EditPositionPasteFEN (char *fen)
16746 {
16747   if (fen != NULL) {
16748     Board initial_position;
16749
16750     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16751       DisplayError(_("Bad FEN position in clipboard"), 0);
16752       return ;
16753     } else {
16754       int savedBlackPlaysFirst = blackPlaysFirst;
16755       EditPositionEvent();
16756       blackPlaysFirst = savedBlackPlaysFirst;
16757       CopyBoard(boards[0], initial_position);
16758       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16759       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16760       DisplayBothClocks();
16761       DrawPosition(FALSE, boards[currentMove]);
16762     }
16763   }
16764 }
16765
16766 static char cseq[12] = "\\   ";
16767
16768 Boolean
16769 set_cont_sequence (char *new_seq)
16770 {
16771     int len;
16772     Boolean ret;
16773
16774     // handle bad attempts to set the sequence
16775         if (!new_seq)
16776                 return 0; // acceptable error - no debug
16777
16778     len = strlen(new_seq);
16779     ret = (len > 0) && (len < sizeof(cseq));
16780     if (ret)
16781       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16782     else if (appData.debugMode)
16783       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16784     return ret;
16785 }
16786
16787 /*
16788     reformat a source message so words don't cross the width boundary.  internal
16789     newlines are not removed.  returns the wrapped size (no null character unless
16790     included in source message).  If dest is NULL, only calculate the size required
16791     for the dest buffer.  lp argument indicats line position upon entry, and it's
16792     passed back upon exit.
16793 */
16794 int
16795 wrap (char *dest, char *src, int count, int width, int *lp)
16796 {
16797     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16798
16799     cseq_len = strlen(cseq);
16800     old_line = line = *lp;
16801     ansi = len = clen = 0;
16802
16803     for (i=0; i < count; i++)
16804     {
16805         if (src[i] == '\033')
16806             ansi = 1;
16807
16808         // if we hit the width, back up
16809         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16810         {
16811             // store i & len in case the word is too long
16812             old_i = i, old_len = len;
16813
16814             // find the end of the last word
16815             while (i && src[i] != ' ' && src[i] != '\n')
16816             {
16817                 i--;
16818                 len--;
16819             }
16820
16821             // word too long?  restore i & len before splitting it
16822             if ((old_i-i+clen) >= width)
16823             {
16824                 i = old_i;
16825                 len = old_len;
16826             }
16827
16828             // extra space?
16829             if (i && src[i-1] == ' ')
16830                 len--;
16831
16832             if (src[i] != ' ' && src[i] != '\n')
16833             {
16834                 i--;
16835                 if (len)
16836                     len--;
16837             }
16838
16839             // now append the newline and continuation sequence
16840             if (dest)
16841                 dest[len] = '\n';
16842             len++;
16843             if (dest)
16844                 strncpy(dest+len, cseq, cseq_len);
16845             len += cseq_len;
16846             line = cseq_len;
16847             clen = cseq_len;
16848             continue;
16849         }
16850
16851         if (dest)
16852             dest[len] = src[i];
16853         len++;
16854         if (!ansi)
16855             line++;
16856         if (src[i] == '\n')
16857             line = 0;
16858         if (src[i] == 'm')
16859             ansi = 0;
16860     }
16861     if (dest && appData.debugMode)
16862     {
16863         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16864             count, width, line, len, *lp);
16865         show_bytes(debugFP, src, count);
16866         fprintf(debugFP, "\ndest: ");
16867         show_bytes(debugFP, dest, len);
16868         fprintf(debugFP, "\n");
16869     }
16870     *lp = dest ? line : old_line;
16871
16872     return len;
16873 }
16874
16875 // [HGM] vari: routines for shelving variations
16876 Boolean modeRestore = FALSE;
16877
16878 void
16879 PushInner (int firstMove, int lastMove)
16880 {
16881         int i, j, nrMoves = lastMove - firstMove;
16882
16883         // push current tail of game on stack
16884         savedResult[storedGames] = gameInfo.result;
16885         savedDetails[storedGames] = gameInfo.resultDetails;
16886         gameInfo.resultDetails = NULL;
16887         savedFirst[storedGames] = firstMove;
16888         savedLast [storedGames] = lastMove;
16889         savedFramePtr[storedGames] = framePtr;
16890         framePtr -= nrMoves; // reserve space for the boards
16891         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16892             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16893             for(j=0; j<MOVE_LEN; j++)
16894                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16895             for(j=0; j<2*MOVE_LEN; j++)
16896                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16897             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16898             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16899             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16900             pvInfoList[firstMove+i-1].depth = 0;
16901             commentList[framePtr+i] = commentList[firstMove+i];
16902             commentList[firstMove+i] = NULL;
16903         }
16904
16905         storedGames++;
16906         forwardMostMove = firstMove; // truncate game so we can start variation
16907 }
16908
16909 void
16910 PushTail (int firstMove, int lastMove)
16911 {
16912         if(appData.icsActive) { // only in local mode
16913                 forwardMostMove = currentMove; // mimic old ICS behavior
16914                 return;
16915         }
16916         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16917
16918         PushInner(firstMove, lastMove);
16919         if(storedGames == 1) GreyRevert(FALSE);
16920         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16921 }
16922
16923 void
16924 PopInner (Boolean annotate)
16925 {
16926         int i, j, nrMoves;
16927         char buf[8000], moveBuf[20];
16928
16929         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16930         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16931         nrMoves = savedLast[storedGames] - currentMove;
16932         if(annotate) {
16933                 int cnt = 10;
16934                 if(!WhiteOnMove(currentMove))
16935                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16936                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16937                 for(i=currentMove; i<forwardMostMove; i++) {
16938                         if(WhiteOnMove(i))
16939                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16940                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16941                         strcat(buf, moveBuf);
16942                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16943                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16944                 }
16945                 strcat(buf, ")");
16946         }
16947         for(i=1; i<=nrMoves; i++) { // copy last variation back
16948             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16949             for(j=0; j<MOVE_LEN; j++)
16950                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16951             for(j=0; j<2*MOVE_LEN; j++)
16952                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16953             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16954             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16955             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16956             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16957             commentList[currentMove+i] = commentList[framePtr+i];
16958             commentList[framePtr+i] = NULL;
16959         }
16960         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16961         framePtr = savedFramePtr[storedGames];
16962         gameInfo.result = savedResult[storedGames];
16963         if(gameInfo.resultDetails != NULL) {
16964             free(gameInfo.resultDetails);
16965       }
16966         gameInfo.resultDetails = savedDetails[storedGames];
16967         forwardMostMove = currentMove + nrMoves;
16968 }
16969
16970 Boolean
16971 PopTail (Boolean annotate)
16972 {
16973         if(appData.icsActive) return FALSE; // only in local mode
16974         if(!storedGames) return FALSE; // sanity
16975         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16976
16977         PopInner(annotate);
16978         if(currentMove < forwardMostMove) ForwardEvent(); else
16979         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16980
16981         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16982         return TRUE;
16983 }
16984
16985 void
16986 CleanupTail ()
16987 {       // remove all shelved variations
16988         int i;
16989         for(i=0; i<storedGames; i++) {
16990             if(savedDetails[i])
16991                 free(savedDetails[i]);
16992             savedDetails[i] = NULL;
16993         }
16994         for(i=framePtr; i<MAX_MOVES; i++) {
16995                 if(commentList[i]) free(commentList[i]);
16996                 commentList[i] = NULL;
16997         }
16998         framePtr = MAX_MOVES-1;
16999         storedGames = 0;
17000 }
17001
17002 void
17003 LoadVariation (int index, char *text)
17004 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17005         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17006         int level = 0, move;
17007
17008         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17009         // first find outermost bracketing variation
17010         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17011             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17012                 if(*p == '{') wait = '}'; else
17013                 if(*p == '[') wait = ']'; else
17014                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17015                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17016             }
17017             if(*p == wait) wait = NULLCHAR; // closing ]} found
17018             p++;
17019         }
17020         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17021         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17022         end[1] = NULLCHAR; // clip off comment beyond variation
17023         ToNrEvent(currentMove-1);
17024         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17025         // kludge: use ParsePV() to append variation to game
17026         move = currentMove;
17027         ParsePV(start, TRUE, TRUE);
17028         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17029         ClearPremoveHighlights();
17030         CommentPopDown();
17031         ToNrEvent(currentMove+1);
17032 }
17033