Implement -autoCopyPV
[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;
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 void
1489 InitBackEnd3 P((void))
1490 {
1491     GameMode initialMode;
1492     char buf[MSG_SIZ];
1493     int err, len;
1494
1495     InitChessProgram(&first, startedFromSetupPosition);
1496
1497     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1498         free(programVersion);
1499         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1500         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1501         FloatToFront(&appData.recentEngineList, appData.firstChessProgram);
1502     }
1503
1504     if (appData.icsActive) {
1505 #ifdef WIN32
1506         /* [DM] Make a console window if needed [HGM] merged ifs */
1507         ConsoleCreate();
1508 #endif
1509         err = establish();
1510         if (err != 0)
1511           {
1512             if (*appData.icsCommPort != NULLCHAR)
1513               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1514                              appData.icsCommPort);
1515             else
1516               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1517                         appData.icsHost, appData.icsPort);
1518
1519             if( (len >= MSG_SIZ) && appData.debugMode )
1520               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1521
1522             DisplayFatalError(buf, err, 1);
1523             return;
1524         }
1525         SetICSMode();
1526         telnetISR =
1527           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1528         fromUserISR =
1529           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1530         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1531             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1532     } else if (appData.noChessProgram) {
1533         SetNCPMode();
1534     } else {
1535         SetGNUMode();
1536     }
1537
1538     if (*appData.cmailGameName != NULLCHAR) {
1539         SetCmailMode();
1540         OpenLoopback(&cmailPR);
1541         cmailISR =
1542           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1543     }
1544
1545     ThawUI();
1546     DisplayMessage("", "");
1547     if (StrCaseCmp(appData.initialMode, "") == 0) {
1548       initialMode = BeginningOfGame;
1549       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1550         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1551         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1552         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1553         ModeHighlight();
1554       }
1555     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1556       initialMode = TwoMachinesPlay;
1557     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1558       initialMode = AnalyzeFile;
1559     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1560       initialMode = AnalyzeMode;
1561     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1562       initialMode = MachinePlaysWhite;
1563     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1564       initialMode = MachinePlaysBlack;
1565     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1566       initialMode = EditGame;
1567     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1568       initialMode = EditPosition;
1569     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1570       initialMode = Training;
1571     } else {
1572       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1573       if( (len >= MSG_SIZ) && appData.debugMode )
1574         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1575
1576       DisplayFatalError(buf, 0, 2);
1577       return;
1578     }
1579
1580     if (appData.matchMode) {
1581         if(appData.tourneyFile[0]) { // start tourney from command line
1582             FILE *f;
1583             if(f = fopen(appData.tourneyFile, "r")) {
1584                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1585                 fclose(f);
1586                 appData.clockMode = TRUE;
1587                 SetGNUMode();
1588             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1589         }
1590         MatchEvent(TRUE);
1591     } else if (*appData.cmailGameName != NULLCHAR) {
1592         /* Set up cmail mode */
1593         ReloadCmailMsgEvent(TRUE);
1594     } else {
1595         /* Set up other modes */
1596         if (initialMode == AnalyzeFile) {
1597           if (*appData.loadGameFile == NULLCHAR) {
1598             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1599             return;
1600           }
1601         }
1602         if (*appData.loadGameFile != NULLCHAR) {
1603             (void) LoadGameFromFile(appData.loadGameFile,
1604                                     appData.loadGameIndex,
1605                                     appData.loadGameFile, TRUE);
1606         } else if (*appData.loadPositionFile != NULLCHAR) {
1607             (void) LoadPositionFromFile(appData.loadPositionFile,
1608                                         appData.loadPositionIndex,
1609                                         appData.loadPositionFile);
1610             /* [HGM] try to make self-starting even after FEN load */
1611             /* to allow automatic setup of fairy variants with wtm */
1612             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1613                 gameMode = BeginningOfGame;
1614                 setboardSpoiledMachineBlack = 1;
1615             }
1616             /* [HGM] loadPos: make that every new game uses the setup */
1617             /* from file as long as we do not switch variant          */
1618             if(!blackPlaysFirst) {
1619                 startedFromPositionFile = TRUE;
1620                 CopyBoard(filePosition, boards[0]);
1621             }
1622         }
1623         if (initialMode == AnalyzeMode) {
1624           if (appData.noChessProgram) {
1625             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1626             return;
1627           }
1628           if (appData.icsActive) {
1629             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1630             return;
1631           }
1632           AnalyzeModeEvent();
1633         } else if (initialMode == AnalyzeFile) {
1634           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1635           ShowThinkingEvent();
1636           AnalyzeFileEvent();
1637           AnalysisPeriodicEvent(1);
1638         } else if (initialMode == MachinePlaysWhite) {
1639           if (appData.noChessProgram) {
1640             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1641                               0, 2);
1642             return;
1643           }
1644           if (appData.icsActive) {
1645             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1646                               0, 2);
1647             return;
1648           }
1649           MachineWhiteEvent();
1650         } else if (initialMode == MachinePlaysBlack) {
1651           if (appData.noChessProgram) {
1652             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1653                               0, 2);
1654             return;
1655           }
1656           if (appData.icsActive) {
1657             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1658                               0, 2);
1659             return;
1660           }
1661           MachineBlackEvent();
1662         } else if (initialMode == TwoMachinesPlay) {
1663           if (appData.noChessProgram) {
1664             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1665                               0, 2);
1666             return;
1667           }
1668           if (appData.icsActive) {
1669             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1670                               0, 2);
1671             return;
1672           }
1673           TwoMachinesEvent();
1674         } else if (initialMode == EditGame) {
1675           EditGameEvent();
1676         } else if (initialMode == EditPosition) {
1677           EditPositionEvent();
1678         } else if (initialMode == Training) {
1679           if (*appData.loadGameFile == NULLCHAR) {
1680             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1681             return;
1682           }
1683           TrainingEvent();
1684         }
1685     }
1686 }
1687
1688 void
1689 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1690 {
1691     DisplayBook(current+1);
1692
1693     MoveHistorySet( movelist, first, last, current, pvInfoList );
1694
1695     EvalGraphSet( first, last, current, pvInfoList );
1696
1697     MakeEngineOutputTitle();
1698 }
1699
1700 /*
1701  * Establish will establish a contact to a remote host.port.
1702  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1703  *  used to talk to the host.
1704  * Returns 0 if okay, error code if not.
1705  */
1706 int
1707 establish ()
1708 {
1709     char buf[MSG_SIZ];
1710
1711     if (*appData.icsCommPort != NULLCHAR) {
1712         /* Talk to the host through a serial comm port */
1713         return OpenCommPort(appData.icsCommPort, &icsPR);
1714
1715     } else if (*appData.gateway != NULLCHAR) {
1716         if (*appData.remoteShell == NULLCHAR) {
1717             /* Use the rcmd protocol to run telnet program on a gateway host */
1718             snprintf(buf, sizeof(buf), "%s %s %s",
1719                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1720             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1721
1722         } else {
1723             /* Use the rsh program to run telnet program on a gateway host */
1724             if (*appData.remoteUser == NULLCHAR) {
1725                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1726                         appData.gateway, appData.telnetProgram,
1727                         appData.icsHost, appData.icsPort);
1728             } else {
1729                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1730                         appData.remoteShell, appData.gateway,
1731                         appData.remoteUser, appData.telnetProgram,
1732                         appData.icsHost, appData.icsPort);
1733             }
1734             return StartChildProcess(buf, "", &icsPR);
1735
1736         }
1737     } else if (appData.useTelnet) {
1738         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1739
1740     } else {
1741         /* TCP socket interface differs somewhat between
1742            Unix and NT; handle details in the front end.
1743            */
1744         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1745     }
1746 }
1747
1748 void
1749 EscapeExpand (char *p, char *q)
1750 {       // [HGM] initstring: routine to shape up string arguments
1751         while(*p++ = *q++) if(p[-1] == '\\')
1752             switch(*q++) {
1753                 case 'n': p[-1] = '\n'; break;
1754                 case 'r': p[-1] = '\r'; break;
1755                 case 't': p[-1] = '\t'; break;
1756                 case '\\': p[-1] = '\\'; break;
1757                 case 0: *p = 0; return;
1758                 default: p[-1] = q[-1]; break;
1759             }
1760 }
1761
1762 void
1763 show_bytes (FILE *fp, char *buf, int count)
1764 {
1765     while (count--) {
1766         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1767             fprintf(fp, "\\%03o", *buf & 0xff);
1768         } else {
1769             putc(*buf, fp);
1770         }
1771         buf++;
1772     }
1773     fflush(fp);
1774 }
1775
1776 /* Returns an errno value */
1777 int
1778 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1779 {
1780     char buf[8192], *p, *q, *buflim;
1781     int left, newcount, outcount;
1782
1783     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1784         *appData.gateway != NULLCHAR) {
1785         if (appData.debugMode) {
1786             fprintf(debugFP, ">ICS: ");
1787             show_bytes(debugFP, message, count);
1788             fprintf(debugFP, "\n");
1789         }
1790         return OutputToProcess(pr, message, count, outError);
1791     }
1792
1793     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1794     p = message;
1795     q = buf;
1796     left = count;
1797     newcount = 0;
1798     while (left) {
1799         if (q >= buflim) {
1800             if (appData.debugMode) {
1801                 fprintf(debugFP, ">ICS: ");
1802                 show_bytes(debugFP, buf, newcount);
1803                 fprintf(debugFP, "\n");
1804             }
1805             outcount = OutputToProcess(pr, buf, newcount, outError);
1806             if (outcount < newcount) return -1; /* to be sure */
1807             q = buf;
1808             newcount = 0;
1809         }
1810         if (*p == '\n') {
1811             *q++ = '\r';
1812             newcount++;
1813         } else if (((unsigned char) *p) == TN_IAC) {
1814             *q++ = (char) TN_IAC;
1815             newcount ++;
1816         }
1817         *q++ = *p++;
1818         newcount++;
1819         left--;
1820     }
1821     if (appData.debugMode) {
1822         fprintf(debugFP, ">ICS: ");
1823         show_bytes(debugFP, buf, newcount);
1824         fprintf(debugFP, "\n");
1825     }
1826     outcount = OutputToProcess(pr, buf, newcount, outError);
1827     if (outcount < newcount) return -1; /* to be sure */
1828     return count;
1829 }
1830
1831 void
1832 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1833 {
1834     int outError, outCount;
1835     static int gotEof = 0;
1836
1837     /* Pass data read from player on to ICS */
1838     if (count > 0) {
1839         gotEof = 0;
1840         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1841         if (outCount < count) {
1842             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1843         }
1844     } else if (count < 0) {
1845         RemoveInputSource(isr);
1846         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1847     } else if (gotEof++ > 0) {
1848         RemoveInputSource(isr);
1849         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1850     }
1851 }
1852
1853 void
1854 KeepAlive ()
1855 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1856     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1857     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1858     SendToICS("date\n");
1859     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1860 }
1861
1862 /* added routine for printf style output to ics */
1863 void
1864 ics_printf (char *format, ...)
1865 {
1866     char buffer[MSG_SIZ];
1867     va_list args;
1868
1869     va_start(args, format);
1870     vsnprintf(buffer, sizeof(buffer), format, args);
1871     buffer[sizeof(buffer)-1] = '\0';
1872     SendToICS(buffer);
1873     va_end(args);
1874 }
1875
1876 void
1877 SendToICS (char *s)
1878 {
1879     int count, outCount, outError;
1880
1881     if (icsPR == NoProc) return;
1882
1883     count = strlen(s);
1884     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1885     if (outCount < count) {
1886         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1887     }
1888 }
1889
1890 /* This is used for sending logon scripts to the ICS. Sending
1891    without a delay causes problems when using timestamp on ICC
1892    (at least on my machine). */
1893 void
1894 SendToICSDelayed (char *s, long msdelay)
1895 {
1896     int count, outCount, outError;
1897
1898     if (icsPR == NoProc) return;
1899
1900     count = strlen(s);
1901     if (appData.debugMode) {
1902         fprintf(debugFP, ">ICS: ");
1903         show_bytes(debugFP, s, count);
1904         fprintf(debugFP, "\n");
1905     }
1906     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1907                                       msdelay);
1908     if (outCount < count) {
1909         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1910     }
1911 }
1912
1913
1914 /* Remove all highlighting escape sequences in s
1915    Also deletes any suffix starting with '('
1916    */
1917 char *
1918 StripHighlightAndTitle (char *s)
1919 {
1920     static char retbuf[MSG_SIZ];
1921     char *p = retbuf;
1922
1923     while (*s != NULLCHAR) {
1924         while (*s == '\033') {
1925             while (*s != NULLCHAR && !isalpha(*s)) s++;
1926             if (*s != NULLCHAR) s++;
1927         }
1928         while (*s != NULLCHAR && *s != '\033') {
1929             if (*s == '(' || *s == '[') {
1930                 *p = NULLCHAR;
1931                 return retbuf;
1932             }
1933             *p++ = *s++;
1934         }
1935     }
1936     *p = NULLCHAR;
1937     return retbuf;
1938 }
1939
1940 /* Remove all highlighting escape sequences in s */
1941 char *
1942 StripHighlight (char *s)
1943 {
1944     static char retbuf[MSG_SIZ];
1945     char *p = retbuf;
1946
1947     while (*s != NULLCHAR) {
1948         while (*s == '\033') {
1949             while (*s != NULLCHAR && !isalpha(*s)) s++;
1950             if (*s != NULLCHAR) s++;
1951         }
1952         while (*s != NULLCHAR && *s != '\033') {
1953             *p++ = *s++;
1954         }
1955     }
1956     *p = NULLCHAR;
1957     return retbuf;
1958 }
1959
1960 char *variantNames[] = VARIANT_NAMES;
1961 char *
1962 VariantName (VariantClass v)
1963 {
1964     return variantNames[v];
1965 }
1966
1967
1968 /* Identify a variant from the strings the chess servers use or the
1969    PGN Variant tag names we use. */
1970 VariantClass
1971 StringToVariant (char *e)
1972 {
1973     char *p;
1974     int wnum = -1;
1975     VariantClass v = VariantNormal;
1976     int i, found = FALSE;
1977     char buf[MSG_SIZ];
1978     int len;
1979
1980     if (!e) return v;
1981
1982     /* [HGM] skip over optional board-size prefixes */
1983     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1984         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1985         while( *e++ != '_');
1986     }
1987
1988     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1989         v = VariantNormal;
1990         found = TRUE;
1991     } else
1992     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1993       if (StrCaseStr(e, variantNames[i])) {
1994         v = (VariantClass) i;
1995         found = TRUE;
1996         break;
1997       }
1998     }
1999
2000     if (!found) {
2001       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2002           || StrCaseStr(e, "wild/fr")
2003           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2004         v = VariantFischeRandom;
2005       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2006                  (i = 1, p = StrCaseStr(e, "w"))) {
2007         p += i;
2008         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2009         if (isdigit(*p)) {
2010           wnum = atoi(p);
2011         } else {
2012           wnum = -1;
2013         }
2014         switch (wnum) {
2015         case 0: /* FICS only, actually */
2016         case 1:
2017           /* Castling legal even if K starts on d-file */
2018           v = VariantWildCastle;
2019           break;
2020         case 2:
2021         case 3:
2022         case 4:
2023           /* Castling illegal even if K & R happen to start in
2024              normal positions. */
2025           v = VariantNoCastle;
2026           break;
2027         case 5:
2028         case 7:
2029         case 8:
2030         case 10:
2031         case 11:
2032         case 12:
2033         case 13:
2034         case 14:
2035         case 15:
2036         case 18:
2037         case 19:
2038           /* Castling legal iff K & R start in normal positions */
2039           v = VariantNormal;
2040           break;
2041         case 6:
2042         case 20:
2043         case 21:
2044           /* Special wilds for position setup; unclear what to do here */
2045           v = VariantLoadable;
2046           break;
2047         case 9:
2048           /* Bizarre ICC game */
2049           v = VariantTwoKings;
2050           break;
2051         case 16:
2052           v = VariantKriegspiel;
2053           break;
2054         case 17:
2055           v = VariantLosers;
2056           break;
2057         case 22:
2058           v = VariantFischeRandom;
2059           break;
2060         case 23:
2061           v = VariantCrazyhouse;
2062           break;
2063         case 24:
2064           v = VariantBughouse;
2065           break;
2066         case 25:
2067           v = Variant3Check;
2068           break;
2069         case 26:
2070           /* Not quite the same as FICS suicide! */
2071           v = VariantGiveaway;
2072           break;
2073         case 27:
2074           v = VariantAtomic;
2075           break;
2076         case 28:
2077           v = VariantShatranj;
2078           break;
2079
2080         /* Temporary names for future ICC types.  The name *will* change in
2081            the next xboard/WinBoard release after ICC defines it. */
2082         case 29:
2083           v = Variant29;
2084           break;
2085         case 30:
2086           v = Variant30;
2087           break;
2088         case 31:
2089           v = Variant31;
2090           break;
2091         case 32:
2092           v = Variant32;
2093           break;
2094         case 33:
2095           v = Variant33;
2096           break;
2097         case 34:
2098           v = Variant34;
2099           break;
2100         case 35:
2101           v = Variant35;
2102           break;
2103         case 36:
2104           v = Variant36;
2105           break;
2106         case 37:
2107           v = VariantShogi;
2108           break;
2109         case 38:
2110           v = VariantXiangqi;
2111           break;
2112         case 39:
2113           v = VariantCourier;
2114           break;
2115         case 40:
2116           v = VariantGothic;
2117           break;
2118         case 41:
2119           v = VariantCapablanca;
2120           break;
2121         case 42:
2122           v = VariantKnightmate;
2123           break;
2124         case 43:
2125           v = VariantFairy;
2126           break;
2127         case 44:
2128           v = VariantCylinder;
2129           break;
2130         case 45:
2131           v = VariantFalcon;
2132           break;
2133         case 46:
2134           v = VariantCapaRandom;
2135           break;
2136         case 47:
2137           v = VariantBerolina;
2138           break;
2139         case 48:
2140           v = VariantJanus;
2141           break;
2142         case 49:
2143           v = VariantSuper;
2144           break;
2145         case 50:
2146           v = VariantGreat;
2147           break;
2148         case -1:
2149           /* Found "wild" or "w" in the string but no number;
2150              must assume it's normal chess. */
2151           v = VariantNormal;
2152           break;
2153         default:
2154           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2155           if( (len >= MSG_SIZ) && appData.debugMode )
2156             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2157
2158           DisplayError(buf, 0);
2159           v = VariantUnknown;
2160           break;
2161         }
2162       }
2163     }
2164     if (appData.debugMode) {
2165       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2166               e, wnum, VariantName(v));
2167     }
2168     return v;
2169 }
2170
2171 static int leftover_start = 0, leftover_len = 0;
2172 char star_match[STAR_MATCH_N][MSG_SIZ];
2173
2174 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2175    advance *index beyond it, and set leftover_start to the new value of
2176    *index; else return FALSE.  If pattern contains the character '*', it
2177    matches any sequence of characters not containing '\r', '\n', or the
2178    character following the '*' (if any), and the matched sequence(s) are
2179    copied into star_match.
2180    */
2181 int
2182 looking_at ( char *buf, int *index, char *pattern)
2183 {
2184     char *bufp = &buf[*index], *patternp = pattern;
2185     int star_count = 0;
2186     char *matchp = star_match[0];
2187
2188     for (;;) {
2189         if (*patternp == NULLCHAR) {
2190             *index = leftover_start = bufp - buf;
2191             *matchp = NULLCHAR;
2192             return TRUE;
2193         }
2194         if (*bufp == NULLCHAR) return FALSE;
2195         if (*patternp == '*') {
2196             if (*bufp == *(patternp + 1)) {
2197                 *matchp = NULLCHAR;
2198                 matchp = star_match[++star_count];
2199                 patternp += 2;
2200                 bufp++;
2201                 continue;
2202             } else if (*bufp == '\n' || *bufp == '\r') {
2203                 patternp++;
2204                 if (*patternp == NULLCHAR)
2205                   continue;
2206                 else
2207                   return FALSE;
2208             } else {
2209                 *matchp++ = *bufp++;
2210                 continue;
2211             }
2212         }
2213         if (*patternp != *bufp) return FALSE;
2214         patternp++;
2215         bufp++;
2216     }
2217 }
2218
2219 void
2220 SendToPlayer (char *data, int length)
2221 {
2222     int error, outCount;
2223     outCount = OutputToProcess(NoProc, data, length, &error);
2224     if (outCount < length) {
2225         DisplayFatalError(_("Error writing to display"), error, 1);
2226     }
2227 }
2228
2229 void
2230 PackHolding (char packed[], char *holding)
2231 {
2232     char *p = holding;
2233     char *q = packed;
2234     int runlength = 0;
2235     int curr = 9999;
2236     do {
2237         if (*p == curr) {
2238             runlength++;
2239         } else {
2240             switch (runlength) {
2241               case 0:
2242                 break;
2243               case 1:
2244                 *q++ = curr;
2245                 break;
2246               case 2:
2247                 *q++ = curr;
2248                 *q++ = curr;
2249                 break;
2250               default:
2251                 sprintf(q, "%d", runlength);
2252                 while (*q) q++;
2253                 *q++ = curr;
2254                 break;
2255             }
2256             runlength = 1;
2257             curr = *p;
2258         }
2259     } while (*p++);
2260     *q = NULLCHAR;
2261 }
2262
2263 /* Telnet protocol requests from the front end */
2264 void
2265 TelnetRequest (unsigned char ddww, unsigned char option)
2266 {
2267     unsigned char msg[3];
2268     int outCount, outError;
2269
2270     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2271
2272     if (appData.debugMode) {
2273         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2274         switch (ddww) {
2275           case TN_DO:
2276             ddwwStr = "DO";
2277             break;
2278           case TN_DONT:
2279             ddwwStr = "DONT";
2280             break;
2281           case TN_WILL:
2282             ddwwStr = "WILL";
2283             break;
2284           case TN_WONT:
2285             ddwwStr = "WONT";
2286             break;
2287           default:
2288             ddwwStr = buf1;
2289             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2290             break;
2291         }
2292         switch (option) {
2293           case TN_ECHO:
2294             optionStr = "ECHO";
2295             break;
2296           default:
2297             optionStr = buf2;
2298             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2299             break;
2300         }
2301         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2302     }
2303     msg[0] = TN_IAC;
2304     msg[1] = ddww;
2305     msg[2] = option;
2306     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2307     if (outCount < 3) {
2308         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2309     }
2310 }
2311
2312 void
2313 DoEcho ()
2314 {
2315     if (!appData.icsActive) return;
2316     TelnetRequest(TN_DO, TN_ECHO);
2317 }
2318
2319 void
2320 DontEcho ()
2321 {
2322     if (!appData.icsActive) return;
2323     TelnetRequest(TN_DONT, TN_ECHO);
2324 }
2325
2326 void
2327 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2328 {
2329     /* put the holdings sent to us by the server on the board holdings area */
2330     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2331     char p;
2332     ChessSquare piece;
2333
2334     if(gameInfo.holdingsWidth < 2)  return;
2335     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2336         return; // prevent overwriting by pre-board holdings
2337
2338     if( (int)lowestPiece >= BlackPawn ) {
2339         holdingsColumn = 0;
2340         countsColumn = 1;
2341         holdingsStartRow = BOARD_HEIGHT-1;
2342         direction = -1;
2343     } else {
2344         holdingsColumn = BOARD_WIDTH-1;
2345         countsColumn = BOARD_WIDTH-2;
2346         holdingsStartRow = 0;
2347         direction = 1;
2348     }
2349
2350     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2351         board[i][holdingsColumn] = EmptySquare;
2352         board[i][countsColumn]   = (ChessSquare) 0;
2353     }
2354     while( (p=*holdings++) != NULLCHAR ) {
2355         piece = CharToPiece( ToUpper(p) );
2356         if(piece == EmptySquare) continue;
2357         /*j = (int) piece - (int) WhitePawn;*/
2358         j = PieceToNumber(piece);
2359         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2360         if(j < 0) continue;               /* should not happen */
2361         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2362         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2363         board[holdingsStartRow+j*direction][countsColumn]++;
2364     }
2365 }
2366
2367
2368 void
2369 VariantSwitch (Board board, VariantClass newVariant)
2370 {
2371    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2372    static Board oldBoard;
2373
2374    startedFromPositionFile = FALSE;
2375    if(gameInfo.variant == newVariant) return;
2376
2377    /* [HGM] This routine is called each time an assignment is made to
2378     * gameInfo.variant during a game, to make sure the board sizes
2379     * are set to match the new variant. If that means adding or deleting
2380     * holdings, we shift the playing board accordingly
2381     * This kludge is needed because in ICS observe mode, we get boards
2382     * of an ongoing game without knowing the variant, and learn about the
2383     * latter only later. This can be because of the move list we requested,
2384     * in which case the game history is refilled from the beginning anyway,
2385     * but also when receiving holdings of a crazyhouse game. In the latter
2386     * case we want to add those holdings to the already received position.
2387     */
2388
2389
2390    if (appData.debugMode) {
2391      fprintf(debugFP, "Switch board from %s to %s\n",
2392              VariantName(gameInfo.variant), VariantName(newVariant));
2393      setbuf(debugFP, NULL);
2394    }
2395    shuffleOpenings = 0;       /* [HGM] shuffle */
2396    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2397    switch(newVariant)
2398      {
2399      case VariantShogi:
2400        newWidth = 9;  newHeight = 9;
2401        gameInfo.holdingsSize = 7;
2402      case VariantBughouse:
2403      case VariantCrazyhouse:
2404        newHoldingsWidth = 2; break;
2405      case VariantGreat:
2406        newWidth = 10;
2407      case VariantSuper:
2408        newHoldingsWidth = 2;
2409        gameInfo.holdingsSize = 8;
2410        break;
2411      case VariantGothic:
2412      case VariantCapablanca:
2413      case VariantCapaRandom:
2414        newWidth = 10;
2415      default:
2416        newHoldingsWidth = gameInfo.holdingsSize = 0;
2417      };
2418
2419    if(newWidth  != gameInfo.boardWidth  ||
2420       newHeight != gameInfo.boardHeight ||
2421       newHoldingsWidth != gameInfo.holdingsWidth ) {
2422
2423      /* shift position to new playing area, if needed */
2424      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2425        for(i=0; i<BOARD_HEIGHT; i++)
2426          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2427            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2428              board[i][j];
2429        for(i=0; i<newHeight; i++) {
2430          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2431          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2432        }
2433      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2434        for(i=0; i<BOARD_HEIGHT; i++)
2435          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2436            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2437              board[i][j];
2438      }
2439      gameInfo.boardWidth  = newWidth;
2440      gameInfo.boardHeight = newHeight;
2441      gameInfo.holdingsWidth = newHoldingsWidth;
2442      gameInfo.variant = newVariant;
2443      InitDrawingSizes(-2, 0);
2444    } else gameInfo.variant = newVariant;
2445    CopyBoard(oldBoard, board);   // remember correctly formatted board
2446      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2447    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2448 }
2449
2450 static int loggedOn = FALSE;
2451
2452 /*-- Game start info cache: --*/
2453 int gs_gamenum;
2454 char gs_kind[MSG_SIZ];
2455 static char player1Name[128] = "";
2456 static char player2Name[128] = "";
2457 static char cont_seq[] = "\n\\   ";
2458 static int player1Rating = -1;
2459 static int player2Rating = -1;
2460 /*----------------------------*/
2461
2462 ColorClass curColor = ColorNormal;
2463 int suppressKibitz = 0;
2464
2465 // [HGM] seekgraph
2466 Boolean soughtPending = FALSE;
2467 Boolean seekGraphUp;
2468 #define MAX_SEEK_ADS 200
2469 #define SQUARE 0x80
2470 char *seekAdList[MAX_SEEK_ADS];
2471 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2472 float tcList[MAX_SEEK_ADS];
2473 char colorList[MAX_SEEK_ADS];
2474 int nrOfSeekAds = 0;
2475 int minRating = 1010, maxRating = 2800;
2476 int hMargin = 10, vMargin = 20, h, w;
2477 extern int squareSize, lineGap;
2478
2479 void
2480 PlotSeekAd (int i)
2481 {
2482         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2483         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2484         if(r < minRating+100 && r >=0 ) r = minRating+100;
2485         if(r > maxRating) r = maxRating;
2486         if(tc < 1.) tc = 1.;
2487         if(tc > 95.) tc = 95.;
2488         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2489         y = ((double)r - minRating)/(maxRating - minRating)
2490             * (h-vMargin-squareSize/8-1) + vMargin;
2491         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2492         if(strstr(seekAdList[i], " u ")) color = 1;
2493         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2494            !strstr(seekAdList[i], "bullet") &&
2495            !strstr(seekAdList[i], "blitz") &&
2496            !strstr(seekAdList[i], "standard") ) color = 2;
2497         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2498         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2499 }
2500
2501 void
2502 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2503 {
2504         char buf[MSG_SIZ], *ext = "";
2505         VariantClass v = StringToVariant(type);
2506         if(strstr(type, "wild")) {
2507             ext = type + 4; // append wild number
2508             if(v == VariantFischeRandom) type = "chess960"; else
2509             if(v == VariantLoadable) type = "setup"; else
2510             type = VariantName(v);
2511         }
2512         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2513         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2514             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2515             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2516             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2517             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2518             seekNrList[nrOfSeekAds] = nr;
2519             zList[nrOfSeekAds] = 0;
2520             seekAdList[nrOfSeekAds++] = StrSave(buf);
2521             if(plot) PlotSeekAd(nrOfSeekAds-1);
2522         }
2523 }
2524
2525 void
2526 EraseSeekDot (int i)
2527 {
2528     int x = xList[i], y = yList[i], d=squareSize/4, k;
2529     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2530     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2531     // now replot every dot that overlapped
2532     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2533         int xx = xList[k], yy = yList[k];
2534         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2535             DrawSeekDot(xx, yy, colorList[k]);
2536     }
2537 }
2538
2539 void
2540 RemoveSeekAd (int nr)
2541 {
2542         int i;
2543         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2544             EraseSeekDot(i);
2545             if(seekAdList[i]) free(seekAdList[i]);
2546             seekAdList[i] = seekAdList[--nrOfSeekAds];
2547             seekNrList[i] = seekNrList[nrOfSeekAds];
2548             ratingList[i] = ratingList[nrOfSeekAds];
2549             colorList[i]  = colorList[nrOfSeekAds];
2550             tcList[i] = tcList[nrOfSeekAds];
2551             xList[i]  = xList[nrOfSeekAds];
2552             yList[i]  = yList[nrOfSeekAds];
2553             zList[i]  = zList[nrOfSeekAds];
2554             seekAdList[nrOfSeekAds] = NULL;
2555             break;
2556         }
2557 }
2558
2559 Boolean
2560 MatchSoughtLine (char *line)
2561 {
2562     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2563     int nr, base, inc, u=0; char dummy;
2564
2565     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2566        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2567        (u=1) &&
2568        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2569         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2570         // match: compact and save the line
2571         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2572         return TRUE;
2573     }
2574     return FALSE;
2575 }
2576
2577 int
2578 DrawSeekGraph ()
2579 {
2580     int i;
2581     if(!seekGraphUp) return FALSE;
2582     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2583     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2584
2585     DrawSeekBackground(0, 0, w, h);
2586     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2587     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2588     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2589         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2590         yy = h-1-yy;
2591         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2592         if(i%500 == 0) {
2593             char buf[MSG_SIZ];
2594             snprintf(buf, MSG_SIZ, "%d", i);
2595             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2596         }
2597     }
2598     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2599     for(i=1; i<100; i+=(i<10?1:5)) {
2600         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2601         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2602         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2603             char buf[MSG_SIZ];
2604             snprintf(buf, MSG_SIZ, "%d", i);
2605             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2606         }
2607     }
2608     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2609     return TRUE;
2610 }
2611
2612 int
2613 SeekGraphClick (ClickType click, int x, int y, int moving)
2614 {
2615     static int lastDown = 0, displayed = 0, lastSecond;
2616     if(y < 0) return FALSE;
2617     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2618         if(click == Release || moving) return FALSE;
2619         nrOfSeekAds = 0;
2620         soughtPending = TRUE;
2621         SendToICS(ics_prefix);
2622         SendToICS("sought\n"); // should this be "sought all"?
2623     } else { // issue challenge based on clicked ad
2624         int dist = 10000; int i, closest = 0, second = 0;
2625         for(i=0; i<nrOfSeekAds; i++) {
2626             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2627             if(d < dist) { dist = d; closest = i; }
2628             second += (d - zList[i] < 120); // count in-range ads
2629             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2630         }
2631         if(dist < 120) {
2632             char buf[MSG_SIZ];
2633             second = (second > 1);
2634             if(displayed != closest || second != lastSecond) {
2635                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2636                 lastSecond = second; displayed = closest;
2637             }
2638             if(click == Press) {
2639                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2640                 lastDown = closest;
2641                 return TRUE;
2642             } // on press 'hit', only show info
2643             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2644             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2645             SendToICS(ics_prefix);
2646             SendToICS(buf);
2647             return TRUE; // let incoming board of started game pop down the graph
2648         } else if(click == Release) { // release 'miss' is ignored
2649             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2650             if(moving == 2) { // right up-click
2651                 nrOfSeekAds = 0; // refresh graph
2652                 soughtPending = TRUE;
2653                 SendToICS(ics_prefix);
2654                 SendToICS("sought\n"); // should this be "sought all"?
2655             }
2656             return TRUE;
2657         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2658         // press miss or release hit 'pop down' seek graph
2659         seekGraphUp = FALSE;
2660         DrawPosition(TRUE, NULL);
2661     }
2662     return TRUE;
2663 }
2664
2665 void
2666 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2667 {
2668 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2669 #define STARTED_NONE 0
2670 #define STARTED_MOVES 1
2671 #define STARTED_BOARD 2
2672 #define STARTED_OBSERVE 3
2673 #define STARTED_HOLDINGS 4
2674 #define STARTED_CHATTER 5
2675 #define STARTED_COMMENT 6
2676 #define STARTED_MOVES_NOHIDE 7
2677
2678     static int started = STARTED_NONE;
2679     static char parse[20000];
2680     static int parse_pos = 0;
2681     static char buf[BUF_SIZE + 1];
2682     static int firstTime = TRUE, intfSet = FALSE;
2683     static ColorClass prevColor = ColorNormal;
2684     static int savingComment = FALSE;
2685     static int cmatch = 0; // continuation sequence match
2686     char *bp;
2687     char str[MSG_SIZ];
2688     int i, oldi;
2689     int buf_len;
2690     int next_out;
2691     int tkind;
2692     int backup;    /* [DM] For zippy color lines */
2693     char *p;
2694     char talker[MSG_SIZ]; // [HGM] chat
2695     int channel;
2696
2697     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2698
2699     if (appData.debugMode) {
2700       if (!error) {
2701         fprintf(debugFP, "<ICS: ");
2702         show_bytes(debugFP, data, count);
2703         fprintf(debugFP, "\n");
2704       }
2705     }
2706
2707     if (appData.debugMode) { int f = forwardMostMove;
2708         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2709                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2710                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2711     }
2712     if (count > 0) {
2713         /* If last read ended with a partial line that we couldn't parse,
2714            prepend it to the new read and try again. */
2715         if (leftover_len > 0) {
2716             for (i=0; i<leftover_len; i++)
2717               buf[i] = buf[leftover_start + i];
2718         }
2719
2720     /* copy new characters into the buffer */
2721     bp = buf + leftover_len;
2722     buf_len=leftover_len;
2723     for (i=0; i<count; i++)
2724     {
2725         // ignore these
2726         if (data[i] == '\r')
2727             continue;
2728
2729         // join lines split by ICS?
2730         if (!appData.noJoin)
2731         {
2732             /*
2733                 Joining just consists of finding matches against the
2734                 continuation sequence, and discarding that sequence
2735                 if found instead of copying it.  So, until a match
2736                 fails, there's nothing to do since it might be the
2737                 complete sequence, and thus, something we don't want
2738                 copied.
2739             */
2740             if (data[i] == cont_seq[cmatch])
2741             {
2742                 cmatch++;
2743                 if (cmatch == strlen(cont_seq))
2744                 {
2745                     cmatch = 0; // complete match.  just reset the counter
2746
2747                     /*
2748                         it's possible for the ICS to not include the space
2749                         at the end of the last word, making our [correct]
2750                         join operation fuse two separate words.  the server
2751                         does this when the space occurs at the width setting.
2752                     */
2753                     if (!buf_len || buf[buf_len-1] != ' ')
2754                     {
2755                         *bp++ = ' ';
2756                         buf_len++;
2757                     }
2758                 }
2759                 continue;
2760             }
2761             else if (cmatch)
2762             {
2763                 /*
2764                     match failed, so we have to copy what matched before
2765                     falling through and copying this character.  In reality,
2766                     this will only ever be just the newline character, but
2767                     it doesn't hurt to be precise.
2768                 */
2769                 strncpy(bp, cont_seq, cmatch);
2770                 bp += cmatch;
2771                 buf_len += cmatch;
2772                 cmatch = 0;
2773             }
2774         }
2775
2776         // copy this char
2777         *bp++ = data[i];
2778         buf_len++;
2779     }
2780
2781         buf[buf_len] = NULLCHAR;
2782 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2783         next_out = 0;
2784         leftover_start = 0;
2785
2786         i = 0;
2787         while (i < buf_len) {
2788             /* Deal with part of the TELNET option negotiation
2789                protocol.  We refuse to do anything beyond the
2790                defaults, except that we allow the WILL ECHO option,
2791                which ICS uses to turn off password echoing when we are
2792                directly connected to it.  We reject this option
2793                if localLineEditing mode is on (always on in xboard)
2794                and we are talking to port 23, which might be a real
2795                telnet server that will try to keep WILL ECHO on permanently.
2796              */
2797             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2798                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2799                 unsigned char option;
2800                 oldi = i;
2801                 switch ((unsigned char) buf[++i]) {
2802                   case TN_WILL:
2803                     if (appData.debugMode)
2804                       fprintf(debugFP, "\n<WILL ");
2805                     switch (option = (unsigned char) buf[++i]) {
2806                       case TN_ECHO:
2807                         if (appData.debugMode)
2808                           fprintf(debugFP, "ECHO ");
2809                         /* Reply only if this is a change, according
2810                            to the protocol rules. */
2811                         if (remoteEchoOption) break;
2812                         if (appData.localLineEditing &&
2813                             atoi(appData.icsPort) == TN_PORT) {
2814                             TelnetRequest(TN_DONT, TN_ECHO);
2815                         } else {
2816                             EchoOff();
2817                             TelnetRequest(TN_DO, TN_ECHO);
2818                             remoteEchoOption = TRUE;
2819                         }
2820                         break;
2821                       default:
2822                         if (appData.debugMode)
2823                           fprintf(debugFP, "%d ", option);
2824                         /* Whatever this is, we don't want it. */
2825                         TelnetRequest(TN_DONT, option);
2826                         break;
2827                     }
2828                     break;
2829                   case TN_WONT:
2830                     if (appData.debugMode)
2831                       fprintf(debugFP, "\n<WONT ");
2832                     switch (option = (unsigned char) buf[++i]) {
2833                       case TN_ECHO:
2834                         if (appData.debugMode)
2835                           fprintf(debugFP, "ECHO ");
2836                         /* Reply only if this is a change, according
2837                            to the protocol rules. */
2838                         if (!remoteEchoOption) break;
2839                         EchoOn();
2840                         TelnetRequest(TN_DONT, TN_ECHO);
2841                         remoteEchoOption = FALSE;
2842                         break;
2843                       default:
2844                         if (appData.debugMode)
2845                           fprintf(debugFP, "%d ", (unsigned char) option);
2846                         /* Whatever this is, it must already be turned
2847                            off, because we never agree to turn on
2848                            anything non-default, so according to the
2849                            protocol rules, we don't reply. */
2850                         break;
2851                     }
2852                     break;
2853                   case TN_DO:
2854                     if (appData.debugMode)
2855                       fprintf(debugFP, "\n<DO ");
2856                     switch (option = (unsigned char) buf[++i]) {
2857                       default:
2858                         /* Whatever this is, we refuse to do it. */
2859                         if (appData.debugMode)
2860                           fprintf(debugFP, "%d ", option);
2861                         TelnetRequest(TN_WONT, option);
2862                         break;
2863                     }
2864                     break;
2865                   case TN_DONT:
2866                     if (appData.debugMode)
2867                       fprintf(debugFP, "\n<DONT ");
2868                     switch (option = (unsigned char) buf[++i]) {
2869                       default:
2870                         if (appData.debugMode)
2871                           fprintf(debugFP, "%d ", option);
2872                         /* Whatever this is, we are already not doing
2873                            it, because we never agree to do anything
2874                            non-default, so according to the protocol
2875                            rules, we don't reply. */
2876                         break;
2877                     }
2878                     break;
2879                   case TN_IAC:
2880                     if (appData.debugMode)
2881                       fprintf(debugFP, "\n<IAC ");
2882                     /* Doubled IAC; pass it through */
2883                     i--;
2884                     break;
2885                   default:
2886                     if (appData.debugMode)
2887                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2888                     /* Drop all other telnet commands on the floor */
2889                     break;
2890                 }
2891                 if (oldi > next_out)
2892                   SendToPlayer(&buf[next_out], oldi - next_out);
2893                 if (++i > next_out)
2894                   next_out = i;
2895                 continue;
2896             }
2897
2898             /* OK, this at least will *usually* work */
2899             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2900                 loggedOn = TRUE;
2901             }
2902
2903             if (loggedOn && !intfSet) {
2904                 if (ics_type == ICS_ICC) {
2905                   snprintf(str, MSG_SIZ,
2906                           "/set-quietly interface %s\n/set-quietly style 12\n",
2907                           programVersion);
2908                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2909                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2910                 } else if (ics_type == ICS_CHESSNET) {
2911                   snprintf(str, MSG_SIZ, "/style 12\n");
2912                 } else {
2913                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2914                   strcat(str, programVersion);
2915                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2916                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2917                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2918 #ifdef WIN32
2919                   strcat(str, "$iset nohighlight 1\n");
2920 #endif
2921                   strcat(str, "$iset lock 1\n$style 12\n");
2922                 }
2923                 SendToICS(str);
2924                 NotifyFrontendLogin();
2925                 intfSet = TRUE;
2926             }
2927
2928             if (started == STARTED_COMMENT) {
2929                 /* Accumulate characters in comment */
2930                 parse[parse_pos++] = buf[i];
2931                 if (buf[i] == '\n') {
2932                     parse[parse_pos] = NULLCHAR;
2933                     if(chattingPartner>=0) {
2934                         char mess[MSG_SIZ];
2935                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2936                         OutputChatMessage(chattingPartner, mess);
2937                         chattingPartner = -1;
2938                         next_out = i+1; // [HGM] suppress printing in ICS window
2939                     } else
2940                     if(!suppressKibitz) // [HGM] kibitz
2941                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2942                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2943                         int nrDigit = 0, nrAlph = 0, j;
2944                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2945                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2946                         parse[parse_pos] = NULLCHAR;
2947                         // try to be smart: if it does not look like search info, it should go to
2948                         // ICS interaction window after all, not to engine-output window.
2949                         for(j=0; j<parse_pos; j++) { // count letters and digits
2950                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2951                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2952                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2953                         }
2954                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2955                             int depth=0; float score;
2956                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2957                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2958                                 pvInfoList[forwardMostMove-1].depth = depth;
2959                                 pvInfoList[forwardMostMove-1].score = 100*score;
2960                             }
2961                             OutputKibitz(suppressKibitz, parse);
2962                         } else {
2963                             char tmp[MSG_SIZ];
2964                             if(gameMode == IcsObserving) // restore original ICS messages
2965                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2966                             else
2967                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2968                             SendToPlayer(tmp, strlen(tmp));
2969                         }
2970                         next_out = i+1; // [HGM] suppress printing in ICS window
2971                     }
2972                     started = STARTED_NONE;
2973                 } else {
2974                     /* Don't match patterns against characters in comment */
2975                     i++;
2976                     continue;
2977                 }
2978             }
2979             if (started == STARTED_CHATTER) {
2980                 if (buf[i] != '\n') {
2981                     /* Don't match patterns against characters in chatter */
2982                     i++;
2983                     continue;
2984                 }
2985                 started = STARTED_NONE;
2986                 if(suppressKibitz) next_out = i+1;
2987             }
2988
2989             /* Kludge to deal with rcmd protocol */
2990             if (firstTime && looking_at(buf, &i, "\001*")) {
2991                 DisplayFatalError(&buf[1], 0, 1);
2992                 continue;
2993             } else {
2994                 firstTime = FALSE;
2995             }
2996
2997             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2998                 ics_type = ICS_ICC;
2999                 ics_prefix = "/";
3000                 if (appData.debugMode)
3001                   fprintf(debugFP, "ics_type %d\n", ics_type);
3002                 continue;
3003             }
3004             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3005                 ics_type = ICS_FICS;
3006                 ics_prefix = "$";
3007                 if (appData.debugMode)
3008                   fprintf(debugFP, "ics_type %d\n", ics_type);
3009                 continue;
3010             }
3011             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3012                 ics_type = ICS_CHESSNET;
3013                 ics_prefix = "/";
3014                 if (appData.debugMode)
3015                   fprintf(debugFP, "ics_type %d\n", ics_type);
3016                 continue;
3017             }
3018
3019             if (!loggedOn &&
3020                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3021                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3022                  looking_at(buf, &i, "will be \"*\""))) {
3023               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3024               continue;
3025             }
3026
3027             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3028               char buf[MSG_SIZ];
3029               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3030               DisplayIcsInteractionTitle(buf);
3031               have_set_title = TRUE;
3032             }
3033
3034             /* skip finger notes */
3035             if (started == STARTED_NONE &&
3036                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3037                  (buf[i] == '1' && buf[i+1] == '0')) &&
3038                 buf[i+2] == ':' && buf[i+3] == ' ') {
3039               started = STARTED_CHATTER;
3040               i += 3;
3041               continue;
3042             }
3043
3044             oldi = i;
3045             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3046             if(appData.seekGraph) {
3047                 if(soughtPending && MatchSoughtLine(buf+i)) {
3048                     i = strstr(buf+i, "rated") - buf;
3049                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3050                     next_out = leftover_start = i;
3051                     started = STARTED_CHATTER;
3052                     suppressKibitz = TRUE;
3053                     continue;
3054                 }
3055                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3056                         && looking_at(buf, &i, "* ads displayed")) {
3057                     soughtPending = FALSE;
3058                     seekGraphUp = TRUE;
3059                     DrawSeekGraph();
3060                     continue;
3061                 }
3062                 if(appData.autoRefresh) {
3063                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3064                         int s = (ics_type == ICS_ICC); // ICC format differs
3065                         if(seekGraphUp)
3066                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3067                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3068                         looking_at(buf, &i, "*% "); // eat prompt
3069                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3070                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3071                         next_out = i; // suppress
3072                         continue;
3073                     }
3074                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3075                         char *p = star_match[0];
3076                         while(*p) {
3077                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3078                             while(*p && *p++ != ' '); // next
3079                         }
3080                         looking_at(buf, &i, "*% "); // eat prompt
3081                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3082                         next_out = i;
3083                         continue;
3084                     }
3085                 }
3086             }
3087
3088             /* skip formula vars */
3089             if (started == STARTED_NONE &&
3090                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3091               started = STARTED_CHATTER;
3092               i += 3;
3093               continue;
3094             }
3095
3096             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3097             if (appData.autoKibitz && started == STARTED_NONE &&
3098                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3099                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3100                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3101                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3102                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3103                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3104                         suppressKibitz = TRUE;
3105                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3106                         next_out = i;
3107                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3108                                 && (gameMode == IcsPlayingWhite)) ||
3109                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3110                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3111                             started = STARTED_CHATTER; // own kibitz we simply discard
3112                         else {
3113                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3114                             parse_pos = 0; parse[0] = NULLCHAR;
3115                             savingComment = TRUE;
3116                             suppressKibitz = gameMode != IcsObserving ? 2 :
3117                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3118                         }
3119                         continue;
3120                 } else
3121                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3122                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3123                          && atoi(star_match[0])) {
3124                     // suppress the acknowledgements of our own autoKibitz
3125                     char *p;
3126                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3127                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3128                     SendToPlayer(star_match[0], strlen(star_match[0]));
3129                     if(looking_at(buf, &i, "*% ")) // eat prompt
3130                         suppressKibitz = FALSE;
3131                     next_out = i;
3132                     continue;
3133                 }
3134             } // [HGM] kibitz: end of patch
3135
3136             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3137
3138             // [HGM] chat: intercept tells by users for which we have an open chat window
3139             channel = -1;
3140             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3141                                            looking_at(buf, &i, "* whispers:") ||
3142                                            looking_at(buf, &i, "* kibitzes:") ||
3143                                            looking_at(buf, &i, "* shouts:") ||
3144                                            looking_at(buf, &i, "* c-shouts:") ||
3145                                            looking_at(buf, &i, "--> * ") ||
3146                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3147                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3148                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3149                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3150                 int p;
3151                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3152                 chattingPartner = -1;
3153
3154                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3155                 for(p=0; p<MAX_CHAT; p++) {
3156                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3157                     talker[0] = '['; strcat(talker, "] ");
3158                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3159                     chattingPartner = p; break;
3160                     }
3161                 } else
3162                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3163                 for(p=0; p<MAX_CHAT; p++) {
3164                     if(!strcmp("kibitzes", chatPartner[p])) {
3165                         talker[0] = '['; strcat(talker, "] ");
3166                         chattingPartner = p; break;
3167                     }
3168                 } else
3169                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3170                 for(p=0; p<MAX_CHAT; p++) {
3171                     if(!strcmp("whispers", chatPartner[p])) {
3172                         talker[0] = '['; strcat(talker, "] ");
3173                         chattingPartner = p; break;
3174                     }
3175                 } else
3176                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3177                   if(buf[i-8] == '-' && buf[i-3] == 't')
3178                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3179                     if(!strcmp("c-shouts", chatPartner[p])) {
3180                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3181                         chattingPartner = p; break;
3182                     }
3183                   }
3184                   if(chattingPartner < 0)
3185                   for(p=0; p<MAX_CHAT; p++) {
3186                     if(!strcmp("shouts", chatPartner[p])) {
3187                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3188                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3189                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3190                         chattingPartner = p; break;
3191                     }
3192                   }
3193                 }
3194                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3195                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3196                     talker[0] = 0; Colorize(ColorTell, FALSE);
3197                     chattingPartner = p; break;
3198                 }
3199                 if(chattingPartner<0) i = oldi; else {
3200                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3201                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3202                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3203                     started = STARTED_COMMENT;
3204                     parse_pos = 0; parse[0] = NULLCHAR;
3205                     savingComment = 3 + chattingPartner; // counts as TRUE
3206                     suppressKibitz = TRUE;
3207                     continue;
3208                 }
3209             } // [HGM] chat: end of patch
3210
3211           backup = i;
3212             if (appData.zippyTalk || appData.zippyPlay) {
3213                 /* [DM] Backup address for color zippy lines */
3214 #if ZIPPY
3215                if (loggedOn == TRUE)
3216                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3217                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3218 #endif
3219             } // [DM] 'else { ' deleted
3220                 if (
3221                     /* Regular tells and says */
3222                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3223                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3224                     looking_at(buf, &i, "* says: ") ||
3225                     /* Don't color "message" or "messages" output */
3226                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3227                     looking_at(buf, &i, "*. * at *:*: ") ||
3228                     looking_at(buf, &i, "--* (*:*): ") ||
3229                     /* Message notifications (same color as tells) */
3230                     looking_at(buf, &i, "* has left a message ") ||
3231                     looking_at(buf, &i, "* just sent you a message:\n") ||
3232                     /* Whispers and kibitzes */
3233                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3234                     looking_at(buf, &i, "* kibitzes: ") ||
3235                     /* Channel tells */
3236                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3237
3238                   if (tkind == 1 && strchr(star_match[0], ':')) {
3239                       /* Avoid "tells you:" spoofs in channels */
3240                      tkind = 3;
3241                   }
3242                   if (star_match[0][0] == NULLCHAR ||
3243                       strchr(star_match[0], ' ') ||
3244                       (tkind == 3 && strchr(star_match[1], ' '))) {
3245                     /* Reject bogus matches */
3246                     i = oldi;
3247                   } else {
3248                     if (appData.colorize) {
3249                       if (oldi > next_out) {
3250                         SendToPlayer(&buf[next_out], oldi - next_out);
3251                         next_out = oldi;
3252                       }
3253                       switch (tkind) {
3254                       case 1:
3255                         Colorize(ColorTell, FALSE);
3256                         curColor = ColorTell;
3257                         break;
3258                       case 2:
3259                         Colorize(ColorKibitz, FALSE);
3260                         curColor = ColorKibitz;
3261                         break;
3262                       case 3:
3263                         p = strrchr(star_match[1], '(');
3264                         if (p == NULL) {
3265                           p = star_match[1];
3266                         } else {
3267                           p++;
3268                         }
3269                         if (atoi(p) == 1) {
3270                           Colorize(ColorChannel1, FALSE);
3271                           curColor = ColorChannel1;
3272                         } else {
3273                           Colorize(ColorChannel, FALSE);
3274                           curColor = ColorChannel;
3275                         }
3276                         break;
3277                       case 5:
3278                         curColor = ColorNormal;
3279                         break;
3280                       }
3281                     }
3282                     if (started == STARTED_NONE && appData.autoComment &&
3283                         (gameMode == IcsObserving ||
3284                          gameMode == IcsPlayingWhite ||
3285                          gameMode == IcsPlayingBlack)) {
3286                       parse_pos = i - oldi;
3287                       memcpy(parse, &buf[oldi], parse_pos);
3288                       parse[parse_pos] = NULLCHAR;
3289                       started = STARTED_COMMENT;
3290                       savingComment = TRUE;
3291                     } else {
3292                       started = STARTED_CHATTER;
3293                       savingComment = FALSE;
3294                     }
3295                     loggedOn = TRUE;
3296                     continue;
3297                   }
3298                 }
3299
3300                 if (looking_at(buf, &i, "* s-shouts: ") ||
3301                     looking_at(buf, &i, "* c-shouts: ")) {
3302                     if (appData.colorize) {
3303                         if (oldi > next_out) {
3304                             SendToPlayer(&buf[next_out], oldi - next_out);
3305                             next_out = oldi;
3306                         }
3307                         Colorize(ColorSShout, FALSE);
3308                         curColor = ColorSShout;
3309                     }
3310                     loggedOn = TRUE;
3311                     started = STARTED_CHATTER;
3312                     continue;
3313                 }
3314
3315                 if (looking_at(buf, &i, "--->")) {
3316                     loggedOn = TRUE;
3317                     continue;
3318                 }
3319
3320                 if (looking_at(buf, &i, "* shouts: ") ||
3321                     looking_at(buf, &i, "--> ")) {
3322                     if (appData.colorize) {
3323                         if (oldi > next_out) {
3324                             SendToPlayer(&buf[next_out], oldi - next_out);
3325                             next_out = oldi;
3326                         }
3327                         Colorize(ColorShout, FALSE);
3328                         curColor = ColorShout;
3329                     }
3330                     loggedOn = TRUE;
3331                     started = STARTED_CHATTER;
3332                     continue;
3333                 }
3334
3335                 if (looking_at( buf, &i, "Challenge:")) {
3336                     if (appData.colorize) {
3337                         if (oldi > next_out) {
3338                             SendToPlayer(&buf[next_out], oldi - next_out);
3339                             next_out = oldi;
3340                         }
3341                         Colorize(ColorChallenge, FALSE);
3342                         curColor = ColorChallenge;
3343                     }
3344                     loggedOn = TRUE;
3345                     continue;
3346                 }
3347
3348                 if (looking_at(buf, &i, "* offers you") ||
3349                     looking_at(buf, &i, "* offers to be") ||
3350                     looking_at(buf, &i, "* would like to") ||
3351                     looking_at(buf, &i, "* requests to") ||
3352                     looking_at(buf, &i, "Your opponent offers") ||
3353                     looking_at(buf, &i, "Your opponent requests")) {
3354
3355                     if (appData.colorize) {
3356                         if (oldi > next_out) {
3357                             SendToPlayer(&buf[next_out], oldi - next_out);
3358                             next_out = oldi;
3359                         }
3360                         Colorize(ColorRequest, FALSE);
3361                         curColor = ColorRequest;
3362                     }
3363                     continue;
3364                 }
3365
3366                 if (looking_at(buf, &i, "* (*) seeking")) {
3367                     if (appData.colorize) {
3368                         if (oldi > next_out) {
3369                             SendToPlayer(&buf[next_out], oldi - next_out);
3370                             next_out = oldi;
3371                         }
3372                         Colorize(ColorSeek, FALSE);
3373                         curColor = ColorSeek;
3374                     }
3375                     continue;
3376             }
3377
3378           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3379
3380             if (looking_at(buf, &i, "\\   ")) {
3381                 if (prevColor != ColorNormal) {
3382                     if (oldi > next_out) {
3383                         SendToPlayer(&buf[next_out], oldi - next_out);
3384                         next_out = oldi;
3385                     }
3386                     Colorize(prevColor, TRUE);
3387                     curColor = prevColor;
3388                 }
3389                 if (savingComment) {
3390                     parse_pos = i - oldi;
3391                     memcpy(parse, &buf[oldi], parse_pos);
3392                     parse[parse_pos] = NULLCHAR;
3393                     started = STARTED_COMMENT;
3394                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3395                         chattingPartner = savingComment - 3; // kludge to remember the box
3396                 } else {
3397                     started = STARTED_CHATTER;
3398                 }
3399                 continue;
3400             }
3401
3402             if (looking_at(buf, &i, "Black Strength :") ||
3403                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3404                 looking_at(buf, &i, "<10>") ||
3405                 looking_at(buf, &i, "#@#")) {
3406                 /* Wrong board style */
3407                 loggedOn = TRUE;
3408                 SendToICS(ics_prefix);
3409                 SendToICS("set style 12\n");
3410                 SendToICS(ics_prefix);
3411                 SendToICS("refresh\n");
3412                 continue;
3413             }
3414
3415             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3416                 ICSInitScript();
3417                 have_sent_ICS_logon = 1;
3418                 continue;
3419             }
3420
3421             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3422                 (looking_at(buf, &i, "\n<12> ") ||
3423                  looking_at(buf, &i, "<12> "))) {
3424                 loggedOn = TRUE;
3425                 if (oldi > next_out) {
3426                     SendToPlayer(&buf[next_out], oldi - next_out);
3427                 }
3428                 next_out = i;
3429                 started = STARTED_BOARD;
3430                 parse_pos = 0;
3431                 continue;
3432             }
3433
3434             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3435                 looking_at(buf, &i, "<b1> ")) {
3436                 if (oldi > next_out) {
3437                     SendToPlayer(&buf[next_out], oldi - next_out);
3438                 }
3439                 next_out = i;
3440                 started = STARTED_HOLDINGS;
3441                 parse_pos = 0;
3442                 continue;
3443             }
3444
3445             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3446                 loggedOn = TRUE;
3447                 /* Header for a move list -- first line */
3448
3449                 switch (ics_getting_history) {
3450                   case H_FALSE:
3451                     switch (gameMode) {
3452                       case IcsIdle:
3453                       case BeginningOfGame:
3454                         /* User typed "moves" or "oldmoves" while we
3455                            were idle.  Pretend we asked for these
3456                            moves and soak them up so user can step
3457                            through them and/or save them.
3458                            */
3459                         Reset(FALSE, TRUE);
3460                         gameMode = IcsObserving;
3461                         ModeHighlight();
3462                         ics_gamenum = -1;
3463                         ics_getting_history = H_GOT_UNREQ_HEADER;
3464                         break;
3465                       case EditGame: /*?*/
3466                       case EditPosition: /*?*/
3467                         /* Should above feature work in these modes too? */
3468                         /* For now it doesn't */
3469                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3470                         break;
3471                       default:
3472                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3473                         break;
3474                     }
3475                     break;
3476                   case H_REQUESTED:
3477                     /* Is this the right one? */
3478                     if (gameInfo.white && gameInfo.black &&
3479                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3480                         strcmp(gameInfo.black, star_match[2]) == 0) {
3481                         /* All is well */
3482                         ics_getting_history = H_GOT_REQ_HEADER;
3483                     }
3484                     break;
3485                   case H_GOT_REQ_HEADER:
3486                   case H_GOT_UNREQ_HEADER:
3487                   case H_GOT_UNWANTED_HEADER:
3488                   case H_GETTING_MOVES:
3489                     /* Should not happen */
3490                     DisplayError(_("Error gathering move list: two headers"), 0);
3491                     ics_getting_history = H_FALSE;
3492                     break;
3493                 }
3494
3495                 /* Save player ratings into gameInfo if needed */
3496                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3497                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3498                     (gameInfo.whiteRating == -1 ||
3499                      gameInfo.blackRating == -1)) {
3500
3501                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3502                     gameInfo.blackRating = string_to_rating(star_match[3]);
3503                     if (appData.debugMode)
3504                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3505                               gameInfo.whiteRating, gameInfo.blackRating);
3506                 }
3507                 continue;
3508             }
3509
3510             if (looking_at(buf, &i,
3511               "* * match, initial time: * minute*, increment: * second")) {
3512                 /* Header for a move list -- second line */
3513                 /* Initial board will follow if this is a wild game */
3514                 if (gameInfo.event != NULL) free(gameInfo.event);
3515                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3516                 gameInfo.event = StrSave(str);
3517                 /* [HGM] we switched variant. Translate boards if needed. */
3518                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3519                 continue;
3520             }
3521
3522             if (looking_at(buf, &i, "Move  ")) {
3523                 /* Beginning of a move list */
3524                 switch (ics_getting_history) {
3525                   case H_FALSE:
3526                     /* Normally should not happen */
3527                     /* Maybe user hit reset while we were parsing */
3528                     break;
3529                   case H_REQUESTED:
3530                     /* Happens if we are ignoring a move list that is not
3531                      * the one we just requested.  Common if the user
3532                      * tries to observe two games without turning off
3533                      * getMoveList */
3534                     break;
3535                   case H_GETTING_MOVES:
3536                     /* Should not happen */
3537                     DisplayError(_("Error gathering move list: nested"), 0);
3538                     ics_getting_history = H_FALSE;
3539                     break;
3540                   case H_GOT_REQ_HEADER:
3541                     ics_getting_history = H_GETTING_MOVES;
3542                     started = STARTED_MOVES;
3543                     parse_pos = 0;
3544                     if (oldi > next_out) {
3545                         SendToPlayer(&buf[next_out], oldi - next_out);
3546                     }
3547                     break;
3548                   case H_GOT_UNREQ_HEADER:
3549                     ics_getting_history = H_GETTING_MOVES;
3550                     started = STARTED_MOVES_NOHIDE;
3551                     parse_pos = 0;
3552                     break;
3553                   case H_GOT_UNWANTED_HEADER:
3554                     ics_getting_history = H_FALSE;
3555                     break;
3556                 }
3557                 continue;
3558             }
3559
3560             if (looking_at(buf, &i, "% ") ||
3561                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3562                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3563                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3564                     soughtPending = FALSE;
3565                     seekGraphUp = TRUE;
3566                     DrawSeekGraph();
3567                 }
3568                 if(suppressKibitz) next_out = i;
3569                 savingComment = FALSE;
3570                 suppressKibitz = 0;
3571                 switch (started) {
3572                   case STARTED_MOVES:
3573                   case STARTED_MOVES_NOHIDE:
3574                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3575                     parse[parse_pos + i - oldi] = NULLCHAR;
3576                     ParseGameHistory(parse);
3577 #if ZIPPY
3578                     if (appData.zippyPlay && first.initDone) {
3579                         FeedMovesToProgram(&first, forwardMostMove);
3580                         if (gameMode == IcsPlayingWhite) {
3581                             if (WhiteOnMove(forwardMostMove)) {
3582                                 if (first.sendTime) {
3583                                   if (first.useColors) {
3584                                     SendToProgram("black\n", &first);
3585                                   }
3586                                   SendTimeRemaining(&first, TRUE);
3587                                 }
3588                                 if (first.useColors) {
3589                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3590                                 }
3591                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3592                                 first.maybeThinking = TRUE;
3593                             } else {
3594                                 if (first.usePlayother) {
3595                                   if (first.sendTime) {
3596                                     SendTimeRemaining(&first, TRUE);
3597                                   }
3598                                   SendToProgram("playother\n", &first);
3599                                   firstMove = FALSE;
3600                                 } else {
3601                                   firstMove = TRUE;
3602                                 }
3603                             }
3604                         } else if (gameMode == IcsPlayingBlack) {
3605                             if (!WhiteOnMove(forwardMostMove)) {
3606                                 if (first.sendTime) {
3607                                   if (first.useColors) {
3608                                     SendToProgram("white\n", &first);
3609                                   }
3610                                   SendTimeRemaining(&first, FALSE);
3611                                 }
3612                                 if (first.useColors) {
3613                                   SendToProgram("black\n", &first);
3614                                 }
3615                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3616                                 first.maybeThinking = TRUE;
3617                             } else {
3618                                 if (first.usePlayother) {
3619                                   if (first.sendTime) {
3620                                     SendTimeRemaining(&first, FALSE);
3621                                   }
3622                                   SendToProgram("playother\n", &first);
3623                                   firstMove = FALSE;
3624                                 } else {
3625                                   firstMove = TRUE;
3626                                 }
3627                             }
3628                         }
3629                     }
3630 #endif
3631                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3632                         /* Moves came from oldmoves or moves command
3633                            while we weren't doing anything else.
3634                            */
3635                         currentMove = forwardMostMove;
3636                         ClearHighlights();/*!!could figure this out*/
3637                         flipView = appData.flipView;
3638                         DrawPosition(TRUE, boards[currentMove]);
3639                         DisplayBothClocks();
3640                         snprintf(str, MSG_SIZ, "%s %s %s",
3641                                 gameInfo.white, _("vs."),  gameInfo.black);
3642                         DisplayTitle(str);
3643                         gameMode = IcsIdle;
3644                     } else {
3645                         /* Moves were history of an active game */
3646                         if (gameInfo.resultDetails != NULL) {
3647                             free(gameInfo.resultDetails);
3648                             gameInfo.resultDetails = NULL;
3649                         }
3650                     }
3651                     HistorySet(parseList, backwardMostMove,
3652                                forwardMostMove, currentMove-1);
3653                     DisplayMove(currentMove - 1);
3654                     if (started == STARTED_MOVES) next_out = i;
3655                     started = STARTED_NONE;
3656                     ics_getting_history = H_FALSE;
3657                     break;
3658
3659                   case STARTED_OBSERVE:
3660                     started = STARTED_NONE;
3661                     SendToICS(ics_prefix);
3662                     SendToICS("refresh\n");
3663                     break;
3664
3665                   default:
3666                     break;
3667                 }
3668                 if(bookHit) { // [HGM] book: simulate book reply
3669                     static char bookMove[MSG_SIZ]; // a bit generous?
3670
3671                     programStats.nodes = programStats.depth = programStats.time =
3672                     programStats.score = programStats.got_only_move = 0;
3673                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3674
3675                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3676                     strcat(bookMove, bookHit);
3677                     HandleMachineMove(bookMove, &first);
3678                 }
3679                 continue;
3680             }
3681
3682             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3683                  started == STARTED_HOLDINGS ||
3684                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3685                 /* Accumulate characters in move list or board */
3686                 parse[parse_pos++] = buf[i];
3687             }
3688
3689             /* Start of game messages.  Mostly we detect start of game
3690                when the first board image arrives.  On some versions
3691                of the ICS, though, we need to do a "refresh" after starting
3692                to observe in order to get the current board right away. */
3693             if (looking_at(buf, &i, "Adding game * to observation list")) {
3694                 started = STARTED_OBSERVE;
3695                 continue;
3696             }
3697
3698             /* Handle auto-observe */
3699             if (appData.autoObserve &&
3700                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3701                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3702                 char *player;
3703                 /* Choose the player that was highlighted, if any. */
3704                 if (star_match[0][0] == '\033' ||
3705                     star_match[1][0] != '\033') {
3706                     player = star_match[0];
3707                 } else {
3708                     player = star_match[2];
3709                 }
3710                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3711                         ics_prefix, StripHighlightAndTitle(player));
3712                 SendToICS(str);
3713
3714                 /* Save ratings from notify string */
3715                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3716                 player1Rating = string_to_rating(star_match[1]);
3717                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3718                 player2Rating = string_to_rating(star_match[3]);
3719
3720                 if (appData.debugMode)
3721                   fprintf(debugFP,
3722                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3723                           player1Name, player1Rating,
3724                           player2Name, player2Rating);
3725
3726                 continue;
3727             }
3728
3729             /* Deal with automatic examine mode after a game,
3730                and with IcsObserving -> IcsExamining transition */
3731             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3732                 looking_at(buf, &i, "has made you an examiner of game *")) {
3733
3734                 int gamenum = atoi(star_match[0]);
3735                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3736                     gamenum == ics_gamenum) {
3737                     /* We were already playing or observing this game;
3738                        no need to refetch history */
3739                     gameMode = IcsExamining;
3740                     if (pausing) {
3741                         pauseExamForwardMostMove = forwardMostMove;
3742                     } else if (currentMove < forwardMostMove) {
3743                         ForwardInner(forwardMostMove);
3744                     }
3745                 } else {
3746                     /* I don't think this case really can happen */
3747                     SendToICS(ics_prefix);
3748                     SendToICS("refresh\n");
3749                 }
3750                 continue;
3751             }
3752
3753             /* Error messages */
3754 //          if (ics_user_moved) {
3755             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3756                 if (looking_at(buf, &i, "Illegal move") ||
3757                     looking_at(buf, &i, "Not a legal move") ||
3758                     looking_at(buf, &i, "Your king is in check") ||
3759                     looking_at(buf, &i, "It isn't your turn") ||
3760                     looking_at(buf, &i, "It is not your move")) {
3761                     /* Illegal move */
3762                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3763                         currentMove = forwardMostMove-1;
3764                         DisplayMove(currentMove - 1); /* before DMError */
3765                         DrawPosition(FALSE, boards[currentMove]);
3766                         SwitchClocks(forwardMostMove-1); // [HGM] race
3767                         DisplayBothClocks();
3768                     }
3769                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3770                     ics_user_moved = 0;
3771                     continue;
3772                 }
3773             }
3774
3775             if (looking_at(buf, &i, "still have time") ||
3776                 looking_at(buf, &i, "not out of time") ||
3777                 looking_at(buf, &i, "either player is out of time") ||
3778                 looking_at(buf, &i, "has timeseal; checking")) {
3779                 /* We must have called his flag a little too soon */
3780                 whiteFlag = blackFlag = FALSE;
3781                 continue;
3782             }
3783
3784             if (looking_at(buf, &i, "added * seconds to") ||
3785                 looking_at(buf, &i, "seconds were added to")) {
3786                 /* Update the clocks */
3787                 SendToICS(ics_prefix);
3788                 SendToICS("refresh\n");
3789                 continue;
3790             }
3791
3792             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3793                 ics_clock_paused = TRUE;
3794                 StopClocks();
3795                 continue;
3796             }
3797
3798             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3799                 ics_clock_paused = FALSE;
3800                 StartClocks();
3801                 continue;
3802             }
3803
3804             /* Grab player ratings from the Creating: message.
3805                Note we have to check for the special case when
3806                the ICS inserts things like [white] or [black]. */
3807             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3808                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3809                 /* star_matches:
3810                    0    player 1 name (not necessarily white)
3811                    1    player 1 rating
3812                    2    empty, white, or black (IGNORED)
3813                    3    player 2 name (not necessarily black)
3814                    4    player 2 rating
3815
3816                    The names/ratings are sorted out when the game
3817                    actually starts (below).
3818                 */
3819                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3820                 player1Rating = string_to_rating(star_match[1]);
3821                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3822                 player2Rating = string_to_rating(star_match[4]);
3823
3824                 if (appData.debugMode)
3825                   fprintf(debugFP,
3826                           "Ratings from 'Creating:' %s %d, %s %d\n",
3827                           player1Name, player1Rating,
3828                           player2Name, player2Rating);
3829
3830                 continue;
3831             }
3832
3833             /* Improved generic start/end-of-game messages */
3834             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3835                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3836                 /* If tkind == 0: */
3837                 /* star_match[0] is the game number */
3838                 /*           [1] is the white player's name */
3839                 /*           [2] is the black player's name */
3840                 /* For end-of-game: */
3841                 /*           [3] is the reason for the game end */
3842                 /*           [4] is a PGN end game-token, preceded by " " */
3843                 /* For start-of-game: */
3844                 /*           [3] begins with "Creating" or "Continuing" */
3845                 /*           [4] is " *" or empty (don't care). */
3846                 int gamenum = atoi(star_match[0]);
3847                 char *whitename, *blackname, *why, *endtoken;
3848                 ChessMove endtype = EndOfFile;
3849
3850                 if (tkind == 0) {
3851                   whitename = star_match[1];
3852                   blackname = star_match[2];
3853                   why = star_match[3];
3854                   endtoken = star_match[4];
3855                 } else {
3856                   whitename = star_match[1];
3857                   blackname = star_match[3];
3858                   why = star_match[5];
3859                   endtoken = star_match[6];
3860                 }
3861
3862                 /* Game start messages */
3863                 if (strncmp(why, "Creating ", 9) == 0 ||
3864                     strncmp(why, "Continuing ", 11) == 0) {
3865                     gs_gamenum = gamenum;
3866                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3867                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3868                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3869 #if ZIPPY
3870                     if (appData.zippyPlay) {
3871                         ZippyGameStart(whitename, blackname);
3872                     }
3873 #endif /*ZIPPY*/
3874                     partnerBoardValid = FALSE; // [HGM] bughouse
3875                     continue;
3876                 }
3877
3878                 /* Game end messages */
3879                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3880                     ics_gamenum != gamenum) {
3881                     continue;
3882                 }
3883                 while (endtoken[0] == ' ') endtoken++;
3884                 switch (endtoken[0]) {
3885                   case '*':
3886                   default:
3887                     endtype = GameUnfinished;
3888                     break;
3889                   case '0':
3890                     endtype = BlackWins;
3891                     break;
3892                   case '1':
3893                     if (endtoken[1] == '/')
3894                       endtype = GameIsDrawn;
3895                     else
3896                       endtype = WhiteWins;
3897                     break;
3898                 }
3899                 GameEnds(endtype, why, GE_ICS);
3900 #if ZIPPY
3901                 if (appData.zippyPlay && first.initDone) {
3902                     ZippyGameEnd(endtype, why);
3903                     if (first.pr == NoProc) {
3904                       /* Start the next process early so that we'll
3905                          be ready for the next challenge */
3906                       StartChessProgram(&first);
3907                     }
3908                     /* Send "new" early, in case this command takes
3909                        a long time to finish, so that we'll be ready
3910                        for the next challenge. */
3911                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3912                     Reset(TRUE, TRUE);
3913                 }
3914 #endif /*ZIPPY*/
3915                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3916                 continue;
3917             }
3918
3919             if (looking_at(buf, &i, "Removing game * from observation") ||
3920                 looking_at(buf, &i, "no longer observing game *") ||
3921                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3922                 if (gameMode == IcsObserving &&
3923                     atoi(star_match[0]) == ics_gamenum)
3924                   {
3925                       /* icsEngineAnalyze */
3926                       if (appData.icsEngineAnalyze) {
3927                             ExitAnalyzeMode();
3928                             ModeHighlight();
3929                       }
3930                       StopClocks();
3931                       gameMode = IcsIdle;
3932                       ics_gamenum = -1;
3933                       ics_user_moved = FALSE;
3934                   }
3935                 continue;
3936             }
3937
3938             if (looking_at(buf, &i, "no longer examining game *")) {
3939                 if (gameMode == IcsExamining &&
3940                     atoi(star_match[0]) == ics_gamenum)
3941                   {
3942                       gameMode = IcsIdle;
3943                       ics_gamenum = -1;
3944                       ics_user_moved = FALSE;
3945                   }
3946                 continue;
3947             }
3948
3949             /* Advance leftover_start past any newlines we find,
3950                so only partial lines can get reparsed */
3951             if (looking_at(buf, &i, "\n")) {
3952                 prevColor = curColor;
3953                 if (curColor != ColorNormal) {
3954                     if (oldi > next_out) {
3955                         SendToPlayer(&buf[next_out], oldi - next_out);
3956                         next_out = oldi;
3957                     }
3958                     Colorize(ColorNormal, FALSE);
3959                     curColor = ColorNormal;
3960                 }
3961                 if (started == STARTED_BOARD) {
3962                     started = STARTED_NONE;
3963                     parse[parse_pos] = NULLCHAR;
3964                     ParseBoard12(parse);
3965                     ics_user_moved = 0;
3966
3967                     /* Send premove here */
3968                     if (appData.premove) {
3969                       char str[MSG_SIZ];
3970                       if (currentMove == 0 &&
3971                           gameMode == IcsPlayingWhite &&
3972                           appData.premoveWhite) {
3973                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3974                         if (appData.debugMode)
3975                           fprintf(debugFP, "Sending premove:\n");
3976                         SendToICS(str);
3977                       } else if (currentMove == 1 &&
3978                                  gameMode == IcsPlayingBlack &&
3979                                  appData.premoveBlack) {
3980                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3981                         if (appData.debugMode)
3982                           fprintf(debugFP, "Sending premove:\n");
3983                         SendToICS(str);
3984                       } else if (gotPremove) {
3985                         gotPremove = 0;
3986                         ClearPremoveHighlights();
3987                         if (appData.debugMode)
3988                           fprintf(debugFP, "Sending premove:\n");
3989                           UserMoveEvent(premoveFromX, premoveFromY,
3990                                         premoveToX, premoveToY,
3991                                         premovePromoChar);
3992                       }
3993                     }
3994
3995                     /* Usually suppress following prompt */
3996                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3997                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3998                         if (looking_at(buf, &i, "*% ")) {
3999                             savingComment = FALSE;
4000                             suppressKibitz = 0;
4001                         }
4002                     }
4003                     next_out = i;
4004                 } else if (started == STARTED_HOLDINGS) {
4005                     int gamenum;
4006                     char new_piece[MSG_SIZ];
4007                     started = STARTED_NONE;
4008                     parse[parse_pos] = NULLCHAR;
4009                     if (appData.debugMode)
4010                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4011                                                         parse, currentMove);
4012                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4013                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4014                         if (gameInfo.variant == VariantNormal) {
4015                           /* [HGM] We seem to switch variant during a game!
4016                            * Presumably no holdings were displayed, so we have
4017                            * to move the position two files to the right to
4018                            * create room for them!
4019                            */
4020                           VariantClass newVariant;
4021                           switch(gameInfo.boardWidth) { // base guess on board width
4022                                 case 9:  newVariant = VariantShogi; break;
4023                                 case 10: newVariant = VariantGreat; break;
4024                                 default: newVariant = VariantCrazyhouse; break;
4025                           }
4026                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4027                           /* Get a move list just to see the header, which
4028                              will tell us whether this is really bug or zh */
4029                           if (ics_getting_history == H_FALSE) {
4030                             ics_getting_history = H_REQUESTED;
4031                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4032                             SendToICS(str);
4033                           }
4034                         }
4035                         new_piece[0] = NULLCHAR;
4036                         sscanf(parse, "game %d white [%s black [%s <- %s",
4037                                &gamenum, white_holding, black_holding,
4038                                new_piece);
4039                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4040                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4041                         /* [HGM] copy holdings to board holdings area */
4042                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4043                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4044                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4045 #if ZIPPY
4046                         if (appData.zippyPlay && first.initDone) {
4047                             ZippyHoldings(white_holding, black_holding,
4048                                           new_piece);
4049                         }
4050 #endif /*ZIPPY*/
4051                         if (tinyLayout || smallLayout) {
4052                             char wh[16], bh[16];
4053                             PackHolding(wh, white_holding);
4054                             PackHolding(bh, black_holding);
4055                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4056                                     gameInfo.white, gameInfo.black);
4057                         } else {
4058                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4059                                     gameInfo.white, white_holding, _("vs."),
4060                                     gameInfo.black, black_holding);
4061                         }
4062                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4063                         DrawPosition(FALSE, boards[currentMove]);
4064                         DisplayTitle(str);
4065                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4066                         sscanf(parse, "game %d white [%s black [%s <- %s",
4067                                &gamenum, white_holding, black_holding,
4068                                new_piece);
4069                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4070                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4071                         /* [HGM] copy holdings to partner-board holdings area */
4072                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4073                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4074                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4075                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4076                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4077                       }
4078                     }
4079                     /* Suppress following prompt */
4080                     if (looking_at(buf, &i, "*% ")) {
4081                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4082                         savingComment = FALSE;
4083                         suppressKibitz = 0;
4084                     }
4085                     next_out = i;
4086                 }
4087                 continue;
4088             }
4089
4090             i++;                /* skip unparsed character and loop back */
4091         }
4092
4093         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4094 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4095 //          SendToPlayer(&buf[next_out], i - next_out);
4096             started != STARTED_HOLDINGS && leftover_start > next_out) {
4097             SendToPlayer(&buf[next_out], leftover_start - next_out);
4098             next_out = i;
4099         }
4100
4101         leftover_len = buf_len - leftover_start;
4102         /* if buffer ends with something we couldn't parse,
4103            reparse it after appending the next read */
4104
4105     } else if (count == 0) {
4106         RemoveInputSource(isr);
4107         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4108     } else {
4109         DisplayFatalError(_("Error reading from ICS"), error, 1);
4110     }
4111 }
4112
4113
4114 /* Board style 12 looks like this:
4115
4116    <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
4117
4118  * The "<12> " is stripped before it gets to this routine.  The two
4119  * trailing 0's (flip state and clock ticking) are later addition, and
4120  * some chess servers may not have them, or may have only the first.
4121  * Additional trailing fields may be added in the future.
4122  */
4123
4124 #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"
4125
4126 #define RELATION_OBSERVING_PLAYED    0
4127 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4128 #define RELATION_PLAYING_MYMOVE      1
4129 #define RELATION_PLAYING_NOTMYMOVE  -1
4130 #define RELATION_EXAMINING           2
4131 #define RELATION_ISOLATED_BOARD     -3
4132 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4133
4134 void
4135 ParseBoard12 (char *string)
4136 {
4137     GameMode newGameMode;
4138     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4139     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4140     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4141     char to_play, board_chars[200];
4142     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4143     char black[32], white[32];
4144     Board board;
4145     int prevMove = currentMove;
4146     int ticking = 2;
4147     ChessMove moveType;
4148     int fromX, fromY, toX, toY;
4149     char promoChar;
4150     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4151     char *bookHit = NULL; // [HGM] book
4152     Boolean weird = FALSE, reqFlag = FALSE;
4153
4154     fromX = fromY = toX = toY = -1;
4155
4156     newGame = FALSE;
4157
4158     if (appData.debugMode)
4159       fprintf(debugFP, _("Parsing board: %s\n"), string);
4160
4161     move_str[0] = NULLCHAR;
4162     elapsed_time[0] = NULLCHAR;
4163     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4164         int  i = 0, j;
4165         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4166             if(string[i] == ' ') { ranks++; files = 0; }
4167             else files++;
4168             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4169             i++;
4170         }
4171         for(j = 0; j <i; j++) board_chars[j] = string[j];
4172         board_chars[i] = '\0';
4173         string += i + 1;
4174     }
4175     n = sscanf(string, PATTERN, &to_play, &double_push,
4176                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4177                &gamenum, white, black, &relation, &basetime, &increment,
4178                &white_stren, &black_stren, &white_time, &black_time,
4179                &moveNum, str, elapsed_time, move_str, &ics_flip,
4180                &ticking);
4181
4182     if (n < 21) {
4183         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4184         DisplayError(str, 0);
4185         return;
4186     }
4187
4188     /* Convert the move number to internal form */
4189     moveNum = (moveNum - 1) * 2;
4190     if (to_play == 'B') moveNum++;
4191     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4192       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4193                         0, 1);
4194       return;
4195     }
4196
4197     switch (relation) {
4198       case RELATION_OBSERVING_PLAYED:
4199       case RELATION_OBSERVING_STATIC:
4200         if (gamenum == -1) {
4201             /* Old ICC buglet */
4202             relation = RELATION_OBSERVING_STATIC;
4203         }
4204         newGameMode = IcsObserving;
4205         break;
4206       case RELATION_PLAYING_MYMOVE:
4207       case RELATION_PLAYING_NOTMYMOVE:
4208         newGameMode =
4209           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4210             IcsPlayingWhite : IcsPlayingBlack;
4211         break;
4212       case RELATION_EXAMINING:
4213         newGameMode = IcsExamining;
4214         break;
4215       case RELATION_ISOLATED_BOARD:
4216       default:
4217         /* Just display this board.  If user was doing something else,
4218            we will forget about it until the next board comes. */
4219         newGameMode = IcsIdle;
4220         break;
4221       case RELATION_STARTING_POSITION:
4222         newGameMode = gameMode;
4223         break;
4224     }
4225
4226     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4227          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4228       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4229       char *toSqr;
4230       for (k = 0; k < ranks; k++) {
4231         for (j = 0; j < files; j++)
4232           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4233         if(gameInfo.holdingsWidth > 1) {
4234              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4235              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4236         }
4237       }
4238       CopyBoard(partnerBoard, board);
4239       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4240         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4241         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4242       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4243       if(toSqr = strchr(str, '-')) {
4244         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4245         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4246       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4247       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4248       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4249       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4250       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4251       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4252                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4253       DisplayMessage(partnerStatus, "");
4254         partnerBoardValid = TRUE;
4255       return;
4256     }
4257
4258     /* Modify behavior for initial board display on move listing
4259        of wild games.
4260        */
4261     switch (ics_getting_history) {
4262       case H_FALSE:
4263       case H_REQUESTED:
4264         break;
4265       case H_GOT_REQ_HEADER:
4266       case H_GOT_UNREQ_HEADER:
4267         /* This is the initial position of the current game */
4268         gamenum = ics_gamenum;
4269         moveNum = 0;            /* old ICS bug workaround */
4270         if (to_play == 'B') {
4271           startedFromSetupPosition = TRUE;
4272           blackPlaysFirst = TRUE;
4273           moveNum = 1;
4274           if (forwardMostMove == 0) forwardMostMove = 1;
4275           if (backwardMostMove == 0) backwardMostMove = 1;
4276           if (currentMove == 0) currentMove = 1;
4277         }
4278         newGameMode = gameMode;
4279         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4280         break;
4281       case H_GOT_UNWANTED_HEADER:
4282         /* This is an initial board that we don't want */
4283         return;
4284       case H_GETTING_MOVES:
4285         /* Should not happen */
4286         DisplayError(_("Error gathering move list: extra board"), 0);
4287         ics_getting_history = H_FALSE;
4288         return;
4289     }
4290
4291    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4292                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4293      /* [HGM] We seem to have switched variant unexpectedly
4294       * Try to guess new variant from board size
4295       */
4296           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4297           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4298           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4299           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4300           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4301           if(!weird) newVariant = VariantNormal;
4302           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4303           /* Get a move list just to see the header, which
4304              will tell us whether this is really bug or zh */
4305           if (ics_getting_history == H_FALSE) {
4306             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4307             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4308             SendToICS(str);
4309           }
4310     }
4311
4312     /* Take action if this is the first board of a new game, or of a
4313        different game than is currently being displayed.  */
4314     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4315         relation == RELATION_ISOLATED_BOARD) {
4316
4317         /* Forget the old game and get the history (if any) of the new one */
4318         if (gameMode != BeginningOfGame) {
4319           Reset(TRUE, TRUE);
4320         }
4321         newGame = TRUE;
4322         if (appData.autoRaiseBoard) BoardToTop();
4323         prevMove = -3;
4324         if (gamenum == -1) {
4325             newGameMode = IcsIdle;
4326         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4327                    appData.getMoveList && !reqFlag) {
4328             /* Need to get game history */
4329             ics_getting_history = H_REQUESTED;
4330             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4331             SendToICS(str);
4332         }
4333
4334         /* Initially flip the board to have black on the bottom if playing
4335            black or if the ICS flip flag is set, but let the user change
4336            it with the Flip View button. */
4337         flipView = appData.autoFlipView ?
4338           (newGameMode == IcsPlayingBlack) || ics_flip :
4339           appData.flipView;
4340
4341         /* Done with values from previous mode; copy in new ones */
4342         gameMode = newGameMode;
4343         ModeHighlight();
4344         ics_gamenum = gamenum;
4345         if (gamenum == gs_gamenum) {
4346             int klen = strlen(gs_kind);
4347             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4348             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4349             gameInfo.event = StrSave(str);
4350         } else {
4351             gameInfo.event = StrSave("ICS game");
4352         }
4353         gameInfo.site = StrSave(appData.icsHost);
4354         gameInfo.date = PGNDate();
4355         gameInfo.round = StrSave("-");
4356         gameInfo.white = StrSave(white);
4357         gameInfo.black = StrSave(black);
4358         timeControl = basetime * 60 * 1000;
4359         timeControl_2 = 0;
4360         timeIncrement = increment * 1000;
4361         movesPerSession = 0;
4362         gameInfo.timeControl = TimeControlTagValue();
4363         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4364   if (appData.debugMode) {
4365     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4366     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4367     setbuf(debugFP, NULL);
4368   }
4369
4370         gameInfo.outOfBook = NULL;
4371
4372         /* Do we have the ratings? */
4373         if (strcmp(player1Name, white) == 0 &&
4374             strcmp(player2Name, black) == 0) {
4375             if (appData.debugMode)
4376               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4377                       player1Rating, player2Rating);
4378             gameInfo.whiteRating = player1Rating;
4379             gameInfo.blackRating = player2Rating;
4380         } else if (strcmp(player2Name, white) == 0 &&
4381                    strcmp(player1Name, black) == 0) {
4382             if (appData.debugMode)
4383               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4384                       player2Rating, player1Rating);
4385             gameInfo.whiteRating = player2Rating;
4386             gameInfo.blackRating = player1Rating;
4387         }
4388         player1Name[0] = player2Name[0] = NULLCHAR;
4389
4390         /* Silence shouts if requested */
4391         if (appData.quietPlay &&
4392             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4393             SendToICS(ics_prefix);
4394             SendToICS("set shout 0\n");
4395         }
4396     }
4397
4398     /* Deal with midgame name changes */
4399     if (!newGame) {
4400         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4401             if (gameInfo.white) free(gameInfo.white);
4402             gameInfo.white = StrSave(white);
4403         }
4404         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4405             if (gameInfo.black) free(gameInfo.black);
4406             gameInfo.black = StrSave(black);
4407         }
4408     }
4409
4410     /* Throw away game result if anything actually changes in examine mode */
4411     if (gameMode == IcsExamining && !newGame) {
4412         gameInfo.result = GameUnfinished;
4413         if (gameInfo.resultDetails != NULL) {
4414             free(gameInfo.resultDetails);
4415             gameInfo.resultDetails = NULL;
4416         }
4417     }
4418
4419     /* In pausing && IcsExamining mode, we ignore boards coming
4420        in if they are in a different variation than we are. */
4421     if (pauseExamInvalid) return;
4422     if (pausing && gameMode == IcsExamining) {
4423         if (moveNum <= pauseExamForwardMostMove) {
4424             pauseExamInvalid = TRUE;
4425             forwardMostMove = pauseExamForwardMostMove;
4426             return;
4427         }
4428     }
4429
4430   if (appData.debugMode) {
4431     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4432   }
4433     /* Parse the board */
4434     for (k = 0; k < ranks; k++) {
4435       for (j = 0; j < files; j++)
4436         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4437       if(gameInfo.holdingsWidth > 1) {
4438            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4439            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4440       }
4441     }
4442     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4443       board[5][BOARD_RGHT+1] = WhiteAngel;
4444       board[6][BOARD_RGHT+1] = WhiteMarshall;
4445       board[1][0] = BlackMarshall;
4446       board[2][0] = BlackAngel;
4447       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4448     }
4449     CopyBoard(boards[moveNum], board);
4450     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4451     if (moveNum == 0) {
4452         startedFromSetupPosition =
4453           !CompareBoards(board, initialPosition);
4454         if(startedFromSetupPosition)
4455             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4456     }
4457
4458     /* [HGM] Set castling rights. Take the outermost Rooks,
4459        to make it also work for FRC opening positions. Note that board12
4460        is really defective for later FRC positions, as it has no way to
4461        indicate which Rook can castle if they are on the same side of King.
4462        For the initial position we grant rights to the outermost Rooks,
4463        and remember thos rights, and we then copy them on positions
4464        later in an FRC game. This means WB might not recognize castlings with
4465        Rooks that have moved back to their original position as illegal,
4466        but in ICS mode that is not its job anyway.
4467     */
4468     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4469     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4470
4471         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4472             if(board[0][i] == WhiteRook) j = i;
4473         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4474         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4475             if(board[0][i] == WhiteRook) j = i;
4476         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4477         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4478             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4479         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4480         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4481             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4482         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4483
4484         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4485         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4486         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4487             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4488         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4489             if(board[BOARD_HEIGHT-1][k] == bKing)
4490                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4491         if(gameInfo.variant == VariantTwoKings) {
4492             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4493             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4494             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4495         }
4496     } else { int r;
4497         r = boards[moveNum][CASTLING][0] = initialRights[0];
4498         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4499         r = boards[moveNum][CASTLING][1] = initialRights[1];
4500         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4501         r = boards[moveNum][CASTLING][3] = initialRights[3];
4502         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4503         r = boards[moveNum][CASTLING][4] = initialRights[4];
4504         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4505         /* wildcastle kludge: always assume King has rights */
4506         r = boards[moveNum][CASTLING][2] = initialRights[2];
4507         r = boards[moveNum][CASTLING][5] = initialRights[5];
4508     }
4509     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4510     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4511
4512
4513     if (ics_getting_history == H_GOT_REQ_HEADER ||
4514         ics_getting_history == H_GOT_UNREQ_HEADER) {
4515         /* This was an initial position from a move list, not
4516            the current position */
4517         return;
4518     }
4519
4520     /* Update currentMove and known move number limits */
4521     newMove = newGame || moveNum > forwardMostMove;
4522
4523     if (newGame) {
4524         forwardMostMove = backwardMostMove = currentMove = moveNum;
4525         if (gameMode == IcsExamining && moveNum == 0) {
4526           /* Workaround for ICS limitation: we are not told the wild
4527              type when starting to examine a game.  But if we ask for
4528              the move list, the move list header will tell us */
4529             ics_getting_history = H_REQUESTED;
4530             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4531             SendToICS(str);
4532         }
4533     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4534                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4535 #if ZIPPY
4536         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4537         /* [HGM] applied this also to an engine that is silently watching        */
4538         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4539             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4540             gameInfo.variant == currentlyInitializedVariant) {
4541           takeback = forwardMostMove - moveNum;
4542           for (i = 0; i < takeback; i++) {
4543             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4544             SendToProgram("undo\n", &first);
4545           }
4546         }
4547 #endif
4548
4549         forwardMostMove = moveNum;
4550         if (!pausing || currentMove > forwardMostMove)
4551           currentMove = forwardMostMove;
4552     } else {
4553         /* New part of history that is not contiguous with old part */
4554         if (pausing && gameMode == IcsExamining) {
4555             pauseExamInvalid = TRUE;
4556             forwardMostMove = pauseExamForwardMostMove;
4557             return;
4558         }
4559         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4560 #if ZIPPY
4561             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4562                 // [HGM] when we will receive the move list we now request, it will be
4563                 // fed to the engine from the first move on. So if the engine is not
4564                 // in the initial position now, bring it there.
4565                 InitChessProgram(&first, 0);
4566             }
4567 #endif
4568             ics_getting_history = H_REQUESTED;
4569             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4570             SendToICS(str);
4571         }
4572         forwardMostMove = backwardMostMove = currentMove = moveNum;
4573     }
4574
4575     /* Update the clocks */
4576     if (strchr(elapsed_time, '.')) {
4577       /* Time is in ms */
4578       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4579       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4580     } else {
4581       /* Time is in seconds */
4582       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4583       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4584     }
4585
4586
4587 #if ZIPPY
4588     if (appData.zippyPlay && newGame &&
4589         gameMode != IcsObserving && gameMode != IcsIdle &&
4590         gameMode != IcsExamining)
4591       ZippyFirstBoard(moveNum, basetime, increment);
4592 #endif
4593
4594     /* Put the move on the move list, first converting
4595        to canonical algebraic form. */
4596     if (moveNum > 0) {
4597   if (appData.debugMode) {
4598     if (appData.debugMode) { int f = forwardMostMove;
4599         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4600                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4601                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4602     }
4603     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4604     fprintf(debugFP, "moveNum = %d\n", moveNum);
4605     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4606     setbuf(debugFP, NULL);
4607   }
4608         if (moveNum <= backwardMostMove) {
4609             /* We don't know what the board looked like before
4610                this move.  Punt. */
4611           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4612             strcat(parseList[moveNum - 1], " ");
4613             strcat(parseList[moveNum - 1], elapsed_time);
4614             moveList[moveNum - 1][0] = NULLCHAR;
4615         } else if (strcmp(move_str, "none") == 0) {
4616             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4617             /* Again, we don't know what the board looked like;
4618                this is really the start of the game. */
4619             parseList[moveNum - 1][0] = NULLCHAR;
4620             moveList[moveNum - 1][0] = NULLCHAR;
4621             backwardMostMove = moveNum;
4622             startedFromSetupPosition = TRUE;
4623             fromX = fromY = toX = toY = -1;
4624         } else {
4625           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4626           //                 So we parse the long-algebraic move string in stead of the SAN move
4627           int valid; char buf[MSG_SIZ], *prom;
4628
4629           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4630                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4631           // str looks something like "Q/a1-a2"; kill the slash
4632           if(str[1] == '/')
4633             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4634           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4635           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4636                 strcat(buf, prom); // long move lacks promo specification!
4637           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4638                 if(appData.debugMode)
4639                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4640                 safeStrCpy(move_str, buf, MSG_SIZ);
4641           }
4642           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4643                                 &fromX, &fromY, &toX, &toY, &promoChar)
4644                || ParseOneMove(buf, moveNum - 1, &moveType,
4645                                 &fromX, &fromY, &toX, &toY, &promoChar);
4646           // end of long SAN patch
4647           if (valid) {
4648             (void) CoordsToAlgebraic(boards[moveNum - 1],
4649                                      PosFlags(moveNum - 1),
4650                                      fromY, fromX, toY, toX, promoChar,
4651                                      parseList[moveNum-1]);
4652             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4653               case MT_NONE:
4654               case MT_STALEMATE:
4655               default:
4656                 break;
4657               case MT_CHECK:
4658                 if(gameInfo.variant != VariantShogi)
4659                     strcat(parseList[moveNum - 1], "+");
4660                 break;
4661               case MT_CHECKMATE:
4662               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4663                 strcat(parseList[moveNum - 1], "#");
4664                 break;
4665             }
4666             strcat(parseList[moveNum - 1], " ");
4667             strcat(parseList[moveNum - 1], elapsed_time);
4668             /* currentMoveString is set as a side-effect of ParseOneMove */
4669             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4670             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4671             strcat(moveList[moveNum - 1], "\n");
4672
4673             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4674                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4675               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4676                 ChessSquare old, new = boards[moveNum][k][j];
4677                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4678                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4679                   if(old == new) continue;
4680                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4681                   else if(new == WhiteWazir || new == BlackWazir) {
4682                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4683                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4684                       else boards[moveNum][k][j] = old; // preserve type of Gold
4685                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4686                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4687               }
4688           } else {
4689             /* Move from ICS was illegal!?  Punt. */
4690             if (appData.debugMode) {
4691               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4692               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4693             }
4694             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4695             strcat(parseList[moveNum - 1], " ");
4696             strcat(parseList[moveNum - 1], elapsed_time);
4697             moveList[moveNum - 1][0] = NULLCHAR;
4698             fromX = fromY = toX = toY = -1;
4699           }
4700         }
4701   if (appData.debugMode) {
4702     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4703     setbuf(debugFP, NULL);
4704   }
4705
4706 #if ZIPPY
4707         /* Send move to chess program (BEFORE animating it). */
4708         if (appData.zippyPlay && !newGame && newMove &&
4709            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4710
4711             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4712                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4713                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4714                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4715                             move_str);
4716                     DisplayError(str, 0);
4717                 } else {
4718                     if (first.sendTime) {
4719                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4720                     }
4721                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4722                     if (firstMove && !bookHit) {
4723                         firstMove = FALSE;
4724                         if (first.useColors) {
4725                           SendToProgram(gameMode == IcsPlayingWhite ?
4726                                         "white\ngo\n" :
4727                                         "black\ngo\n", &first);
4728                         } else {
4729                           SendToProgram("go\n", &first);
4730                         }
4731                         first.maybeThinking = TRUE;
4732                     }
4733                 }
4734             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4735               if (moveList[moveNum - 1][0] == NULLCHAR) {
4736                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4737                 DisplayError(str, 0);
4738               } else {
4739                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4740                 SendMoveToProgram(moveNum - 1, &first);
4741               }
4742             }
4743         }
4744 #endif
4745     }
4746
4747     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4748         /* If move comes from a remote source, animate it.  If it
4749            isn't remote, it will have already been animated. */
4750         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4751             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4752         }
4753         if (!pausing && appData.highlightLastMove) {
4754             SetHighlights(fromX, fromY, toX, toY);
4755         }
4756     }
4757
4758     /* Start the clocks */
4759     whiteFlag = blackFlag = FALSE;
4760     appData.clockMode = !(basetime == 0 && increment == 0);
4761     if (ticking == 0) {
4762       ics_clock_paused = TRUE;
4763       StopClocks();
4764     } else if (ticking == 1) {
4765       ics_clock_paused = FALSE;
4766     }
4767     if (gameMode == IcsIdle ||
4768         relation == RELATION_OBSERVING_STATIC ||
4769         relation == RELATION_EXAMINING ||
4770         ics_clock_paused)
4771       DisplayBothClocks();
4772     else
4773       StartClocks();
4774
4775     /* Display opponents and material strengths */
4776     if (gameInfo.variant != VariantBughouse &&
4777         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4778         if (tinyLayout || smallLayout) {
4779             if(gameInfo.variant == VariantNormal)
4780               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4781                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4782                     basetime, increment);
4783             else
4784               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4785                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4786                     basetime, increment, (int) gameInfo.variant);
4787         } else {
4788             if(gameInfo.variant == VariantNormal)
4789               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4790                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4791                     basetime, increment);
4792             else
4793               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4794                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4795                     basetime, increment, VariantName(gameInfo.variant));
4796         }
4797         DisplayTitle(str);
4798   if (appData.debugMode) {
4799     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4800   }
4801     }
4802
4803
4804     /* Display the board */
4805     if (!pausing && !appData.noGUI) {
4806
4807       if (appData.premove)
4808           if (!gotPremove ||
4809              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4810              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4811               ClearPremoveHighlights();
4812
4813       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4814         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4815       DrawPosition(j, boards[currentMove]);
4816
4817       DisplayMove(moveNum - 1);
4818       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4819             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4820               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4821         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4822       }
4823     }
4824
4825     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4826 #if ZIPPY
4827     if(bookHit) { // [HGM] book: simulate book reply
4828         static char bookMove[MSG_SIZ]; // a bit generous?
4829
4830         programStats.nodes = programStats.depth = programStats.time =
4831         programStats.score = programStats.got_only_move = 0;
4832         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4833
4834         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4835         strcat(bookMove, bookHit);
4836         HandleMachineMove(bookMove, &first);
4837     }
4838 #endif
4839 }
4840
4841 void
4842 GetMoveListEvent ()
4843 {
4844     char buf[MSG_SIZ];
4845     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4846         ics_getting_history = H_REQUESTED;
4847         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4848         SendToICS(buf);
4849     }
4850 }
4851
4852 void
4853 AnalysisPeriodicEvent (int force)
4854 {
4855     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4856          && !force) || !appData.periodicUpdates)
4857       return;
4858
4859     /* Send . command to Crafty to collect stats */
4860     SendToProgram(".\n", &first);
4861
4862     /* Don't send another until we get a response (this makes
4863        us stop sending to old Crafty's which don't understand
4864        the "." command (sending illegal cmds resets node count & time,
4865        which looks bad)) */
4866     programStats.ok_to_send = 0;
4867 }
4868
4869 void
4870 ics_update_width (int new_width)
4871 {
4872         ics_printf("set width %d\n", new_width);
4873 }
4874
4875 void
4876 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4877 {
4878     char buf[MSG_SIZ];
4879
4880     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4881         // null move in variant where engine does not understand it (for analysis purposes)
4882         SendBoard(cps, moveNum + 1); // send position after move in stead.
4883         return;
4884     }
4885     if (cps->useUsermove) {
4886       SendToProgram("usermove ", cps);
4887     }
4888     if (cps->useSAN) {
4889       char *space;
4890       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4891         int len = space - parseList[moveNum];
4892         memcpy(buf, parseList[moveNum], len);
4893         buf[len++] = '\n';
4894         buf[len] = NULLCHAR;
4895       } else {
4896         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4897       }
4898       SendToProgram(buf, cps);
4899     } else {
4900       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4901         AlphaRank(moveList[moveNum], 4);
4902         SendToProgram(moveList[moveNum], cps);
4903         AlphaRank(moveList[moveNum], 4); // and back
4904       } else
4905       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4906        * the engine. It would be nice to have a better way to identify castle
4907        * moves here. */
4908       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4909                                                                          && cps->useOOCastle) {
4910         int fromX = moveList[moveNum][0] - AAA;
4911         int fromY = moveList[moveNum][1] - ONE;
4912         int toX = moveList[moveNum][2] - AAA;
4913         int toY = moveList[moveNum][3] - ONE;
4914         if((boards[moveNum][fromY][fromX] == WhiteKing
4915             && boards[moveNum][toY][toX] == WhiteRook)
4916            || (boards[moveNum][fromY][fromX] == BlackKing
4917                && boards[moveNum][toY][toX] == BlackRook)) {
4918           if(toX > fromX) SendToProgram("O-O\n", cps);
4919           else SendToProgram("O-O-O\n", cps);
4920         }
4921         else SendToProgram(moveList[moveNum], cps);
4922       } else
4923       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4924         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4925           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4926           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4927                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4928         } else
4929           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4930                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4931         SendToProgram(buf, cps);
4932       }
4933       else SendToProgram(moveList[moveNum], cps);
4934       /* End of additions by Tord */
4935     }
4936
4937     /* [HGM] setting up the opening has brought engine in force mode! */
4938     /*       Send 'go' if we are in a mode where machine should play. */
4939     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4940         (gameMode == TwoMachinesPlay   ||
4941 #if ZIPPY
4942          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4943 #endif
4944          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4945         SendToProgram("go\n", cps);
4946   if (appData.debugMode) {
4947     fprintf(debugFP, "(extra)\n");
4948   }
4949     }
4950     setboardSpoiledMachineBlack = 0;
4951 }
4952
4953 void
4954 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4955 {
4956     char user_move[MSG_SIZ];
4957     char suffix[4];
4958
4959     if(gameInfo.variant == VariantSChess && promoChar) {
4960         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4961         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4962     } else suffix[0] = NULLCHAR;
4963
4964     switch (moveType) {
4965       default:
4966         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4967                 (int)moveType, fromX, fromY, toX, toY);
4968         DisplayError(user_move + strlen("say "), 0);
4969         break;
4970       case WhiteKingSideCastle:
4971       case BlackKingSideCastle:
4972       case WhiteQueenSideCastleWild:
4973       case BlackQueenSideCastleWild:
4974       /* PUSH Fabien */
4975       case WhiteHSideCastleFR:
4976       case BlackHSideCastleFR:
4977       /* POP Fabien */
4978         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4979         break;
4980       case WhiteQueenSideCastle:
4981       case BlackQueenSideCastle:
4982       case WhiteKingSideCastleWild:
4983       case BlackKingSideCastleWild:
4984       /* PUSH Fabien */
4985       case WhiteASideCastleFR:
4986       case BlackASideCastleFR:
4987       /* POP Fabien */
4988         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4989         break;
4990       case WhiteNonPromotion:
4991       case BlackNonPromotion:
4992         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4993         break;
4994       case WhitePromotion:
4995       case BlackPromotion:
4996         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4997           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4998                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4999                 PieceToChar(WhiteFerz));
5000         else if(gameInfo.variant == VariantGreat)
5001           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5002                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5003                 PieceToChar(WhiteMan));
5004         else
5005           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5006                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5007                 promoChar);
5008         break;
5009       case WhiteDrop:
5010       case BlackDrop:
5011       drop:
5012         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5013                  ToUpper(PieceToChar((ChessSquare) fromX)),
5014                  AAA + toX, ONE + toY);
5015         break;
5016       case IllegalMove:  /* could be a variant we don't quite understand */
5017         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5018       case NormalMove:
5019       case WhiteCapturesEnPassant:
5020       case BlackCapturesEnPassant:
5021         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5022                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5023         break;
5024     }
5025     SendToICS(user_move);
5026     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5027         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5028 }
5029
5030 void
5031 UploadGameEvent ()
5032 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5033     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5034     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5035     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5036       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5037       return;
5038     }
5039     if(gameMode != IcsExamining) { // is this ever not the case?
5040         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5041
5042         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5043           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5044         } else { // on FICS we must first go to general examine mode
5045           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5046         }
5047         if(gameInfo.variant != VariantNormal) {
5048             // try figure out wild number, as xboard names are not always valid on ICS
5049             for(i=1; i<=36; i++) {
5050               snprintf(buf, MSG_SIZ, "wild/%d", i);
5051                 if(StringToVariant(buf) == gameInfo.variant) break;
5052             }
5053             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5054             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5055             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5056         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5057         SendToICS(ics_prefix);
5058         SendToICS(buf);
5059         if(startedFromSetupPosition || backwardMostMove != 0) {
5060           fen = PositionToFEN(backwardMostMove, NULL);
5061           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5062             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5063             SendToICS(buf);
5064           } else { // FICS: everything has to set by separate bsetup commands
5065             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5066             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5067             SendToICS(buf);
5068             if(!WhiteOnMove(backwardMostMove)) {
5069                 SendToICS("bsetup tomove black\n");
5070             }
5071             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5072             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5073             SendToICS(buf);
5074             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5075             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5076             SendToICS(buf);
5077             i = boards[backwardMostMove][EP_STATUS];
5078             if(i >= 0) { // set e.p.
5079               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5080                 SendToICS(buf);
5081             }
5082             bsetup++;
5083           }
5084         }
5085       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5086             SendToICS("bsetup done\n"); // switch to normal examining.
5087     }
5088     for(i = backwardMostMove; i<last; i++) {
5089         char buf[20];
5090         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5091         SendToICS(buf);
5092     }
5093     SendToICS(ics_prefix);
5094     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5095 }
5096
5097 void
5098 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5099 {
5100     if (rf == DROP_RANK) {
5101       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5102       sprintf(move, "%c@%c%c\n",
5103                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5104     } else {
5105         if (promoChar == 'x' || promoChar == NULLCHAR) {
5106           sprintf(move, "%c%c%c%c\n",
5107                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5108         } else {
5109             sprintf(move, "%c%c%c%c%c\n",
5110                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5111         }
5112     }
5113 }
5114
5115 void
5116 ProcessICSInitScript (FILE *f)
5117 {
5118     char buf[MSG_SIZ];
5119
5120     while (fgets(buf, MSG_SIZ, f)) {
5121         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5122     }
5123
5124     fclose(f);
5125 }
5126
5127
5128 static int lastX, lastY, selectFlag, dragging;
5129
5130 void
5131 Sweep (int step)
5132 {
5133     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5134     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5135     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5136     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5137     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5138     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5139     do {
5140         promoSweep -= step;
5141         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5142         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5143         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5144         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5145         if(!step) step = -1;
5146     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5147             appData.testLegality && (promoSweep == king ||
5148             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5149     ChangeDragPiece(promoSweep);
5150 }
5151
5152 int
5153 PromoScroll (int x, int y)
5154 {
5155   int step = 0;
5156
5157   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5158   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5159   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5160   if(!step) return FALSE;
5161   lastX = x; lastY = y;
5162   if((promoSweep < BlackPawn) == flipView) step = -step;
5163   if(step > 0) selectFlag = 1;
5164   if(!selectFlag) Sweep(step);
5165   return FALSE;
5166 }
5167
5168 void
5169 NextPiece (int step)
5170 {
5171     ChessSquare piece = boards[currentMove][toY][toX];
5172     do {
5173         pieceSweep -= step;
5174         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5175         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5176         if(!step) step = -1;
5177     } while(PieceToChar(pieceSweep) == '.');
5178     boards[currentMove][toY][toX] = pieceSweep;
5179     DrawPosition(FALSE, boards[currentMove]);
5180     boards[currentMove][toY][toX] = piece;
5181 }
5182 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5183 void
5184 AlphaRank (char *move, int n)
5185 {
5186 //    char *p = move, c; int x, y;
5187
5188     if (appData.debugMode) {
5189         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5190     }
5191
5192     if(move[1]=='*' &&
5193        move[2]>='0' && move[2]<='9' &&
5194        move[3]>='a' && move[3]<='x'    ) {
5195         move[1] = '@';
5196         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5197         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5198     } else
5199     if(move[0]>='0' && move[0]<='9' &&
5200        move[1]>='a' && move[1]<='x' &&
5201        move[2]>='0' && move[2]<='9' &&
5202        move[3]>='a' && move[3]<='x'    ) {
5203         /* input move, Shogi -> normal */
5204         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5205         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5206         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5207         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5208     } else
5209     if(move[1]=='@' &&
5210        move[3]>='0' && move[3]<='9' &&
5211        move[2]>='a' && move[2]<='x'    ) {
5212         move[1] = '*';
5213         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5214         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5215     } else
5216     if(
5217        move[0]>='a' && move[0]<='x' &&
5218        move[3]>='0' && move[3]<='9' &&
5219        move[2]>='a' && move[2]<='x'    ) {
5220          /* output move, normal -> Shogi */
5221         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5222         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5223         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5224         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5225         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5226     }
5227     if (appData.debugMode) {
5228         fprintf(debugFP, "   out = '%s'\n", move);
5229     }
5230 }
5231
5232 char yy_textstr[8000];
5233
5234 /* Parser for moves from gnuchess, ICS, or user typein box */
5235 Boolean
5236 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5237 {
5238     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5239
5240     switch (*moveType) {
5241       case WhitePromotion:
5242       case BlackPromotion:
5243       case WhiteNonPromotion:
5244       case BlackNonPromotion:
5245       case NormalMove:
5246       case WhiteCapturesEnPassant:
5247       case BlackCapturesEnPassant:
5248       case WhiteKingSideCastle:
5249       case WhiteQueenSideCastle:
5250       case BlackKingSideCastle:
5251       case BlackQueenSideCastle:
5252       case WhiteKingSideCastleWild:
5253       case WhiteQueenSideCastleWild:
5254       case BlackKingSideCastleWild:
5255       case BlackQueenSideCastleWild:
5256       /* Code added by Tord: */
5257       case WhiteHSideCastleFR:
5258       case WhiteASideCastleFR:
5259       case BlackHSideCastleFR:
5260       case BlackASideCastleFR:
5261       /* End of code added by Tord */
5262       case IllegalMove:         /* bug or odd chess variant */
5263         *fromX = currentMoveString[0] - AAA;
5264         *fromY = currentMoveString[1] - ONE;
5265         *toX = currentMoveString[2] - AAA;
5266         *toY = currentMoveString[3] - ONE;
5267         *promoChar = currentMoveString[4];
5268         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5269             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5270     if (appData.debugMode) {
5271         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5272     }
5273             *fromX = *fromY = *toX = *toY = 0;
5274             return FALSE;
5275         }
5276         if (appData.testLegality) {
5277           return (*moveType != IllegalMove);
5278         } else {
5279           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5280                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5281         }
5282
5283       case WhiteDrop:
5284       case BlackDrop:
5285         *fromX = *moveType == WhiteDrop ?
5286           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5287           (int) CharToPiece(ToLower(currentMoveString[0]));
5288         *fromY = DROP_RANK;
5289         *toX = currentMoveString[2] - AAA;
5290         *toY = currentMoveString[3] - ONE;
5291         *promoChar = NULLCHAR;
5292         return TRUE;
5293
5294       case AmbiguousMove:
5295       case ImpossibleMove:
5296       case EndOfFile:
5297       case ElapsedTime:
5298       case Comment:
5299       case PGNTag:
5300       case NAG:
5301       case WhiteWins:
5302       case BlackWins:
5303       case GameIsDrawn:
5304       default:
5305     if (appData.debugMode) {
5306         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5307     }
5308         /* bug? */
5309         *fromX = *fromY = *toX = *toY = 0;
5310         *promoChar = NULLCHAR;
5311         return FALSE;
5312     }
5313 }
5314
5315 Boolean pushed = FALSE;
5316 char *lastParseAttempt;
5317
5318 void
5319 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5320 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5321   int fromX, fromY, toX, toY; char promoChar;
5322   ChessMove moveType;
5323   Boolean valid;
5324   int nr = 0;
5325
5326   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5327     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5328     pushed = TRUE;
5329   }
5330   endPV = forwardMostMove;
5331   do {
5332     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5333     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5334     lastParseAttempt = pv;
5335     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5336     if(!valid && nr == 0 &&
5337        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5338         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5339         // Hande case where played move is different from leading PV move
5340         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5341         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5342         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5343         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5344           endPV += 2; // if position different, keep this
5345           moveList[endPV-1][0] = fromX + AAA;
5346           moveList[endPV-1][1] = fromY + ONE;
5347           moveList[endPV-1][2] = toX + AAA;
5348           moveList[endPV-1][3] = toY + ONE;
5349           parseList[endPV-1][0] = NULLCHAR;
5350           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5351         }
5352       }
5353     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5354     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5355     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5356     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5357         valid++; // allow comments in PV
5358         continue;
5359     }
5360     nr++;
5361     if(endPV+1 > framePtr) break; // no space, truncate
5362     if(!valid) break;
5363     endPV++;
5364     CopyBoard(boards[endPV], boards[endPV-1]);
5365     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5366     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5367     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5368     CoordsToAlgebraic(boards[endPV - 1],
5369                              PosFlags(endPV - 1),
5370                              fromY, fromX, toY, toX, promoChar,
5371                              parseList[endPV - 1]);
5372   } while(valid);
5373   if(atEnd == 2) return; // used hidden, for PV conversion
5374   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5375   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5376   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5377                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5378   DrawPosition(TRUE, boards[currentMove]);
5379 }
5380
5381 int
5382 MultiPV (ChessProgramState *cps)
5383 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5384         int i;
5385         for(i=0; i<cps->nrOptions; i++)
5386             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5387                 return i;
5388         return -1;
5389 }
5390
5391 Boolean
5392 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5393 {
5394         int startPV, multi, lineStart, origIndex = index;
5395         char *p, buf2[MSG_SIZ];
5396
5397         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5398         lastX = x; lastY = y;
5399         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5400         lineStart = startPV = index;
5401         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5402         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5403         index = startPV;
5404         do{ while(buf[index] && buf[index] != '\n') index++;
5405         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5406         buf[index] = 0;
5407         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5408                 int n = first.option[multi].value;
5409                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5410                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5411                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5412                 first.option[multi].value = n;
5413                 *start = *end = 0;
5414                 return FALSE;
5415         }
5416         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5417         *start = startPV; *end = index-1;
5418         return TRUE;
5419 }
5420
5421 char *
5422 PvToSAN (char *pv)
5423 {
5424         static char buf[10*MSG_SIZ];
5425         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5426         *buf = NULLCHAR;
5427         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5428         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5429         for(i = forwardMostMove; i<endPV; i++){
5430             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5431             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5432             k += strlen(buf+k);
5433         }
5434         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5435         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5436         endPV = savedEnd;
5437         return buf;
5438 }
5439
5440 Boolean
5441 LoadPV (int x, int y)
5442 { // called on right mouse click to load PV
5443   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5444   lastX = x; lastY = y;
5445   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5446   return TRUE;
5447 }
5448
5449 void
5450 UnLoadPV ()
5451 {
5452   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5453   if(endPV < 0) return;
5454   if(appData.autoCopyPV) CopyFENToClipboard();
5455   endPV = -1;
5456   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5457         Boolean saveAnimate = appData.animate;
5458         if(pushed) {
5459             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5460                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5461             } else storedGames--; // abandon shelved tail of original game
5462         }
5463         pushed = FALSE;
5464         forwardMostMove = currentMove;
5465         currentMove = oldFMM;
5466         appData.animate = FALSE;
5467         ToNrEvent(forwardMostMove);
5468         appData.animate = saveAnimate;
5469   }
5470   currentMove = forwardMostMove;
5471   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5472   ClearPremoveHighlights();
5473   DrawPosition(TRUE, boards[currentMove]);
5474 }
5475
5476 void
5477 MovePV (int x, int y, int h)
5478 { // step through PV based on mouse coordinates (called on mouse move)
5479   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5480
5481   // we must somehow check if right button is still down (might be released off board!)
5482   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5483   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5484   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5485   if(!step) return;
5486   lastX = x; lastY = y;
5487
5488   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5489   if(endPV < 0) return;
5490   if(y < margin) step = 1; else
5491   if(y > h - margin) step = -1;
5492   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5493   currentMove += step;
5494   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5495   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5496                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5497   DrawPosition(FALSE, boards[currentMove]);
5498 }
5499
5500
5501 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5502 // All positions will have equal probability, but the current method will not provide a unique
5503 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5504 #define DARK 1
5505 #define LITE 2
5506 #define ANY 3
5507
5508 int squaresLeft[4];
5509 int piecesLeft[(int)BlackPawn];
5510 int seed, nrOfShuffles;
5511
5512 void
5513 GetPositionNumber ()
5514 {       // sets global variable seed
5515         int i;
5516
5517         seed = appData.defaultFrcPosition;
5518         if(seed < 0) { // randomize based on time for negative FRC position numbers
5519                 for(i=0; i<50; i++) seed += random();
5520                 seed = random() ^ random() >> 8 ^ random() << 8;
5521                 if(seed<0) seed = -seed;
5522         }
5523 }
5524
5525 int
5526 put (Board board, int pieceType, int rank, int n, int shade)
5527 // put the piece on the (n-1)-th empty squares of the given shade
5528 {
5529         int i;
5530
5531         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5532                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5533                         board[rank][i] = (ChessSquare) pieceType;
5534                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5535                         squaresLeft[ANY]--;
5536                         piecesLeft[pieceType]--;
5537                         return i;
5538                 }
5539         }
5540         return -1;
5541 }
5542
5543
5544 void
5545 AddOnePiece (Board board, int pieceType, int rank, int shade)
5546 // calculate where the next piece goes, (any empty square), and put it there
5547 {
5548         int i;
5549
5550         i = seed % squaresLeft[shade];
5551         nrOfShuffles *= squaresLeft[shade];
5552         seed /= squaresLeft[shade];
5553         put(board, pieceType, rank, i, shade);
5554 }
5555
5556 void
5557 AddTwoPieces (Board board, int pieceType, int rank)
5558 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5559 {
5560         int i, n=squaresLeft[ANY], j=n-1, k;
5561
5562         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5563         i = seed % k;  // pick one
5564         nrOfShuffles *= k;
5565         seed /= k;
5566         while(i >= j) i -= j--;
5567         j = n - 1 - j; i += j;
5568         put(board, pieceType, rank, j, ANY);
5569         put(board, pieceType, rank, i, ANY);
5570 }
5571
5572 void
5573 SetUpShuffle (Board board, int number)
5574 {
5575         int i, p, first=1;
5576
5577         GetPositionNumber(); nrOfShuffles = 1;
5578
5579         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5580         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5581         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5582
5583         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5584
5585         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5586             p = (int) board[0][i];
5587             if(p < (int) BlackPawn) piecesLeft[p] ++;
5588             board[0][i] = EmptySquare;
5589         }
5590
5591         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5592             // shuffles restricted to allow normal castling put KRR first
5593             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5594                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5595             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5596                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5597             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5598                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5599             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5600                 put(board, WhiteRook, 0, 0, ANY);
5601             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5602         }
5603
5604         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5605             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5606             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5607                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5608                 while(piecesLeft[p] >= 2) {
5609                     AddOnePiece(board, p, 0, LITE);
5610                     AddOnePiece(board, p, 0, DARK);
5611                 }
5612                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5613             }
5614
5615         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5616             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5617             // but we leave King and Rooks for last, to possibly obey FRC restriction
5618             if(p == (int)WhiteRook) continue;
5619             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5620             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5621         }
5622
5623         // now everything is placed, except perhaps King (Unicorn) and Rooks
5624
5625         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5626             // Last King gets castling rights
5627             while(piecesLeft[(int)WhiteUnicorn]) {
5628                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5629                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5630             }
5631
5632             while(piecesLeft[(int)WhiteKing]) {
5633                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5634                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5635             }
5636
5637
5638         } else {
5639             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5640             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5641         }
5642
5643         // Only Rooks can be left; simply place them all
5644         while(piecesLeft[(int)WhiteRook]) {
5645                 i = put(board, WhiteRook, 0, 0, ANY);
5646                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5647                         if(first) {
5648                                 first=0;
5649                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5650                         }
5651                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5652                 }
5653         }
5654         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5655             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5656         }
5657
5658         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5659 }
5660
5661 int
5662 SetCharTable (char *table, const char * map)
5663 /* [HGM] moved here from winboard.c because of its general usefulness */
5664 /*       Basically a safe strcpy that uses the last character as King */
5665 {
5666     int result = FALSE; int NrPieces;
5667
5668     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5669                     && NrPieces >= 12 && !(NrPieces&1)) {
5670         int i; /* [HGM] Accept even length from 12 to 34 */
5671
5672         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5673         for( i=0; i<NrPieces/2-1; i++ ) {
5674             table[i] = map[i];
5675             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5676         }
5677         table[(int) WhiteKing]  = map[NrPieces/2-1];
5678         table[(int) BlackKing]  = map[NrPieces-1];
5679
5680         result = TRUE;
5681     }
5682
5683     return result;
5684 }
5685
5686 void
5687 Prelude (Board board)
5688 {       // [HGM] superchess: random selection of exo-pieces
5689         int i, j, k; ChessSquare p;
5690         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5691
5692         GetPositionNumber(); // use FRC position number
5693
5694         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5695             SetCharTable(pieceToChar, appData.pieceToCharTable);
5696             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5697                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5698         }
5699
5700         j = seed%4;                 seed /= 4;
5701         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5702         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5703         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5704         j = seed%3 + (seed%3 >= j); seed /= 3;
5705         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5706         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5707         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5708         j = seed%3;                 seed /= 3;
5709         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5710         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5711         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5712         j = seed%2 + (seed%2 >= j); seed /= 2;
5713         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5714         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5715         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5716         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5717         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5718         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5719         put(board, exoPieces[0],    0, 0, ANY);
5720         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5721 }
5722
5723 void
5724 InitPosition (int redraw)
5725 {
5726     ChessSquare (* pieces)[BOARD_FILES];
5727     int i, j, pawnRow, overrule,
5728     oldx = gameInfo.boardWidth,
5729     oldy = gameInfo.boardHeight,
5730     oldh = gameInfo.holdingsWidth;
5731     static int oldv;
5732
5733     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5734
5735     /* [AS] Initialize pv info list [HGM] and game status */
5736     {
5737         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5738             pvInfoList[i].depth = 0;
5739             boards[i][EP_STATUS] = EP_NONE;
5740             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5741         }
5742
5743         initialRulePlies = 0; /* 50-move counter start */
5744
5745         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5746         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5747     }
5748
5749
5750     /* [HGM] logic here is completely changed. In stead of full positions */
5751     /* the initialized data only consist of the two backranks. The switch */
5752     /* selects which one we will use, which is than copied to the Board   */
5753     /* initialPosition, which for the rest is initialized by Pawns and    */
5754     /* empty squares. This initial position is then copied to boards[0],  */
5755     /* possibly after shuffling, so that it remains available.            */
5756
5757     gameInfo.holdingsWidth = 0; /* default board sizes */
5758     gameInfo.boardWidth    = 8;
5759     gameInfo.boardHeight   = 8;
5760     gameInfo.holdingsSize  = 0;
5761     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5762     for(i=0; i<BOARD_FILES-2; i++)
5763       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5764     initialPosition[EP_STATUS] = EP_NONE;
5765     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5766     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5767          SetCharTable(pieceNickName, appData.pieceNickNames);
5768     else SetCharTable(pieceNickName, "............");
5769     pieces = FIDEArray;
5770
5771     switch (gameInfo.variant) {
5772     case VariantFischeRandom:
5773       shuffleOpenings = TRUE;
5774     default:
5775       break;
5776     case VariantShatranj:
5777       pieces = ShatranjArray;
5778       nrCastlingRights = 0;
5779       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5780       break;
5781     case VariantMakruk:
5782       pieces = makrukArray;
5783       nrCastlingRights = 0;
5784       startedFromSetupPosition = TRUE;
5785       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5786       break;
5787     case VariantTwoKings:
5788       pieces = twoKingsArray;
5789       break;
5790     case VariantGrand:
5791       pieces = GrandArray;
5792       nrCastlingRights = 0;
5793       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5794       gameInfo.boardWidth = 10;
5795       gameInfo.boardHeight = 10;
5796       gameInfo.holdingsSize = 7;
5797       break;
5798     case VariantCapaRandom:
5799       shuffleOpenings = TRUE;
5800     case VariantCapablanca:
5801       pieces = CapablancaArray;
5802       gameInfo.boardWidth = 10;
5803       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5804       break;
5805     case VariantGothic:
5806       pieces = GothicArray;
5807       gameInfo.boardWidth = 10;
5808       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5809       break;
5810     case VariantSChess:
5811       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5812       gameInfo.holdingsSize = 7;
5813       break;
5814     case VariantJanus:
5815       pieces = JanusArray;
5816       gameInfo.boardWidth = 10;
5817       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5818       nrCastlingRights = 6;
5819         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5820         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5821         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5822         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5823         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5824         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5825       break;
5826     case VariantFalcon:
5827       pieces = FalconArray;
5828       gameInfo.boardWidth = 10;
5829       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5830       break;
5831     case VariantXiangqi:
5832       pieces = XiangqiArray;
5833       gameInfo.boardWidth  = 9;
5834       gameInfo.boardHeight = 10;
5835       nrCastlingRights = 0;
5836       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5837       break;
5838     case VariantShogi:
5839       pieces = ShogiArray;
5840       gameInfo.boardWidth  = 9;
5841       gameInfo.boardHeight = 9;
5842       gameInfo.holdingsSize = 7;
5843       nrCastlingRights = 0;
5844       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5845       break;
5846     case VariantCourier:
5847       pieces = CourierArray;
5848       gameInfo.boardWidth  = 12;
5849       nrCastlingRights = 0;
5850       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5851       break;
5852     case VariantKnightmate:
5853       pieces = KnightmateArray;
5854       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5855       break;
5856     case VariantSpartan:
5857       pieces = SpartanArray;
5858       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5859       break;
5860     case VariantFairy:
5861       pieces = fairyArray;
5862       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5863       break;
5864     case VariantGreat:
5865       pieces = GreatArray;
5866       gameInfo.boardWidth = 10;
5867       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5868       gameInfo.holdingsSize = 8;
5869       break;
5870     case VariantSuper:
5871       pieces = FIDEArray;
5872       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5873       gameInfo.holdingsSize = 8;
5874       startedFromSetupPosition = TRUE;
5875       break;
5876     case VariantCrazyhouse:
5877     case VariantBughouse:
5878       pieces = FIDEArray;
5879       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5880       gameInfo.holdingsSize = 5;
5881       break;
5882     case VariantWildCastle:
5883       pieces = FIDEArray;
5884       /* !!?shuffle with kings guaranteed to be on d or e file */
5885       shuffleOpenings = 1;
5886       break;
5887     case VariantNoCastle:
5888       pieces = FIDEArray;
5889       nrCastlingRights = 0;
5890       /* !!?unconstrained back-rank shuffle */
5891       shuffleOpenings = 1;
5892       break;
5893     }
5894
5895     overrule = 0;
5896     if(appData.NrFiles >= 0) {
5897         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5898         gameInfo.boardWidth = appData.NrFiles;
5899     }
5900     if(appData.NrRanks >= 0) {
5901         gameInfo.boardHeight = appData.NrRanks;
5902     }
5903     if(appData.holdingsSize >= 0) {
5904         i = appData.holdingsSize;
5905         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5906         gameInfo.holdingsSize = i;
5907     }
5908     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5909     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5910         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5911
5912     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5913     if(pawnRow < 1) pawnRow = 1;
5914     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5915
5916     /* User pieceToChar list overrules defaults */
5917     if(appData.pieceToCharTable != NULL)
5918         SetCharTable(pieceToChar, appData.pieceToCharTable);
5919
5920     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5921
5922         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5923             s = (ChessSquare) 0; /* account holding counts in guard band */
5924         for( i=0; i<BOARD_HEIGHT; i++ )
5925             initialPosition[i][j] = s;
5926
5927         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5928         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5929         initialPosition[pawnRow][j] = WhitePawn;
5930         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5931         if(gameInfo.variant == VariantXiangqi) {
5932             if(j&1) {
5933                 initialPosition[pawnRow][j] =
5934                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5935                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5936                    initialPosition[2][j] = WhiteCannon;
5937                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5938                 }
5939             }
5940         }
5941         if(gameInfo.variant == VariantGrand) {
5942             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5943                initialPosition[0][j] = WhiteRook;
5944                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5945             }
5946         }
5947         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5948     }
5949     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5950
5951             j=BOARD_LEFT+1;
5952             initialPosition[1][j] = WhiteBishop;
5953             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5954             j=BOARD_RGHT-2;
5955             initialPosition[1][j] = WhiteRook;
5956             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5957     }
5958
5959     if( nrCastlingRights == -1) {
5960         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5961         /*       This sets default castling rights from none to normal corners   */
5962         /* Variants with other castling rights must set them themselves above    */
5963         nrCastlingRights = 6;
5964
5965         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5966         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5967         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5968         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5969         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5970         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5971      }
5972
5973      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5974      if(gameInfo.variant == VariantGreat) { // promotion commoners
5975         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5976         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5977         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5978         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5979      }
5980      if( gameInfo.variant == VariantSChess ) {
5981       initialPosition[1][0] = BlackMarshall;
5982       initialPosition[2][0] = BlackAngel;
5983       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5984       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5985       initialPosition[1][1] = initialPosition[2][1] = 
5986       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5987      }
5988   if (appData.debugMode) {
5989     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5990   }
5991     if(shuffleOpenings) {
5992         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5993         startedFromSetupPosition = TRUE;
5994     }
5995     if(startedFromPositionFile) {
5996       /* [HGM] loadPos: use PositionFile for every new game */
5997       CopyBoard(initialPosition, filePosition);
5998       for(i=0; i<nrCastlingRights; i++)
5999           initialRights[i] = filePosition[CASTLING][i];
6000       startedFromSetupPosition = TRUE;
6001     }
6002
6003     CopyBoard(boards[0], initialPosition);
6004
6005     if(oldx != gameInfo.boardWidth ||
6006        oldy != gameInfo.boardHeight ||
6007        oldv != gameInfo.variant ||
6008        oldh != gameInfo.holdingsWidth
6009                                          )
6010             InitDrawingSizes(-2 ,0);
6011
6012     oldv = gameInfo.variant;
6013     if (redraw)
6014       DrawPosition(TRUE, boards[currentMove]);
6015 }
6016
6017 void
6018 SendBoard (ChessProgramState *cps, int moveNum)
6019 {
6020     char message[MSG_SIZ];
6021
6022     if (cps->useSetboard) {
6023       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6024       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6025       SendToProgram(message, cps);
6026       free(fen);
6027
6028     } else {
6029       ChessSquare *bp;
6030       int i, j, left=0, right=BOARD_WIDTH;
6031       /* Kludge to set black to move, avoiding the troublesome and now
6032        * deprecated "black" command.
6033        */
6034       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6035         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6036
6037       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6038
6039       SendToProgram("edit\n", cps);
6040       SendToProgram("#\n", cps);
6041       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6042         bp = &boards[moveNum][i][left];
6043         for (j = left; j < right; j++, bp++) {
6044           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6045           if ((int) *bp < (int) BlackPawn) {
6046             if(j == BOARD_RGHT+1)
6047                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6048             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6049             if(message[0] == '+' || message[0] == '~') {
6050               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6051                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6052                         AAA + j, ONE + i);
6053             }
6054             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6055                 message[1] = BOARD_RGHT   - 1 - j + '1';
6056                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6057             }
6058             SendToProgram(message, cps);
6059           }
6060         }
6061       }
6062
6063       SendToProgram("c\n", cps);
6064       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6065         bp = &boards[moveNum][i][left];
6066         for (j = left; j < right; j++, bp++) {
6067           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6068           if (((int) *bp != (int) EmptySquare)
6069               && ((int) *bp >= (int) BlackPawn)) {
6070             if(j == BOARD_LEFT-2)
6071                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6072             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6073                     AAA + j, ONE + i);
6074             if(message[0] == '+' || message[0] == '~') {
6075               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6076                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6077                         AAA + j, ONE + i);
6078             }
6079             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6080                 message[1] = BOARD_RGHT   - 1 - j + '1';
6081                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6082             }
6083             SendToProgram(message, cps);
6084           }
6085         }
6086       }
6087
6088       SendToProgram(".\n", cps);
6089     }
6090     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6091 }
6092
6093 ChessSquare
6094 DefaultPromoChoice (int white)
6095 {
6096     ChessSquare result;
6097     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6098         result = WhiteFerz; // no choice
6099     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6100         result= WhiteKing; // in Suicide Q is the last thing we want
6101     else if(gameInfo.variant == VariantSpartan)
6102         result = white ? WhiteQueen : WhiteAngel;
6103     else result = WhiteQueen;
6104     if(!white) result = WHITE_TO_BLACK result;
6105     return result;
6106 }
6107
6108 static int autoQueen; // [HGM] oneclick
6109
6110 int
6111 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6112 {
6113     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6114     /* [HGM] add Shogi promotions */
6115     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6116     ChessSquare piece;
6117     ChessMove moveType;
6118     Boolean premove;
6119
6120     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6121     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6122
6123     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6124       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6125         return FALSE;
6126
6127     piece = boards[currentMove][fromY][fromX];
6128     if(gameInfo.variant == VariantShogi) {
6129         promotionZoneSize = BOARD_HEIGHT/3;
6130         highestPromotingPiece = (int)WhiteFerz;
6131     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6132         promotionZoneSize = 3;
6133     }
6134
6135     // Treat Lance as Pawn when it is not representing Amazon
6136     if(gameInfo.variant != VariantSuper) {
6137         if(piece == WhiteLance) piece = WhitePawn; else
6138         if(piece == BlackLance) piece = BlackPawn;
6139     }
6140
6141     // next weed out all moves that do not touch the promotion zone at all
6142     if((int)piece >= BlackPawn) {
6143         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6144              return FALSE;
6145         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6146     } else {
6147         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6148            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6149     }
6150
6151     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6152
6153     // weed out mandatory Shogi promotions
6154     if(gameInfo.variant == VariantShogi) {
6155         if(piece >= BlackPawn) {
6156             if(toY == 0 && piece == BlackPawn ||
6157                toY == 0 && piece == BlackQueen ||
6158                toY <= 1 && piece == BlackKnight) {
6159                 *promoChoice = '+';
6160                 return FALSE;
6161             }
6162         } else {
6163             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6164                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6165                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6166                 *promoChoice = '+';
6167                 return FALSE;
6168             }
6169         }
6170     }
6171
6172     // weed out obviously illegal Pawn moves
6173     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6174         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6175         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6176         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6177         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6178         // note we are not allowed to test for valid (non-)capture, due to premove
6179     }
6180
6181     // we either have a choice what to promote to, or (in Shogi) whether to promote
6182     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6183         *promoChoice = PieceToChar(BlackFerz);  // no choice
6184         return FALSE;
6185     }
6186     // no sense asking what we must promote to if it is going to explode...
6187     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6188         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6189         return FALSE;
6190     }
6191     // give caller the default choice even if we will not make it
6192     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6193     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6194     if(        sweepSelect && gameInfo.variant != VariantGreat
6195                            && gameInfo.variant != VariantGrand
6196                            && gameInfo.variant != VariantSuper) return FALSE;
6197     if(autoQueen) return FALSE; // predetermined
6198
6199     // suppress promotion popup on illegal moves that are not premoves
6200     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6201               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6202     if(appData.testLegality && !premove) {
6203         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6204                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6205         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6206             return FALSE;
6207     }
6208
6209     return TRUE;
6210 }
6211
6212 int
6213 InPalace (int row, int column)
6214 {   /* [HGM] for Xiangqi */
6215     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6216          column < (BOARD_WIDTH + 4)/2 &&
6217          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6218     return FALSE;
6219 }
6220
6221 int
6222 PieceForSquare (int x, int y)
6223 {
6224   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6225      return -1;
6226   else
6227      return boards[currentMove][y][x];
6228 }
6229
6230 int
6231 OKToStartUserMove (int x, int y)
6232 {
6233     ChessSquare from_piece;
6234     int white_piece;
6235
6236     if (matchMode) return FALSE;
6237     if (gameMode == EditPosition) return TRUE;
6238
6239     if (x >= 0 && y >= 0)
6240       from_piece = boards[currentMove][y][x];
6241     else
6242       from_piece = EmptySquare;
6243
6244     if (from_piece == EmptySquare) return FALSE;
6245
6246     white_piece = (int)from_piece >= (int)WhitePawn &&
6247       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6248
6249     switch (gameMode) {
6250       case AnalyzeFile:
6251       case TwoMachinesPlay:
6252       case EndOfGame:
6253         return FALSE;
6254
6255       case IcsObserving:
6256       case IcsIdle:
6257         return FALSE;
6258
6259       case MachinePlaysWhite:
6260       case IcsPlayingBlack:
6261         if (appData.zippyPlay) return FALSE;
6262         if (white_piece) {
6263             DisplayMoveError(_("You are playing Black"));
6264             return FALSE;
6265         }
6266         break;
6267
6268       case MachinePlaysBlack:
6269       case IcsPlayingWhite:
6270         if (appData.zippyPlay) return FALSE;
6271         if (!white_piece) {
6272             DisplayMoveError(_("You are playing White"));
6273             return FALSE;
6274         }
6275         break;
6276
6277       case PlayFromGameFile:
6278             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6279       case EditGame:
6280         if (!white_piece && WhiteOnMove(currentMove)) {
6281             DisplayMoveError(_("It is White's turn"));
6282             return FALSE;
6283         }
6284         if (white_piece && !WhiteOnMove(currentMove)) {
6285             DisplayMoveError(_("It is Black's turn"));
6286             return FALSE;
6287         }
6288         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6289             /* Editing correspondence game history */
6290             /* Could disallow this or prompt for confirmation */
6291             cmailOldMove = -1;
6292         }
6293         break;
6294
6295       case BeginningOfGame:
6296         if (appData.icsActive) return FALSE;
6297         if (!appData.noChessProgram) {
6298             if (!white_piece) {
6299                 DisplayMoveError(_("You are playing White"));
6300                 return FALSE;
6301             }
6302         }
6303         break;
6304
6305       case Training:
6306         if (!white_piece && WhiteOnMove(currentMove)) {
6307             DisplayMoveError(_("It is White's turn"));
6308             return FALSE;
6309         }
6310         if (white_piece && !WhiteOnMove(currentMove)) {
6311             DisplayMoveError(_("It is Black's turn"));
6312             return FALSE;
6313         }
6314         break;
6315
6316       default:
6317       case IcsExamining:
6318         break;
6319     }
6320     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6321         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6322         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6323         && gameMode != AnalyzeFile && gameMode != Training) {
6324         DisplayMoveError(_("Displayed position is not current"));
6325         return FALSE;
6326     }
6327     return TRUE;
6328 }
6329
6330 Boolean
6331 OnlyMove (int *x, int *y, Boolean captures) 
6332 {
6333     DisambiguateClosure cl;
6334     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6335     switch(gameMode) {
6336       case MachinePlaysBlack:
6337       case IcsPlayingWhite:
6338       case BeginningOfGame:
6339         if(!WhiteOnMove(currentMove)) return FALSE;
6340         break;
6341       case MachinePlaysWhite:
6342       case IcsPlayingBlack:
6343         if(WhiteOnMove(currentMove)) return FALSE;
6344         break;
6345       case EditGame:
6346         break;
6347       default:
6348         return FALSE;
6349     }
6350     cl.pieceIn = EmptySquare;
6351     cl.rfIn = *y;
6352     cl.ffIn = *x;
6353     cl.rtIn = -1;
6354     cl.ftIn = -1;
6355     cl.promoCharIn = NULLCHAR;
6356     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6357     if( cl.kind == NormalMove ||
6358         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6359         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6360         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6361       fromX = cl.ff;
6362       fromY = cl.rf;
6363       *x = cl.ft;
6364       *y = cl.rt;
6365       return TRUE;
6366     }
6367     if(cl.kind != ImpossibleMove) return FALSE;
6368     cl.pieceIn = EmptySquare;
6369     cl.rfIn = -1;
6370     cl.ffIn = -1;
6371     cl.rtIn = *y;
6372     cl.ftIn = *x;
6373     cl.promoCharIn = NULLCHAR;
6374     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6375     if( cl.kind == NormalMove ||
6376         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6377         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6378         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6379       fromX = cl.ff;
6380       fromY = cl.rf;
6381       *x = cl.ft;
6382       *y = cl.rt;
6383       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6384       return TRUE;
6385     }
6386     return FALSE;
6387 }
6388
6389 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6390 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6391 int lastLoadGameUseList = FALSE;
6392 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6393 ChessMove lastLoadGameStart = EndOfFile;
6394
6395 void
6396 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6397 {
6398     ChessMove moveType;
6399     ChessSquare pdown, pup;
6400
6401     /* Check if the user is playing in turn.  This is complicated because we
6402        let the user "pick up" a piece before it is his turn.  So the piece he
6403        tried to pick up may have been captured by the time he puts it down!
6404        Therefore we use the color the user is supposed to be playing in this
6405        test, not the color of the piece that is currently on the starting
6406        square---except in EditGame mode, where the user is playing both
6407        sides; fortunately there the capture race can't happen.  (It can
6408        now happen in IcsExamining mode, but that's just too bad.  The user
6409        will get a somewhat confusing message in that case.)
6410        */
6411
6412     switch (gameMode) {
6413       case AnalyzeFile:
6414       case TwoMachinesPlay:
6415       case EndOfGame:
6416       case IcsObserving:
6417       case IcsIdle:
6418         /* We switched into a game mode where moves are not accepted,
6419            perhaps while the mouse button was down. */
6420         return;
6421
6422       case MachinePlaysWhite:
6423         /* User is moving for Black */
6424         if (WhiteOnMove(currentMove)) {
6425             DisplayMoveError(_("It is White's turn"));
6426             return;
6427         }
6428         break;
6429
6430       case MachinePlaysBlack:
6431         /* User is moving for White */
6432         if (!WhiteOnMove(currentMove)) {
6433             DisplayMoveError(_("It is Black's turn"));
6434             return;
6435         }
6436         break;
6437
6438       case PlayFromGameFile:
6439             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6440       case EditGame:
6441       case IcsExamining:
6442       case BeginningOfGame:
6443       case AnalyzeMode:
6444       case Training:
6445         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6446         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6447             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6448             /* User is moving for Black */
6449             if (WhiteOnMove(currentMove)) {
6450                 DisplayMoveError(_("It is White's turn"));
6451                 return;
6452             }
6453         } else {
6454             /* User is moving for White */
6455             if (!WhiteOnMove(currentMove)) {
6456                 DisplayMoveError(_("It is Black's turn"));
6457                 return;
6458             }
6459         }
6460         break;
6461
6462       case IcsPlayingBlack:
6463         /* User is moving for Black */
6464         if (WhiteOnMove(currentMove)) {
6465             if (!appData.premove) {
6466                 DisplayMoveError(_("It is White's turn"));
6467             } else if (toX >= 0 && toY >= 0) {
6468                 premoveToX = toX;
6469                 premoveToY = toY;
6470                 premoveFromX = fromX;
6471                 premoveFromY = fromY;
6472                 premovePromoChar = promoChar;
6473                 gotPremove = 1;
6474                 if (appData.debugMode)
6475                     fprintf(debugFP, "Got premove: fromX %d,"
6476                             "fromY %d, toX %d, toY %d\n",
6477                             fromX, fromY, toX, toY);
6478             }
6479             return;
6480         }
6481         break;
6482
6483       case IcsPlayingWhite:
6484         /* User is moving for White */
6485         if (!WhiteOnMove(currentMove)) {
6486             if (!appData.premove) {
6487                 DisplayMoveError(_("It is Black's turn"));
6488             } else if (toX >= 0 && toY >= 0) {
6489                 premoveToX = toX;
6490                 premoveToY = toY;
6491                 premoveFromX = fromX;
6492                 premoveFromY = fromY;
6493                 premovePromoChar = promoChar;
6494                 gotPremove = 1;
6495                 if (appData.debugMode)
6496                     fprintf(debugFP, "Got premove: fromX %d,"
6497                             "fromY %d, toX %d, toY %d\n",
6498                             fromX, fromY, toX, toY);
6499             }
6500             return;
6501         }
6502         break;
6503
6504       default:
6505         break;
6506
6507       case EditPosition:
6508         /* EditPosition, empty square, or different color piece;
6509            click-click move is possible */
6510         if (toX == -2 || toY == -2) {
6511             boards[0][fromY][fromX] = EmptySquare;
6512             DrawPosition(FALSE, boards[currentMove]);
6513             return;
6514         } else if (toX >= 0 && toY >= 0) {
6515             boards[0][toY][toX] = boards[0][fromY][fromX];
6516             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6517                 if(boards[0][fromY][0] != EmptySquare) {
6518                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6519                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6520                 }
6521             } else
6522             if(fromX == BOARD_RGHT+1) {
6523                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6524                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6525                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6526                 }
6527             } else
6528             boards[0][fromY][fromX] = EmptySquare;
6529             DrawPosition(FALSE, boards[currentMove]);
6530             return;
6531         }
6532         return;
6533     }
6534
6535     if(toX < 0 || toY < 0) return;
6536     pdown = boards[currentMove][fromY][fromX];
6537     pup = boards[currentMove][toY][toX];
6538
6539     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6540     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6541          if( pup != EmptySquare ) return;
6542          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6543            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6544                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6545            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6546            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6547            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6548            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6549          fromY = DROP_RANK;
6550     }
6551
6552     /* [HGM] always test for legality, to get promotion info */
6553     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6554                                          fromY, fromX, toY, toX, promoChar);
6555
6556     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6557
6558     /* [HGM] but possibly ignore an IllegalMove result */
6559     if (appData.testLegality) {
6560         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6561             DisplayMoveError(_("Illegal move"));
6562             return;
6563         }
6564     }
6565
6566     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6567 }
6568
6569 /* Common tail of UserMoveEvent and DropMenuEvent */
6570 int
6571 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6572 {
6573     char *bookHit = 0;
6574
6575     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6576         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6577         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6578         if(WhiteOnMove(currentMove)) {
6579             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6580         } else {
6581             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6582         }
6583     }
6584
6585     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6586        move type in caller when we know the move is a legal promotion */
6587     if(moveType == NormalMove && promoChar)
6588         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6589
6590     /* [HGM] <popupFix> The following if has been moved here from
6591        UserMoveEvent(). Because it seemed to belong here (why not allow
6592        piece drops in training games?), and because it can only be
6593        performed after it is known to what we promote. */
6594     if (gameMode == Training) {
6595       /* compare the move played on the board to the next move in the
6596        * game. If they match, display the move and the opponent's response.
6597        * If they don't match, display an error message.
6598        */
6599       int saveAnimate;
6600       Board testBoard;
6601       CopyBoard(testBoard, boards[currentMove]);
6602       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6603
6604       if (CompareBoards(testBoard, boards[currentMove+1])) {
6605         ForwardInner(currentMove+1);
6606
6607         /* Autoplay the opponent's response.
6608          * if appData.animate was TRUE when Training mode was entered,
6609          * the response will be animated.
6610          */
6611         saveAnimate = appData.animate;
6612         appData.animate = animateTraining;
6613         ForwardInner(currentMove+1);
6614         appData.animate = saveAnimate;
6615
6616         /* check for the end of the game */
6617         if (currentMove >= forwardMostMove) {
6618           gameMode = PlayFromGameFile;
6619           ModeHighlight();
6620           SetTrainingModeOff();
6621           DisplayInformation(_("End of game"));
6622         }
6623       } else {
6624         DisplayError(_("Incorrect move"), 0);
6625       }
6626       return 1;
6627     }
6628
6629   /* Ok, now we know that the move is good, so we can kill
6630      the previous line in Analysis Mode */
6631   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6632                                 && currentMove < forwardMostMove) {
6633     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6634     else forwardMostMove = currentMove;
6635   }
6636
6637   /* If we need the chess program but it's dead, restart it */
6638   ResurrectChessProgram();
6639
6640   /* A user move restarts a paused game*/
6641   if (pausing)
6642     PauseEvent();
6643
6644   thinkOutput[0] = NULLCHAR;
6645
6646   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6647
6648   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6649     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6650     return 1;
6651   }
6652
6653   if (gameMode == BeginningOfGame) {
6654     if (appData.noChessProgram) {
6655       gameMode = EditGame;
6656       SetGameInfo();
6657     } else {
6658       char buf[MSG_SIZ];
6659       gameMode = MachinePlaysBlack;
6660       StartClocks();
6661       SetGameInfo();
6662       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6663       DisplayTitle(buf);
6664       if (first.sendName) {
6665         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6666         SendToProgram(buf, &first);
6667       }
6668       StartClocks();
6669     }
6670     ModeHighlight();
6671   }
6672
6673   /* Relay move to ICS or chess engine */
6674   if (appData.icsActive) {
6675     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6676         gameMode == IcsExamining) {
6677       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6678         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6679         SendToICS("draw ");
6680         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6681       }
6682       // also send plain move, in case ICS does not understand atomic claims
6683       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6684       ics_user_moved = 1;
6685     }
6686   } else {
6687     if (first.sendTime && (gameMode == BeginningOfGame ||
6688                            gameMode == MachinePlaysWhite ||
6689                            gameMode == MachinePlaysBlack)) {
6690       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6691     }
6692     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6693          // [HGM] book: if program might be playing, let it use book
6694         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6695         first.maybeThinking = TRUE;
6696     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6697         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6698         SendBoard(&first, currentMove+1);
6699     } else SendMoveToProgram(forwardMostMove-1, &first);
6700     if (currentMove == cmailOldMove + 1) {
6701       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6702     }
6703   }
6704
6705   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6706
6707   switch (gameMode) {
6708   case EditGame:
6709     if(appData.testLegality)
6710     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6711     case MT_NONE:
6712     case MT_CHECK:
6713       break;
6714     case MT_CHECKMATE:
6715     case MT_STAINMATE:
6716       if (WhiteOnMove(currentMove)) {
6717         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6718       } else {
6719         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6720       }
6721       break;
6722     case MT_STALEMATE:
6723       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6724       break;
6725     }
6726     break;
6727
6728   case MachinePlaysBlack:
6729   case MachinePlaysWhite:
6730     /* disable certain menu options while machine is thinking */
6731     SetMachineThinkingEnables();
6732     break;
6733
6734   default:
6735     break;
6736   }
6737
6738   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6739   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6740
6741   if(bookHit) { // [HGM] book: simulate book reply
6742         static char bookMove[MSG_SIZ]; // a bit generous?
6743
6744         programStats.nodes = programStats.depth = programStats.time =
6745         programStats.score = programStats.got_only_move = 0;
6746         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6747
6748         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6749         strcat(bookMove, bookHit);
6750         HandleMachineMove(bookMove, &first);
6751   }
6752   return 1;
6753 }
6754
6755 void
6756 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6757 {
6758     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6759     Markers *m = (Markers *) closure;
6760     if(rf == fromY && ff == fromX)
6761         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6762                          || kind == WhiteCapturesEnPassant
6763                          || kind == BlackCapturesEnPassant);
6764     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6765 }
6766
6767 void
6768 MarkTargetSquares (int clear)
6769 {
6770   int x, y;
6771   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6772      !appData.testLegality || gameMode == EditPosition) return;
6773   if(clear) {
6774     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6775   } else {
6776     int capt = 0;
6777     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6778     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6779       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6780       if(capt)
6781       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6782     }
6783   }
6784   DrawPosition(TRUE, NULL);
6785 }
6786
6787 int
6788 Explode (Board board, int fromX, int fromY, int toX, int toY)
6789 {
6790     if(gameInfo.variant == VariantAtomic &&
6791        (board[toY][toX] != EmptySquare ||                     // capture?
6792         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6793                          board[fromY][fromX] == BlackPawn   )
6794       )) {
6795         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6796         return TRUE;
6797     }
6798     return FALSE;
6799 }
6800
6801 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6802
6803 int
6804 CanPromote (ChessSquare piece, int y)
6805 {
6806         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6807         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6808         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6809            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6810            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6811                                                   gameInfo.variant == VariantMakruk) return FALSE;
6812         return (piece == BlackPawn && y == 1 ||
6813                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6814                 piece == BlackLance && y == 1 ||
6815                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6816 }
6817
6818 void
6819 LeftClick (ClickType clickType, int xPix, int yPix)
6820 {
6821     int x, y;
6822     Boolean saveAnimate;
6823     static int second = 0, promotionChoice = 0, clearFlag = 0;
6824     char promoChoice = NULLCHAR;
6825     ChessSquare piece;
6826
6827     if(appData.seekGraph && appData.icsActive && loggedOn &&
6828         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6829         SeekGraphClick(clickType, xPix, yPix, 0);
6830         return;
6831     }
6832
6833     if (clickType == Press) ErrorPopDown();
6834
6835     x = EventToSquare(xPix, BOARD_WIDTH);
6836     y = EventToSquare(yPix, BOARD_HEIGHT);
6837     if (!flipView && y >= 0) {
6838         y = BOARD_HEIGHT - 1 - y;
6839     }
6840     if (flipView && x >= 0) {
6841         x = BOARD_WIDTH - 1 - x;
6842     }
6843
6844     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6845         defaultPromoChoice = promoSweep;
6846         promoSweep = EmptySquare;   // terminate sweep
6847         promoDefaultAltered = TRUE;
6848         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6849     }
6850
6851     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6852         if(clickType == Release) return; // ignore upclick of click-click destination
6853         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6854         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6855         if(gameInfo.holdingsWidth &&
6856                 (WhiteOnMove(currentMove)
6857                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6858                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6859             // click in right holdings, for determining promotion piece
6860             ChessSquare p = boards[currentMove][y][x];
6861             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6862             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6863             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6864                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6865                 fromX = fromY = -1;
6866                 return;
6867             }
6868         }
6869         DrawPosition(FALSE, boards[currentMove]);
6870         return;
6871     }
6872
6873     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6874     if(clickType == Press
6875             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6876               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6877               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6878         return;
6879
6880     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6881         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6882
6883     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6884         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6885                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6886         defaultPromoChoice = DefaultPromoChoice(side);
6887     }
6888
6889     autoQueen = appData.alwaysPromoteToQueen;
6890
6891     if (fromX == -1) {
6892       int originalY = y;
6893       gatingPiece = EmptySquare;
6894       if (clickType != Press) {
6895         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6896             DragPieceEnd(xPix, yPix); dragging = 0;
6897             DrawPosition(FALSE, NULL);
6898         }
6899         return;
6900       }
6901       fromX = x; fromY = y; toX = toY = -1;
6902       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6903          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6904          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6905             /* First square */
6906             if (OKToStartUserMove(fromX, fromY)) {
6907                 second = 0;
6908                 MarkTargetSquares(0);
6909                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6910                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6911                     promoSweep = defaultPromoChoice;
6912                     selectFlag = 0; lastX = xPix; lastY = yPix;
6913                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6914                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6915                 }
6916                 if (appData.highlightDragging) {
6917                     SetHighlights(fromX, fromY, -1, -1);
6918                 }
6919             } else fromX = fromY = -1;
6920             return;
6921         }
6922     }
6923
6924     /* fromX != -1 */
6925     if (clickType == Press && gameMode != EditPosition) {
6926         ChessSquare fromP;
6927         ChessSquare toP;
6928         int frc;
6929
6930         // ignore off-board to clicks
6931         if(y < 0 || x < 0) return;
6932
6933         /* Check if clicking again on the same color piece */
6934         fromP = boards[currentMove][fromY][fromX];
6935         toP = boards[currentMove][y][x];
6936         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6937         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6938              WhitePawn <= toP && toP <= WhiteKing &&
6939              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6940              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6941             (BlackPawn <= fromP && fromP <= BlackKing &&
6942              BlackPawn <= toP && toP <= BlackKing &&
6943              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6944              !(fromP == BlackKing && toP == BlackRook && frc))) {
6945             /* Clicked again on same color piece -- changed his mind */
6946             second = (x == fromX && y == fromY);
6947             promoDefaultAltered = FALSE;
6948             MarkTargetSquares(1);
6949            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6950             if (appData.highlightDragging) {
6951                 SetHighlights(x, y, -1, -1);
6952             } else {
6953                 ClearHighlights();
6954             }
6955             if (OKToStartUserMove(x, y)) {
6956                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6957                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6958                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6959                  gatingPiece = boards[currentMove][fromY][fromX];
6960                 else gatingPiece = EmptySquare;
6961                 fromX = x;
6962                 fromY = y; dragging = 1;
6963                 MarkTargetSquares(0);
6964                 DragPieceBegin(xPix, yPix, FALSE);
6965                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6966                     promoSweep = defaultPromoChoice;
6967                     selectFlag = 0; lastX = xPix; lastY = yPix;
6968                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6969                 }
6970             }
6971            }
6972            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6973            second = FALSE; 
6974         }
6975         // ignore clicks on holdings
6976         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6977     }
6978
6979     if (clickType == Release && x == fromX && y == fromY) {
6980         DragPieceEnd(xPix, yPix); dragging = 0;
6981         if(clearFlag) {
6982             // a deferred attempt to click-click move an empty square on top of a piece
6983             boards[currentMove][y][x] = EmptySquare;
6984             ClearHighlights();
6985             DrawPosition(FALSE, boards[currentMove]);
6986             fromX = fromY = -1; clearFlag = 0;
6987             return;
6988         }
6989         if (appData.animateDragging) {
6990             /* Undo animation damage if any */
6991             DrawPosition(FALSE, NULL);
6992         }
6993         if (second) {
6994             /* Second up/down in same square; just abort move */
6995             second = 0;
6996             fromX = fromY = -1;
6997             gatingPiece = EmptySquare;
6998             ClearHighlights();
6999             gotPremove = 0;
7000             ClearPremoveHighlights();
7001         } else {
7002             /* First upclick in same square; start click-click mode */
7003             SetHighlights(x, y, -1, -1);
7004         }
7005         return;
7006     }
7007
7008     clearFlag = 0;
7009
7010     /* we now have a different from- and (possibly off-board) to-square */
7011     /* Completed move */
7012     toX = x;
7013     toY = y;
7014     saveAnimate = appData.animate;
7015     MarkTargetSquares(1);
7016     if (clickType == Press) {
7017         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7018             // must be Edit Position mode with empty-square selected
7019             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7020             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7021             return;
7022         }
7023         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7024             ChessSquare piece = boards[currentMove][fromY][fromX];
7025             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7026             promoSweep = defaultPromoChoice;
7027             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7028             selectFlag = 0; lastX = xPix; lastY = yPix;
7029             Sweep(0); // Pawn that is going to promote: preview promotion piece
7030             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7031             DrawPosition(FALSE, boards[currentMove]);
7032             return;
7033         }
7034         /* Finish clickclick move */
7035         if (appData.animate || appData.highlightLastMove) {
7036             SetHighlights(fromX, fromY, toX, toY);
7037         } else {
7038             ClearHighlights();
7039         }
7040     } else {
7041         /* Finish drag move */
7042         if (appData.highlightLastMove) {
7043             SetHighlights(fromX, fromY, toX, toY);
7044         } else {
7045             ClearHighlights();
7046         }
7047         DragPieceEnd(xPix, yPix); dragging = 0;
7048         /* Don't animate move and drag both */
7049         appData.animate = FALSE;
7050     }
7051
7052     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7053     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7054         ChessSquare piece = boards[currentMove][fromY][fromX];
7055         if(gameMode == EditPosition && piece != EmptySquare &&
7056            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7057             int n;
7058
7059             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7060                 n = PieceToNumber(piece - (int)BlackPawn);
7061                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7062                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7063                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7064             } else
7065             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7066                 n = PieceToNumber(piece);
7067                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7068                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7069                 boards[currentMove][n][BOARD_WIDTH-2]++;
7070             }
7071             boards[currentMove][fromY][fromX] = EmptySquare;
7072         }
7073         ClearHighlights();
7074         fromX = fromY = -1;
7075         DrawPosition(TRUE, boards[currentMove]);
7076         return;
7077     }
7078
7079     // off-board moves should not be highlighted
7080     if(x < 0 || y < 0) ClearHighlights();
7081
7082     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7083
7084     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7085         SetHighlights(fromX, fromY, toX, toY);
7086         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7087             // [HGM] super: promotion to captured piece selected from holdings
7088             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7089             promotionChoice = TRUE;
7090             // kludge follows to temporarily execute move on display, without promoting yet
7091             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7092             boards[currentMove][toY][toX] = p;
7093             DrawPosition(FALSE, boards[currentMove]);
7094             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7095             boards[currentMove][toY][toX] = q;
7096             DisplayMessage("Click in holdings to choose piece", "");
7097             return;
7098         }
7099         PromotionPopUp();
7100     } else {
7101         int oldMove = currentMove;
7102         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7103         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7104         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7105         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7106            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7107             DrawPosition(TRUE, boards[currentMove]);
7108         fromX = fromY = -1;
7109     }
7110     appData.animate = saveAnimate;
7111     if (appData.animate || appData.animateDragging) {
7112         /* Undo animation damage if needed */
7113         DrawPosition(FALSE, NULL);
7114     }
7115 }
7116
7117 int
7118 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7119 {   // front-end-free part taken out of PieceMenuPopup
7120     int whichMenu; int xSqr, ySqr;
7121
7122     if(seekGraphUp) { // [HGM] seekgraph
7123         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7124         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7125         return -2;
7126     }
7127
7128     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7129          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7130         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7131         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7132         if(action == Press)   {
7133             originalFlip = flipView;
7134             flipView = !flipView; // temporarily flip board to see game from partners perspective
7135             DrawPosition(TRUE, partnerBoard);
7136             DisplayMessage(partnerStatus, "");
7137             partnerUp = TRUE;
7138         } else if(action == Release) {
7139             flipView = originalFlip;
7140             DrawPosition(TRUE, boards[currentMove]);
7141             partnerUp = FALSE;
7142         }
7143         return -2;
7144     }
7145
7146     xSqr = EventToSquare(x, BOARD_WIDTH);
7147     ySqr = EventToSquare(y, BOARD_HEIGHT);
7148     if (action == Release) {
7149         if(pieceSweep != EmptySquare) {
7150             EditPositionMenuEvent(pieceSweep, toX, toY);
7151             pieceSweep = EmptySquare;
7152         } else UnLoadPV(); // [HGM] pv
7153     }
7154     if (action != Press) return -2; // return code to be ignored
7155     switch (gameMode) {
7156       case IcsExamining:
7157         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7158       case EditPosition:
7159         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7160         if (xSqr < 0 || ySqr < 0) return -1;
7161         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7162         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7163         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7164         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7165         NextPiece(0);
7166         return 2; // grab
7167       case IcsObserving:
7168         if(!appData.icsEngineAnalyze) return -1;
7169       case IcsPlayingWhite:
7170       case IcsPlayingBlack:
7171         if(!appData.zippyPlay) goto noZip;
7172       case AnalyzeMode:
7173       case AnalyzeFile:
7174       case MachinePlaysWhite:
7175       case MachinePlaysBlack:
7176       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7177         if (!appData.dropMenu) {
7178           LoadPV(x, y);
7179           return 2; // flag front-end to grab mouse events
7180         }
7181         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7182            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7183       case EditGame:
7184       noZip:
7185         if (xSqr < 0 || ySqr < 0) return -1;
7186         if (!appData.dropMenu || appData.testLegality &&
7187             gameInfo.variant != VariantBughouse &&
7188             gameInfo.variant != VariantCrazyhouse) return -1;
7189         whichMenu = 1; // drop menu
7190         break;
7191       default:
7192         return -1;
7193     }
7194
7195     if (((*fromX = xSqr) < 0) ||
7196         ((*fromY = ySqr) < 0)) {
7197         *fromX = *fromY = -1;
7198         return -1;
7199     }
7200     if (flipView)
7201       *fromX = BOARD_WIDTH - 1 - *fromX;
7202     else
7203       *fromY = BOARD_HEIGHT - 1 - *fromY;
7204
7205     return whichMenu;
7206 }
7207
7208 void
7209 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7210 {
7211 //    char * hint = lastHint;
7212     FrontEndProgramStats stats;
7213
7214     stats.which = cps == &first ? 0 : 1;
7215     stats.depth = cpstats->depth;
7216     stats.nodes = cpstats->nodes;
7217     stats.score = cpstats->score;
7218     stats.time = cpstats->time;
7219     stats.pv = cpstats->movelist;
7220     stats.hint = lastHint;
7221     stats.an_move_index = 0;
7222     stats.an_move_count = 0;
7223
7224     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7225         stats.hint = cpstats->move_name;
7226         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7227         stats.an_move_count = cpstats->nr_moves;
7228     }
7229
7230     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
7231
7232     SetProgramStats( &stats );
7233 }
7234
7235 void
7236 ClearEngineOutputPane (int which)
7237 {
7238     static FrontEndProgramStats dummyStats;
7239     dummyStats.which = which;
7240     dummyStats.pv = "#";
7241     SetProgramStats( &dummyStats );
7242 }
7243
7244 #define MAXPLAYERS 500
7245
7246 char *
7247 TourneyStandings (int display)
7248 {
7249     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7250     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7251     char result, *p, *names[MAXPLAYERS];
7252
7253     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7254         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7255     names[0] = p = strdup(appData.participants);
7256     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7257
7258     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7259
7260     while(result = appData.results[nr]) {
7261         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7262         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7263         wScore = bScore = 0;
7264         switch(result) {
7265           case '+': wScore = 2; break;
7266           case '-': bScore = 2; break;
7267           case '=': wScore = bScore = 1; break;
7268           case ' ':
7269           case '*': return strdup("busy"); // tourney not finished
7270         }
7271         score[w] += wScore;
7272         score[b] += bScore;
7273         games[w]++;
7274         games[b]++;
7275         nr++;
7276     }
7277     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7278     for(w=0; w<nPlayers; w++) {
7279         bScore = -1;
7280         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7281         ranking[w] = b; points[w] = bScore; score[b] = -2;
7282     }
7283     p = malloc(nPlayers*34+1);
7284     for(w=0; w<nPlayers && w<display; w++)
7285         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7286     free(names[0]);
7287     return p;
7288 }
7289
7290 void
7291 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7292 {       // count all piece types
7293         int p, f, r;
7294         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7295         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7296         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7297                 p = board[r][f];
7298                 pCnt[p]++;
7299                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7300                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7301                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7302                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7303                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7304                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7305         }
7306 }
7307
7308 int
7309 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7310 {
7311         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7312         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7313
7314         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7315         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7316         if(myPawns == 2 && nMine == 3) // KPP
7317             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7318         if(myPawns == 1 && nMine == 2) // KP
7319             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7320         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7321             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7322         if(myPawns) return FALSE;
7323         if(pCnt[WhiteRook+side])
7324             return pCnt[BlackRook-side] ||
7325                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7326                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7327                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7328         if(pCnt[WhiteCannon+side]) {
7329             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7330             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7331         }
7332         if(pCnt[WhiteKnight+side])
7333             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7334         return FALSE;
7335 }
7336
7337 int
7338 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7339 {
7340         VariantClass v = gameInfo.variant;
7341
7342         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7343         if(v == VariantShatranj) return TRUE; // always winnable through baring
7344         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7345         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7346
7347         if(v == VariantXiangqi) {
7348                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7349
7350                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7351                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7352                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7353                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7354                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7355                 if(stale) // we have at least one last-rank P plus perhaps C
7356                     return majors // KPKX
7357                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7358                 else // KCA*E*
7359                     return pCnt[WhiteFerz+side] // KCAK
7360                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7361                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7362                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7363
7364         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7365                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7366
7367                 if(nMine == 1) return FALSE; // bare King
7368                 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
7369                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7370                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7371                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7372                 if(pCnt[WhiteKnight+side])
7373                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7374                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7375                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7376                 if(nBishops)
7377                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7378                 if(pCnt[WhiteAlfil+side])
7379                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7380                 if(pCnt[WhiteWazir+side])
7381                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7382         }
7383
7384         return TRUE;
7385 }
7386
7387 int
7388 CompareWithRights (Board b1, Board b2)
7389 {
7390     int rights = 0;
7391     if(!CompareBoards(b1, b2)) return FALSE;
7392     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7393     /* compare castling rights */
7394     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7395            rights++; /* King lost rights, while rook still had them */
7396     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7397         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7398            rights++; /* but at least one rook lost them */
7399     }
7400     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7401            rights++;
7402     if( b1[CASTLING][5] != NoRights ) {
7403         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7404            rights++;
7405     }
7406     return rights == 0;
7407 }
7408
7409 int
7410 Adjudicate (ChessProgramState *cps)
7411 {       // [HGM] some adjudications useful with buggy engines
7412         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7413         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7414         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7415         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7416         int k, count = 0; static int bare = 1;
7417         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7418         Boolean canAdjudicate = !appData.icsActive;
7419
7420         // most tests only when we understand the game, i.e. legality-checking on
7421             if( appData.testLegality )
7422             {   /* [HGM] Some more adjudications for obstinate engines */
7423                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7424                 static int moveCount = 6;
7425                 ChessMove result;
7426                 char *reason = NULL;
7427
7428                 /* Count what is on board. */
7429                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7430
7431                 /* Some material-based adjudications that have to be made before stalemate test */
7432                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7433                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7434                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7435                      if(canAdjudicate && appData.checkMates) {
7436                          if(engineOpponent)
7437                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7438                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7439                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7440                          return 1;
7441                      }
7442                 }
7443
7444                 /* Bare King in Shatranj (loses) or Losers (wins) */
7445                 if( nrW == 1 || nrB == 1) {
7446                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7447                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7448                      if(canAdjudicate && appData.checkMates) {
7449                          if(engineOpponent)
7450                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7451                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7452                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7453                          return 1;
7454                      }
7455                   } else
7456                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7457                   {    /* bare King */
7458                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7459                         if(canAdjudicate && appData.checkMates) {
7460                             /* but only adjudicate if adjudication enabled */
7461                             if(engineOpponent)
7462                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7463                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7464                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7465                             return 1;
7466                         }
7467                   }
7468                 } else bare = 1;
7469
7470
7471             // don't wait for engine to announce game end if we can judge ourselves
7472             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7473               case MT_CHECK:
7474                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7475                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7476                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7477                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7478                             checkCnt++;
7479                         if(checkCnt >= 2) {
7480                             reason = "Xboard adjudication: 3rd check";
7481                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7482                             break;
7483                         }
7484                     }
7485                 }
7486               case MT_NONE:
7487               default:
7488                 break;
7489               case MT_STALEMATE:
7490               case MT_STAINMATE:
7491                 reason = "Xboard adjudication: Stalemate";
7492                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7493                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7494                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7495                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7496                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7497                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7498                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7499                                                                         EP_CHECKMATE : EP_WINS);
7500                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7501                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7502                 }
7503                 break;
7504               case MT_CHECKMATE:
7505                 reason = "Xboard adjudication: Checkmate";
7506                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7507                 break;
7508             }
7509
7510                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7511                     case EP_STALEMATE:
7512                         result = GameIsDrawn; break;
7513                     case EP_CHECKMATE:
7514                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7515                     case EP_WINS:
7516                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7517                     default:
7518                         result = EndOfFile;
7519                 }
7520                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7521                     if(engineOpponent)
7522                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7523                     GameEnds( result, reason, GE_XBOARD );
7524                     return 1;
7525                 }
7526
7527                 /* Next absolutely insufficient mating material. */
7528                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7529                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7530                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7531
7532                      /* always flag draws, for judging claims */
7533                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7534
7535                      if(canAdjudicate && appData.materialDraws) {
7536                          /* but only adjudicate them if adjudication enabled */
7537                          if(engineOpponent) {
7538                            SendToProgram("force\n", engineOpponent); // suppress reply
7539                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7540                          }
7541                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7542                          return 1;
7543                      }
7544                 }
7545
7546                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7547                 if(gameInfo.variant == VariantXiangqi ?
7548                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7549                  : nrW + nrB == 4 &&
7550                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7551                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7552                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7553                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7554                    ) ) {
7555                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7556                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7557                           if(engineOpponent) {
7558                             SendToProgram("force\n", engineOpponent); // suppress reply
7559                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7560                           }
7561                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7562                           return 1;
7563                      }
7564                 } else moveCount = 6;
7565             }
7566
7567         // Repetition draws and 50-move rule can be applied independently of legality testing
7568
7569                 /* Check for rep-draws */
7570                 count = 0;
7571                 for(k = forwardMostMove-2;
7572                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7573                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7574                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7575                     k-=2)
7576                 {   int rights=0;
7577                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7578                         /* compare castling rights */
7579                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7580                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7581                                 rights++; /* King lost rights, while rook still had them */
7582                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7583                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7584                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7585                                    rights++; /* but at least one rook lost them */
7586                         }
7587                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7588                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7589                                 rights++;
7590                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7591                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7592                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7593                                    rights++;
7594                         }
7595                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7596                             && appData.drawRepeats > 1) {
7597                              /* adjudicate after user-specified nr of repeats */
7598                              int result = GameIsDrawn;
7599                              char *details = "XBoard adjudication: repetition draw";
7600                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7601                                 // [HGM] xiangqi: check for forbidden perpetuals
7602                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7603                                 for(m=forwardMostMove; m>k; m-=2) {
7604                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7605                                         ourPerpetual = 0; // the current mover did not always check
7606                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7607                                         hisPerpetual = 0; // the opponent did not always check
7608                                 }
7609                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7610                                                                         ourPerpetual, hisPerpetual);
7611                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7612                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7613                                     details = "Xboard adjudication: perpetual checking";
7614                                 } else
7615                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7616                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7617                                 } else
7618                                 // Now check for perpetual chases
7619                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7620                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7621                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7622                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7623                                         static char resdet[MSG_SIZ];
7624                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7625                                         details = resdet;
7626                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7627                                     } else
7628                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7629                                         break; // Abort repetition-checking loop.
7630                                 }
7631                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7632                              }
7633                              if(engineOpponent) {
7634                                SendToProgram("force\n", engineOpponent); // suppress reply
7635                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7636                              }
7637                              GameEnds( result, details, GE_XBOARD );
7638                              return 1;
7639                         }
7640                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7641                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7642                     }
7643                 }
7644
7645                 /* Now we test for 50-move draws. Determine ply count */
7646                 count = forwardMostMove;
7647                 /* look for last irreversble move */
7648                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7649                     count--;
7650                 /* if we hit starting position, add initial plies */
7651                 if( count == backwardMostMove )
7652                     count -= initialRulePlies;
7653                 count = forwardMostMove - count;
7654                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7655                         // adjust reversible move counter for checks in Xiangqi
7656                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7657                         if(i < backwardMostMove) i = backwardMostMove;
7658                         while(i <= forwardMostMove) {
7659                                 lastCheck = inCheck; // check evasion does not count
7660                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7661                                 if(inCheck || lastCheck) count--; // check does not count
7662                                 i++;
7663                         }
7664                 }
7665                 if( count >= 100)
7666                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7667                          /* this is used to judge if draw claims are legal */
7668                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7669                          if(engineOpponent) {
7670                            SendToProgram("force\n", engineOpponent); // suppress reply
7671                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7672                          }
7673                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7674                          return 1;
7675                 }
7676
7677                 /* if draw offer is pending, treat it as a draw claim
7678                  * when draw condition present, to allow engines a way to
7679                  * claim draws before making their move to avoid a race
7680                  * condition occurring after their move
7681                  */
7682                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7683                          char *p = NULL;
7684                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7685                              p = "Draw claim: 50-move rule";
7686                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7687                              p = "Draw claim: 3-fold repetition";
7688                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7689                              p = "Draw claim: insufficient mating material";
7690                          if( p != NULL && canAdjudicate) {
7691                              if(engineOpponent) {
7692                                SendToProgram("force\n", engineOpponent); // suppress reply
7693                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7694                              }
7695                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7696                              return 1;
7697                          }
7698                 }
7699
7700                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7701                     if(engineOpponent) {
7702                       SendToProgram("force\n", engineOpponent); // suppress reply
7703                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7704                     }
7705                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7706                     return 1;
7707                 }
7708         return 0;
7709 }
7710
7711 char *
7712 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7713 {   // [HGM] book: this routine intercepts moves to simulate book replies
7714     char *bookHit = NULL;
7715
7716     //first determine if the incoming move brings opponent into his book
7717     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7718         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7719     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7720     if(bookHit != NULL && !cps->bookSuspend) {
7721         // make sure opponent is not going to reply after receiving move to book position
7722         SendToProgram("force\n", cps);
7723         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7724     }
7725     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7726     // now arrange restart after book miss
7727     if(bookHit) {
7728         // after a book hit we never send 'go', and the code after the call to this routine
7729         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7730         char buf[MSG_SIZ], *move = bookHit;
7731         if(cps->useSAN) {
7732             int fromX, fromY, toX, toY;
7733             char promoChar;
7734             ChessMove moveType;
7735             move = buf + 30;
7736             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7737                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7738                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7739                                     PosFlags(forwardMostMove),
7740                                     fromY, fromX, toY, toX, promoChar, move);
7741             } else {
7742                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7743                 bookHit = NULL;
7744             }
7745         }
7746         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7747         SendToProgram(buf, cps);
7748         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7749     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7750         SendToProgram("go\n", cps);
7751         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7752     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7753         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7754             SendToProgram("go\n", cps);
7755         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7756     }
7757     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7758 }
7759
7760 char *savedMessage;
7761 ChessProgramState *savedState;
7762 void
7763 DeferredBookMove (void)
7764 {
7765         if(savedState->lastPing != savedState->lastPong)
7766                     ScheduleDelayedEvent(DeferredBookMove, 10);
7767         else
7768         HandleMachineMove(savedMessage, savedState);
7769 }
7770
7771 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7772
7773 void
7774 HandleMachineMove (char *message, ChessProgramState *cps)
7775 {
7776     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7777     char realname[MSG_SIZ];
7778     int fromX, fromY, toX, toY;
7779     ChessMove moveType;
7780     char promoChar;
7781     char *p, *pv=buf1;
7782     int machineWhite;
7783     char *bookHit;
7784
7785     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7786         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7787         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7788             DisplayError(_("Invalid pairing from pairing engine"), 0);
7789             return;
7790         }
7791         pairingReceived = 1;
7792         NextMatchGame();
7793         return; // Skim the pairing messages here.
7794     }
7795
7796     cps->userError = 0;
7797
7798 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7799     /*
7800      * Kludge to ignore BEL characters
7801      */
7802     while (*message == '\007') message++;
7803
7804     /*
7805      * [HGM] engine debug message: ignore lines starting with '#' character
7806      */
7807     if(cps->debug && *message == '#') return;
7808
7809     /*
7810      * Look for book output
7811      */
7812     if (cps == &first && bookRequested) {
7813         if (message[0] == '\t' || message[0] == ' ') {
7814             /* Part of the book output is here; append it */
7815             strcat(bookOutput, message);
7816             strcat(bookOutput, "  \n");
7817             return;
7818         } else if (bookOutput[0] != NULLCHAR) {
7819             /* All of book output has arrived; display it */
7820             char *p = bookOutput;
7821             while (*p != NULLCHAR) {
7822                 if (*p == '\t') *p = ' ';
7823                 p++;
7824             }
7825             DisplayInformation(bookOutput);
7826             bookRequested = FALSE;
7827             /* Fall through to parse the current output */
7828         }
7829     }
7830
7831     /*
7832      * Look for machine move.
7833      */
7834     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7835         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7836     {
7837         /* This method is only useful on engines that support ping */
7838         if (cps->lastPing != cps->lastPong) {
7839           if (gameMode == BeginningOfGame) {
7840             /* Extra move from before last new; ignore */
7841             if (appData.debugMode) {
7842                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7843             }
7844           } else {
7845             if (appData.debugMode) {
7846                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7847                         cps->which, gameMode);
7848             }
7849
7850             SendToProgram("undo\n", cps);
7851           }
7852           return;
7853         }
7854
7855         switch (gameMode) {
7856           case BeginningOfGame:
7857             /* Extra move from before last reset; ignore */
7858             if (appData.debugMode) {
7859                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7860             }
7861             return;
7862
7863           case EndOfGame:
7864           case IcsIdle:
7865           default:
7866             /* Extra move after we tried to stop.  The mode test is
7867                not a reliable way of detecting this problem, but it's
7868                the best we can do on engines that don't support ping.
7869             */
7870             if (appData.debugMode) {
7871                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7872                         cps->which, gameMode);
7873             }
7874             SendToProgram("undo\n", cps);
7875             return;
7876
7877           case MachinePlaysWhite:
7878           case IcsPlayingWhite:
7879             machineWhite = TRUE;
7880             break;
7881
7882           case MachinePlaysBlack:
7883           case IcsPlayingBlack:
7884             machineWhite = FALSE;
7885             break;
7886
7887           case TwoMachinesPlay:
7888             machineWhite = (cps->twoMachinesColor[0] == 'w');
7889             break;
7890         }
7891         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7892             if (appData.debugMode) {
7893                 fprintf(debugFP,
7894                         "Ignoring move out of turn by %s, gameMode %d"
7895                         ", forwardMost %d\n",
7896                         cps->which, gameMode, forwardMostMove);
7897             }
7898             return;
7899         }
7900
7901         if(cps->alphaRank) AlphaRank(machineMove, 4);
7902         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7903                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7904             /* Machine move could not be parsed; ignore it. */
7905           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7906                     machineMove, _(cps->which));
7907             DisplayError(buf1, 0);
7908             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7909                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7910             if (gameMode == TwoMachinesPlay) {
7911               GameEnds(machineWhite ? BlackWins : WhiteWins,
7912                        buf1, GE_XBOARD);
7913             }
7914             return;
7915         }
7916
7917         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7918         /* So we have to redo legality test with true e.p. status here,  */
7919         /* to make sure an illegal e.p. capture does not slip through,   */
7920         /* to cause a forfeit on a justified illegal-move complaint      */
7921         /* of the opponent.                                              */
7922         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7923            ChessMove moveType;
7924            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7925                              fromY, fromX, toY, toX, promoChar);
7926             if(moveType == IllegalMove) {
7927               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7928                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7929                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7930                            buf1, GE_XBOARD);
7931                 return;
7932            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7933            /* [HGM] Kludge to handle engines that send FRC-style castling
7934               when they shouldn't (like TSCP-Gothic) */
7935            switch(moveType) {
7936              case WhiteASideCastleFR:
7937              case BlackASideCastleFR:
7938                toX+=2;
7939                currentMoveString[2]++;
7940                break;
7941              case WhiteHSideCastleFR:
7942              case BlackHSideCastleFR:
7943                toX--;
7944                currentMoveString[2]--;
7945                break;
7946              default: ; // nothing to do, but suppresses warning of pedantic compilers
7947            }
7948         }
7949         hintRequested = FALSE;
7950         lastHint[0] = NULLCHAR;
7951         bookRequested = FALSE;
7952         /* Program may be pondering now */
7953         cps->maybeThinking = TRUE;
7954         if (cps->sendTime == 2) cps->sendTime = 1;
7955         if (cps->offeredDraw) cps->offeredDraw--;
7956
7957         /* [AS] Save move info*/
7958         pvInfoList[ forwardMostMove ].score = programStats.score;
7959         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7960         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7961
7962         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7963
7964         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7965         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7966             int count = 0;
7967
7968             while( count < adjudicateLossPlies ) {
7969                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7970
7971                 if( count & 1 ) {
7972                     score = -score; /* Flip score for winning side */
7973                 }
7974
7975                 if( score > adjudicateLossThreshold ) {
7976                     break;
7977                 }
7978
7979                 count++;
7980             }
7981
7982             if( count >= adjudicateLossPlies ) {
7983                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7984
7985                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7986                     "Xboard adjudication",
7987                     GE_XBOARD );
7988
7989                 return;
7990             }
7991         }
7992
7993         if(Adjudicate(cps)) {
7994             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7995             return; // [HGM] adjudicate: for all automatic game ends
7996         }
7997
7998 #if ZIPPY
7999         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8000             first.initDone) {
8001           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8002                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8003                 SendToICS("draw ");
8004                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8005           }
8006           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8007           ics_user_moved = 1;
8008           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8009                 char buf[3*MSG_SIZ];
8010
8011                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8012                         programStats.score / 100.,
8013                         programStats.depth,
8014                         programStats.time / 100.,
8015                         (unsigned int)programStats.nodes,
8016                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8017                         programStats.movelist);
8018                 SendToICS(buf);
8019 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8020           }
8021         }
8022 #endif
8023
8024         /* [AS] Clear stats for next move */
8025         ClearProgramStats();
8026         thinkOutput[0] = NULLCHAR;
8027         hiddenThinkOutputState = 0;
8028
8029         bookHit = NULL;
8030         if (gameMode == TwoMachinesPlay) {
8031             /* [HGM] relaying draw offers moved to after reception of move */
8032             /* and interpreting offer as claim if it brings draw condition */
8033             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8034                 SendToProgram("draw\n", cps->other);
8035             }
8036             if (cps->other->sendTime) {
8037                 SendTimeRemaining(cps->other,
8038                                   cps->other->twoMachinesColor[0] == 'w');
8039             }
8040             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8041             if (firstMove && !bookHit) {
8042                 firstMove = FALSE;
8043                 if (cps->other->useColors) {
8044                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8045                 }
8046                 SendToProgram("go\n", cps->other);
8047             }
8048             cps->other->maybeThinking = TRUE;
8049         }
8050
8051         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8052
8053         if (!pausing && appData.ringBellAfterMoves) {
8054             RingBell();
8055         }
8056
8057         /*
8058          * Reenable menu items that were disabled while
8059          * machine was thinking
8060          */
8061         if (gameMode != TwoMachinesPlay)
8062             SetUserThinkingEnables();
8063
8064         // [HGM] book: after book hit opponent has received move and is now in force mode
8065         // force the book reply into it, and then fake that it outputted this move by jumping
8066         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8067         if(bookHit) {
8068                 static char bookMove[MSG_SIZ]; // a bit generous?
8069
8070                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8071                 strcat(bookMove, bookHit);
8072                 message = bookMove;
8073                 cps = cps->other;
8074                 programStats.nodes = programStats.depth = programStats.time =
8075                 programStats.score = programStats.got_only_move = 0;
8076                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8077
8078                 if(cps->lastPing != cps->lastPong) {
8079                     savedMessage = message; // args for deferred call
8080                     savedState = cps;
8081                     ScheduleDelayedEvent(DeferredBookMove, 10);
8082                     return;
8083                 }
8084                 goto FakeBookMove;
8085         }
8086
8087         return;
8088     }
8089
8090     /* Set special modes for chess engines.  Later something general
8091      *  could be added here; for now there is just one kludge feature,
8092      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8093      *  when "xboard" is given as an interactive command.
8094      */
8095     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8096         cps->useSigint = FALSE;
8097         cps->useSigterm = FALSE;
8098     }
8099     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8100       ParseFeatures(message+8, cps);
8101       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8102     }
8103
8104     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8105                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8106       int dummy, s=6; char buf[MSG_SIZ];
8107       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8108       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8109       if(startedFromSetupPosition) return;
8110       ParseFEN(boards[0], &dummy, message+s);
8111       DrawPosition(TRUE, boards[0]);
8112       startedFromSetupPosition = TRUE;
8113       return;
8114     }
8115     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8116      * want this, I was asked to put it in, and obliged.
8117      */
8118     if (!strncmp(message, "setboard ", 9)) {
8119         Board initial_position;
8120
8121         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8122
8123         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8124             DisplayError(_("Bad FEN received from engine"), 0);
8125             return ;
8126         } else {
8127            Reset(TRUE, FALSE);
8128            CopyBoard(boards[0], initial_position);
8129            initialRulePlies = FENrulePlies;
8130            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8131            else gameMode = MachinePlaysBlack;
8132            DrawPosition(FALSE, boards[currentMove]);
8133         }
8134         return;
8135     }
8136
8137     /*
8138      * Look for communication commands
8139      */
8140     if (!strncmp(message, "telluser ", 9)) {
8141         if(message[9] == '\\' && message[10] == '\\')
8142             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8143         PlayTellSound();
8144         DisplayNote(message + 9);
8145         return;
8146     }
8147     if (!strncmp(message, "tellusererror ", 14)) {
8148         cps->userError = 1;
8149         if(message[14] == '\\' && message[15] == '\\')
8150             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8151         PlayTellSound();
8152         DisplayError(message + 14, 0);
8153         return;
8154     }
8155     if (!strncmp(message, "tellopponent ", 13)) {
8156       if (appData.icsActive) {
8157         if (loggedOn) {
8158           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8159           SendToICS(buf1);
8160         }
8161       } else {
8162         DisplayNote(message + 13);
8163       }
8164       return;
8165     }
8166     if (!strncmp(message, "tellothers ", 11)) {
8167       if (appData.icsActive) {
8168         if (loggedOn) {
8169           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8170           SendToICS(buf1);
8171         }
8172       }
8173       return;
8174     }
8175     if (!strncmp(message, "tellall ", 8)) {
8176       if (appData.icsActive) {
8177         if (loggedOn) {
8178           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8179           SendToICS(buf1);
8180         }
8181       } else {
8182         DisplayNote(message + 8);
8183       }
8184       return;
8185     }
8186     if (strncmp(message, "warning", 7) == 0) {
8187         /* Undocumented feature, use tellusererror in new code */
8188         DisplayError(message, 0);
8189         return;
8190     }
8191     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8192         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8193         strcat(realname, " query");
8194         AskQuestion(realname, buf2, buf1, cps->pr);
8195         return;
8196     }
8197     /* Commands from the engine directly to ICS.  We don't allow these to be
8198      *  sent until we are logged on. Crafty kibitzes have been known to
8199      *  interfere with the login process.
8200      */
8201     if (loggedOn) {
8202         if (!strncmp(message, "tellics ", 8)) {
8203             SendToICS(message + 8);
8204             SendToICS("\n");
8205             return;
8206         }
8207         if (!strncmp(message, "tellicsnoalias ", 15)) {
8208             SendToICS(ics_prefix);
8209             SendToICS(message + 15);
8210             SendToICS("\n");
8211             return;
8212         }
8213         /* The following are for backward compatibility only */
8214         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8215             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8216             SendToICS(ics_prefix);
8217             SendToICS(message);
8218             SendToICS("\n");
8219             return;
8220         }
8221     }
8222     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8223         return;
8224     }
8225     /*
8226      * If the move is illegal, cancel it and redraw the board.
8227      * Also deal with other error cases.  Matching is rather loose
8228      * here to accommodate engines written before the spec.
8229      */
8230     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8231         strncmp(message, "Error", 5) == 0) {
8232         if (StrStr(message, "name") ||
8233             StrStr(message, "rating") || StrStr(message, "?") ||
8234             StrStr(message, "result") || StrStr(message, "board") ||
8235             StrStr(message, "bk") || StrStr(message, "computer") ||
8236             StrStr(message, "variant") || StrStr(message, "hint") ||
8237             StrStr(message, "random") || StrStr(message, "depth") ||
8238             StrStr(message, "accepted")) {
8239             return;
8240         }
8241         if (StrStr(message, "protover")) {
8242           /* Program is responding to input, so it's apparently done
8243              initializing, and this error message indicates it is
8244              protocol version 1.  So we don't need to wait any longer
8245              for it to initialize and send feature commands. */
8246           FeatureDone(cps, 1);
8247           cps->protocolVersion = 1;
8248           return;
8249         }
8250         cps->maybeThinking = FALSE;
8251
8252         if (StrStr(message, "draw")) {
8253             /* Program doesn't have "draw" command */
8254             cps->sendDrawOffers = 0;
8255             return;
8256         }
8257         if (cps->sendTime != 1 &&
8258             (StrStr(message, "time") || StrStr(message, "otim"))) {
8259           /* Program apparently doesn't have "time" or "otim" command */
8260           cps->sendTime = 0;
8261           return;
8262         }
8263         if (StrStr(message, "analyze")) {
8264             cps->analysisSupport = FALSE;
8265             cps->analyzing = FALSE;
8266 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8267             EditGameEvent(); // [HGM] try to preserve loaded game
8268             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8269             DisplayError(buf2, 0);
8270             return;
8271         }
8272         if (StrStr(message, "(no matching move)st")) {
8273           /* Special kludge for GNU Chess 4 only */
8274           cps->stKludge = TRUE;
8275           SendTimeControl(cps, movesPerSession, timeControl,
8276                           timeIncrement, appData.searchDepth,
8277                           searchTime);
8278           return;
8279         }
8280         if (StrStr(message, "(no matching move)sd")) {
8281           /* Special kludge for GNU Chess 4 only */
8282           cps->sdKludge = TRUE;
8283           SendTimeControl(cps, movesPerSession, timeControl,
8284                           timeIncrement, appData.searchDepth,
8285                           searchTime);
8286           return;
8287         }
8288         if (!StrStr(message, "llegal")) {
8289             return;
8290         }
8291         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8292             gameMode == IcsIdle) return;
8293         if (forwardMostMove <= backwardMostMove) return;
8294         if (pausing) PauseEvent();
8295       if(appData.forceIllegal) {
8296             // [HGM] illegal: machine refused move; force position after move into it
8297           SendToProgram("force\n", cps);
8298           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8299                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8300                 // when black is to move, while there might be nothing on a2 or black
8301                 // might already have the move. So send the board as if white has the move.
8302                 // But first we must change the stm of the engine, as it refused the last move
8303                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8304                 if(WhiteOnMove(forwardMostMove)) {
8305                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8306                     SendBoard(cps, forwardMostMove); // kludgeless board
8307                 } else {
8308                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8309                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8310                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8311                 }
8312           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8313             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8314                  gameMode == TwoMachinesPlay)
8315               SendToProgram("go\n", cps);
8316             return;
8317       } else
8318         if (gameMode == PlayFromGameFile) {
8319             /* Stop reading this game file */
8320             gameMode = EditGame;
8321             ModeHighlight();
8322         }
8323         /* [HGM] illegal-move claim should forfeit game when Xboard */
8324         /* only passes fully legal moves                            */
8325         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8326             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8327                                 "False illegal-move claim", GE_XBOARD );
8328             return; // do not take back move we tested as valid
8329         }
8330         currentMove = forwardMostMove-1;
8331         DisplayMove(currentMove-1); /* before DisplayMoveError */
8332         SwitchClocks(forwardMostMove-1); // [HGM] race
8333         DisplayBothClocks();
8334         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8335                 parseList[currentMove], _(cps->which));
8336         DisplayMoveError(buf1);
8337         DrawPosition(FALSE, boards[currentMove]);
8338
8339         SetUserThinkingEnables();
8340         return;
8341     }
8342     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8343         /* Program has a broken "time" command that
8344            outputs a string not ending in newline.
8345            Don't use it. */
8346         cps->sendTime = 0;
8347     }
8348
8349     /*
8350      * If chess program startup fails, exit with an error message.
8351      * Attempts to recover here are futile.
8352      */
8353     if ((StrStr(message, "unknown host") != NULL)
8354         || (StrStr(message, "No remote directory") != NULL)
8355         || (StrStr(message, "not found") != NULL)
8356         || (StrStr(message, "No such file") != NULL)
8357         || (StrStr(message, "can't alloc") != NULL)
8358         || (StrStr(message, "Permission denied") != NULL)) {
8359
8360         cps->maybeThinking = FALSE;
8361         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8362                 _(cps->which), cps->program, cps->host, message);
8363         RemoveInputSource(cps->isr);
8364         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8365             if(cps == &first) appData.noChessProgram = TRUE;
8366             DisplayError(buf1, 0);
8367         }
8368         return;
8369     }
8370
8371     /*
8372      * Look for hint output
8373      */
8374     if (sscanf(message, "Hint: %s", buf1) == 1) {
8375         if (cps == &first && hintRequested) {
8376             hintRequested = FALSE;
8377             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8378                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8379                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8380                                     PosFlags(forwardMostMove),
8381                                     fromY, fromX, toY, toX, promoChar, buf1);
8382                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8383                 DisplayInformation(buf2);
8384             } else {
8385                 /* Hint move could not be parsed!? */
8386               snprintf(buf2, sizeof(buf2),
8387                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8388                         buf1, _(cps->which));
8389                 DisplayError(buf2, 0);
8390             }
8391         } else {
8392           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8393         }
8394         return;
8395     }
8396
8397     /*
8398      * Ignore other messages if game is not in progress
8399      */
8400     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8401         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8402
8403     /*
8404      * look for win, lose, draw, or draw offer
8405      */
8406     if (strncmp(message, "1-0", 3) == 0) {
8407         char *p, *q, *r = "";
8408         p = strchr(message, '{');
8409         if (p) {
8410             q = strchr(p, '}');
8411             if (q) {
8412                 *q = NULLCHAR;
8413                 r = p + 1;
8414             }
8415         }
8416         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8417         return;
8418     } else if (strncmp(message, "0-1", 3) == 0) {
8419         char *p, *q, *r = "";
8420         p = strchr(message, '{');
8421         if (p) {
8422             q = strchr(p, '}');
8423             if (q) {
8424                 *q = NULLCHAR;
8425                 r = p + 1;
8426             }
8427         }
8428         /* Kludge for Arasan 4.1 bug */
8429         if (strcmp(r, "Black resigns") == 0) {
8430             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8431             return;
8432         }
8433         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8434         return;
8435     } else if (strncmp(message, "1/2", 3) == 0) {
8436         char *p, *q, *r = "";
8437         p = strchr(message, '{');
8438         if (p) {
8439             q = strchr(p, '}');
8440             if (q) {
8441                 *q = NULLCHAR;
8442                 r = p + 1;
8443             }
8444         }
8445
8446         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8447         return;
8448
8449     } else if (strncmp(message, "White resign", 12) == 0) {
8450         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8451         return;
8452     } else if (strncmp(message, "Black resign", 12) == 0) {
8453         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8454         return;
8455     } else if (strncmp(message, "White matches", 13) == 0 ||
8456                strncmp(message, "Black matches", 13) == 0   ) {
8457         /* [HGM] ignore GNUShogi noises */
8458         return;
8459     } else if (strncmp(message, "White", 5) == 0 &&
8460                message[5] != '(' &&
8461                StrStr(message, "Black") == NULL) {
8462         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8463         return;
8464     } else if (strncmp(message, "Black", 5) == 0 &&
8465                message[5] != '(') {
8466         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8467         return;
8468     } else if (strcmp(message, "resign") == 0 ||
8469                strcmp(message, "computer resigns") == 0) {
8470         switch (gameMode) {
8471           case MachinePlaysBlack:
8472           case IcsPlayingBlack:
8473             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8474             break;
8475           case MachinePlaysWhite:
8476           case IcsPlayingWhite:
8477             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8478             break;
8479           case TwoMachinesPlay:
8480             if (cps->twoMachinesColor[0] == 'w')
8481               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8482             else
8483               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8484             break;
8485           default:
8486             /* can't happen */
8487             break;
8488         }
8489         return;
8490     } else if (strncmp(message, "opponent mates", 14) == 0) {
8491         switch (gameMode) {
8492           case MachinePlaysBlack:
8493           case IcsPlayingBlack:
8494             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8495             break;
8496           case MachinePlaysWhite:
8497           case IcsPlayingWhite:
8498             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8499             break;
8500           case TwoMachinesPlay:
8501             if (cps->twoMachinesColor[0] == 'w')
8502               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8503             else
8504               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8505             break;
8506           default:
8507             /* can't happen */
8508             break;
8509         }
8510         return;
8511     } else if (strncmp(message, "computer mates", 14) == 0) {
8512         switch (gameMode) {
8513           case MachinePlaysBlack:
8514           case IcsPlayingBlack:
8515             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8516             break;
8517           case MachinePlaysWhite:
8518           case IcsPlayingWhite:
8519             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8520             break;
8521           case TwoMachinesPlay:
8522             if (cps->twoMachinesColor[0] == 'w')
8523               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8524             else
8525               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8526             break;
8527           default:
8528             /* can't happen */
8529             break;
8530         }
8531         return;
8532     } else if (strncmp(message, "checkmate", 9) == 0) {
8533         if (WhiteOnMove(forwardMostMove)) {
8534             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8535         } else {
8536             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8537         }
8538         return;
8539     } else if (strstr(message, "Draw") != NULL ||
8540                strstr(message, "game is a draw") != NULL) {
8541         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8542         return;
8543     } else if (strstr(message, "offer") != NULL &&
8544                strstr(message, "draw") != NULL) {
8545 #if ZIPPY
8546         if (appData.zippyPlay && first.initDone) {
8547             /* Relay offer to ICS */
8548             SendToICS(ics_prefix);
8549             SendToICS("draw\n");
8550         }
8551 #endif
8552         cps->offeredDraw = 2; /* valid until this engine moves twice */
8553         if (gameMode == TwoMachinesPlay) {
8554             if (cps->other->offeredDraw) {
8555                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8556             /* [HGM] in two-machine mode we delay relaying draw offer      */
8557             /* until after we also have move, to see if it is really claim */
8558             }
8559         } else if (gameMode == MachinePlaysWhite ||
8560                    gameMode == MachinePlaysBlack) {
8561           if (userOfferedDraw) {
8562             DisplayInformation(_("Machine accepts your draw offer"));
8563             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8564           } else {
8565             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8566           }
8567         }
8568     }
8569
8570
8571     /*
8572      * Look for thinking output
8573      */
8574     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8575           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8576                                 ) {
8577         int plylev, mvleft, mvtot, curscore, time;
8578         char mvname[MOVE_LEN];
8579         u64 nodes; // [DM]
8580         char plyext;
8581         int ignore = FALSE;
8582         int prefixHint = FALSE;
8583         mvname[0] = NULLCHAR;
8584
8585         switch (gameMode) {
8586           case MachinePlaysBlack:
8587           case IcsPlayingBlack:
8588             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8589             break;
8590           case MachinePlaysWhite:
8591           case IcsPlayingWhite:
8592             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8593             break;
8594           case AnalyzeMode:
8595           case AnalyzeFile:
8596             break;
8597           case IcsObserving: /* [DM] icsEngineAnalyze */
8598             if (!appData.icsEngineAnalyze) ignore = TRUE;
8599             break;
8600           case TwoMachinesPlay:
8601             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8602                 ignore = TRUE;
8603             }
8604             break;
8605           default:
8606             ignore = TRUE;
8607             break;
8608         }
8609
8610         if (!ignore) {
8611             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8612             buf1[0] = NULLCHAR;
8613             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8614                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8615
8616                 if (plyext != ' ' && plyext != '\t') {
8617                     time *= 100;
8618                 }
8619
8620                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8621                 if( cps->scoreIsAbsolute &&
8622                     ( gameMode == MachinePlaysBlack ||
8623                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8624                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8625                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8626                      !WhiteOnMove(currentMove)
8627                     ) )
8628                 {
8629                     curscore = -curscore;
8630                 }
8631
8632                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8633
8634                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8635                         char buf[MSG_SIZ];
8636                         FILE *f;
8637                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8638                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8639                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8640                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8641                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8642                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8643                                 fclose(f);
8644                         } else DisplayError(_("failed writing PV"), 0);
8645                 }
8646
8647                 tempStats.depth = plylev;
8648                 tempStats.nodes = nodes;
8649                 tempStats.time = time;
8650                 tempStats.score = curscore;
8651                 tempStats.got_only_move = 0;
8652
8653                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8654                         int ticklen;
8655
8656                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8657                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8658                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8659                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8660                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8661                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8662                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8663                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8664                 }
8665
8666                 /* Buffer overflow protection */
8667                 if (pv[0] != NULLCHAR) {
8668                     if (strlen(pv) >= sizeof(tempStats.movelist)
8669                         && appData.debugMode) {
8670                         fprintf(debugFP,
8671                                 "PV is too long; using the first %u bytes.\n",
8672                                 (unsigned) sizeof(tempStats.movelist) - 1);
8673                     }
8674
8675                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8676                 } else {
8677                     sprintf(tempStats.movelist, " no PV\n");
8678                 }
8679
8680                 if (tempStats.seen_stat) {
8681                     tempStats.ok_to_send = 1;
8682                 }
8683
8684                 if (strchr(tempStats.movelist, '(') != NULL) {
8685                     tempStats.line_is_book = 1;
8686                     tempStats.nr_moves = 0;
8687                     tempStats.moves_left = 0;
8688                 } else {
8689                     tempStats.line_is_book = 0;
8690                 }
8691
8692                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8693                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8694
8695                 SendProgramStatsToFrontend( cps, &tempStats );
8696
8697                 /*
8698                     [AS] Protect the thinkOutput buffer from overflow... this
8699                     is only useful if buf1 hasn't overflowed first!
8700                 */
8701                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8702                          plylev,
8703                          (gameMode == TwoMachinesPlay ?
8704                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8705                          ((double) curscore) / 100.0,
8706                          prefixHint ? lastHint : "",
8707                          prefixHint ? " " : "" );
8708
8709                 if( buf1[0] != NULLCHAR ) {
8710                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8711
8712                     if( strlen(pv) > max_len ) {
8713                         if( appData.debugMode) {
8714                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8715                         }
8716                         pv[max_len+1] = '\0';
8717                     }
8718
8719                     strcat( thinkOutput, pv);
8720                 }
8721
8722                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8723                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8724                     DisplayMove(currentMove - 1);
8725                 }
8726                 return;
8727
8728             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8729                 /* crafty (9.25+) says "(only move) <move>"
8730                  * if there is only 1 legal move
8731                  */
8732                 sscanf(p, "(only move) %s", buf1);
8733                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8734                 sprintf(programStats.movelist, "%s (only move)", buf1);
8735                 programStats.depth = 1;
8736                 programStats.nr_moves = 1;
8737                 programStats.moves_left = 1;
8738                 programStats.nodes = 1;
8739                 programStats.time = 1;
8740                 programStats.got_only_move = 1;
8741
8742                 /* Not really, but we also use this member to
8743                    mean "line isn't going to change" (Crafty
8744                    isn't searching, so stats won't change) */
8745                 programStats.line_is_book = 1;
8746
8747                 SendProgramStatsToFrontend( cps, &programStats );
8748
8749                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8750                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8751                     DisplayMove(currentMove - 1);
8752                 }
8753                 return;
8754             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8755                               &time, &nodes, &plylev, &mvleft,
8756                               &mvtot, mvname) >= 5) {
8757                 /* The stat01: line is from Crafty (9.29+) in response
8758                    to the "." command */
8759                 programStats.seen_stat = 1;
8760                 cps->maybeThinking = TRUE;
8761
8762                 if (programStats.got_only_move || !appData.periodicUpdates)
8763                   return;
8764
8765                 programStats.depth = plylev;
8766                 programStats.time = time;
8767                 programStats.nodes = nodes;
8768                 programStats.moves_left = mvleft;
8769                 programStats.nr_moves = mvtot;
8770                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8771                 programStats.ok_to_send = 1;
8772                 programStats.movelist[0] = '\0';
8773
8774                 SendProgramStatsToFrontend( cps, &programStats );
8775
8776                 return;
8777
8778             } else if (strncmp(message,"++",2) == 0) {
8779                 /* Crafty 9.29+ outputs this */
8780                 programStats.got_fail = 2;
8781                 return;
8782
8783             } else if (strncmp(message,"--",2) == 0) {
8784                 /* Crafty 9.29+ outputs this */
8785                 programStats.got_fail = 1;
8786                 return;
8787
8788             } else if (thinkOutput[0] != NULLCHAR &&
8789                        strncmp(message, "    ", 4) == 0) {
8790                 unsigned message_len;
8791
8792                 p = message;
8793                 while (*p && *p == ' ') p++;
8794
8795                 message_len = strlen( p );
8796
8797                 /* [AS] Avoid buffer overflow */
8798                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8799                     strcat(thinkOutput, " ");
8800                     strcat(thinkOutput, p);
8801                 }
8802
8803                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8804                     strcat(programStats.movelist, " ");
8805                     strcat(programStats.movelist, p);
8806                 }
8807
8808                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8809                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8810                     DisplayMove(currentMove - 1);
8811                 }
8812                 return;
8813             }
8814         }
8815         else {
8816             buf1[0] = NULLCHAR;
8817
8818             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8819                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8820             {
8821                 ChessProgramStats cpstats;
8822
8823                 if (plyext != ' ' && plyext != '\t') {
8824                     time *= 100;
8825                 }
8826
8827                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8828                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8829                     curscore = -curscore;
8830                 }
8831
8832                 cpstats.depth = plylev;
8833                 cpstats.nodes = nodes;
8834                 cpstats.time = time;
8835                 cpstats.score = curscore;
8836                 cpstats.got_only_move = 0;
8837                 cpstats.movelist[0] = '\0';
8838
8839                 if (buf1[0] != NULLCHAR) {
8840                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8841                 }
8842
8843                 cpstats.ok_to_send = 0;
8844                 cpstats.line_is_book = 0;
8845                 cpstats.nr_moves = 0;
8846                 cpstats.moves_left = 0;
8847
8848                 SendProgramStatsToFrontend( cps, &cpstats );
8849             }
8850         }
8851     }
8852 }
8853
8854
8855 /* Parse a game score from the character string "game", and
8856    record it as the history of the current game.  The game
8857    score is NOT assumed to start from the standard position.
8858    The display is not updated in any way.
8859    */
8860 void
8861 ParseGameHistory (char *game)
8862 {
8863     ChessMove moveType;
8864     int fromX, fromY, toX, toY, boardIndex;
8865     char promoChar;
8866     char *p, *q;
8867     char buf[MSG_SIZ];
8868
8869     if (appData.debugMode)
8870       fprintf(debugFP, "Parsing game history: %s\n", game);
8871
8872     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8873     gameInfo.site = StrSave(appData.icsHost);
8874     gameInfo.date = PGNDate();
8875     gameInfo.round = StrSave("-");
8876
8877     /* Parse out names of players */
8878     while (*game == ' ') game++;
8879     p = buf;
8880     while (*game != ' ') *p++ = *game++;
8881     *p = NULLCHAR;
8882     gameInfo.white = StrSave(buf);
8883     while (*game == ' ') game++;
8884     p = buf;
8885     while (*game != ' ' && *game != '\n') *p++ = *game++;
8886     *p = NULLCHAR;
8887     gameInfo.black = StrSave(buf);
8888
8889     /* Parse moves */
8890     boardIndex = blackPlaysFirst ? 1 : 0;
8891     yynewstr(game);
8892     for (;;) {
8893         yyboardindex = boardIndex;
8894         moveType = (ChessMove) Myylex();
8895         switch (moveType) {
8896           case IllegalMove:             /* maybe suicide chess, etc. */
8897   if (appData.debugMode) {
8898     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8899     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8900     setbuf(debugFP, NULL);
8901   }
8902           case WhitePromotion:
8903           case BlackPromotion:
8904           case WhiteNonPromotion:
8905           case BlackNonPromotion:
8906           case NormalMove:
8907           case WhiteCapturesEnPassant:
8908           case BlackCapturesEnPassant:
8909           case WhiteKingSideCastle:
8910           case WhiteQueenSideCastle:
8911           case BlackKingSideCastle:
8912           case BlackQueenSideCastle:
8913           case WhiteKingSideCastleWild:
8914           case WhiteQueenSideCastleWild:
8915           case BlackKingSideCastleWild:
8916           case BlackQueenSideCastleWild:
8917           /* PUSH Fabien */
8918           case WhiteHSideCastleFR:
8919           case WhiteASideCastleFR:
8920           case BlackHSideCastleFR:
8921           case BlackASideCastleFR:
8922           /* POP Fabien */
8923             fromX = currentMoveString[0] - AAA;
8924             fromY = currentMoveString[1] - ONE;
8925             toX = currentMoveString[2] - AAA;
8926             toY = currentMoveString[3] - ONE;
8927             promoChar = currentMoveString[4];
8928             break;
8929           case WhiteDrop:
8930           case BlackDrop:
8931             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8932             fromX = moveType == WhiteDrop ?
8933               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8934             (int) CharToPiece(ToLower(currentMoveString[0]));
8935             fromY = DROP_RANK;
8936             toX = currentMoveString[2] - AAA;
8937             toY = currentMoveString[3] - ONE;
8938             promoChar = NULLCHAR;
8939             break;
8940           case AmbiguousMove:
8941             /* bug? */
8942             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8943   if (appData.debugMode) {
8944     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8945     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8946     setbuf(debugFP, NULL);
8947   }
8948             DisplayError(buf, 0);
8949             return;
8950           case ImpossibleMove:
8951             /* bug? */
8952             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8953   if (appData.debugMode) {
8954     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8955     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8956     setbuf(debugFP, NULL);
8957   }
8958             DisplayError(buf, 0);
8959             return;
8960           case EndOfFile:
8961             if (boardIndex < backwardMostMove) {
8962                 /* Oops, gap.  How did that happen? */
8963                 DisplayError(_("Gap in move list"), 0);
8964                 return;
8965             }
8966             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8967             if (boardIndex > forwardMostMove) {
8968                 forwardMostMove = boardIndex;
8969             }
8970             return;
8971           case ElapsedTime:
8972             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8973                 strcat(parseList[boardIndex-1], " ");
8974                 strcat(parseList[boardIndex-1], yy_text);
8975             }
8976             continue;
8977           case Comment:
8978           case PGNTag:
8979           case NAG:
8980           default:
8981             /* ignore */
8982             continue;
8983           case WhiteWins:
8984           case BlackWins:
8985           case GameIsDrawn:
8986           case GameUnfinished:
8987             if (gameMode == IcsExamining) {
8988                 if (boardIndex < backwardMostMove) {
8989                     /* Oops, gap.  How did that happen? */
8990                     return;
8991                 }
8992                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8993                 return;
8994             }
8995             gameInfo.result = moveType;
8996             p = strchr(yy_text, '{');
8997             if (p == NULL) p = strchr(yy_text, '(');
8998             if (p == NULL) {
8999                 p = yy_text;
9000                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9001             } else {
9002                 q = strchr(p, *p == '{' ? '}' : ')');
9003                 if (q != NULL) *q = NULLCHAR;
9004                 p++;
9005             }
9006             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9007             gameInfo.resultDetails = StrSave(p);
9008             continue;
9009         }
9010         if (boardIndex >= forwardMostMove &&
9011             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9012             backwardMostMove = blackPlaysFirst ? 1 : 0;
9013             return;
9014         }
9015         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9016                                  fromY, fromX, toY, toX, promoChar,
9017                                  parseList[boardIndex]);
9018         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9019         /* currentMoveString is set as a side-effect of yylex */
9020         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9021         strcat(moveList[boardIndex], "\n");
9022         boardIndex++;
9023         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9024         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9025           case MT_NONE:
9026           case MT_STALEMATE:
9027           default:
9028             break;
9029           case MT_CHECK:
9030             if(gameInfo.variant != VariantShogi)
9031                 strcat(parseList[boardIndex - 1], "+");
9032             break;
9033           case MT_CHECKMATE:
9034           case MT_STAINMATE:
9035             strcat(parseList[boardIndex - 1], "#");
9036             break;
9037         }
9038     }
9039 }
9040
9041
9042 /* Apply a move to the given board  */
9043 void
9044 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9045 {
9046   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9047   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9048
9049     /* [HGM] compute & store e.p. status and castling rights for new position */
9050     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9051
9052       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9053       oldEP = (signed char)board[EP_STATUS];
9054       board[EP_STATUS] = EP_NONE;
9055
9056   if (fromY == DROP_RANK) {
9057         /* must be first */
9058         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9059             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9060             return;
9061         }
9062         piece = board[toY][toX] = (ChessSquare) fromX;
9063   } else {
9064       int i;
9065
9066       if( board[toY][toX] != EmptySquare )
9067            board[EP_STATUS] = EP_CAPTURE;
9068
9069       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9070            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9071                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9072       } else
9073       if( board[fromY][fromX] == WhitePawn ) {
9074            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9075                board[EP_STATUS] = EP_PAWN_MOVE;
9076            if( toY-fromY==2) {
9077                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9078                         gameInfo.variant != VariantBerolina || toX < fromX)
9079                       board[EP_STATUS] = toX | berolina;
9080                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9081                         gameInfo.variant != VariantBerolina || toX > fromX)
9082                       board[EP_STATUS] = toX;
9083            }
9084       } else
9085       if( board[fromY][fromX] == BlackPawn ) {
9086            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9087                board[EP_STATUS] = EP_PAWN_MOVE;
9088            if( toY-fromY== -2) {
9089                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9090                         gameInfo.variant != VariantBerolina || toX < fromX)
9091                       board[EP_STATUS] = toX | berolina;
9092                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9093                         gameInfo.variant != VariantBerolina || toX > fromX)
9094                       board[EP_STATUS] = toX;
9095            }
9096        }
9097
9098        for(i=0; i<nrCastlingRights; i++) {
9099            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9100               board[CASTLING][i] == toX   && castlingRank[i] == toY
9101              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9102        }
9103
9104      if (fromX == toX && fromY == toY) return;
9105
9106      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9107      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9108      if(gameInfo.variant == VariantKnightmate)
9109          king += (int) WhiteUnicorn - (int) WhiteKing;
9110
9111     /* Code added by Tord: */
9112     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9113     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9114         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9115       board[fromY][fromX] = EmptySquare;
9116       board[toY][toX] = EmptySquare;
9117       if((toX > fromX) != (piece == WhiteRook)) {
9118         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9119       } else {
9120         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9121       }
9122     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9123                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9124       board[fromY][fromX] = EmptySquare;
9125       board[toY][toX] = EmptySquare;
9126       if((toX > fromX) != (piece == BlackRook)) {
9127         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9128       } else {
9129         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9130       }
9131     /* End of code added by Tord */
9132
9133     } else if (board[fromY][fromX] == king
9134         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9135         && toY == fromY && toX > fromX+1) {
9136         board[fromY][fromX] = EmptySquare;
9137         board[toY][toX] = king;
9138         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9139         board[fromY][BOARD_RGHT-1] = EmptySquare;
9140     } else if (board[fromY][fromX] == king
9141         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9142                && toY == fromY && toX < fromX-1) {
9143         board[fromY][fromX] = EmptySquare;
9144         board[toY][toX] = king;
9145         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9146         board[fromY][BOARD_LEFT] = EmptySquare;
9147     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9148                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9149                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9150                ) {
9151         /* white pawn promotion */
9152         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9153         if(gameInfo.variant==VariantBughouse ||
9154            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9155             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9156         board[fromY][fromX] = EmptySquare;
9157     } else if ((fromY >= BOARD_HEIGHT>>1)
9158                && (toX != fromX)
9159                && gameInfo.variant != VariantXiangqi
9160                && gameInfo.variant != VariantBerolina
9161                && (board[fromY][fromX] == WhitePawn)
9162                && (board[toY][toX] == EmptySquare)) {
9163         board[fromY][fromX] = EmptySquare;
9164         board[toY][toX] = WhitePawn;
9165         captured = board[toY - 1][toX];
9166         board[toY - 1][toX] = EmptySquare;
9167     } else if ((fromY == BOARD_HEIGHT-4)
9168                && (toX == fromX)
9169                && gameInfo.variant == VariantBerolina
9170                && (board[fromY][fromX] == WhitePawn)
9171                && (board[toY][toX] == EmptySquare)) {
9172         board[fromY][fromX] = EmptySquare;
9173         board[toY][toX] = WhitePawn;
9174         if(oldEP & EP_BEROLIN_A) {
9175                 captured = board[fromY][fromX-1];
9176                 board[fromY][fromX-1] = EmptySquare;
9177         }else{  captured = board[fromY][fromX+1];
9178                 board[fromY][fromX+1] = EmptySquare;
9179         }
9180     } else if (board[fromY][fromX] == king
9181         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9182                && toY == fromY && toX > fromX+1) {
9183         board[fromY][fromX] = EmptySquare;
9184         board[toY][toX] = king;
9185         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9186         board[fromY][BOARD_RGHT-1] = EmptySquare;
9187     } else if (board[fromY][fromX] == king
9188         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9189                && toY == fromY && toX < fromX-1) {
9190         board[fromY][fromX] = EmptySquare;
9191         board[toY][toX] = king;
9192         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9193         board[fromY][BOARD_LEFT] = EmptySquare;
9194     } else if (fromY == 7 && fromX == 3
9195                && board[fromY][fromX] == BlackKing
9196                && toY == 7 && toX == 5) {
9197         board[fromY][fromX] = EmptySquare;
9198         board[toY][toX] = BlackKing;
9199         board[fromY][7] = EmptySquare;
9200         board[toY][4] = BlackRook;
9201     } else if (fromY == 7 && fromX == 3
9202                && board[fromY][fromX] == BlackKing
9203                && toY == 7 && toX == 1) {
9204         board[fromY][fromX] = EmptySquare;
9205         board[toY][toX] = BlackKing;
9206         board[fromY][0] = EmptySquare;
9207         board[toY][2] = BlackRook;
9208     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9209                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9210                && toY < promoRank && promoChar
9211                ) {
9212         /* black pawn promotion */
9213         board[toY][toX] = CharToPiece(ToLower(promoChar));
9214         if(gameInfo.variant==VariantBughouse ||
9215            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9216             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9217         board[fromY][fromX] = EmptySquare;
9218     } else if ((fromY < BOARD_HEIGHT>>1)
9219                && (toX != fromX)
9220                && gameInfo.variant != VariantXiangqi
9221                && gameInfo.variant != VariantBerolina
9222                && (board[fromY][fromX] == BlackPawn)
9223                && (board[toY][toX] == EmptySquare)) {
9224         board[fromY][fromX] = EmptySquare;
9225         board[toY][toX] = BlackPawn;
9226         captured = board[toY + 1][toX];
9227         board[toY + 1][toX] = EmptySquare;
9228     } else if ((fromY == 3)
9229                && (toX == fromX)
9230                && gameInfo.variant == VariantBerolina
9231                && (board[fromY][fromX] == BlackPawn)
9232                && (board[toY][toX] == EmptySquare)) {
9233         board[fromY][fromX] = EmptySquare;
9234         board[toY][toX] = BlackPawn;
9235         if(oldEP & EP_BEROLIN_A) {
9236                 captured = board[fromY][fromX-1];
9237                 board[fromY][fromX-1] = EmptySquare;
9238         }else{  captured = board[fromY][fromX+1];
9239                 board[fromY][fromX+1] = EmptySquare;
9240         }
9241     } else {
9242         board[toY][toX] = board[fromY][fromX];
9243         board[fromY][fromX] = EmptySquare;
9244     }
9245   }
9246
9247     if (gameInfo.holdingsWidth != 0) {
9248
9249       /* !!A lot more code needs to be written to support holdings  */
9250       /* [HGM] OK, so I have written it. Holdings are stored in the */
9251       /* penultimate board files, so they are automaticlly stored   */
9252       /* in the game history.                                       */
9253       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9254                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9255         /* Delete from holdings, by decreasing count */
9256         /* and erasing image if necessary            */
9257         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9258         if(p < (int) BlackPawn) { /* white drop */
9259              p -= (int)WhitePawn;
9260                  p = PieceToNumber((ChessSquare)p);
9261              if(p >= gameInfo.holdingsSize) p = 0;
9262              if(--board[p][BOARD_WIDTH-2] <= 0)
9263                   board[p][BOARD_WIDTH-1] = EmptySquare;
9264              if((int)board[p][BOARD_WIDTH-2] < 0)
9265                         board[p][BOARD_WIDTH-2] = 0;
9266         } else {                  /* black drop */
9267              p -= (int)BlackPawn;
9268                  p = PieceToNumber((ChessSquare)p);
9269              if(p >= gameInfo.holdingsSize) p = 0;
9270              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9271                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9272              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9273                         board[BOARD_HEIGHT-1-p][1] = 0;
9274         }
9275       }
9276       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9277           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9278         /* [HGM] holdings: Add to holdings, if holdings exist */
9279         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9280                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9281                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9282         }
9283         p = (int) captured;
9284         if (p >= (int) BlackPawn) {
9285           p -= (int)BlackPawn;
9286           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9287                   /* in Shogi restore piece to its original  first */
9288                   captured = (ChessSquare) (DEMOTED captured);
9289                   p = DEMOTED p;
9290           }
9291           p = PieceToNumber((ChessSquare)p);
9292           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9293           board[p][BOARD_WIDTH-2]++;
9294           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9295         } else {
9296           p -= (int)WhitePawn;
9297           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9298                   captured = (ChessSquare) (DEMOTED captured);
9299                   p = DEMOTED p;
9300           }
9301           p = PieceToNumber((ChessSquare)p);
9302           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9303           board[BOARD_HEIGHT-1-p][1]++;
9304           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9305         }
9306       }
9307     } else if (gameInfo.variant == VariantAtomic) {
9308       if (captured != EmptySquare) {
9309         int y, x;
9310         for (y = toY-1; y <= toY+1; y++) {
9311           for (x = toX-1; x <= toX+1; x++) {
9312             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9313                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9314               board[y][x] = EmptySquare;
9315             }
9316           }
9317         }
9318         board[toY][toX] = EmptySquare;
9319       }
9320     }
9321     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9322         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9323     } else
9324     if(promoChar == '+') {
9325         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9326         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9327     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9328         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9329     }
9330     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9331                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9332         // [HGM] superchess: take promotion piece out of holdings
9333         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9334         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9335             if(!--board[k][BOARD_WIDTH-2])
9336                 board[k][BOARD_WIDTH-1] = EmptySquare;
9337         } else {
9338             if(!--board[BOARD_HEIGHT-1-k][1])
9339                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9340         }
9341     }
9342
9343 }
9344
9345 /* Updates forwardMostMove */
9346 void
9347 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9348 {
9349 //    forwardMostMove++; // [HGM] bare: moved downstream
9350
9351     (void) CoordsToAlgebraic(boards[forwardMostMove],
9352                              PosFlags(forwardMostMove),
9353                              fromY, fromX, toY, toX, promoChar,
9354                              parseList[forwardMostMove]);
9355
9356     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9357         int timeLeft; static int lastLoadFlag=0; int king, piece;
9358         piece = boards[forwardMostMove][fromY][fromX];
9359         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9360         if(gameInfo.variant == VariantKnightmate)
9361             king += (int) WhiteUnicorn - (int) WhiteKing;
9362         if(forwardMostMove == 0) {
9363             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9364                 fprintf(serverMoves, "%s;", UserName());
9365             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9366                 fprintf(serverMoves, "%s;", second.tidy);
9367             fprintf(serverMoves, "%s;", first.tidy);
9368             if(gameMode == MachinePlaysWhite)
9369                 fprintf(serverMoves, "%s;", UserName());
9370             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9371                 fprintf(serverMoves, "%s;", second.tidy);
9372         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9373         lastLoadFlag = loadFlag;
9374         // print base move
9375         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9376         // print castling suffix
9377         if( toY == fromY && piece == king ) {
9378             if(toX-fromX > 1)
9379                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9380             if(fromX-toX >1)
9381                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9382         }
9383         // e.p. suffix
9384         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9385              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9386              boards[forwardMostMove][toY][toX] == EmptySquare
9387              && fromX != toX && fromY != toY)
9388                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9389         // promotion suffix
9390         if(promoChar != NULLCHAR)
9391                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9392         if(!loadFlag) {
9393                 char buf[MOVE_LEN*2], *p; int len;
9394             fprintf(serverMoves, "/%d/%d",
9395                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9396             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9397             else                      timeLeft = blackTimeRemaining/1000;
9398             fprintf(serverMoves, "/%d", timeLeft);
9399                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9400                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9401                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9402             fprintf(serverMoves, "/%s", buf);
9403         }
9404         fflush(serverMoves);
9405     }
9406
9407     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9408         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9409       return;
9410     }
9411     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9412     if (commentList[forwardMostMove+1] != NULL) {
9413         free(commentList[forwardMostMove+1]);
9414         commentList[forwardMostMove+1] = NULL;
9415     }
9416     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9417     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9418     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9419     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9420     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9421     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9422     adjustedClock = FALSE;
9423     gameInfo.result = GameUnfinished;
9424     if (gameInfo.resultDetails != NULL) {
9425         free(gameInfo.resultDetails);
9426         gameInfo.resultDetails = NULL;
9427     }
9428     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9429                               moveList[forwardMostMove - 1]);
9430     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9431       case MT_NONE:
9432       case MT_STALEMATE:
9433       default:
9434         break;
9435       case MT_CHECK:
9436         if(gameInfo.variant != VariantShogi)
9437             strcat(parseList[forwardMostMove - 1], "+");
9438         break;
9439       case MT_CHECKMATE:
9440       case MT_STAINMATE:
9441         strcat(parseList[forwardMostMove - 1], "#");
9442         break;
9443     }
9444
9445 }
9446
9447 /* Updates currentMove if not pausing */
9448 void
9449 ShowMove (int fromX, int fromY, int toX, int toY)
9450 {
9451     int instant = (gameMode == PlayFromGameFile) ?
9452         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9453     if(appData.noGUI) return;
9454     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9455         if (!instant) {
9456             if (forwardMostMove == currentMove + 1) {
9457                 AnimateMove(boards[forwardMostMove - 1],
9458                             fromX, fromY, toX, toY);
9459             }
9460             if (appData.highlightLastMove) {
9461                 SetHighlights(fromX, fromY, toX, toY);
9462             }
9463         }
9464         currentMove = forwardMostMove;
9465     }
9466
9467     if (instant) return;
9468
9469     DisplayMove(currentMove - 1);
9470     DrawPosition(FALSE, boards[currentMove]);
9471     DisplayBothClocks();
9472     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9473 }
9474
9475 void
9476 SendEgtPath (ChessProgramState *cps)
9477 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9478         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9479
9480         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9481
9482         while(*p) {
9483             char c, *q = name+1, *r, *s;
9484
9485             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9486             while(*p && *p != ',') *q++ = *p++;
9487             *q++ = ':'; *q = 0;
9488             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9489                 strcmp(name, ",nalimov:") == 0 ) {
9490                 // take nalimov path from the menu-changeable option first, if it is defined
9491               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9492                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9493             } else
9494             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9495                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9496                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9497                 s = r = StrStr(s, ":") + 1; // beginning of path info
9498                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9499                 c = *r; *r = 0;             // temporarily null-terminate path info
9500                     *--q = 0;               // strip of trailig ':' from name
9501                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9502                 *r = c;
9503                 SendToProgram(buf,cps);     // send egtbpath command for this format
9504             }
9505             if(*p == ',') p++; // read away comma to position for next format name
9506         }
9507 }
9508
9509 void
9510 InitChessProgram (ChessProgramState *cps, int setup)
9511 /* setup needed to setup FRC opening position */
9512 {
9513     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9514     if (appData.noChessProgram) return;
9515     hintRequested = FALSE;
9516     bookRequested = FALSE;
9517
9518     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9519     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9520     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9521     if(cps->memSize) { /* [HGM] memory */
9522       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9523         SendToProgram(buf, cps);
9524     }
9525     SendEgtPath(cps); /* [HGM] EGT */
9526     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9527       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9528         SendToProgram(buf, cps);
9529     }
9530
9531     SendToProgram(cps->initString, cps);
9532     if (gameInfo.variant != VariantNormal &&
9533         gameInfo.variant != VariantLoadable
9534         /* [HGM] also send variant if board size non-standard */
9535         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9536                                             ) {
9537       char *v = VariantName(gameInfo.variant);
9538       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9539         /* [HGM] in protocol 1 we have to assume all variants valid */
9540         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9541         DisplayFatalError(buf, 0, 1);
9542         return;
9543       }
9544
9545       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9546       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9547       if( gameInfo.variant == VariantXiangqi )
9548            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9549       if( gameInfo.variant == VariantShogi )
9550            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9551       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9552            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9553       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9554           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9555            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9556       if( gameInfo.variant == VariantCourier )
9557            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9558       if( gameInfo.variant == VariantSuper )
9559            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9560       if( gameInfo.variant == VariantGreat )
9561            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9562       if( gameInfo.variant == VariantSChess )
9563            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9564       if( gameInfo.variant == VariantGrand )
9565            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9566
9567       if(overruled) {
9568         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9569                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9570            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9571            if(StrStr(cps->variants, b) == NULL) {
9572                // specific sized variant not known, check if general sizing allowed
9573                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9574                    if(StrStr(cps->variants, "boardsize") == NULL) {
9575                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9576                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9577                        DisplayFatalError(buf, 0, 1);
9578                        return;
9579                    }
9580                    /* [HGM] here we really should compare with the maximum supported board size */
9581                }
9582            }
9583       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9584       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9585       SendToProgram(buf, cps);
9586     }
9587     currentlyInitializedVariant = gameInfo.variant;
9588
9589     /* [HGM] send opening position in FRC to first engine */
9590     if(setup) {
9591           SendToProgram("force\n", cps);
9592           SendBoard(cps, 0);
9593           /* engine is now in force mode! Set flag to wake it up after first move. */
9594           setboardSpoiledMachineBlack = 1;
9595     }
9596
9597     if (cps->sendICS) {
9598       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9599       SendToProgram(buf, cps);
9600     }
9601     cps->maybeThinking = FALSE;
9602     cps->offeredDraw = 0;
9603     if (!appData.icsActive) {
9604         SendTimeControl(cps, movesPerSession, timeControl,
9605                         timeIncrement, appData.searchDepth,
9606                         searchTime);
9607     }
9608     if (appData.showThinking
9609         // [HGM] thinking: four options require thinking output to be sent
9610         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9611                                 ) {
9612         SendToProgram("post\n", cps);
9613     }
9614     SendToProgram("hard\n", cps);
9615     if (!appData.ponderNextMove) {
9616         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9617            it without being sure what state we are in first.  "hard"
9618            is not a toggle, so that one is OK.
9619          */
9620         SendToProgram("easy\n", cps);
9621     }
9622     if (cps->usePing) {
9623       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9624       SendToProgram(buf, cps);
9625     }
9626     cps->initDone = TRUE;
9627     ClearEngineOutputPane(cps == &second);
9628 }
9629
9630
9631 void
9632 StartChessProgram (ChessProgramState *cps)
9633 {
9634     char buf[MSG_SIZ];
9635     int err;
9636
9637     if (appData.noChessProgram) return;
9638     cps->initDone = FALSE;
9639
9640     if (strcmp(cps->host, "localhost") == 0) {
9641         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9642     } else if (*appData.remoteShell == NULLCHAR) {
9643         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9644     } else {
9645         if (*appData.remoteUser == NULLCHAR) {
9646           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9647                     cps->program);
9648         } else {
9649           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9650                     cps->host, appData.remoteUser, cps->program);
9651         }
9652         err = StartChildProcess(buf, "", &cps->pr);
9653     }
9654
9655     if (err != 0) {
9656       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9657         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9658         if(cps != &first) return;
9659         appData.noChessProgram = TRUE;
9660         ThawUI();
9661         SetNCPMode();
9662 //      DisplayFatalError(buf, err, 1);
9663 //      cps->pr = NoProc;
9664 //      cps->isr = NULL;
9665         return;
9666     }
9667
9668     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9669     if (cps->protocolVersion > 1) {
9670       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9671       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9672       cps->comboCnt = 0;  //                and values of combo boxes
9673       SendToProgram(buf, cps);
9674     } else {
9675       SendToProgram("xboard\n", cps);
9676     }
9677 }
9678
9679 void
9680 TwoMachinesEventIfReady P((void))
9681 {
9682   static int curMess = 0;
9683   if (first.lastPing != first.lastPong) {
9684     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9685     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9686     return;
9687   }
9688   if (second.lastPing != second.lastPong) {
9689     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9690     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9691     return;
9692   }
9693   DisplayMessage("", ""); curMess = 0;
9694   ThawUI();
9695   TwoMachinesEvent();
9696 }
9697
9698 char *
9699 MakeName (char *template)
9700 {
9701     time_t clock;
9702     struct tm *tm;
9703     static char buf[MSG_SIZ];
9704     char *p = buf;
9705     int i;
9706
9707     clock = time((time_t *)NULL);
9708     tm = localtime(&clock);
9709
9710     while(*p++ = *template++) if(p[-1] == '%') {
9711         switch(*template++) {
9712           case 0:   *p = 0; return buf;
9713           case 'Y': i = tm->tm_year+1900; break;
9714           case 'y': i = tm->tm_year-100; break;
9715           case 'M': i = tm->tm_mon+1; break;
9716           case 'd': i = tm->tm_mday; break;
9717           case 'h': i = tm->tm_hour; break;
9718           case 'm': i = tm->tm_min; break;
9719           case 's': i = tm->tm_sec; break;
9720           default:  i = 0;
9721         }
9722         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9723     }
9724     return buf;
9725 }
9726
9727 int
9728 CountPlayers (char *p)
9729 {
9730     int n = 0;
9731     while(p = strchr(p, '\n')) p++, n++; // count participants
9732     return n;
9733 }
9734
9735 FILE *
9736 WriteTourneyFile (char *results, FILE *f)
9737 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9738     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9739     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9740         // create a file with tournament description
9741         fprintf(f, "-participants {%s}\n", appData.participants);
9742         fprintf(f, "-seedBase %d\n", appData.seedBase);
9743         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9744         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9745         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9746         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9747         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9748         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9749         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9750         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9751         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9752         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9753         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9754         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9755         if(searchTime > 0)
9756                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9757         else {
9758                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9759                 fprintf(f, "-tc %s\n", appData.timeControl);
9760                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9761         }
9762         fprintf(f, "-results \"%s\"\n", results);
9763     }
9764     return f;
9765 }
9766
9767 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9768
9769 void
9770 Substitute (char *participants, int expunge)
9771 {
9772     int i, changed, changes=0, nPlayers=0;
9773     char *p, *q, *r, buf[MSG_SIZ];
9774     if(participants == NULL) return;
9775     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9776     r = p = participants; q = appData.participants;
9777     while(*p && *p == *q) {
9778         if(*p == '\n') r = p+1, nPlayers++;
9779         p++; q++;
9780     }
9781     if(*p) { // difference
9782         while(*p && *p++ != '\n');
9783         while(*q && *q++ != '\n');
9784       changed = nPlayers;
9785         changes = 1 + (strcmp(p, q) != 0);
9786     }
9787     if(changes == 1) { // a single engine mnemonic was changed
9788         q = r; while(*q) nPlayers += (*q++ == '\n');
9789         p = buf; while(*r && (*p = *r++) != '\n') p++;
9790         *p = NULLCHAR;
9791         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9792         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9793         if(mnemonic[i]) { // The substitute is valid
9794             FILE *f;
9795             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9796                 flock(fileno(f), LOCK_EX);
9797                 ParseArgsFromFile(f);
9798                 fseek(f, 0, SEEK_SET);
9799                 FREE(appData.participants); appData.participants = participants;
9800                 if(expunge) { // erase results of replaced engine
9801                     int len = strlen(appData.results), w, b, dummy;
9802                     for(i=0; i<len; i++) {
9803                         Pairing(i, nPlayers, &w, &b, &dummy);
9804                         if((w == changed || b == changed) && appData.results[i] == '*') {
9805                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9806                             fclose(f);
9807                             return;
9808                         }
9809                     }
9810                     for(i=0; i<len; i++) {
9811                         Pairing(i, nPlayers, &w, &b, &dummy);
9812                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9813                     }
9814                 }
9815                 WriteTourneyFile(appData.results, f);
9816                 fclose(f); // release lock
9817                 return;
9818             }
9819         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9820     }
9821     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9822     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9823     free(participants);
9824     return;
9825 }
9826
9827 int
9828 CreateTourney (char *name)
9829 {
9830         FILE *f;
9831         if(matchMode && strcmp(name, appData.tourneyFile)) {
9832              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9833         }
9834         if(name[0] == NULLCHAR) {
9835             if(appData.participants[0])
9836                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9837             return 0;
9838         }
9839         f = fopen(name, "r");
9840         if(f) { // file exists
9841             ASSIGN(appData.tourneyFile, name);
9842             ParseArgsFromFile(f); // parse it
9843         } else {
9844             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9845             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9846                 DisplayError(_("Not enough participants"), 0);
9847                 return 0;
9848             }
9849             ASSIGN(appData.tourneyFile, name);
9850             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9851             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9852         }
9853         fclose(f);
9854         appData.noChessProgram = FALSE;
9855         appData.clockMode = TRUE;
9856         SetGNUMode();
9857         return 1;
9858 }
9859
9860 int
9861 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9862 {
9863     char buf[MSG_SIZ], *p, *q;
9864     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9865     skip = !all && group[0]; // if group requested, we start in skip mode
9866     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9867         p = names; q = buf; header = 0;
9868         while(*p && *p != '\n') *q++ = *p++;
9869         *q = 0;
9870         if(*p == '\n') p++;
9871         if(buf[0] == '#') {
9872             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9873             depth++; // we must be entering a new group
9874             if(all) continue; // suppress printing group headers when complete list requested
9875             header = 1;
9876             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9877         }
9878         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9879         if(engineList[i]) free(engineList[i]);
9880         engineList[i] = strdup(buf);
9881         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9882         if(engineMnemonic[i]) free(engineMnemonic[i]);
9883         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9884             strcat(buf, " (");
9885             sscanf(q + 8, "%s", buf + strlen(buf));
9886             strcat(buf, ")");
9887         }
9888         engineMnemonic[i] = strdup(buf);
9889         i++;
9890     }
9891     engineList[i] = engineMnemonic[i] = NULL;
9892     return i;
9893 }
9894
9895 // following implemented as macro to avoid type limitations
9896 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9897
9898 void
9899 SwapEngines (int n)
9900 {   // swap settings for first engine and other engine (so far only some selected options)
9901     int h;
9902     char *p;
9903     if(n == 0) return;
9904     SWAP(directory, p)
9905     SWAP(chessProgram, p)
9906     SWAP(isUCI, h)
9907     SWAP(hasOwnBookUCI, h)
9908     SWAP(protocolVersion, h)
9909     SWAP(reuse, h)
9910     SWAP(scoreIsAbsolute, h)
9911     SWAP(timeOdds, h)
9912     SWAP(logo, p)
9913     SWAP(pgnName, p)
9914     SWAP(pvSAN, h)
9915     SWAP(engOptions, p)
9916 }
9917
9918 int
9919 SetPlayer (int player, char *p)
9920 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9921     int i;
9922     char buf[MSG_SIZ], *engineName;
9923     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9924     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9925     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9926     if(mnemonic[i]) {
9927         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9928         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9929         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9930         ParseArgsFromString(buf);
9931     }
9932     free(engineName);
9933     return i;
9934 }
9935
9936 char *recentEngines;
9937
9938 void
9939 RecentEngineEvent (int nr)
9940 {
9941     int n;
9942 //    SwapEngines(1); // bump first to second
9943 //    ReplaceEngine(&second, 1); // and load it there
9944     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9945     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9946     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9947         ReplaceEngine(&first, 0);
9948         FloatToFront(&appData.recentEngineList, command[n]);
9949     }
9950 }
9951
9952 int
9953 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9954 {   // determine players from game number
9955     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9956
9957     if(appData.tourneyType == 0) {
9958         roundsPerCycle = (nPlayers - 1) | 1;
9959         pairingsPerRound = nPlayers / 2;
9960     } else if(appData.tourneyType > 0) {
9961         roundsPerCycle = nPlayers - appData.tourneyType;
9962         pairingsPerRound = appData.tourneyType;
9963     }
9964     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9965     gamesPerCycle = gamesPerRound * roundsPerCycle;
9966     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9967     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9968     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9969     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9970     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9971     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9972
9973     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9974     if(appData.roundSync) *syncInterval = gamesPerRound;
9975
9976     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9977
9978     if(appData.tourneyType == 0) {
9979         if(curPairing == (nPlayers-1)/2 ) {
9980             *whitePlayer = curRound;
9981             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9982         } else {
9983             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9984             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9985             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9986             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9987         }
9988     } else if(appData.tourneyType > 1) {
9989         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
9990         *whitePlayer = curRound + appData.tourneyType;
9991     } else if(appData.tourneyType > 0) {
9992         *whitePlayer = curPairing;
9993         *blackPlayer = curRound + appData.tourneyType;
9994     }
9995
9996     // take care of white/black alternation per round. 
9997     // For cycles and games this is already taken care of by default, derived from matchGame!
9998     return curRound & 1;
9999 }
10000
10001 int
10002 NextTourneyGame (int nr, int *swapColors)
10003 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10004     char *p, *q;
10005     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10006     FILE *tf;
10007     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10008     tf = fopen(appData.tourneyFile, "r");
10009     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10010     ParseArgsFromFile(tf); fclose(tf);
10011     InitTimeControls(); // TC might be altered from tourney file
10012
10013     nPlayers = CountPlayers(appData.participants); // count participants
10014     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10015     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10016
10017     if(syncInterval) {
10018         p = q = appData.results;
10019         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10020         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10021             DisplayMessage(_("Waiting for other game(s)"),"");
10022             waitingForGame = TRUE;
10023             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10024             return 0;
10025         }
10026         waitingForGame = FALSE;
10027     }
10028
10029     if(appData.tourneyType < 0) {
10030         if(nr>=0 && !pairingReceived) {
10031             char buf[1<<16];
10032             if(pairing.pr == NoProc) {
10033                 if(!appData.pairingEngine[0]) {
10034                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10035                     return 0;
10036                 }
10037                 StartChessProgram(&pairing); // starts the pairing engine
10038             }
10039             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10040             SendToProgram(buf, &pairing);
10041             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10042             SendToProgram(buf, &pairing);
10043             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10044         }
10045         pairingReceived = 0;                              // ... so we continue here 
10046         *swapColors = 0;
10047         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10048         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10049         matchGame = 1; roundNr = nr / syncInterval + 1;
10050     }
10051
10052     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10053
10054     // redefine engines, engine dir, etc.
10055     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10056     if(first.pr == NoProc) {
10057       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10058       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10059     }
10060     if(second.pr == NoProc) {
10061       SwapEngines(1);
10062       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10063       SwapEngines(1);         // and make that valid for second engine by swapping
10064       InitEngine(&second, 1);
10065     }
10066     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10067     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10068     return 1;
10069 }
10070
10071 void
10072 NextMatchGame ()
10073 {   // performs game initialization that does not invoke engines, and then tries to start the game
10074     int res, firstWhite, swapColors = 0;
10075     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10076     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
10077         char buf[MSG_SIZ];
10078         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10079         if(strcmp(buf, currentDebugFile)) { // name has changed
10080             FILE *f = fopen(buf, "w");
10081             if(f) { // if opening the new file failed, just keep using the old one
10082                 ASSIGN(currentDebugFile, buf);
10083                 fclose(debugFP);
10084                 debugFP = f;
10085             }
10086         }
10087     }
10088     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10089     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10090     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10091     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10092     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10093     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10094     Reset(FALSE, first.pr != NoProc);
10095     res = LoadGameOrPosition(matchGame); // setup game
10096     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10097     if(!res) return; // abort when bad game/pos file
10098     TwoMachinesEvent();
10099 }
10100
10101 void
10102 UserAdjudicationEvent (int result)
10103 {
10104     ChessMove gameResult = GameIsDrawn;
10105
10106     if( result > 0 ) {
10107         gameResult = WhiteWins;
10108     }
10109     else if( result < 0 ) {
10110         gameResult = BlackWins;
10111     }
10112
10113     if( gameMode == TwoMachinesPlay ) {
10114         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10115     }
10116 }
10117
10118
10119 // [HGM] save: calculate checksum of game to make games easily identifiable
10120 int
10121 StringCheckSum (char *s)
10122 {
10123         int i = 0;
10124         if(s==NULL) return 0;
10125         while(*s) i = i*259 + *s++;
10126         return i;
10127 }
10128
10129 int
10130 GameCheckSum ()
10131 {
10132         int i, sum=0;
10133         for(i=backwardMostMove; i<forwardMostMove; i++) {
10134                 sum += pvInfoList[i].depth;
10135                 sum += StringCheckSum(parseList[i]);
10136                 sum += StringCheckSum(commentList[i]);
10137                 sum *= 261;
10138         }
10139         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10140         return sum + StringCheckSum(commentList[i]);
10141 } // end of save patch
10142
10143 void
10144 GameEnds (ChessMove result, char *resultDetails, int whosays)
10145 {
10146     GameMode nextGameMode;
10147     int isIcsGame;
10148     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10149
10150     if(endingGame) return; /* [HGM] crash: forbid recursion */
10151     endingGame = 1;
10152     if(twoBoards) { // [HGM] dual: switch back to one board
10153         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10154         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10155     }
10156     if (appData.debugMode) {
10157       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10158               result, resultDetails ? resultDetails : "(null)", whosays);
10159     }
10160
10161     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10162
10163     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10164         /* If we are playing on ICS, the server decides when the
10165            game is over, but the engine can offer to draw, claim
10166            a draw, or resign.
10167          */
10168 #if ZIPPY
10169         if (appData.zippyPlay && first.initDone) {
10170             if (result == GameIsDrawn) {
10171                 /* In case draw still needs to be claimed */
10172                 SendToICS(ics_prefix);
10173                 SendToICS("draw\n");
10174             } else if (StrCaseStr(resultDetails, "resign")) {
10175                 SendToICS(ics_prefix);
10176                 SendToICS("resign\n");
10177             }
10178         }
10179 #endif
10180         endingGame = 0; /* [HGM] crash */
10181         return;
10182     }
10183
10184     /* If we're loading the game from a file, stop */
10185     if (whosays == GE_FILE) {
10186       (void) StopLoadGameTimer();
10187       gameFileFP = NULL;
10188     }
10189
10190     /* Cancel draw offers */
10191     first.offeredDraw = second.offeredDraw = 0;
10192
10193     /* If this is an ICS game, only ICS can really say it's done;
10194        if not, anyone can. */
10195     isIcsGame = (gameMode == IcsPlayingWhite ||
10196                  gameMode == IcsPlayingBlack ||
10197                  gameMode == IcsObserving    ||
10198                  gameMode == IcsExamining);
10199
10200     if (!isIcsGame || whosays == GE_ICS) {
10201         /* OK -- not an ICS game, or ICS said it was done */
10202         StopClocks();
10203         if (!isIcsGame && !appData.noChessProgram)
10204           SetUserThinkingEnables();
10205
10206         /* [HGM] if a machine claims the game end we verify this claim */
10207         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10208             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10209                 char claimer;
10210                 ChessMove trueResult = (ChessMove) -1;
10211
10212                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10213                                             first.twoMachinesColor[0] :
10214                                             second.twoMachinesColor[0] ;
10215
10216                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10217                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10218                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10219                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10220                 } else
10221                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10222                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10223                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10224                 } else
10225                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10226                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10227                 }
10228
10229                 // now verify win claims, but not in drop games, as we don't understand those yet
10230                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10231                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10232                     (result == WhiteWins && claimer == 'w' ||
10233                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10234                       if (appData.debugMode) {
10235                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10236                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10237                       }
10238                       if(result != trueResult) {
10239                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10240                               result = claimer == 'w' ? BlackWins : WhiteWins;
10241                               resultDetails = buf;
10242                       }
10243                 } else
10244                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10245                     && (forwardMostMove <= backwardMostMove ||
10246                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10247                         (claimer=='b')==(forwardMostMove&1))
10248                                                                                   ) {
10249                       /* [HGM] verify: draws that were not flagged are false claims */
10250                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10251                       result = claimer == 'w' ? BlackWins : WhiteWins;
10252                       resultDetails = buf;
10253                 }
10254                 /* (Claiming a loss is accepted no questions asked!) */
10255             }
10256             /* [HGM] bare: don't allow bare King to win */
10257             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10258                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10259                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10260                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10261                && result != GameIsDrawn)
10262             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10263                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10264                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10265                         if(p >= 0 && p <= (int)WhiteKing) k++;
10266                 }
10267                 if (appData.debugMode) {
10268                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10269                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10270                 }
10271                 if(k <= 1) {
10272                         result = GameIsDrawn;
10273                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10274                         resultDetails = buf;
10275                 }
10276             }
10277         }
10278
10279
10280         if(serverMoves != NULL && !loadFlag) { char c = '=';
10281             if(result==WhiteWins) c = '+';
10282             if(result==BlackWins) c = '-';
10283             if(resultDetails != NULL)
10284                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10285         }
10286         if (resultDetails != NULL) {
10287             gameInfo.result = result;
10288             gameInfo.resultDetails = StrSave(resultDetails);
10289
10290             /* display last move only if game was not loaded from file */
10291             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10292                 DisplayMove(currentMove - 1);
10293
10294             if (forwardMostMove != 0) {
10295                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10296                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10297                                                                 ) {
10298                     if (*appData.saveGameFile != NULLCHAR) {
10299                         SaveGameToFile(appData.saveGameFile, TRUE);
10300                     } else if (appData.autoSaveGames) {
10301                         AutoSaveGame();
10302                     }
10303                     if (*appData.savePositionFile != NULLCHAR) {
10304                         SavePositionToFile(appData.savePositionFile);
10305                     }
10306                 }
10307             }
10308
10309             /* Tell program how game ended in case it is learning */
10310             /* [HGM] Moved this to after saving the PGN, just in case */
10311             /* engine died and we got here through time loss. In that */
10312             /* case we will get a fatal error writing the pipe, which */
10313             /* would otherwise lose us the PGN.                       */
10314             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10315             /* output during GameEnds should never be fatal anymore   */
10316             if (gameMode == MachinePlaysWhite ||
10317                 gameMode == MachinePlaysBlack ||
10318                 gameMode == TwoMachinesPlay ||
10319                 gameMode == IcsPlayingWhite ||
10320                 gameMode == IcsPlayingBlack ||
10321                 gameMode == BeginningOfGame) {
10322                 char buf[MSG_SIZ];
10323                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10324                         resultDetails);
10325                 if (first.pr != NoProc) {
10326                     SendToProgram(buf, &first);
10327                 }
10328                 if (second.pr != NoProc &&
10329                     gameMode == TwoMachinesPlay) {
10330                     SendToProgram(buf, &second);
10331                 }
10332             }
10333         }
10334
10335         if (appData.icsActive) {
10336             if (appData.quietPlay &&
10337                 (gameMode == IcsPlayingWhite ||
10338                  gameMode == IcsPlayingBlack)) {
10339                 SendToICS(ics_prefix);
10340                 SendToICS("set shout 1\n");
10341             }
10342             nextGameMode = IcsIdle;
10343             ics_user_moved = FALSE;
10344             /* clean up premove.  It's ugly when the game has ended and the
10345              * premove highlights are still on the board.
10346              */
10347             if (gotPremove) {
10348               gotPremove = FALSE;
10349               ClearPremoveHighlights();
10350               DrawPosition(FALSE, boards[currentMove]);
10351             }
10352             if (whosays == GE_ICS) {
10353                 switch (result) {
10354                 case WhiteWins:
10355                     if (gameMode == IcsPlayingWhite)
10356                         PlayIcsWinSound();
10357                     else if(gameMode == IcsPlayingBlack)
10358                         PlayIcsLossSound();
10359                     break;
10360                 case BlackWins:
10361                     if (gameMode == IcsPlayingBlack)
10362                         PlayIcsWinSound();
10363                     else if(gameMode == IcsPlayingWhite)
10364                         PlayIcsLossSound();
10365                     break;
10366                 case GameIsDrawn:
10367                     PlayIcsDrawSound();
10368                     break;
10369                 default:
10370                     PlayIcsUnfinishedSound();
10371                 }
10372             }
10373         } else if (gameMode == EditGame ||
10374                    gameMode == PlayFromGameFile ||
10375                    gameMode == AnalyzeMode ||
10376                    gameMode == AnalyzeFile) {
10377             nextGameMode = gameMode;
10378         } else {
10379             nextGameMode = EndOfGame;
10380         }
10381         pausing = FALSE;
10382         ModeHighlight();
10383     } else {
10384         nextGameMode = gameMode;
10385     }
10386
10387     if (appData.noChessProgram) {
10388         gameMode = nextGameMode;
10389         ModeHighlight();
10390         endingGame = 0; /* [HGM] crash */
10391         return;
10392     }
10393
10394     if (first.reuse) {
10395         /* Put first chess program into idle state */
10396         if (first.pr != NoProc &&
10397             (gameMode == MachinePlaysWhite ||
10398              gameMode == MachinePlaysBlack ||
10399              gameMode == TwoMachinesPlay ||
10400              gameMode == IcsPlayingWhite ||
10401              gameMode == IcsPlayingBlack ||
10402              gameMode == BeginningOfGame)) {
10403             SendToProgram("force\n", &first);
10404             if (first.usePing) {
10405               char buf[MSG_SIZ];
10406               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10407               SendToProgram(buf, &first);
10408             }
10409         }
10410     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10411         /* Kill off first chess program */
10412         if (first.isr != NULL)
10413           RemoveInputSource(first.isr);
10414         first.isr = NULL;
10415
10416         if (first.pr != NoProc) {
10417             ExitAnalyzeMode();
10418             DoSleep( appData.delayBeforeQuit );
10419             SendToProgram("quit\n", &first);
10420             DoSleep( appData.delayAfterQuit );
10421             DestroyChildProcess(first.pr, first.useSigterm);
10422         }
10423         first.pr = NoProc;
10424     }
10425     if (second.reuse) {
10426         /* Put second chess program into idle state */
10427         if (second.pr != NoProc &&
10428             gameMode == TwoMachinesPlay) {
10429             SendToProgram("force\n", &second);
10430             if (second.usePing) {
10431               char buf[MSG_SIZ];
10432               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10433               SendToProgram(buf, &second);
10434             }
10435         }
10436     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10437         /* Kill off second chess program */
10438         if (second.isr != NULL)
10439           RemoveInputSource(second.isr);
10440         second.isr = NULL;
10441
10442         if (second.pr != NoProc) {
10443             DoSleep( appData.delayBeforeQuit );
10444             SendToProgram("quit\n", &second);
10445             DoSleep( appData.delayAfterQuit );
10446             DestroyChildProcess(second.pr, second.useSigterm);
10447         }
10448         second.pr = NoProc;
10449     }
10450
10451     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10452         char resChar = '=';
10453         switch (result) {
10454         case WhiteWins:
10455           resChar = '+';
10456           if (first.twoMachinesColor[0] == 'w') {
10457             first.matchWins++;
10458           } else {
10459             second.matchWins++;
10460           }
10461           break;
10462         case BlackWins:
10463           resChar = '-';
10464           if (first.twoMachinesColor[0] == 'b') {
10465             first.matchWins++;
10466           } else {
10467             second.matchWins++;
10468           }
10469           break;
10470         case GameUnfinished:
10471           resChar = ' ';
10472         default:
10473           break;
10474         }
10475
10476         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10477         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10478             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10479             ReserveGame(nextGame, resChar); // sets nextGame
10480             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10481             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10482         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10483
10484         if (nextGame <= appData.matchGames && !abortMatch) {
10485             gameMode = nextGameMode;
10486             matchGame = nextGame; // this will be overruled in tourney mode!
10487             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10488             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10489             endingGame = 0; /* [HGM] crash */
10490             return;
10491         } else {
10492             gameMode = nextGameMode;
10493             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10494                      first.tidy, second.tidy,
10495                      first.matchWins, second.matchWins,
10496                      appData.matchGames - (first.matchWins + second.matchWins));
10497             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10498             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10499             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10500             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10501                 first.twoMachinesColor = "black\n";
10502                 second.twoMachinesColor = "white\n";
10503             } else {
10504                 first.twoMachinesColor = "white\n";
10505                 second.twoMachinesColor = "black\n";
10506             }
10507         }
10508     }
10509     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10510         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10511       ExitAnalyzeMode();
10512     gameMode = nextGameMode;
10513     ModeHighlight();
10514     endingGame = 0;  /* [HGM] crash */
10515     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10516         if(matchMode == TRUE) { // match through command line: exit with or without popup
10517             if(ranking) {
10518                 ToNrEvent(forwardMostMove);
10519                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10520                 else ExitEvent(0);
10521             } else DisplayFatalError(buf, 0, 0);
10522         } else { // match through menu; just stop, with or without popup
10523             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10524             ModeHighlight();
10525             if(ranking){
10526                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10527             } else DisplayNote(buf);
10528       }
10529       if(ranking) free(ranking);
10530     }
10531 }
10532
10533 /* Assumes program was just initialized (initString sent).
10534    Leaves program in force mode. */
10535 void
10536 FeedMovesToProgram (ChessProgramState *cps, int upto)
10537 {
10538     int i;
10539
10540     if (appData.debugMode)
10541       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10542               startedFromSetupPosition ? "position and " : "",
10543               backwardMostMove, upto, cps->which);
10544     if(currentlyInitializedVariant != gameInfo.variant) {
10545       char buf[MSG_SIZ];
10546         // [HGM] variantswitch: make engine aware of new variant
10547         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10548                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10549         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10550         SendToProgram(buf, cps);
10551         currentlyInitializedVariant = gameInfo.variant;
10552     }
10553     SendToProgram("force\n", cps);
10554     if (startedFromSetupPosition) {
10555         SendBoard(cps, backwardMostMove);
10556     if (appData.debugMode) {
10557         fprintf(debugFP, "feedMoves\n");
10558     }
10559     }
10560     for (i = backwardMostMove; i < upto; i++) {
10561         SendMoveToProgram(i, cps);
10562     }
10563 }
10564
10565
10566 int
10567 ResurrectChessProgram ()
10568 {
10569      /* The chess program may have exited.
10570         If so, restart it and feed it all the moves made so far. */
10571     static int doInit = 0;
10572
10573     if (appData.noChessProgram) return 1;
10574
10575     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10576         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10577         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10578         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10579     } else {
10580         if (first.pr != NoProc) return 1;
10581         StartChessProgram(&first);
10582     }
10583     InitChessProgram(&first, FALSE);
10584     FeedMovesToProgram(&first, currentMove);
10585
10586     if (!first.sendTime) {
10587         /* can't tell gnuchess what its clock should read,
10588            so we bow to its notion. */
10589         ResetClocks();
10590         timeRemaining[0][currentMove] = whiteTimeRemaining;
10591         timeRemaining[1][currentMove] = blackTimeRemaining;
10592     }
10593
10594     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10595                 appData.icsEngineAnalyze) && first.analysisSupport) {
10596       SendToProgram("analyze\n", &first);
10597       first.analyzing = TRUE;
10598     }
10599     return 1;
10600 }
10601
10602 /*
10603  * Button procedures
10604  */
10605 void
10606 Reset (int redraw, int init)
10607 {
10608     int i;
10609
10610     if (appData.debugMode) {
10611         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10612                 redraw, init, gameMode);
10613     }
10614     CleanupTail(); // [HGM] vari: delete any stored variations
10615     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10616     pausing = pauseExamInvalid = FALSE;
10617     startedFromSetupPosition = blackPlaysFirst = FALSE;
10618     firstMove = TRUE;
10619     whiteFlag = blackFlag = FALSE;
10620     userOfferedDraw = FALSE;
10621     hintRequested = bookRequested = FALSE;
10622     first.maybeThinking = FALSE;
10623     second.maybeThinking = FALSE;
10624     first.bookSuspend = FALSE; // [HGM] book
10625     second.bookSuspend = FALSE;
10626     thinkOutput[0] = NULLCHAR;
10627     lastHint[0] = NULLCHAR;
10628     ClearGameInfo(&gameInfo);
10629     gameInfo.variant = StringToVariant(appData.variant);
10630     ics_user_moved = ics_clock_paused = FALSE;
10631     ics_getting_history = H_FALSE;
10632     ics_gamenum = -1;
10633     white_holding[0] = black_holding[0] = NULLCHAR;
10634     ClearProgramStats();
10635     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10636
10637     ResetFrontEnd();
10638     ClearHighlights();
10639     flipView = appData.flipView;
10640     ClearPremoveHighlights();
10641     gotPremove = FALSE;
10642     alarmSounded = FALSE;
10643
10644     GameEnds(EndOfFile, NULL, GE_PLAYER);
10645     if(appData.serverMovesName != NULL) {
10646         /* [HGM] prepare to make moves file for broadcasting */
10647         clock_t t = clock();
10648         if(serverMoves != NULL) fclose(serverMoves);
10649         serverMoves = fopen(appData.serverMovesName, "r");
10650         if(serverMoves != NULL) {
10651             fclose(serverMoves);
10652             /* delay 15 sec before overwriting, so all clients can see end */
10653             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10654         }
10655         serverMoves = fopen(appData.serverMovesName, "w");
10656     }
10657
10658     ExitAnalyzeMode();
10659     gameMode = BeginningOfGame;
10660     ModeHighlight();
10661     if(appData.icsActive) gameInfo.variant = VariantNormal;
10662     currentMove = forwardMostMove = backwardMostMove = 0;
10663     MarkTargetSquares(1);
10664     InitPosition(redraw);
10665     for (i = 0; i < MAX_MOVES; i++) {
10666         if (commentList[i] != NULL) {
10667             free(commentList[i]);
10668             commentList[i] = NULL;
10669         }
10670     }
10671     ResetClocks();
10672     timeRemaining[0][0] = whiteTimeRemaining;
10673     timeRemaining[1][0] = blackTimeRemaining;
10674
10675     if (first.pr == NoProc) {
10676         StartChessProgram(&first);
10677     }
10678     if (init) {
10679             InitChessProgram(&first, startedFromSetupPosition);
10680     }
10681     DisplayTitle("");
10682     DisplayMessage("", "");
10683     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10684     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10685 }
10686
10687 void
10688 AutoPlayGameLoop ()
10689 {
10690     for (;;) {
10691         if (!AutoPlayOneMove())
10692           return;
10693         if (matchMode || appData.timeDelay == 0)
10694           continue;
10695         if (appData.timeDelay < 0)
10696           return;
10697         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10698         break;
10699     }
10700 }
10701
10702
10703 int
10704 AutoPlayOneMove ()
10705 {
10706     int fromX, fromY, toX, toY;
10707
10708     if (appData.debugMode) {
10709       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10710     }
10711
10712     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10713       return FALSE;
10714
10715     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10716       pvInfoList[currentMove].depth = programStats.depth;
10717       pvInfoList[currentMove].score = programStats.score;
10718       pvInfoList[currentMove].time  = 0;
10719       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10720     }
10721
10722     if (currentMove >= forwardMostMove) {
10723       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10724 //      gameMode = EndOfGame;
10725 //      ModeHighlight();
10726
10727       /* [AS] Clear current move marker at the end of a game */
10728       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10729
10730       return FALSE;
10731     }
10732
10733     toX = moveList[currentMove][2] - AAA;
10734     toY = moveList[currentMove][3] - ONE;
10735
10736     if (moveList[currentMove][1] == '@') {
10737         if (appData.highlightLastMove) {
10738             SetHighlights(-1, -1, toX, toY);
10739         }
10740     } else {
10741         fromX = moveList[currentMove][0] - AAA;
10742         fromY = moveList[currentMove][1] - ONE;
10743
10744         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10745
10746         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10747
10748         if (appData.highlightLastMove) {
10749             SetHighlights(fromX, fromY, toX, toY);
10750         }
10751     }
10752     DisplayMove(currentMove);
10753     SendMoveToProgram(currentMove++, &first);
10754     DisplayBothClocks();
10755     DrawPosition(FALSE, boards[currentMove]);
10756     // [HGM] PV info: always display, routine tests if empty
10757     DisplayComment(currentMove - 1, commentList[currentMove]);
10758     return TRUE;
10759 }
10760
10761
10762 int
10763 LoadGameOneMove (ChessMove readAhead)
10764 {
10765     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10766     char promoChar = NULLCHAR;
10767     ChessMove moveType;
10768     char move[MSG_SIZ];
10769     char *p, *q;
10770
10771     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10772         gameMode != AnalyzeMode && gameMode != Training) {
10773         gameFileFP = NULL;
10774         return FALSE;
10775     }
10776
10777     yyboardindex = forwardMostMove;
10778     if (readAhead != EndOfFile) {
10779       moveType = readAhead;
10780     } else {
10781       if (gameFileFP == NULL)
10782           return FALSE;
10783       moveType = (ChessMove) Myylex();
10784     }
10785
10786     done = FALSE;
10787     switch (moveType) {
10788       case Comment:
10789         if (appData.debugMode)
10790           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10791         p = yy_text;
10792
10793         /* append the comment but don't display it */
10794         AppendComment(currentMove, p, FALSE);
10795         return TRUE;
10796
10797       case WhiteCapturesEnPassant:
10798       case BlackCapturesEnPassant:
10799       case WhitePromotion:
10800       case BlackPromotion:
10801       case WhiteNonPromotion:
10802       case BlackNonPromotion:
10803       case NormalMove:
10804       case WhiteKingSideCastle:
10805       case WhiteQueenSideCastle:
10806       case BlackKingSideCastle:
10807       case BlackQueenSideCastle:
10808       case WhiteKingSideCastleWild:
10809       case WhiteQueenSideCastleWild:
10810       case BlackKingSideCastleWild:
10811       case BlackQueenSideCastleWild:
10812       /* PUSH Fabien */
10813       case WhiteHSideCastleFR:
10814       case WhiteASideCastleFR:
10815       case BlackHSideCastleFR:
10816       case BlackASideCastleFR:
10817       /* POP Fabien */
10818         if (appData.debugMode)
10819           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10820         fromX = currentMoveString[0] - AAA;
10821         fromY = currentMoveString[1] - ONE;
10822         toX = currentMoveString[2] - AAA;
10823         toY = currentMoveString[3] - ONE;
10824         promoChar = currentMoveString[4];
10825         break;
10826
10827       case WhiteDrop:
10828       case BlackDrop:
10829         if (appData.debugMode)
10830           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10831         fromX = moveType == WhiteDrop ?
10832           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10833         (int) CharToPiece(ToLower(currentMoveString[0]));
10834         fromY = DROP_RANK;
10835         toX = currentMoveString[2] - AAA;
10836         toY = currentMoveString[3] - ONE;
10837         break;
10838
10839       case WhiteWins:
10840       case BlackWins:
10841       case GameIsDrawn:
10842       case GameUnfinished:
10843         if (appData.debugMode)
10844           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10845         p = strchr(yy_text, '{');
10846         if (p == NULL) p = strchr(yy_text, '(');
10847         if (p == NULL) {
10848             p = yy_text;
10849             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10850         } else {
10851             q = strchr(p, *p == '{' ? '}' : ')');
10852             if (q != NULL) *q = NULLCHAR;
10853             p++;
10854         }
10855         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10856         GameEnds(moveType, p, GE_FILE);
10857         done = TRUE;
10858         if (cmailMsgLoaded) {
10859             ClearHighlights();
10860             flipView = WhiteOnMove(currentMove);
10861             if (moveType == GameUnfinished) flipView = !flipView;
10862             if (appData.debugMode)
10863               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10864         }
10865         break;
10866
10867       case EndOfFile:
10868         if (appData.debugMode)
10869           fprintf(debugFP, "Parser hit end of file\n");
10870         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10871           case MT_NONE:
10872           case MT_CHECK:
10873             break;
10874           case MT_CHECKMATE:
10875           case MT_STAINMATE:
10876             if (WhiteOnMove(currentMove)) {
10877                 GameEnds(BlackWins, "Black mates", GE_FILE);
10878             } else {
10879                 GameEnds(WhiteWins, "White mates", GE_FILE);
10880             }
10881             break;
10882           case MT_STALEMATE:
10883             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10884             break;
10885         }
10886         done = TRUE;
10887         break;
10888
10889       case MoveNumberOne:
10890         if (lastLoadGameStart == GNUChessGame) {
10891             /* GNUChessGames have numbers, but they aren't move numbers */
10892             if (appData.debugMode)
10893               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10894                       yy_text, (int) moveType);
10895             return LoadGameOneMove(EndOfFile); /* tail recursion */
10896         }
10897         /* else fall thru */
10898
10899       case XBoardGame:
10900       case GNUChessGame:
10901       case PGNTag:
10902         /* Reached start of next game in file */
10903         if (appData.debugMode)
10904           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10905         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10906           case MT_NONE:
10907           case MT_CHECK:
10908             break;
10909           case MT_CHECKMATE:
10910           case MT_STAINMATE:
10911             if (WhiteOnMove(currentMove)) {
10912                 GameEnds(BlackWins, "Black mates", GE_FILE);
10913             } else {
10914                 GameEnds(WhiteWins, "White mates", GE_FILE);
10915             }
10916             break;
10917           case MT_STALEMATE:
10918             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10919             break;
10920         }
10921         done = TRUE;
10922         break;
10923
10924       case PositionDiagram:     /* should not happen; ignore */
10925       case ElapsedTime:         /* ignore */
10926       case NAG:                 /* ignore */
10927         if (appData.debugMode)
10928           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10929                   yy_text, (int) moveType);
10930         return LoadGameOneMove(EndOfFile); /* tail recursion */
10931
10932       case IllegalMove:
10933         if (appData.testLegality) {
10934             if (appData.debugMode)
10935               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10936             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10937                     (forwardMostMove / 2) + 1,
10938                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10939             DisplayError(move, 0);
10940             done = TRUE;
10941         } else {
10942             if (appData.debugMode)
10943               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10944                       yy_text, currentMoveString);
10945             fromX = currentMoveString[0] - AAA;
10946             fromY = currentMoveString[1] - ONE;
10947             toX = currentMoveString[2] - AAA;
10948             toY = currentMoveString[3] - ONE;
10949             promoChar = currentMoveString[4];
10950         }
10951         break;
10952
10953       case AmbiguousMove:
10954         if (appData.debugMode)
10955           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10956         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10957                 (forwardMostMove / 2) + 1,
10958                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10959         DisplayError(move, 0);
10960         done = TRUE;
10961         break;
10962
10963       default:
10964       case ImpossibleMove:
10965         if (appData.debugMode)
10966           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10967         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10968                 (forwardMostMove / 2) + 1,
10969                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10970         DisplayError(move, 0);
10971         done = TRUE;
10972         break;
10973     }
10974
10975     if (done) {
10976         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10977             DrawPosition(FALSE, boards[currentMove]);
10978             DisplayBothClocks();
10979             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10980               DisplayComment(currentMove - 1, commentList[currentMove]);
10981         }
10982         (void) StopLoadGameTimer();
10983         gameFileFP = NULL;
10984         cmailOldMove = forwardMostMove;
10985         return FALSE;
10986     } else {
10987         /* currentMoveString is set as a side-effect of yylex */
10988
10989         thinkOutput[0] = NULLCHAR;
10990         MakeMove(fromX, fromY, toX, toY, promoChar);
10991         currentMove = forwardMostMove;
10992         return TRUE;
10993     }
10994 }
10995
10996 /* Load the nth game from the given file */
10997 int
10998 LoadGameFromFile (char *filename, int n, char *title, int useList)
10999 {
11000     FILE *f;
11001     char buf[MSG_SIZ];
11002
11003     if (strcmp(filename, "-") == 0) {
11004         f = stdin;
11005         title = "stdin";
11006     } else {
11007         f = fopen(filename, "rb");
11008         if (f == NULL) {
11009           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11010             DisplayError(buf, errno);
11011             return FALSE;
11012         }
11013     }
11014     if (fseek(f, 0, 0) == -1) {
11015         /* f is not seekable; probably a pipe */
11016         useList = FALSE;
11017     }
11018     if (useList && n == 0) {
11019         int error = GameListBuild(f);
11020         if (error) {
11021             DisplayError(_("Cannot build game list"), error);
11022         } else if (!ListEmpty(&gameList) &&
11023                    ((ListGame *) gameList.tailPred)->number > 1) {
11024             GameListPopUp(f, title);
11025             return TRUE;
11026         }
11027         GameListDestroy();
11028         n = 1;
11029     }
11030     if (n == 0) n = 1;
11031     return LoadGame(f, n, title, FALSE);
11032 }
11033
11034
11035 void
11036 MakeRegisteredMove ()
11037 {
11038     int fromX, fromY, toX, toY;
11039     char promoChar;
11040     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11041         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11042           case CMAIL_MOVE:
11043           case CMAIL_DRAW:
11044             if (appData.debugMode)
11045               fprintf(debugFP, "Restoring %s for game %d\n",
11046                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11047
11048             thinkOutput[0] = NULLCHAR;
11049             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11050             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11051             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11052             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11053             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11054             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11055             MakeMove(fromX, fromY, toX, toY, promoChar);
11056             ShowMove(fromX, fromY, toX, toY);
11057
11058             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11059               case MT_NONE:
11060               case MT_CHECK:
11061                 break;
11062
11063               case MT_CHECKMATE:
11064               case MT_STAINMATE:
11065                 if (WhiteOnMove(currentMove)) {
11066                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11067                 } else {
11068                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11069                 }
11070                 break;
11071
11072               case MT_STALEMATE:
11073                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11074                 break;
11075             }
11076
11077             break;
11078
11079           case CMAIL_RESIGN:
11080             if (WhiteOnMove(currentMove)) {
11081                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11082             } else {
11083                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11084             }
11085             break;
11086
11087           case CMAIL_ACCEPT:
11088             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11089             break;
11090
11091           default:
11092             break;
11093         }
11094     }
11095
11096     return;
11097 }
11098
11099 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11100 int
11101 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11102 {
11103     int retVal;
11104
11105     if (gameNumber > nCmailGames) {
11106         DisplayError(_("No more games in this message"), 0);
11107         return FALSE;
11108     }
11109     if (f == lastLoadGameFP) {
11110         int offset = gameNumber - lastLoadGameNumber;
11111         if (offset == 0) {
11112             cmailMsg[0] = NULLCHAR;
11113             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11114                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11115                 nCmailMovesRegistered--;
11116             }
11117             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11118             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11119                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11120             }
11121         } else {
11122             if (! RegisterMove()) return FALSE;
11123         }
11124     }
11125
11126     retVal = LoadGame(f, gameNumber, title, useList);
11127
11128     /* Make move registered during previous look at this game, if any */
11129     MakeRegisteredMove();
11130
11131     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11132         commentList[currentMove]
11133           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11134         DisplayComment(currentMove - 1, commentList[currentMove]);
11135     }
11136
11137     return retVal;
11138 }
11139
11140 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11141 int
11142 ReloadGame (int offset)
11143 {
11144     int gameNumber = lastLoadGameNumber + offset;
11145     if (lastLoadGameFP == NULL) {
11146         DisplayError(_("No game has been loaded yet"), 0);
11147         return FALSE;
11148     }
11149     if (gameNumber <= 0) {
11150         DisplayError(_("Can't back up any further"), 0);
11151         return FALSE;
11152     }
11153     if (cmailMsgLoaded) {
11154         return CmailLoadGame(lastLoadGameFP, gameNumber,
11155                              lastLoadGameTitle, lastLoadGameUseList);
11156     } else {
11157         return LoadGame(lastLoadGameFP, gameNumber,
11158                         lastLoadGameTitle, lastLoadGameUseList);
11159     }
11160 }
11161
11162 int keys[EmptySquare+1];
11163
11164 int
11165 PositionMatches (Board b1, Board b2)
11166 {
11167     int r, f, sum=0;
11168     switch(appData.searchMode) {
11169         case 1: return CompareWithRights(b1, b2);
11170         case 2:
11171             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11172                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11173             }
11174             return TRUE;
11175         case 3:
11176             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11177               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11178                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11179             }
11180             return sum==0;
11181         case 4:
11182             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11183                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11184             }
11185             return sum==0;
11186     }
11187     return TRUE;
11188 }
11189
11190 #define Q_PROMO  4
11191 #define Q_EP     3
11192 #define Q_BCASTL 2
11193 #define Q_WCASTL 1
11194
11195 int pieceList[256], quickBoard[256];
11196 ChessSquare pieceType[256] = { EmptySquare };
11197 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11198 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11199 int soughtTotal, turn;
11200 Boolean epOK, flipSearch;
11201
11202 typedef struct {
11203     unsigned char piece, to;
11204 } Move;
11205
11206 #define DSIZE (250000)
11207
11208 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11209 Move *moveDatabase = initialSpace;
11210 unsigned int movePtr, dataSize = DSIZE;
11211
11212 int
11213 MakePieceList (Board board, int *counts)
11214 {
11215     int r, f, n=Q_PROMO, total=0;
11216     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11217     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11218         int sq = f + (r<<4);
11219         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11220             quickBoard[sq] = ++n;
11221             pieceList[n] = sq;
11222             pieceType[n] = board[r][f];
11223             counts[board[r][f]]++;
11224             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11225             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11226             total++;
11227         }
11228     }
11229     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11230     return total;
11231 }
11232
11233 void
11234 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11235 {
11236     int sq = fromX + (fromY<<4);
11237     int piece = quickBoard[sq];
11238     quickBoard[sq] = 0;
11239     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11240     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11241         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11242         moveDatabase[movePtr++].piece = Q_WCASTL;
11243         quickBoard[sq] = piece;
11244         piece = quickBoard[from]; quickBoard[from] = 0;
11245         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11246     } else
11247     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11248         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11249         moveDatabase[movePtr++].piece = Q_BCASTL;
11250         quickBoard[sq] = piece;
11251         piece = quickBoard[from]; quickBoard[from] = 0;
11252         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11253     } else
11254     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11255         quickBoard[(fromY<<4)+toX] = 0;
11256         moveDatabase[movePtr].piece = Q_EP;
11257         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11258         moveDatabase[movePtr].to = sq;
11259     } else
11260     if(promoPiece != pieceType[piece]) {
11261         moveDatabase[movePtr++].piece = Q_PROMO;
11262         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11263     }
11264     moveDatabase[movePtr].piece = piece;
11265     quickBoard[sq] = piece;
11266     movePtr++;
11267 }
11268
11269 int
11270 PackGame (Board board)
11271 {
11272     Move *newSpace = NULL;
11273     moveDatabase[movePtr].piece = 0; // terminate previous game
11274     if(movePtr > dataSize) {
11275         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11276         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11277         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11278         if(newSpace) {
11279             int i;
11280             Move *p = moveDatabase, *q = newSpace;
11281             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11282             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11283             moveDatabase = newSpace;
11284         } else { // calloc failed, we must be out of memory. Too bad...
11285             dataSize = 0; // prevent calloc events for all subsequent games
11286             return 0;     // and signal this one isn't cached
11287         }
11288     }
11289     movePtr++;
11290     MakePieceList(board, counts);
11291     return movePtr;
11292 }
11293
11294 int
11295 QuickCompare (Board board, int *minCounts, int *maxCounts)
11296 {   // compare according to search mode
11297     int r, f;
11298     switch(appData.searchMode)
11299     {
11300       case 1: // exact position match
11301         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11302         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11303             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11304         }
11305         break;
11306       case 2: // can have extra material on empty squares
11307         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11308             if(board[r][f] == EmptySquare) continue;
11309             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11310         }
11311         break;
11312       case 3: // material with exact Pawn structure
11313         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11314             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11315             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11316         } // fall through to material comparison
11317       case 4: // exact material
11318         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11319         break;
11320       case 6: // material range with given imbalance
11321         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11322         // fall through to range comparison
11323       case 5: // material range
11324         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11325     }
11326     return TRUE;
11327 }
11328
11329 int
11330 QuickScan (Board board, Move *move)
11331 {   // reconstruct game,and compare all positions in it
11332     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11333     do {
11334         int piece = move->piece;
11335         int to = move->to, from = pieceList[piece];
11336         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11337           if(!piece) return -1;
11338           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11339             piece = (++move)->piece;
11340             from = pieceList[piece];
11341             counts[pieceType[piece]]--;
11342             pieceType[piece] = (ChessSquare) move->to;
11343             counts[move->to]++;
11344           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11345             counts[pieceType[quickBoard[to]]]--;
11346             quickBoard[to] = 0; total--;
11347             move++;
11348             continue;
11349           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11350             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11351             from  = pieceList[piece]; // so this must be King
11352             quickBoard[from] = 0;
11353             quickBoard[to] = piece;
11354             pieceList[piece] = to;
11355             move++;
11356             continue;
11357           }
11358         }
11359         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11360         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11361         quickBoard[from] = 0;
11362         quickBoard[to] = piece;
11363         pieceList[piece] = to;
11364         cnt++; turn ^= 3;
11365         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11366            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11367            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11368                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11369           ) {
11370             static int lastCounts[EmptySquare+1];
11371             int i;
11372             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11373             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11374         } else stretch = 0;
11375         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11376         move++;
11377     } while(1);
11378 }
11379
11380 void
11381 InitSearch ()
11382 {
11383     int r, f;
11384     flipSearch = FALSE;
11385     CopyBoard(soughtBoard, boards[currentMove]);
11386     soughtTotal = MakePieceList(soughtBoard, maxSought);
11387     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11388     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11389     CopyBoard(reverseBoard, boards[currentMove]);
11390     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11391         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11392         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11393         reverseBoard[r][f] = piece;
11394     }
11395     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11396     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11397     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11398                  || (boards[currentMove][CASTLING][2] == NoRights || 
11399                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11400                  && (boards[currentMove][CASTLING][5] == NoRights || 
11401                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11402       ) {
11403         flipSearch = TRUE;
11404         CopyBoard(flipBoard, soughtBoard);
11405         CopyBoard(rotateBoard, reverseBoard);
11406         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11407             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11408             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11409         }
11410     }
11411     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11412     if(appData.searchMode >= 5) {
11413         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11414         MakePieceList(soughtBoard, minSought);
11415         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11416     }
11417     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11418         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11419 }
11420
11421 GameInfo dummyInfo;
11422
11423 int
11424 GameContainsPosition (FILE *f, ListGame *lg)
11425 {
11426     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11427     int fromX, fromY, toX, toY;
11428     char promoChar;
11429     static int initDone=FALSE;
11430
11431     // weed out games based on numerical tag comparison
11432     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11433     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11434     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11435     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11436     if(!initDone) {
11437         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11438         initDone = TRUE;
11439     }
11440     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11441     else CopyBoard(boards[scratch], initialPosition); // default start position
11442     if(lg->moves) {
11443         turn = btm + 1;
11444         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11445         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11446     }
11447     if(btm) plyNr++;
11448     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11449     fseek(f, lg->offset, 0);
11450     yynewfile(f);
11451     while(1) {
11452         yyboardindex = scratch;
11453         quickFlag = plyNr+1;
11454         next = Myylex();
11455         quickFlag = 0;
11456         switch(next) {
11457             case PGNTag:
11458                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11459             default:
11460                 continue;
11461
11462             case XBoardGame:
11463             case GNUChessGame:
11464                 if(plyNr) return -1; // after we have seen moves, this is for new game
11465               continue;
11466
11467             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11468             case ImpossibleMove:
11469             case WhiteWins: // game ends here with these four
11470             case BlackWins:
11471             case GameIsDrawn:
11472             case GameUnfinished:
11473                 return -1;
11474
11475             case IllegalMove:
11476                 if(appData.testLegality) return -1;
11477             case WhiteCapturesEnPassant:
11478             case BlackCapturesEnPassant:
11479             case WhitePromotion:
11480             case BlackPromotion:
11481             case WhiteNonPromotion:
11482             case BlackNonPromotion:
11483             case NormalMove:
11484             case WhiteKingSideCastle:
11485             case WhiteQueenSideCastle:
11486             case BlackKingSideCastle:
11487             case BlackQueenSideCastle:
11488             case WhiteKingSideCastleWild:
11489             case WhiteQueenSideCastleWild:
11490             case BlackKingSideCastleWild:
11491             case BlackQueenSideCastleWild:
11492             case WhiteHSideCastleFR:
11493             case WhiteASideCastleFR:
11494             case BlackHSideCastleFR:
11495             case BlackASideCastleFR:
11496                 fromX = currentMoveString[0] - AAA;
11497                 fromY = currentMoveString[1] - ONE;
11498                 toX = currentMoveString[2] - AAA;
11499                 toY = currentMoveString[3] - ONE;
11500                 promoChar = currentMoveString[4];
11501                 break;
11502             case WhiteDrop:
11503             case BlackDrop:
11504                 fromX = next == WhiteDrop ?
11505                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11506                   (int) CharToPiece(ToLower(currentMoveString[0]));
11507                 fromY = DROP_RANK;
11508                 toX = currentMoveString[2] - AAA;
11509                 toY = currentMoveString[3] - ONE;
11510                 promoChar = 0;
11511                 break;
11512         }
11513         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11514         plyNr++;
11515         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11516         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11517         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11518         if(appData.findMirror) {
11519             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11520             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11521         }
11522     }
11523 }
11524
11525 /* Load the nth game from open file f */
11526 int
11527 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11528 {
11529     ChessMove cm;
11530     char buf[MSG_SIZ];
11531     int gn = gameNumber;
11532     ListGame *lg = NULL;
11533     int numPGNTags = 0;
11534     int err, pos = -1;
11535     GameMode oldGameMode;
11536     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11537
11538     if (appData.debugMode)
11539         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11540
11541     if (gameMode == Training )
11542         SetTrainingModeOff();
11543
11544     oldGameMode = gameMode;
11545     if (gameMode != BeginningOfGame) {
11546       Reset(FALSE, TRUE);
11547     }
11548
11549     gameFileFP = f;
11550     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11551         fclose(lastLoadGameFP);
11552     }
11553
11554     if (useList) {
11555         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11556
11557         if (lg) {
11558             fseek(f, lg->offset, 0);
11559             GameListHighlight(gameNumber);
11560             pos = lg->position;
11561             gn = 1;
11562         }
11563         else {
11564             DisplayError(_("Game number out of range"), 0);
11565             return FALSE;
11566         }
11567     } else {
11568         GameListDestroy();
11569         if (fseek(f, 0, 0) == -1) {
11570             if (f == lastLoadGameFP ?
11571                 gameNumber == lastLoadGameNumber + 1 :
11572                 gameNumber == 1) {
11573                 gn = 1;
11574             } else {
11575                 DisplayError(_("Can't seek on game file"), 0);
11576                 return FALSE;
11577             }
11578         }
11579     }
11580     lastLoadGameFP = f;
11581     lastLoadGameNumber = gameNumber;
11582     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11583     lastLoadGameUseList = useList;
11584
11585     yynewfile(f);
11586
11587     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11588       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11589                 lg->gameInfo.black);
11590             DisplayTitle(buf);
11591     } else if (*title != NULLCHAR) {
11592         if (gameNumber > 1) {
11593           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11594             DisplayTitle(buf);
11595         } else {
11596             DisplayTitle(title);
11597         }
11598     }
11599
11600     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11601         gameMode = PlayFromGameFile;
11602         ModeHighlight();
11603     }
11604
11605     currentMove = forwardMostMove = backwardMostMove = 0;
11606     CopyBoard(boards[0], initialPosition);
11607     StopClocks();
11608
11609     /*
11610      * Skip the first gn-1 games in the file.
11611      * Also skip over anything that precedes an identifiable
11612      * start of game marker, to avoid being confused by
11613      * garbage at the start of the file.  Currently
11614      * recognized start of game markers are the move number "1",
11615      * the pattern "gnuchess .* game", the pattern
11616      * "^[#;%] [^ ]* game file", and a PGN tag block.
11617      * A game that starts with one of the latter two patterns
11618      * will also have a move number 1, possibly
11619      * following a position diagram.
11620      * 5-4-02: Let's try being more lenient and allowing a game to
11621      * start with an unnumbered move.  Does that break anything?
11622      */
11623     cm = lastLoadGameStart = EndOfFile;
11624     while (gn > 0) {
11625         yyboardindex = forwardMostMove;
11626         cm = (ChessMove) Myylex();
11627         switch (cm) {
11628           case EndOfFile:
11629             if (cmailMsgLoaded) {
11630                 nCmailGames = CMAIL_MAX_GAMES - gn;
11631             } else {
11632                 Reset(TRUE, TRUE);
11633                 DisplayError(_("Game not found in file"), 0);
11634             }
11635             return FALSE;
11636
11637           case GNUChessGame:
11638           case XBoardGame:
11639             gn--;
11640             lastLoadGameStart = cm;
11641             break;
11642
11643           case MoveNumberOne:
11644             switch (lastLoadGameStart) {
11645               case GNUChessGame:
11646               case XBoardGame:
11647               case PGNTag:
11648                 break;
11649               case MoveNumberOne:
11650               case EndOfFile:
11651                 gn--;           /* count this game */
11652                 lastLoadGameStart = cm;
11653                 break;
11654               default:
11655                 /* impossible */
11656                 break;
11657             }
11658             break;
11659
11660           case PGNTag:
11661             switch (lastLoadGameStart) {
11662               case GNUChessGame:
11663               case PGNTag:
11664               case MoveNumberOne:
11665               case EndOfFile:
11666                 gn--;           /* count this game */
11667                 lastLoadGameStart = cm;
11668                 break;
11669               case XBoardGame:
11670                 lastLoadGameStart = cm; /* game counted already */
11671                 break;
11672               default:
11673                 /* impossible */
11674                 break;
11675             }
11676             if (gn > 0) {
11677                 do {
11678                     yyboardindex = forwardMostMove;
11679                     cm = (ChessMove) Myylex();
11680                 } while (cm == PGNTag || cm == Comment);
11681             }
11682             break;
11683
11684           case WhiteWins:
11685           case BlackWins:
11686           case GameIsDrawn:
11687             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11688                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11689                     != CMAIL_OLD_RESULT) {
11690                     nCmailResults ++ ;
11691                     cmailResult[  CMAIL_MAX_GAMES
11692                                 - gn - 1] = CMAIL_OLD_RESULT;
11693                 }
11694             }
11695             break;
11696
11697           case NormalMove:
11698             /* Only a NormalMove can be at the start of a game
11699              * without a position diagram. */
11700             if (lastLoadGameStart == EndOfFile ) {
11701               gn--;
11702               lastLoadGameStart = MoveNumberOne;
11703             }
11704             break;
11705
11706           default:
11707             break;
11708         }
11709     }
11710
11711     if (appData.debugMode)
11712       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11713
11714     if (cm == XBoardGame) {
11715         /* Skip any header junk before position diagram and/or move 1 */
11716         for (;;) {
11717             yyboardindex = forwardMostMove;
11718             cm = (ChessMove) Myylex();
11719
11720             if (cm == EndOfFile ||
11721                 cm == GNUChessGame || cm == XBoardGame) {
11722                 /* Empty game; pretend end-of-file and handle later */
11723                 cm = EndOfFile;
11724                 break;
11725             }
11726
11727             if (cm == MoveNumberOne || cm == PositionDiagram ||
11728                 cm == PGNTag || cm == Comment)
11729               break;
11730         }
11731     } else if (cm == GNUChessGame) {
11732         if (gameInfo.event != NULL) {
11733             free(gameInfo.event);
11734         }
11735         gameInfo.event = StrSave(yy_text);
11736     }
11737
11738     startedFromSetupPosition = FALSE;
11739     while (cm == PGNTag) {
11740         if (appData.debugMode)
11741           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11742         err = ParsePGNTag(yy_text, &gameInfo);
11743         if (!err) numPGNTags++;
11744
11745         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11746         if(gameInfo.variant != oldVariant) {
11747             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11748             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11749             InitPosition(TRUE);
11750             oldVariant = gameInfo.variant;
11751             if (appData.debugMode)
11752               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11753         }
11754
11755
11756         if (gameInfo.fen != NULL) {
11757           Board initial_position;
11758           startedFromSetupPosition = TRUE;
11759           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11760             Reset(TRUE, TRUE);
11761             DisplayError(_("Bad FEN position in file"), 0);
11762             return FALSE;
11763           }
11764           CopyBoard(boards[0], initial_position);
11765           if (blackPlaysFirst) {
11766             currentMove = forwardMostMove = backwardMostMove = 1;
11767             CopyBoard(boards[1], initial_position);
11768             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11769             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11770             timeRemaining[0][1] = whiteTimeRemaining;
11771             timeRemaining[1][1] = blackTimeRemaining;
11772             if (commentList[0] != NULL) {
11773               commentList[1] = commentList[0];
11774               commentList[0] = NULL;
11775             }
11776           } else {
11777             currentMove = forwardMostMove = backwardMostMove = 0;
11778           }
11779           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11780           {   int i;
11781               initialRulePlies = FENrulePlies;
11782               for( i=0; i< nrCastlingRights; i++ )
11783                   initialRights[i] = initial_position[CASTLING][i];
11784           }
11785           yyboardindex = forwardMostMove;
11786           free(gameInfo.fen);
11787           gameInfo.fen = NULL;
11788         }
11789
11790         yyboardindex = forwardMostMove;
11791         cm = (ChessMove) Myylex();
11792
11793         /* Handle comments interspersed among the tags */
11794         while (cm == Comment) {
11795             char *p;
11796             if (appData.debugMode)
11797               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11798             p = yy_text;
11799             AppendComment(currentMove, p, FALSE);
11800             yyboardindex = forwardMostMove;
11801             cm = (ChessMove) Myylex();
11802         }
11803     }
11804
11805     /* don't rely on existence of Event tag since if game was
11806      * pasted from clipboard the Event tag may not exist
11807      */
11808     if (numPGNTags > 0){
11809         char *tags;
11810         if (gameInfo.variant == VariantNormal) {
11811           VariantClass v = StringToVariant(gameInfo.event);
11812           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11813           if(v < VariantShogi) gameInfo.variant = v;
11814         }
11815         if (!matchMode) {
11816           if( appData.autoDisplayTags ) {
11817             tags = PGNTags(&gameInfo);
11818             TagsPopUp(tags, CmailMsg());
11819             free(tags);
11820           }
11821         }
11822     } else {
11823         /* Make something up, but don't display it now */
11824         SetGameInfo();
11825         TagsPopDown();
11826     }
11827
11828     if (cm == PositionDiagram) {
11829         int i, j;
11830         char *p;
11831         Board initial_position;
11832
11833         if (appData.debugMode)
11834           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11835
11836         if (!startedFromSetupPosition) {
11837             p = yy_text;
11838             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11839               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11840                 switch (*p) {
11841                   case '{':
11842                   case '[':
11843                   case '-':
11844                   case ' ':
11845                   case '\t':
11846                   case '\n':
11847                   case '\r':
11848                     break;
11849                   default:
11850                     initial_position[i][j++] = CharToPiece(*p);
11851                     break;
11852                 }
11853             while (*p == ' ' || *p == '\t' ||
11854                    *p == '\n' || *p == '\r') p++;
11855
11856             if (strncmp(p, "black", strlen("black"))==0)
11857               blackPlaysFirst = TRUE;
11858             else
11859               blackPlaysFirst = FALSE;
11860             startedFromSetupPosition = TRUE;
11861
11862             CopyBoard(boards[0], initial_position);
11863             if (blackPlaysFirst) {
11864                 currentMove = forwardMostMove = backwardMostMove = 1;
11865                 CopyBoard(boards[1], initial_position);
11866                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11867                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11868                 timeRemaining[0][1] = whiteTimeRemaining;
11869                 timeRemaining[1][1] = blackTimeRemaining;
11870                 if (commentList[0] != NULL) {
11871                     commentList[1] = commentList[0];
11872                     commentList[0] = NULL;
11873                 }
11874             } else {
11875                 currentMove = forwardMostMove = backwardMostMove = 0;
11876             }
11877         }
11878         yyboardindex = forwardMostMove;
11879         cm = (ChessMove) Myylex();
11880     }
11881
11882     if (first.pr == NoProc) {
11883         StartChessProgram(&first);
11884     }
11885     InitChessProgram(&first, FALSE);
11886     SendToProgram("force\n", &first);
11887     if (startedFromSetupPosition) {
11888         SendBoard(&first, forwardMostMove);
11889     if (appData.debugMode) {
11890         fprintf(debugFP, "Load Game\n");
11891     }
11892         DisplayBothClocks();
11893     }
11894
11895     /* [HGM] server: flag to write setup moves in broadcast file as one */
11896     loadFlag = appData.suppressLoadMoves;
11897
11898     while (cm == Comment) {
11899         char *p;
11900         if (appData.debugMode)
11901           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11902         p = yy_text;
11903         AppendComment(currentMove, p, FALSE);
11904         yyboardindex = forwardMostMove;
11905         cm = (ChessMove) Myylex();
11906     }
11907
11908     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11909         cm == WhiteWins || cm == BlackWins ||
11910         cm == GameIsDrawn || cm == GameUnfinished) {
11911         DisplayMessage("", _("No moves in game"));
11912         if (cmailMsgLoaded) {
11913             if (appData.debugMode)
11914               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11915             ClearHighlights();
11916             flipView = FALSE;
11917         }
11918         DrawPosition(FALSE, boards[currentMove]);
11919         DisplayBothClocks();
11920         gameMode = EditGame;
11921         ModeHighlight();
11922         gameFileFP = NULL;
11923         cmailOldMove = 0;
11924         return TRUE;
11925     }
11926
11927     // [HGM] PV info: routine tests if comment empty
11928     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11929         DisplayComment(currentMove - 1, commentList[currentMove]);
11930     }
11931     if (!matchMode && appData.timeDelay != 0)
11932       DrawPosition(FALSE, boards[currentMove]);
11933
11934     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11935       programStats.ok_to_send = 1;
11936     }
11937
11938     /* if the first token after the PGN tags is a move
11939      * and not move number 1, retrieve it from the parser
11940      */
11941     if (cm != MoveNumberOne)
11942         LoadGameOneMove(cm);
11943
11944     /* load the remaining moves from the file */
11945     while (LoadGameOneMove(EndOfFile)) {
11946       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11947       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11948     }
11949
11950     /* rewind to the start of the game */
11951     currentMove = backwardMostMove;
11952
11953     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11954
11955     if (oldGameMode == AnalyzeFile ||
11956         oldGameMode == AnalyzeMode) {
11957       AnalyzeFileEvent();
11958     }
11959
11960     if (!matchMode && pos >= 0) {
11961         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11962     } else
11963     if (matchMode || appData.timeDelay == 0) {
11964       ToEndEvent();
11965     } else if (appData.timeDelay > 0) {
11966       AutoPlayGameLoop();
11967     }
11968
11969     if (appData.debugMode)
11970         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11971
11972     loadFlag = 0; /* [HGM] true game starts */
11973     return TRUE;
11974 }
11975
11976 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11977 int
11978 ReloadPosition (int offset)
11979 {
11980     int positionNumber = lastLoadPositionNumber + offset;
11981     if (lastLoadPositionFP == NULL) {
11982         DisplayError(_("No position has been loaded yet"), 0);
11983         return FALSE;
11984     }
11985     if (positionNumber <= 0) {
11986         DisplayError(_("Can't back up any further"), 0);
11987         return FALSE;
11988     }
11989     return LoadPosition(lastLoadPositionFP, positionNumber,
11990                         lastLoadPositionTitle);
11991 }
11992
11993 /* Load the nth position from the given file */
11994 int
11995 LoadPositionFromFile (char *filename, int n, char *title)
11996 {
11997     FILE *f;
11998     char buf[MSG_SIZ];
11999
12000     if (strcmp(filename, "-") == 0) {
12001         return LoadPosition(stdin, n, "stdin");
12002     } else {
12003         f = fopen(filename, "rb");
12004         if (f == NULL) {
12005             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12006             DisplayError(buf, errno);
12007             return FALSE;
12008         } else {
12009             return LoadPosition(f, n, title);
12010         }
12011     }
12012 }
12013
12014 /* Load the nth position from the given open file, and close it */
12015 int
12016 LoadPosition (FILE *f, int positionNumber, char *title)
12017 {
12018     char *p, line[MSG_SIZ];
12019     Board initial_position;
12020     int i, j, fenMode, pn;
12021
12022     if (gameMode == Training )
12023         SetTrainingModeOff();
12024
12025     if (gameMode != BeginningOfGame) {
12026         Reset(FALSE, TRUE);
12027     }
12028     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12029         fclose(lastLoadPositionFP);
12030     }
12031     if (positionNumber == 0) positionNumber = 1;
12032     lastLoadPositionFP = f;
12033     lastLoadPositionNumber = positionNumber;
12034     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12035     if (first.pr == NoProc && !appData.noChessProgram) {
12036       StartChessProgram(&first);
12037       InitChessProgram(&first, FALSE);
12038     }
12039     pn = positionNumber;
12040     if (positionNumber < 0) {
12041         /* Negative position number means to seek to that byte offset */
12042         if (fseek(f, -positionNumber, 0) == -1) {
12043             DisplayError(_("Can't seek on position file"), 0);
12044             return FALSE;
12045         };
12046         pn = 1;
12047     } else {
12048         if (fseek(f, 0, 0) == -1) {
12049             if (f == lastLoadPositionFP ?
12050                 positionNumber == lastLoadPositionNumber + 1 :
12051                 positionNumber == 1) {
12052                 pn = 1;
12053             } else {
12054                 DisplayError(_("Can't seek on position file"), 0);
12055                 return FALSE;
12056             }
12057         }
12058     }
12059     /* See if this file is FEN or old-style xboard */
12060     if (fgets(line, MSG_SIZ, f) == NULL) {
12061         DisplayError(_("Position not found in file"), 0);
12062         return FALSE;
12063     }
12064     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12065     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12066
12067     if (pn >= 2) {
12068         if (fenMode || line[0] == '#') pn--;
12069         while (pn > 0) {
12070             /* skip positions before number pn */
12071             if (fgets(line, MSG_SIZ, f) == NULL) {
12072                 Reset(TRUE, TRUE);
12073                 DisplayError(_("Position not found in file"), 0);
12074                 return FALSE;
12075             }
12076             if (fenMode || line[0] == '#') pn--;
12077         }
12078     }
12079
12080     if (fenMode) {
12081         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12082             DisplayError(_("Bad FEN position in file"), 0);
12083             return FALSE;
12084         }
12085     } else {
12086         (void) fgets(line, MSG_SIZ, f);
12087         (void) fgets(line, MSG_SIZ, f);
12088
12089         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12090             (void) fgets(line, MSG_SIZ, f);
12091             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12092                 if (*p == ' ')
12093                   continue;
12094                 initial_position[i][j++] = CharToPiece(*p);
12095             }
12096         }
12097
12098         blackPlaysFirst = FALSE;
12099         if (!feof(f)) {
12100             (void) fgets(line, MSG_SIZ, f);
12101             if (strncmp(line, "black", strlen("black"))==0)
12102               blackPlaysFirst = TRUE;
12103         }
12104     }
12105     startedFromSetupPosition = TRUE;
12106
12107     CopyBoard(boards[0], initial_position);
12108     if (blackPlaysFirst) {
12109         currentMove = forwardMostMove = backwardMostMove = 1;
12110         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12111         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12112         CopyBoard(boards[1], initial_position);
12113         DisplayMessage("", _("Black to play"));
12114     } else {
12115         currentMove = forwardMostMove = backwardMostMove = 0;
12116         DisplayMessage("", _("White to play"));
12117     }
12118     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12119     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12120         SendToProgram("force\n", &first);
12121         SendBoard(&first, forwardMostMove);
12122     }
12123     if (appData.debugMode) {
12124 int i, j;
12125   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12126   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12127         fprintf(debugFP, "Load Position\n");
12128     }
12129
12130     if (positionNumber > 1) {
12131       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12132         DisplayTitle(line);
12133     } else {
12134         DisplayTitle(title);
12135     }
12136     gameMode = EditGame;
12137     ModeHighlight();
12138     ResetClocks();
12139     timeRemaining[0][1] = whiteTimeRemaining;
12140     timeRemaining[1][1] = blackTimeRemaining;
12141     DrawPosition(FALSE, boards[currentMove]);
12142
12143     return TRUE;
12144 }
12145
12146
12147 void
12148 CopyPlayerNameIntoFileName (char **dest, char *src)
12149 {
12150     while (*src != NULLCHAR && *src != ',') {
12151         if (*src == ' ') {
12152             *(*dest)++ = '_';
12153             src++;
12154         } else {
12155             *(*dest)++ = *src++;
12156         }
12157     }
12158 }
12159
12160 char *
12161 DefaultFileName (char *ext)
12162 {
12163     static char def[MSG_SIZ];
12164     char *p;
12165
12166     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12167         p = def;
12168         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12169         *p++ = '-';
12170         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12171         *p++ = '.';
12172         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12173     } else {
12174         def[0] = NULLCHAR;
12175     }
12176     return def;
12177 }
12178
12179 /* Save the current game to the given file */
12180 int
12181 SaveGameToFile (char *filename, int append)
12182 {
12183     FILE *f;
12184     char buf[MSG_SIZ];
12185     int result, i, t,tot=0;
12186
12187     if (strcmp(filename, "-") == 0) {
12188         return SaveGame(stdout, 0, NULL);
12189     } else {
12190         for(i=0; i<10; i++) { // upto 10 tries
12191              f = fopen(filename, append ? "a" : "w");
12192              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12193              if(f || errno != 13) break;
12194              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12195              tot += t;
12196         }
12197         if (f == NULL) {
12198             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12199             DisplayError(buf, errno);
12200             return FALSE;
12201         } else {
12202             safeStrCpy(buf, lastMsg, MSG_SIZ);
12203             DisplayMessage(_("Waiting for access to save file"), "");
12204             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12205             DisplayMessage(_("Saving game"), "");
12206             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12207             result = SaveGame(f, 0, NULL);
12208             DisplayMessage(buf, "");
12209             return result;
12210         }
12211     }
12212 }
12213
12214 char *
12215 SavePart (char *str)
12216 {
12217     static char buf[MSG_SIZ];
12218     char *p;
12219
12220     p = strchr(str, ' ');
12221     if (p == NULL) return str;
12222     strncpy(buf, str, p - str);
12223     buf[p - str] = NULLCHAR;
12224     return buf;
12225 }
12226
12227 #define PGN_MAX_LINE 75
12228
12229 #define PGN_SIDE_WHITE  0
12230 #define PGN_SIDE_BLACK  1
12231
12232 static int
12233 FindFirstMoveOutOfBook (int side)
12234 {
12235     int result = -1;
12236
12237     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12238         int index = backwardMostMove;
12239         int has_book_hit = 0;
12240
12241         if( (index % 2) != side ) {
12242             index++;
12243         }
12244
12245         while( index < forwardMostMove ) {
12246             /* Check to see if engine is in book */
12247             int depth = pvInfoList[index].depth;
12248             int score = pvInfoList[index].score;
12249             int in_book = 0;
12250
12251             if( depth <= 2 ) {
12252                 in_book = 1;
12253             }
12254             else if( score == 0 && depth == 63 ) {
12255                 in_book = 1; /* Zappa */
12256             }
12257             else if( score == 2 && depth == 99 ) {
12258                 in_book = 1; /* Abrok */
12259             }
12260
12261             has_book_hit += in_book;
12262
12263             if( ! in_book ) {
12264                 result = index;
12265
12266                 break;
12267             }
12268
12269             index += 2;
12270         }
12271     }
12272
12273     return result;
12274 }
12275
12276 void
12277 GetOutOfBookInfo (char * buf)
12278 {
12279     int oob[2];
12280     int i;
12281     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12282
12283     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12284     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12285
12286     *buf = '\0';
12287
12288     if( oob[0] >= 0 || oob[1] >= 0 ) {
12289         for( i=0; i<2; i++ ) {
12290             int idx = oob[i];
12291
12292             if( idx >= 0 ) {
12293                 if( i > 0 && oob[0] >= 0 ) {
12294                     strcat( buf, "   " );
12295                 }
12296
12297                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12298                 sprintf( buf+strlen(buf), "%s%.2f",
12299                     pvInfoList[idx].score >= 0 ? "+" : "",
12300                     pvInfoList[idx].score / 100.0 );
12301             }
12302         }
12303     }
12304 }
12305
12306 /* Save game in PGN style and close the file */
12307 int
12308 SaveGamePGN (FILE *f)
12309 {
12310     int i, offset, linelen, newblock;
12311     time_t tm;
12312 //    char *movetext;
12313     char numtext[32];
12314     int movelen, numlen, blank;
12315     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12316
12317     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12318
12319     tm = time((time_t *) NULL);
12320
12321     PrintPGNTags(f, &gameInfo);
12322
12323     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12324
12325     if (backwardMostMove > 0 || startedFromSetupPosition) {
12326         char *fen = PositionToFEN(backwardMostMove, NULL);
12327         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12328         fprintf(f, "\n{--------------\n");
12329         PrintPosition(f, backwardMostMove);
12330         fprintf(f, "--------------}\n");
12331         free(fen);
12332     }
12333     else {
12334         /* [AS] Out of book annotation */
12335         if( appData.saveOutOfBookInfo ) {
12336             char buf[64];
12337
12338             GetOutOfBookInfo( buf );
12339
12340             if( buf[0] != '\0' ) {
12341                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12342             }
12343         }
12344
12345         fprintf(f, "\n");
12346     }
12347
12348     i = backwardMostMove;
12349     linelen = 0;
12350     newblock = TRUE;
12351
12352     while (i < forwardMostMove) {
12353         /* Print comments preceding this move */
12354         if (commentList[i] != NULL) {
12355             if (linelen > 0) fprintf(f, "\n");
12356             fprintf(f, "%s", commentList[i]);
12357             linelen = 0;
12358             newblock = TRUE;
12359         }
12360
12361         /* Format move number */
12362         if ((i % 2) == 0)
12363           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12364         else
12365           if (newblock)
12366             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12367           else
12368             numtext[0] = NULLCHAR;
12369
12370         numlen = strlen(numtext);
12371         newblock = FALSE;
12372
12373         /* Print move number */
12374         blank = linelen > 0 && numlen > 0;
12375         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12376             fprintf(f, "\n");
12377             linelen = 0;
12378             blank = 0;
12379         }
12380         if (blank) {
12381             fprintf(f, " ");
12382             linelen++;
12383         }
12384         fprintf(f, "%s", numtext);
12385         linelen += numlen;
12386
12387         /* Get move */
12388         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12389         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12390
12391         /* Print move */
12392         blank = linelen > 0 && movelen > 0;
12393         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12394             fprintf(f, "\n");
12395             linelen = 0;
12396             blank = 0;
12397         }
12398         if (blank) {
12399             fprintf(f, " ");
12400             linelen++;
12401         }
12402         fprintf(f, "%s", move_buffer);
12403         linelen += movelen;
12404
12405         /* [AS] Add PV info if present */
12406         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12407             /* [HGM] add time */
12408             char buf[MSG_SIZ]; int seconds;
12409
12410             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12411
12412             if( seconds <= 0)
12413               buf[0] = 0;
12414             else
12415               if( seconds < 30 )
12416                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12417               else
12418                 {
12419                   seconds = (seconds + 4)/10; // round to full seconds
12420                   if( seconds < 60 )
12421                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12422                   else
12423                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12424                 }
12425
12426             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12427                       pvInfoList[i].score >= 0 ? "+" : "",
12428                       pvInfoList[i].score / 100.0,
12429                       pvInfoList[i].depth,
12430                       buf );
12431
12432             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12433
12434             /* Print score/depth */
12435             blank = linelen > 0 && movelen > 0;
12436             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12437                 fprintf(f, "\n");
12438                 linelen = 0;
12439                 blank = 0;
12440             }
12441             if (blank) {
12442                 fprintf(f, " ");
12443                 linelen++;
12444             }
12445             fprintf(f, "%s", move_buffer);
12446             linelen += movelen;
12447         }
12448
12449         i++;
12450     }
12451
12452     /* Start a new line */
12453     if (linelen > 0) fprintf(f, "\n");
12454
12455     /* Print comments after last move */
12456     if (commentList[i] != NULL) {
12457         fprintf(f, "%s\n", commentList[i]);
12458     }
12459
12460     /* Print result */
12461     if (gameInfo.resultDetails != NULL &&
12462         gameInfo.resultDetails[0] != NULLCHAR) {
12463         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12464                 PGNResult(gameInfo.result));
12465     } else {
12466         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12467     }
12468
12469     fclose(f);
12470     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12471     return TRUE;
12472 }
12473
12474 /* Save game in old style and close the file */
12475 int
12476 SaveGameOldStyle (FILE *f)
12477 {
12478     int i, offset;
12479     time_t tm;
12480
12481     tm = time((time_t *) NULL);
12482
12483     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12484     PrintOpponents(f);
12485
12486     if (backwardMostMove > 0 || startedFromSetupPosition) {
12487         fprintf(f, "\n[--------------\n");
12488         PrintPosition(f, backwardMostMove);
12489         fprintf(f, "--------------]\n");
12490     } else {
12491         fprintf(f, "\n");
12492     }
12493
12494     i = backwardMostMove;
12495     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12496
12497     while (i < forwardMostMove) {
12498         if (commentList[i] != NULL) {
12499             fprintf(f, "[%s]\n", commentList[i]);
12500         }
12501
12502         if ((i % 2) == 1) {
12503             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12504             i++;
12505         } else {
12506             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12507             i++;
12508             if (commentList[i] != NULL) {
12509                 fprintf(f, "\n");
12510                 continue;
12511             }
12512             if (i >= forwardMostMove) {
12513                 fprintf(f, "\n");
12514                 break;
12515             }
12516             fprintf(f, "%s\n", parseList[i]);
12517             i++;
12518         }
12519     }
12520
12521     if (commentList[i] != NULL) {
12522         fprintf(f, "[%s]\n", commentList[i]);
12523     }
12524
12525     /* This isn't really the old style, but it's close enough */
12526     if (gameInfo.resultDetails != NULL &&
12527         gameInfo.resultDetails[0] != NULLCHAR) {
12528         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12529                 gameInfo.resultDetails);
12530     } else {
12531         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12532     }
12533
12534     fclose(f);
12535     return TRUE;
12536 }
12537
12538 /* Save the current game to open file f and close the file */
12539 int
12540 SaveGame (FILE *f, int dummy, char *dummy2)
12541 {
12542     if (gameMode == EditPosition) EditPositionDone(TRUE);
12543     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12544     if (appData.oldSaveStyle)
12545       return SaveGameOldStyle(f);
12546     else
12547       return SaveGamePGN(f);
12548 }
12549
12550 /* Save the current position to the given file */
12551 int
12552 SavePositionToFile (char *filename)
12553 {
12554     FILE *f;
12555     char buf[MSG_SIZ];
12556
12557     if (strcmp(filename, "-") == 0) {
12558         return SavePosition(stdout, 0, NULL);
12559     } else {
12560         f = fopen(filename, "a");
12561         if (f == NULL) {
12562             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12563             DisplayError(buf, errno);
12564             return FALSE;
12565         } else {
12566             safeStrCpy(buf, lastMsg, MSG_SIZ);
12567             DisplayMessage(_("Waiting for access to save file"), "");
12568             flock(fileno(f), LOCK_EX); // [HGM] lock
12569             DisplayMessage(_("Saving position"), "");
12570             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12571             SavePosition(f, 0, NULL);
12572             DisplayMessage(buf, "");
12573             return TRUE;
12574         }
12575     }
12576 }
12577
12578 /* Save the current position to the given open file and close the file */
12579 int
12580 SavePosition (FILE *f, int dummy, char *dummy2)
12581 {
12582     time_t tm;
12583     char *fen;
12584
12585     if (gameMode == EditPosition) EditPositionDone(TRUE);
12586     if (appData.oldSaveStyle) {
12587         tm = time((time_t *) NULL);
12588
12589         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12590         PrintOpponents(f);
12591         fprintf(f, "[--------------\n");
12592         PrintPosition(f, currentMove);
12593         fprintf(f, "--------------]\n");
12594     } else {
12595         fen = PositionToFEN(currentMove, NULL);
12596         fprintf(f, "%s\n", fen);
12597         free(fen);
12598     }
12599     fclose(f);
12600     return TRUE;
12601 }
12602
12603 void
12604 ReloadCmailMsgEvent (int unregister)
12605 {
12606 #if !WIN32
12607     static char *inFilename = NULL;
12608     static char *outFilename;
12609     int i;
12610     struct stat inbuf, outbuf;
12611     int status;
12612
12613     /* Any registered moves are unregistered if unregister is set, */
12614     /* i.e. invoked by the signal handler */
12615     if (unregister) {
12616         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12617             cmailMoveRegistered[i] = FALSE;
12618             if (cmailCommentList[i] != NULL) {
12619                 free(cmailCommentList[i]);
12620                 cmailCommentList[i] = NULL;
12621             }
12622         }
12623         nCmailMovesRegistered = 0;
12624     }
12625
12626     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12627         cmailResult[i] = CMAIL_NOT_RESULT;
12628     }
12629     nCmailResults = 0;
12630
12631     if (inFilename == NULL) {
12632         /* Because the filenames are static they only get malloced once  */
12633         /* and they never get freed                                      */
12634         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12635         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12636
12637         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12638         sprintf(outFilename, "%s.out", appData.cmailGameName);
12639     }
12640
12641     status = stat(outFilename, &outbuf);
12642     if (status < 0) {
12643         cmailMailedMove = FALSE;
12644     } else {
12645         status = stat(inFilename, &inbuf);
12646         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12647     }
12648
12649     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12650        counts the games, notes how each one terminated, etc.
12651
12652        It would be nice to remove this kludge and instead gather all
12653        the information while building the game list.  (And to keep it
12654        in the game list nodes instead of having a bunch of fixed-size
12655        parallel arrays.)  Note this will require getting each game's
12656        termination from the PGN tags, as the game list builder does
12657        not process the game moves.  --mann
12658        */
12659     cmailMsgLoaded = TRUE;
12660     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12661
12662     /* Load first game in the file or popup game menu */
12663     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12664
12665 #endif /* !WIN32 */
12666     return;
12667 }
12668
12669 int
12670 RegisterMove ()
12671 {
12672     FILE *f;
12673     char string[MSG_SIZ];
12674
12675     if (   cmailMailedMove
12676         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12677         return TRUE;            /* Allow free viewing  */
12678     }
12679
12680     /* Unregister move to ensure that we don't leave RegisterMove        */
12681     /* with the move registered when the conditions for registering no   */
12682     /* longer hold                                                       */
12683     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12684         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12685         nCmailMovesRegistered --;
12686
12687         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12688           {
12689               free(cmailCommentList[lastLoadGameNumber - 1]);
12690               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12691           }
12692     }
12693
12694     if (cmailOldMove == -1) {
12695         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12696         return FALSE;
12697     }
12698
12699     if (currentMove > cmailOldMove + 1) {
12700         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12701         return FALSE;
12702     }
12703
12704     if (currentMove < cmailOldMove) {
12705         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12706         return FALSE;
12707     }
12708
12709     if (forwardMostMove > currentMove) {
12710         /* Silently truncate extra moves */
12711         TruncateGame();
12712     }
12713
12714     if (   (currentMove == cmailOldMove + 1)
12715         || (   (currentMove == cmailOldMove)
12716             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12717                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12718         if (gameInfo.result != GameUnfinished) {
12719             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12720         }
12721
12722         if (commentList[currentMove] != NULL) {
12723             cmailCommentList[lastLoadGameNumber - 1]
12724               = StrSave(commentList[currentMove]);
12725         }
12726         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12727
12728         if (appData.debugMode)
12729           fprintf(debugFP, "Saving %s for game %d\n",
12730                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12731
12732         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12733
12734         f = fopen(string, "w");
12735         if (appData.oldSaveStyle) {
12736             SaveGameOldStyle(f); /* also closes the file */
12737
12738             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12739             f = fopen(string, "w");
12740             SavePosition(f, 0, NULL); /* also closes the file */
12741         } else {
12742             fprintf(f, "{--------------\n");
12743             PrintPosition(f, currentMove);
12744             fprintf(f, "--------------}\n\n");
12745
12746             SaveGame(f, 0, NULL); /* also closes the file*/
12747         }
12748
12749         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12750         nCmailMovesRegistered ++;
12751     } else if (nCmailGames == 1) {
12752         DisplayError(_("You have not made a move yet"), 0);
12753         return FALSE;
12754     }
12755
12756     return TRUE;
12757 }
12758
12759 void
12760 MailMoveEvent ()
12761 {
12762 #if !WIN32
12763     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12764     FILE *commandOutput;
12765     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12766     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12767     int nBuffers;
12768     int i;
12769     int archived;
12770     char *arcDir;
12771
12772     if (! cmailMsgLoaded) {
12773         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12774         return;
12775     }
12776
12777     if (nCmailGames == nCmailResults) {
12778         DisplayError(_("No unfinished games"), 0);
12779         return;
12780     }
12781
12782 #if CMAIL_PROHIBIT_REMAIL
12783     if (cmailMailedMove) {
12784       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);
12785         DisplayError(msg, 0);
12786         return;
12787     }
12788 #endif
12789
12790     if (! (cmailMailedMove || RegisterMove())) return;
12791
12792     if (   cmailMailedMove
12793         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12794       snprintf(string, MSG_SIZ, partCommandString,
12795                appData.debugMode ? " -v" : "", appData.cmailGameName);
12796         commandOutput = popen(string, "r");
12797
12798         if (commandOutput == NULL) {
12799             DisplayError(_("Failed to invoke cmail"), 0);
12800         } else {
12801             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12802                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12803             }
12804             if (nBuffers > 1) {
12805                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12806                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12807                 nBytes = MSG_SIZ - 1;
12808             } else {
12809                 (void) memcpy(msg, buffer, nBytes);
12810             }
12811             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12812
12813             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12814                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12815
12816                 archived = TRUE;
12817                 for (i = 0; i < nCmailGames; i ++) {
12818                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12819                         archived = FALSE;
12820                     }
12821                 }
12822                 if (   archived
12823                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12824                         != NULL)) {
12825                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12826                            arcDir,
12827                            appData.cmailGameName,
12828                            gameInfo.date);
12829                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12830                     cmailMsgLoaded = FALSE;
12831                 }
12832             }
12833
12834             DisplayInformation(msg);
12835             pclose(commandOutput);
12836         }
12837     } else {
12838         if ((*cmailMsg) != '\0') {
12839             DisplayInformation(cmailMsg);
12840         }
12841     }
12842
12843     return;
12844 #endif /* !WIN32 */
12845 }
12846
12847 char *
12848 CmailMsg ()
12849 {
12850 #if WIN32
12851     return NULL;
12852 #else
12853     int  prependComma = 0;
12854     char number[5];
12855     char string[MSG_SIZ];       /* Space for game-list */
12856     int  i;
12857
12858     if (!cmailMsgLoaded) return "";
12859
12860     if (cmailMailedMove) {
12861       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12862     } else {
12863         /* Create a list of games left */
12864       snprintf(string, MSG_SIZ, "[");
12865         for (i = 0; i < nCmailGames; i ++) {
12866             if (! (   cmailMoveRegistered[i]
12867                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12868                 if (prependComma) {
12869                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12870                 } else {
12871                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12872                     prependComma = 1;
12873                 }
12874
12875                 strcat(string, number);
12876             }
12877         }
12878         strcat(string, "]");
12879
12880         if (nCmailMovesRegistered + nCmailResults == 0) {
12881             switch (nCmailGames) {
12882               case 1:
12883                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12884                 break;
12885
12886               case 2:
12887                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12888                 break;
12889
12890               default:
12891                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12892                          nCmailGames);
12893                 break;
12894             }
12895         } else {
12896             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12897               case 1:
12898                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12899                          string);
12900                 break;
12901
12902               case 0:
12903                 if (nCmailResults == nCmailGames) {
12904                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12905                 } else {
12906                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12907                 }
12908                 break;
12909
12910               default:
12911                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12912                          string);
12913             }
12914         }
12915     }
12916     return cmailMsg;
12917 #endif /* WIN32 */
12918 }
12919
12920 void
12921 ResetGameEvent ()
12922 {
12923     if (gameMode == Training)
12924       SetTrainingModeOff();
12925
12926     Reset(TRUE, TRUE);
12927     cmailMsgLoaded = FALSE;
12928     if (appData.icsActive) {
12929       SendToICS(ics_prefix);
12930       SendToICS("refresh\n");
12931     }
12932 }
12933
12934 void
12935 ExitEvent (int status)
12936 {
12937     exiting++;
12938     if (exiting > 2) {
12939       /* Give up on clean exit */
12940       exit(status);
12941     }
12942     if (exiting > 1) {
12943       /* Keep trying for clean exit */
12944       return;
12945     }
12946
12947     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12948
12949     if (telnetISR != NULL) {
12950       RemoveInputSource(telnetISR);
12951     }
12952     if (icsPR != NoProc) {
12953       DestroyChildProcess(icsPR, TRUE);
12954     }
12955
12956     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12957     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12958
12959     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12960     /* make sure this other one finishes before killing it!                  */
12961     if(endingGame) { int count = 0;
12962         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12963         while(endingGame && count++ < 10) DoSleep(1);
12964         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12965     }
12966
12967     /* Kill off chess programs */
12968     if (first.pr != NoProc) {
12969         ExitAnalyzeMode();
12970
12971         DoSleep( appData.delayBeforeQuit );
12972         SendToProgram("quit\n", &first);
12973         DoSleep( appData.delayAfterQuit );
12974         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12975     }
12976     if (second.pr != NoProc) {
12977         DoSleep( appData.delayBeforeQuit );
12978         SendToProgram("quit\n", &second);
12979         DoSleep( appData.delayAfterQuit );
12980         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12981     }
12982     if (first.isr != NULL) {
12983         RemoveInputSource(first.isr);
12984     }
12985     if (second.isr != NULL) {
12986         RemoveInputSource(second.isr);
12987     }
12988
12989     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12990     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12991
12992     ShutDownFrontEnd();
12993     exit(status);
12994 }
12995
12996 void
12997 PauseEvent ()
12998 {
12999     if (appData.debugMode)
13000         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13001     if (pausing) {
13002         pausing = FALSE;
13003         ModeHighlight();
13004         if (gameMode == MachinePlaysWhite ||
13005             gameMode == MachinePlaysBlack) {
13006             StartClocks();
13007         } else {
13008             DisplayBothClocks();
13009         }
13010         if (gameMode == PlayFromGameFile) {
13011             if (appData.timeDelay >= 0)
13012                 AutoPlayGameLoop();
13013         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13014             Reset(FALSE, TRUE);
13015             SendToICS(ics_prefix);
13016             SendToICS("refresh\n");
13017         } else if (currentMove < forwardMostMove) {
13018             ForwardInner(forwardMostMove);
13019         }
13020         pauseExamInvalid = FALSE;
13021     } else {
13022         switch (gameMode) {
13023           default:
13024             return;
13025           case IcsExamining:
13026             pauseExamForwardMostMove = forwardMostMove;
13027             pauseExamInvalid = FALSE;
13028             /* fall through */
13029           case IcsObserving:
13030           case IcsPlayingWhite:
13031           case IcsPlayingBlack:
13032             pausing = TRUE;
13033             ModeHighlight();
13034             return;
13035           case PlayFromGameFile:
13036             (void) StopLoadGameTimer();
13037             pausing = TRUE;
13038             ModeHighlight();
13039             break;
13040           case BeginningOfGame:
13041             if (appData.icsActive) return;
13042             /* else fall through */
13043           case MachinePlaysWhite:
13044           case MachinePlaysBlack:
13045           case TwoMachinesPlay:
13046             if (forwardMostMove == 0)
13047               return;           /* don't pause if no one has moved */
13048             if ((gameMode == MachinePlaysWhite &&
13049                  !WhiteOnMove(forwardMostMove)) ||
13050                 (gameMode == MachinePlaysBlack &&
13051                  WhiteOnMove(forwardMostMove))) {
13052                 StopClocks();
13053             }
13054             pausing = TRUE;
13055             ModeHighlight();
13056             break;
13057         }
13058     }
13059 }
13060
13061 void
13062 EditCommentEvent ()
13063 {
13064     char title[MSG_SIZ];
13065
13066     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13067       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13068     } else {
13069       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13070                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13071                parseList[currentMove - 1]);
13072     }
13073
13074     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13075 }
13076
13077
13078 void
13079 EditTagsEvent ()
13080 {
13081     char *tags = PGNTags(&gameInfo);
13082     bookUp = FALSE;
13083     EditTagsPopUp(tags, NULL);
13084     free(tags);
13085 }
13086
13087 void
13088 AnalyzeModeEvent ()
13089 {
13090     if (appData.noChessProgram || gameMode == AnalyzeMode)
13091       return;
13092
13093     if (gameMode != AnalyzeFile) {
13094         if (!appData.icsEngineAnalyze) {
13095                EditGameEvent();
13096                if (gameMode != EditGame) return;
13097         }
13098         ResurrectChessProgram();
13099         SendToProgram("analyze\n", &first);
13100         first.analyzing = TRUE;
13101         /*first.maybeThinking = TRUE;*/
13102         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13103         EngineOutputPopUp();
13104     }
13105     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13106     pausing = FALSE;
13107     ModeHighlight();
13108     SetGameInfo();
13109
13110     StartAnalysisClock();
13111     GetTimeMark(&lastNodeCountTime);
13112     lastNodeCount = 0;
13113 }
13114
13115 void
13116 AnalyzeFileEvent ()
13117 {
13118     if (appData.noChessProgram || gameMode == AnalyzeFile)
13119       return;
13120
13121     if (gameMode != AnalyzeMode) {
13122         EditGameEvent();
13123         if (gameMode != EditGame) return;
13124         ResurrectChessProgram();
13125         SendToProgram("analyze\n", &first);
13126         first.analyzing = TRUE;
13127         /*first.maybeThinking = TRUE;*/
13128         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13129         EngineOutputPopUp();
13130     }
13131     gameMode = AnalyzeFile;
13132     pausing = FALSE;
13133     ModeHighlight();
13134     SetGameInfo();
13135
13136     StartAnalysisClock();
13137     GetTimeMark(&lastNodeCountTime);
13138     lastNodeCount = 0;
13139     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13140 }
13141
13142 void
13143 MachineWhiteEvent ()
13144 {
13145     char buf[MSG_SIZ];
13146     char *bookHit = NULL;
13147
13148     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13149       return;
13150
13151
13152     if (gameMode == PlayFromGameFile ||
13153         gameMode == TwoMachinesPlay  ||
13154         gameMode == Training         ||
13155         gameMode == AnalyzeMode      ||
13156         gameMode == EndOfGame)
13157         EditGameEvent();
13158
13159     if (gameMode == EditPosition)
13160         EditPositionDone(TRUE);
13161
13162     if (!WhiteOnMove(currentMove)) {
13163         DisplayError(_("It is not White's turn"), 0);
13164         return;
13165     }
13166
13167     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13168       ExitAnalyzeMode();
13169
13170     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13171         gameMode == AnalyzeFile)
13172         TruncateGame();
13173
13174     ResurrectChessProgram();    /* in case it isn't running */
13175     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13176         gameMode = MachinePlaysWhite;
13177         ResetClocks();
13178     } else
13179     gameMode = MachinePlaysWhite;
13180     pausing = FALSE;
13181     ModeHighlight();
13182     SetGameInfo();
13183     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13184     DisplayTitle(buf);
13185     if (first.sendName) {
13186       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13187       SendToProgram(buf, &first);
13188     }
13189     if (first.sendTime) {
13190       if (first.useColors) {
13191         SendToProgram("black\n", &first); /*gnu kludge*/
13192       }
13193       SendTimeRemaining(&first, TRUE);
13194     }
13195     if (first.useColors) {
13196       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13197     }
13198     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13199     SetMachineThinkingEnables();
13200     first.maybeThinking = TRUE;
13201     StartClocks();
13202     firstMove = FALSE;
13203
13204     if (appData.autoFlipView && !flipView) {
13205       flipView = !flipView;
13206       DrawPosition(FALSE, NULL);
13207       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13208     }
13209
13210     if(bookHit) { // [HGM] book: simulate book reply
13211         static char bookMove[MSG_SIZ]; // a bit generous?
13212
13213         programStats.nodes = programStats.depth = programStats.time =
13214         programStats.score = programStats.got_only_move = 0;
13215         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13216
13217         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13218         strcat(bookMove, bookHit);
13219         HandleMachineMove(bookMove, &first);
13220     }
13221 }
13222
13223 void
13224 MachineBlackEvent ()
13225 {
13226   char buf[MSG_SIZ];
13227   char *bookHit = NULL;
13228
13229     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13230         return;
13231
13232
13233     if (gameMode == PlayFromGameFile ||
13234         gameMode == TwoMachinesPlay  ||
13235         gameMode == Training         ||
13236         gameMode == AnalyzeMode      ||
13237         gameMode == EndOfGame)
13238         EditGameEvent();
13239
13240     if (gameMode == EditPosition)
13241         EditPositionDone(TRUE);
13242
13243     if (WhiteOnMove(currentMove)) {
13244         DisplayError(_("It is not Black's turn"), 0);
13245         return;
13246     }
13247
13248     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13249       ExitAnalyzeMode();
13250
13251     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13252         gameMode == AnalyzeFile)
13253         TruncateGame();
13254
13255     ResurrectChessProgram();    /* in case it isn't running */
13256     gameMode = MachinePlaysBlack;
13257     pausing = FALSE;
13258     ModeHighlight();
13259     SetGameInfo();
13260     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13261     DisplayTitle(buf);
13262     if (first.sendName) {
13263       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13264       SendToProgram(buf, &first);
13265     }
13266     if (first.sendTime) {
13267       if (first.useColors) {
13268         SendToProgram("white\n", &first); /*gnu kludge*/
13269       }
13270       SendTimeRemaining(&first, FALSE);
13271     }
13272     if (first.useColors) {
13273       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13274     }
13275     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13276     SetMachineThinkingEnables();
13277     first.maybeThinking = TRUE;
13278     StartClocks();
13279
13280     if (appData.autoFlipView && flipView) {
13281       flipView = !flipView;
13282       DrawPosition(FALSE, NULL);
13283       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13284     }
13285     if(bookHit) { // [HGM] book: simulate book reply
13286         static char bookMove[MSG_SIZ]; // a bit generous?
13287
13288         programStats.nodes = programStats.depth = programStats.time =
13289         programStats.score = programStats.got_only_move = 0;
13290         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13291
13292         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13293         strcat(bookMove, bookHit);
13294         HandleMachineMove(bookMove, &first);
13295     }
13296 }
13297
13298
13299 void
13300 DisplayTwoMachinesTitle ()
13301 {
13302     char buf[MSG_SIZ];
13303     if (appData.matchGames > 0) {
13304         if(appData.tourneyFile[0]) {
13305           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13306                    gameInfo.white, _("vs."), gameInfo.black,
13307                    nextGame+1, appData.matchGames+1,
13308                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13309         } else 
13310         if (first.twoMachinesColor[0] == 'w') {
13311           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13312                    gameInfo.white, _("vs."),  gameInfo.black,
13313                    first.matchWins, second.matchWins,
13314                    matchGame - 1 - (first.matchWins + second.matchWins));
13315         } else {
13316           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13317                    gameInfo.white, _("vs."), gameInfo.black,
13318                    second.matchWins, first.matchWins,
13319                    matchGame - 1 - (first.matchWins + second.matchWins));
13320         }
13321     } else {
13322       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13323     }
13324     DisplayTitle(buf);
13325 }
13326
13327 void
13328 SettingsMenuIfReady ()
13329 {
13330   if (second.lastPing != second.lastPong) {
13331     DisplayMessage("", _("Waiting for second chess program"));
13332     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13333     return;
13334   }
13335   ThawUI();
13336   DisplayMessage("", "");
13337   SettingsPopUp(&second);
13338 }
13339
13340 int
13341 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13342 {
13343     char buf[MSG_SIZ];
13344     if (cps->pr == NoProc) {
13345         StartChessProgram(cps);
13346         if (cps->protocolVersion == 1) {
13347           retry();
13348         } else {
13349           /* kludge: allow timeout for initial "feature" command */
13350           FreezeUI();
13351           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13352           DisplayMessage("", buf);
13353           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13354         }
13355         return 1;
13356     }
13357     return 0;
13358 }
13359
13360 void
13361 TwoMachinesEvent P((void))
13362 {
13363     int i;
13364     char buf[MSG_SIZ];
13365     ChessProgramState *onmove;
13366     char *bookHit = NULL;
13367     static int stalling = 0;
13368     TimeMark now;
13369     long wait;
13370
13371     if (appData.noChessProgram) return;
13372
13373     switch (gameMode) {
13374       case TwoMachinesPlay:
13375         return;
13376       case MachinePlaysWhite:
13377       case MachinePlaysBlack:
13378         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13379             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13380             return;
13381         }
13382         /* fall through */
13383       case BeginningOfGame:
13384       case PlayFromGameFile:
13385       case EndOfGame:
13386         EditGameEvent();
13387         if (gameMode != EditGame) return;
13388         break;
13389       case EditPosition:
13390         EditPositionDone(TRUE);
13391         break;
13392       case AnalyzeMode:
13393       case AnalyzeFile:
13394         ExitAnalyzeMode();
13395         break;
13396       case EditGame:
13397       default:
13398         break;
13399     }
13400
13401 //    forwardMostMove = currentMove;
13402     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13403
13404     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13405
13406     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13407     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13408       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13409       return;
13410     }
13411     if(!stalling) {
13412       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13413       SendToProgram("force\n", &second);
13414       stalling = 1;
13415       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13416       return;
13417     }
13418     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13419     if(appData.matchPause>10000 || appData.matchPause<10)
13420                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13421     wait = SubtractTimeMarks(&now, &pauseStart);
13422     if(wait < appData.matchPause) {
13423         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13424         return;
13425     }
13426     // we are now committed to starting the game
13427     stalling = 0;
13428     DisplayMessage("", "");
13429     if (startedFromSetupPosition) {
13430         SendBoard(&second, backwardMostMove);
13431     if (appData.debugMode) {
13432         fprintf(debugFP, "Two Machines\n");
13433     }
13434     }
13435     for (i = backwardMostMove; i < forwardMostMove; i++) {
13436         SendMoveToProgram(i, &second);
13437     }
13438
13439     gameMode = TwoMachinesPlay;
13440     pausing = FALSE;
13441     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13442     SetGameInfo();
13443     DisplayTwoMachinesTitle();
13444     firstMove = TRUE;
13445     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13446         onmove = &first;
13447     } else {
13448         onmove = &second;
13449     }
13450     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13451     SendToProgram(first.computerString, &first);
13452     if (first.sendName) {
13453       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13454       SendToProgram(buf, &first);
13455     }
13456     SendToProgram(second.computerString, &second);
13457     if (second.sendName) {
13458       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13459       SendToProgram(buf, &second);
13460     }
13461
13462     ResetClocks();
13463     if (!first.sendTime || !second.sendTime) {
13464         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13465         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13466     }
13467     if (onmove->sendTime) {
13468       if (onmove->useColors) {
13469         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13470       }
13471       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13472     }
13473     if (onmove->useColors) {
13474       SendToProgram(onmove->twoMachinesColor, onmove);
13475     }
13476     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13477 //    SendToProgram("go\n", onmove);
13478     onmove->maybeThinking = TRUE;
13479     SetMachineThinkingEnables();
13480
13481     StartClocks();
13482
13483     if(bookHit) { // [HGM] book: simulate book reply
13484         static char bookMove[MSG_SIZ]; // a bit generous?
13485
13486         programStats.nodes = programStats.depth = programStats.time =
13487         programStats.score = programStats.got_only_move = 0;
13488         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13489
13490         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13491         strcat(bookMove, bookHit);
13492         savedMessage = bookMove; // args for deferred call
13493         savedState = onmove;
13494         ScheduleDelayedEvent(DeferredBookMove, 1);
13495     }
13496 }
13497
13498 void
13499 TrainingEvent ()
13500 {
13501     if (gameMode == Training) {
13502       SetTrainingModeOff();
13503       gameMode = PlayFromGameFile;
13504       DisplayMessage("", _("Training mode off"));
13505     } else {
13506       gameMode = Training;
13507       animateTraining = appData.animate;
13508
13509       /* make sure we are not already at the end of the game */
13510       if (currentMove < forwardMostMove) {
13511         SetTrainingModeOn();
13512         DisplayMessage("", _("Training mode on"));
13513       } else {
13514         gameMode = PlayFromGameFile;
13515         DisplayError(_("Already at end of game"), 0);
13516       }
13517     }
13518     ModeHighlight();
13519 }
13520
13521 void
13522 IcsClientEvent ()
13523 {
13524     if (!appData.icsActive) return;
13525     switch (gameMode) {
13526       case IcsPlayingWhite:
13527       case IcsPlayingBlack:
13528       case IcsObserving:
13529       case IcsIdle:
13530       case BeginningOfGame:
13531       case IcsExamining:
13532         return;
13533
13534       case EditGame:
13535         break;
13536
13537       case EditPosition:
13538         EditPositionDone(TRUE);
13539         break;
13540
13541       case AnalyzeMode:
13542       case AnalyzeFile:
13543         ExitAnalyzeMode();
13544         break;
13545
13546       default:
13547         EditGameEvent();
13548         break;
13549     }
13550
13551     gameMode = IcsIdle;
13552     ModeHighlight();
13553     return;
13554 }
13555
13556 void
13557 EditGameEvent ()
13558 {
13559     int i;
13560
13561     switch (gameMode) {
13562       case Training:
13563         SetTrainingModeOff();
13564         break;
13565       case MachinePlaysWhite:
13566       case MachinePlaysBlack:
13567       case BeginningOfGame:
13568         SendToProgram("force\n", &first);
13569         SetUserThinkingEnables();
13570         break;
13571       case PlayFromGameFile:
13572         (void) StopLoadGameTimer();
13573         if (gameFileFP != NULL) {
13574             gameFileFP = NULL;
13575         }
13576         break;
13577       case EditPosition:
13578         EditPositionDone(TRUE);
13579         break;
13580       case AnalyzeMode:
13581       case AnalyzeFile:
13582         ExitAnalyzeMode();
13583         SendToProgram("force\n", &first);
13584         break;
13585       case TwoMachinesPlay:
13586         GameEnds(EndOfFile, NULL, GE_PLAYER);
13587         ResurrectChessProgram();
13588         SetUserThinkingEnables();
13589         break;
13590       case EndOfGame:
13591         ResurrectChessProgram();
13592         break;
13593       case IcsPlayingBlack:
13594       case IcsPlayingWhite:
13595         DisplayError(_("Warning: You are still playing a game"), 0);
13596         break;
13597       case IcsObserving:
13598         DisplayError(_("Warning: You are still observing a game"), 0);
13599         break;
13600       case IcsExamining:
13601         DisplayError(_("Warning: You are still examining a game"), 0);
13602         break;
13603       case IcsIdle:
13604         break;
13605       case EditGame:
13606       default:
13607         return;
13608     }
13609
13610     pausing = FALSE;
13611     StopClocks();
13612     first.offeredDraw = second.offeredDraw = 0;
13613
13614     if (gameMode == PlayFromGameFile) {
13615         whiteTimeRemaining = timeRemaining[0][currentMove];
13616         blackTimeRemaining = timeRemaining[1][currentMove];
13617         DisplayTitle("");
13618     }
13619
13620     if (gameMode == MachinePlaysWhite ||
13621         gameMode == MachinePlaysBlack ||
13622         gameMode == TwoMachinesPlay ||
13623         gameMode == EndOfGame) {
13624         i = forwardMostMove;
13625         while (i > currentMove) {
13626             SendToProgram("undo\n", &first);
13627             i--;
13628         }
13629         if(!adjustedClock) {
13630         whiteTimeRemaining = timeRemaining[0][currentMove];
13631         blackTimeRemaining = timeRemaining[1][currentMove];
13632         DisplayBothClocks();
13633         }
13634         if (whiteFlag || blackFlag) {
13635             whiteFlag = blackFlag = 0;
13636         }
13637         DisplayTitle("");
13638     }
13639
13640     gameMode = EditGame;
13641     ModeHighlight();
13642     SetGameInfo();
13643 }
13644
13645
13646 void
13647 EditPositionEvent ()
13648 {
13649     if (gameMode == EditPosition) {
13650         EditGameEvent();
13651         return;
13652     }
13653
13654     EditGameEvent();
13655     if (gameMode != EditGame) return;
13656
13657     gameMode = EditPosition;
13658     ModeHighlight();
13659     SetGameInfo();
13660     if (currentMove > 0)
13661       CopyBoard(boards[0], boards[currentMove]);
13662
13663     blackPlaysFirst = !WhiteOnMove(currentMove);
13664     ResetClocks();
13665     currentMove = forwardMostMove = backwardMostMove = 0;
13666     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13667     DisplayMove(-1);
13668 }
13669
13670 void
13671 ExitAnalyzeMode ()
13672 {
13673     /* [DM] icsEngineAnalyze - possible call from other functions */
13674     if (appData.icsEngineAnalyze) {
13675         appData.icsEngineAnalyze = FALSE;
13676
13677         DisplayMessage("",_("Close ICS engine analyze..."));
13678     }
13679     if (first.analysisSupport && first.analyzing) {
13680       SendToProgram("exit\n", &first);
13681       first.analyzing = FALSE;
13682     }
13683     thinkOutput[0] = NULLCHAR;
13684 }
13685
13686 void
13687 EditPositionDone (Boolean fakeRights)
13688 {
13689     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13690
13691     startedFromSetupPosition = TRUE;
13692     InitChessProgram(&first, FALSE);
13693     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13694       boards[0][EP_STATUS] = EP_NONE;
13695       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13696     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13697         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13698         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13699       } else boards[0][CASTLING][2] = NoRights;
13700     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13701         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13702         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13703       } else boards[0][CASTLING][5] = NoRights;
13704     }
13705     SendToProgram("force\n", &first);
13706     if (blackPlaysFirst) {
13707         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13708         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13709         currentMove = forwardMostMove = backwardMostMove = 1;
13710         CopyBoard(boards[1], boards[0]);
13711     } else {
13712         currentMove = forwardMostMove = backwardMostMove = 0;
13713     }
13714     SendBoard(&first, forwardMostMove);
13715     if (appData.debugMode) {
13716         fprintf(debugFP, "EditPosDone\n");
13717     }
13718     DisplayTitle("");
13719     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13720     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13721     gameMode = EditGame;
13722     ModeHighlight();
13723     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13724     ClearHighlights(); /* [AS] */
13725 }
13726
13727 /* Pause for `ms' milliseconds */
13728 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13729 void
13730 TimeDelay (long ms)
13731 {
13732     TimeMark m1, m2;
13733
13734     GetTimeMark(&m1);
13735     do {
13736         GetTimeMark(&m2);
13737     } while (SubtractTimeMarks(&m2, &m1) < ms);
13738 }
13739
13740 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13741 void
13742 SendMultiLineToICS (char *buf)
13743 {
13744     char temp[MSG_SIZ+1], *p;
13745     int len;
13746
13747     len = strlen(buf);
13748     if (len > MSG_SIZ)
13749       len = MSG_SIZ;
13750
13751     strncpy(temp, buf, len);
13752     temp[len] = 0;
13753
13754     p = temp;
13755     while (*p) {
13756         if (*p == '\n' || *p == '\r')
13757           *p = ' ';
13758         ++p;
13759     }
13760
13761     strcat(temp, "\n");
13762     SendToICS(temp);
13763     SendToPlayer(temp, strlen(temp));
13764 }
13765
13766 void
13767 SetWhiteToPlayEvent ()
13768 {
13769     if (gameMode == EditPosition) {
13770         blackPlaysFirst = FALSE;
13771         DisplayBothClocks();    /* works because currentMove is 0 */
13772     } else if (gameMode == IcsExamining) {
13773         SendToICS(ics_prefix);
13774         SendToICS("tomove white\n");
13775     }
13776 }
13777
13778 void
13779 SetBlackToPlayEvent ()
13780 {
13781     if (gameMode == EditPosition) {
13782         blackPlaysFirst = TRUE;
13783         currentMove = 1;        /* kludge */
13784         DisplayBothClocks();
13785         currentMove = 0;
13786     } else if (gameMode == IcsExamining) {
13787         SendToICS(ics_prefix);
13788         SendToICS("tomove black\n");
13789     }
13790 }
13791
13792 void
13793 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13794 {
13795     char buf[MSG_SIZ];
13796     ChessSquare piece = boards[0][y][x];
13797
13798     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13799
13800     switch (selection) {
13801       case ClearBoard:
13802         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13803             SendToICS(ics_prefix);
13804             SendToICS("bsetup clear\n");
13805         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13806             SendToICS(ics_prefix);
13807             SendToICS("clearboard\n");
13808         } else {
13809             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13810                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13811                 for (y = 0; y < BOARD_HEIGHT; y++) {
13812                     if (gameMode == IcsExamining) {
13813                         if (boards[currentMove][y][x] != EmptySquare) {
13814                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13815                                     AAA + x, ONE + y);
13816                             SendToICS(buf);
13817                         }
13818                     } else {
13819                         boards[0][y][x] = p;
13820                     }
13821                 }
13822             }
13823         }
13824         if (gameMode == EditPosition) {
13825             DrawPosition(FALSE, boards[0]);
13826         }
13827         break;
13828
13829       case WhitePlay:
13830         SetWhiteToPlayEvent();
13831         break;
13832
13833       case BlackPlay:
13834         SetBlackToPlayEvent();
13835         break;
13836
13837       case EmptySquare:
13838         if (gameMode == IcsExamining) {
13839             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13840             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13841             SendToICS(buf);
13842         } else {
13843             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13844                 if(x == BOARD_LEFT-2) {
13845                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13846                     boards[0][y][1] = 0;
13847                 } else
13848                 if(x == BOARD_RGHT+1) {
13849                     if(y >= gameInfo.holdingsSize) break;
13850                     boards[0][y][BOARD_WIDTH-2] = 0;
13851                 } else break;
13852             }
13853             boards[0][y][x] = EmptySquare;
13854             DrawPosition(FALSE, boards[0]);
13855         }
13856         break;
13857
13858       case PromotePiece:
13859         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13860            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13861             selection = (ChessSquare) (PROMOTED piece);
13862         } else if(piece == EmptySquare) selection = WhiteSilver;
13863         else selection = (ChessSquare)((int)piece - 1);
13864         goto defaultlabel;
13865
13866       case DemotePiece:
13867         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13868            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13869             selection = (ChessSquare) (DEMOTED piece);
13870         } else if(piece == EmptySquare) selection = BlackSilver;
13871         else selection = (ChessSquare)((int)piece + 1);
13872         goto defaultlabel;
13873
13874       case WhiteQueen:
13875       case BlackQueen:
13876         if(gameInfo.variant == VariantShatranj ||
13877            gameInfo.variant == VariantXiangqi  ||
13878            gameInfo.variant == VariantCourier  ||
13879            gameInfo.variant == VariantMakruk     )
13880             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13881         goto defaultlabel;
13882
13883       case WhiteKing:
13884       case BlackKing:
13885         if(gameInfo.variant == VariantXiangqi)
13886             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13887         if(gameInfo.variant == VariantKnightmate)
13888             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13889       default:
13890         defaultlabel:
13891         if (gameMode == IcsExamining) {
13892             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13893             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13894                      PieceToChar(selection), AAA + x, ONE + y);
13895             SendToICS(buf);
13896         } else {
13897             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13898                 int n;
13899                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13900                     n = PieceToNumber(selection - BlackPawn);
13901                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13902                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13903                     boards[0][BOARD_HEIGHT-1-n][1]++;
13904                 } else
13905                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13906                     n = PieceToNumber(selection);
13907                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13908                     boards[0][n][BOARD_WIDTH-1] = selection;
13909                     boards[0][n][BOARD_WIDTH-2]++;
13910                 }
13911             } else
13912             boards[0][y][x] = selection;
13913             DrawPosition(TRUE, boards[0]);
13914             ClearHighlights();
13915             fromX = fromY = -1;
13916         }
13917         break;
13918     }
13919 }
13920
13921
13922 void
13923 DropMenuEvent (ChessSquare selection, int x, int y)
13924 {
13925     ChessMove moveType;
13926
13927     switch (gameMode) {
13928       case IcsPlayingWhite:
13929       case MachinePlaysBlack:
13930         if (!WhiteOnMove(currentMove)) {
13931             DisplayMoveError(_("It is Black's turn"));
13932             return;
13933         }
13934         moveType = WhiteDrop;
13935         break;
13936       case IcsPlayingBlack:
13937       case MachinePlaysWhite:
13938         if (WhiteOnMove(currentMove)) {
13939             DisplayMoveError(_("It is White's turn"));
13940             return;
13941         }
13942         moveType = BlackDrop;
13943         break;
13944       case EditGame:
13945         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13946         break;
13947       default:
13948         return;
13949     }
13950
13951     if (moveType == BlackDrop && selection < BlackPawn) {
13952       selection = (ChessSquare) ((int) selection
13953                                  + (int) BlackPawn - (int) WhitePawn);
13954     }
13955     if (boards[currentMove][y][x] != EmptySquare) {
13956         DisplayMoveError(_("That square is occupied"));
13957         return;
13958     }
13959
13960     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13961 }
13962
13963 void
13964 AcceptEvent ()
13965 {
13966     /* Accept a pending offer of any kind from opponent */
13967
13968     if (appData.icsActive) {
13969         SendToICS(ics_prefix);
13970         SendToICS("accept\n");
13971     } else if (cmailMsgLoaded) {
13972         if (currentMove == cmailOldMove &&
13973             commentList[cmailOldMove] != NULL &&
13974             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13975                    "Black offers a draw" : "White offers a draw")) {
13976             TruncateGame();
13977             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13978             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13979         } else {
13980             DisplayError(_("There is no pending offer on this move"), 0);
13981             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13982         }
13983     } else {
13984         /* Not used for offers from chess program */
13985     }
13986 }
13987
13988 void
13989 DeclineEvent ()
13990 {
13991     /* Decline a pending offer of any kind from opponent */
13992
13993     if (appData.icsActive) {
13994         SendToICS(ics_prefix);
13995         SendToICS("decline\n");
13996     } else if (cmailMsgLoaded) {
13997         if (currentMove == cmailOldMove &&
13998             commentList[cmailOldMove] != NULL &&
13999             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14000                    "Black offers a draw" : "White offers a draw")) {
14001 #ifdef NOTDEF
14002             AppendComment(cmailOldMove, "Draw declined", TRUE);
14003             DisplayComment(cmailOldMove - 1, "Draw declined");
14004 #endif /*NOTDEF*/
14005         } else {
14006             DisplayError(_("There is no pending offer on this move"), 0);
14007         }
14008     } else {
14009         /* Not used for offers from chess program */
14010     }
14011 }
14012
14013 void
14014 RematchEvent ()
14015 {
14016     /* Issue ICS rematch command */
14017     if (appData.icsActive) {
14018         SendToICS(ics_prefix);
14019         SendToICS("rematch\n");
14020     }
14021 }
14022
14023 void
14024 CallFlagEvent ()
14025 {
14026     /* Call your opponent's flag (claim a win on time) */
14027     if (appData.icsActive) {
14028         SendToICS(ics_prefix);
14029         SendToICS("flag\n");
14030     } else {
14031         switch (gameMode) {
14032           default:
14033             return;
14034           case MachinePlaysWhite:
14035             if (whiteFlag) {
14036                 if (blackFlag)
14037                   GameEnds(GameIsDrawn, "Both players ran out of time",
14038                            GE_PLAYER);
14039                 else
14040                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14041             } else {
14042                 DisplayError(_("Your opponent is not out of time"), 0);
14043             }
14044             break;
14045           case MachinePlaysBlack:
14046             if (blackFlag) {
14047                 if (whiteFlag)
14048                   GameEnds(GameIsDrawn, "Both players ran out of time",
14049                            GE_PLAYER);
14050                 else
14051                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14052             } else {
14053                 DisplayError(_("Your opponent is not out of time"), 0);
14054             }
14055             break;
14056         }
14057     }
14058 }
14059
14060 void
14061 ClockClick (int which)
14062 {       // [HGM] code moved to back-end from winboard.c
14063         if(which) { // black clock
14064           if (gameMode == EditPosition || gameMode == IcsExamining) {
14065             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14066             SetBlackToPlayEvent();
14067           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14068           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14069           } else if (shiftKey) {
14070             AdjustClock(which, -1);
14071           } else if (gameMode == IcsPlayingWhite ||
14072                      gameMode == MachinePlaysBlack) {
14073             CallFlagEvent();
14074           }
14075         } else { // white clock
14076           if (gameMode == EditPosition || gameMode == IcsExamining) {
14077             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14078             SetWhiteToPlayEvent();
14079           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14080           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14081           } else if (shiftKey) {
14082             AdjustClock(which, -1);
14083           } else if (gameMode == IcsPlayingBlack ||
14084                    gameMode == MachinePlaysWhite) {
14085             CallFlagEvent();
14086           }
14087         }
14088 }
14089
14090 void
14091 DrawEvent ()
14092 {
14093     /* Offer draw or accept pending draw offer from opponent */
14094
14095     if (appData.icsActive) {
14096         /* Note: tournament rules require draw offers to be
14097            made after you make your move but before you punch
14098            your clock.  Currently ICS doesn't let you do that;
14099            instead, you immediately punch your clock after making
14100            a move, but you can offer a draw at any time. */
14101
14102         SendToICS(ics_prefix);
14103         SendToICS("draw\n");
14104         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14105     } else if (cmailMsgLoaded) {
14106         if (currentMove == cmailOldMove &&
14107             commentList[cmailOldMove] != NULL &&
14108             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14109                    "Black offers a draw" : "White offers a draw")) {
14110             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14111             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14112         } else if (currentMove == cmailOldMove + 1) {
14113             char *offer = WhiteOnMove(cmailOldMove) ?
14114               "White offers a draw" : "Black offers a draw";
14115             AppendComment(currentMove, offer, TRUE);
14116             DisplayComment(currentMove - 1, offer);
14117             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14118         } else {
14119             DisplayError(_("You must make your move before offering a draw"), 0);
14120             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14121         }
14122     } else if (first.offeredDraw) {
14123         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14124     } else {
14125         if (first.sendDrawOffers) {
14126             SendToProgram("draw\n", &first);
14127             userOfferedDraw = TRUE;
14128         }
14129     }
14130 }
14131
14132 void
14133 AdjournEvent ()
14134 {
14135     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14136
14137     if (appData.icsActive) {
14138         SendToICS(ics_prefix);
14139         SendToICS("adjourn\n");
14140     } else {
14141         /* Currently GNU Chess doesn't offer or accept Adjourns */
14142     }
14143 }
14144
14145
14146 void
14147 AbortEvent ()
14148 {
14149     /* Offer Abort or accept pending Abort offer from opponent */
14150
14151     if (appData.icsActive) {
14152         SendToICS(ics_prefix);
14153         SendToICS("abort\n");
14154     } else {
14155         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14156     }
14157 }
14158
14159 void
14160 ResignEvent ()
14161 {
14162     /* Resign.  You can do this even if it's not your turn. */
14163
14164     if (appData.icsActive) {
14165         SendToICS(ics_prefix);
14166         SendToICS("resign\n");
14167     } else {
14168         switch (gameMode) {
14169           case MachinePlaysWhite:
14170             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14171             break;
14172           case MachinePlaysBlack:
14173             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14174             break;
14175           case EditGame:
14176             if (cmailMsgLoaded) {
14177                 TruncateGame();
14178                 if (WhiteOnMove(cmailOldMove)) {
14179                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14180                 } else {
14181                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14182                 }
14183                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14184             }
14185             break;
14186           default:
14187             break;
14188         }
14189     }
14190 }
14191
14192
14193 void
14194 StopObservingEvent ()
14195 {
14196     /* Stop observing current games */
14197     SendToICS(ics_prefix);
14198     SendToICS("unobserve\n");
14199 }
14200
14201 void
14202 StopExaminingEvent ()
14203 {
14204     /* Stop observing current game */
14205     SendToICS(ics_prefix);
14206     SendToICS("unexamine\n");
14207 }
14208
14209 void
14210 ForwardInner (int target)
14211 {
14212     int limit; int oldSeekGraphUp = seekGraphUp;
14213
14214     if (appData.debugMode)
14215         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14216                 target, currentMove, forwardMostMove);
14217
14218     if (gameMode == EditPosition)
14219       return;
14220
14221     seekGraphUp = FALSE;
14222     MarkTargetSquares(1);
14223
14224     if (gameMode == PlayFromGameFile && !pausing)
14225       PauseEvent();
14226
14227     if (gameMode == IcsExamining && pausing)
14228       limit = pauseExamForwardMostMove;
14229     else
14230       limit = forwardMostMove;
14231
14232     if (target > limit) target = limit;
14233
14234     if (target > 0 && moveList[target - 1][0]) {
14235         int fromX, fromY, toX, toY;
14236         toX = moveList[target - 1][2] - AAA;
14237         toY = moveList[target - 1][3] - ONE;
14238         if (moveList[target - 1][1] == '@') {
14239             if (appData.highlightLastMove) {
14240                 SetHighlights(-1, -1, toX, toY);
14241             }
14242         } else {
14243             fromX = moveList[target - 1][0] - AAA;
14244             fromY = moveList[target - 1][1] - ONE;
14245             if (target == currentMove + 1) {
14246                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14247             }
14248             if (appData.highlightLastMove) {
14249                 SetHighlights(fromX, fromY, toX, toY);
14250             }
14251         }
14252     }
14253     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14254         gameMode == Training || gameMode == PlayFromGameFile ||
14255         gameMode == AnalyzeFile) {
14256         while (currentMove < target) {
14257             SendMoveToProgram(currentMove++, &first);
14258         }
14259     } else {
14260         currentMove = target;
14261     }
14262
14263     if (gameMode == EditGame || gameMode == EndOfGame) {
14264         whiteTimeRemaining = timeRemaining[0][currentMove];
14265         blackTimeRemaining = timeRemaining[1][currentMove];
14266     }
14267     DisplayBothClocks();
14268     DisplayMove(currentMove - 1);
14269     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14270     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14271     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14272         DisplayComment(currentMove - 1, commentList[currentMove]);
14273     }
14274 }
14275
14276
14277 void
14278 ForwardEvent ()
14279 {
14280     if (gameMode == IcsExamining && !pausing) {
14281         SendToICS(ics_prefix);
14282         SendToICS("forward\n");
14283     } else {
14284         ForwardInner(currentMove + 1);
14285     }
14286 }
14287
14288 void
14289 ToEndEvent ()
14290 {
14291     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14292         /* to optimze, we temporarily turn off analysis mode while we feed
14293          * the remaining moves to the engine. Otherwise we get analysis output
14294          * after each move.
14295          */
14296         if (first.analysisSupport) {
14297           SendToProgram("exit\nforce\n", &first);
14298           first.analyzing = FALSE;
14299         }
14300     }
14301
14302     if (gameMode == IcsExamining && !pausing) {
14303         SendToICS(ics_prefix);
14304         SendToICS("forward 999999\n");
14305     } else {
14306         ForwardInner(forwardMostMove);
14307     }
14308
14309     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14310         /* we have fed all the moves, so reactivate analysis mode */
14311         SendToProgram("analyze\n", &first);
14312         first.analyzing = TRUE;
14313         /*first.maybeThinking = TRUE;*/
14314         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14315     }
14316 }
14317
14318 void
14319 BackwardInner (int target)
14320 {
14321     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14322
14323     if (appData.debugMode)
14324         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14325                 target, currentMove, forwardMostMove);
14326
14327     if (gameMode == EditPosition) return;
14328     seekGraphUp = FALSE;
14329     MarkTargetSquares(1);
14330     if (currentMove <= backwardMostMove) {
14331         ClearHighlights();
14332         DrawPosition(full_redraw, boards[currentMove]);
14333         return;
14334     }
14335     if (gameMode == PlayFromGameFile && !pausing)
14336       PauseEvent();
14337
14338     if (moveList[target][0]) {
14339         int fromX, fromY, toX, toY;
14340         toX = moveList[target][2] - AAA;
14341         toY = moveList[target][3] - ONE;
14342         if (moveList[target][1] == '@') {
14343             if (appData.highlightLastMove) {
14344                 SetHighlights(-1, -1, toX, toY);
14345             }
14346         } else {
14347             fromX = moveList[target][0] - AAA;
14348             fromY = moveList[target][1] - ONE;
14349             if (target == currentMove - 1) {
14350                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14351             }
14352             if (appData.highlightLastMove) {
14353                 SetHighlights(fromX, fromY, toX, toY);
14354             }
14355         }
14356     }
14357     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14358         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14359         while (currentMove > target) {
14360             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14361                 // null move cannot be undone. Reload program with move history before it.
14362                 int i;
14363                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14364                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14365                 }
14366                 SendBoard(&first, i); 
14367                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14368                 break;
14369             }
14370             SendToProgram("undo\n", &first);
14371             currentMove--;
14372         }
14373     } else {
14374         currentMove = target;
14375     }
14376
14377     if (gameMode == EditGame || gameMode == EndOfGame) {
14378         whiteTimeRemaining = timeRemaining[0][currentMove];
14379         blackTimeRemaining = timeRemaining[1][currentMove];
14380     }
14381     DisplayBothClocks();
14382     DisplayMove(currentMove - 1);
14383     DrawPosition(full_redraw, boards[currentMove]);
14384     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14385     // [HGM] PV info: routine tests if comment empty
14386     DisplayComment(currentMove - 1, commentList[currentMove]);
14387 }
14388
14389 void
14390 BackwardEvent ()
14391 {
14392     if (gameMode == IcsExamining && !pausing) {
14393         SendToICS(ics_prefix);
14394         SendToICS("backward\n");
14395     } else {
14396         BackwardInner(currentMove - 1);
14397     }
14398 }
14399
14400 void
14401 ToStartEvent ()
14402 {
14403     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14404         /* to optimize, we temporarily turn off analysis mode while we undo
14405          * all the moves. Otherwise we get analysis output after each undo.
14406          */
14407         if (first.analysisSupport) {
14408           SendToProgram("exit\nforce\n", &first);
14409           first.analyzing = FALSE;
14410         }
14411     }
14412
14413     if (gameMode == IcsExamining && !pausing) {
14414         SendToICS(ics_prefix);
14415         SendToICS("backward 999999\n");
14416     } else {
14417         BackwardInner(backwardMostMove);
14418     }
14419
14420     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14421         /* we have fed all the moves, so reactivate analysis mode */
14422         SendToProgram("analyze\n", &first);
14423         first.analyzing = TRUE;
14424         /*first.maybeThinking = TRUE;*/
14425         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14426     }
14427 }
14428
14429 void
14430 ToNrEvent (int to)
14431 {
14432   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14433   if (to >= forwardMostMove) to = forwardMostMove;
14434   if (to <= backwardMostMove) to = backwardMostMove;
14435   if (to < currentMove) {
14436     BackwardInner(to);
14437   } else {
14438     ForwardInner(to);
14439   }
14440 }
14441
14442 void
14443 RevertEvent (Boolean annotate)
14444 {
14445     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14446         return;
14447     }
14448     if (gameMode != IcsExamining) {
14449         DisplayError(_("You are not examining a game"), 0);
14450         return;
14451     }
14452     if (pausing) {
14453         DisplayError(_("You can't revert while pausing"), 0);
14454         return;
14455     }
14456     SendToICS(ics_prefix);
14457     SendToICS("revert\n");
14458 }
14459
14460 void
14461 RetractMoveEvent ()
14462 {
14463     switch (gameMode) {
14464       case MachinePlaysWhite:
14465       case MachinePlaysBlack:
14466         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14467             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14468             return;
14469         }
14470         if (forwardMostMove < 2) return;
14471         currentMove = forwardMostMove = forwardMostMove - 2;
14472         whiteTimeRemaining = timeRemaining[0][currentMove];
14473         blackTimeRemaining = timeRemaining[1][currentMove];
14474         DisplayBothClocks();
14475         DisplayMove(currentMove - 1);
14476         ClearHighlights();/*!! could figure this out*/
14477         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14478         SendToProgram("remove\n", &first);
14479         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14480         break;
14481
14482       case BeginningOfGame:
14483       default:
14484         break;
14485
14486       case IcsPlayingWhite:
14487       case IcsPlayingBlack:
14488         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14489             SendToICS(ics_prefix);
14490             SendToICS("takeback 2\n");
14491         } else {
14492             SendToICS(ics_prefix);
14493             SendToICS("takeback 1\n");
14494         }
14495         break;
14496     }
14497 }
14498
14499 void
14500 MoveNowEvent ()
14501 {
14502     ChessProgramState *cps;
14503
14504     switch (gameMode) {
14505       case MachinePlaysWhite:
14506         if (!WhiteOnMove(forwardMostMove)) {
14507             DisplayError(_("It is your turn"), 0);
14508             return;
14509         }
14510         cps = &first;
14511         break;
14512       case MachinePlaysBlack:
14513         if (WhiteOnMove(forwardMostMove)) {
14514             DisplayError(_("It is your turn"), 0);
14515             return;
14516         }
14517         cps = &first;
14518         break;
14519       case TwoMachinesPlay:
14520         if (WhiteOnMove(forwardMostMove) ==
14521             (first.twoMachinesColor[0] == 'w')) {
14522             cps = &first;
14523         } else {
14524             cps = &second;
14525         }
14526         break;
14527       case BeginningOfGame:
14528       default:
14529         return;
14530     }
14531     SendToProgram("?\n", cps);
14532 }
14533
14534 void
14535 TruncateGameEvent ()
14536 {
14537     EditGameEvent();
14538     if (gameMode != EditGame) return;
14539     TruncateGame();
14540 }
14541
14542 void
14543 TruncateGame ()
14544 {
14545     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14546     if (forwardMostMove > currentMove) {
14547         if (gameInfo.resultDetails != NULL) {
14548             free(gameInfo.resultDetails);
14549             gameInfo.resultDetails = NULL;
14550             gameInfo.result = GameUnfinished;
14551         }
14552         forwardMostMove = currentMove;
14553         HistorySet(parseList, backwardMostMove, forwardMostMove,
14554                    currentMove-1);
14555     }
14556 }
14557
14558 void
14559 HintEvent ()
14560 {
14561     if (appData.noChessProgram) return;
14562     switch (gameMode) {
14563       case MachinePlaysWhite:
14564         if (WhiteOnMove(forwardMostMove)) {
14565             DisplayError(_("Wait until your turn"), 0);
14566             return;
14567         }
14568         break;
14569       case BeginningOfGame:
14570       case MachinePlaysBlack:
14571         if (!WhiteOnMove(forwardMostMove)) {
14572             DisplayError(_("Wait until your turn"), 0);
14573             return;
14574         }
14575         break;
14576       default:
14577         DisplayError(_("No hint available"), 0);
14578         return;
14579     }
14580     SendToProgram("hint\n", &first);
14581     hintRequested = TRUE;
14582 }
14583
14584 void
14585 BookEvent ()
14586 {
14587     if (appData.noChessProgram) return;
14588     switch (gameMode) {
14589       case MachinePlaysWhite:
14590         if (WhiteOnMove(forwardMostMove)) {
14591             DisplayError(_("Wait until your turn"), 0);
14592             return;
14593         }
14594         break;
14595       case BeginningOfGame:
14596       case MachinePlaysBlack:
14597         if (!WhiteOnMove(forwardMostMove)) {
14598             DisplayError(_("Wait until your turn"), 0);
14599             return;
14600         }
14601         break;
14602       case EditPosition:
14603         EditPositionDone(TRUE);
14604         break;
14605       case TwoMachinesPlay:
14606         return;
14607       default:
14608         break;
14609     }
14610     SendToProgram("bk\n", &first);
14611     bookOutput[0] = NULLCHAR;
14612     bookRequested = TRUE;
14613 }
14614
14615 void
14616 AboutGameEvent ()
14617 {
14618     char *tags = PGNTags(&gameInfo);
14619     TagsPopUp(tags, CmailMsg());
14620     free(tags);
14621 }
14622
14623 /* end button procedures */
14624
14625 void
14626 PrintPosition (FILE *fp, int move)
14627 {
14628     int i, j;
14629
14630     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14631         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14632             char c = PieceToChar(boards[move][i][j]);
14633             fputc(c == 'x' ? '.' : c, fp);
14634             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14635         }
14636     }
14637     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14638       fprintf(fp, "white to play\n");
14639     else
14640       fprintf(fp, "black to play\n");
14641 }
14642
14643 void
14644 PrintOpponents (FILE *fp)
14645 {
14646     if (gameInfo.white != NULL) {
14647         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14648     } else {
14649         fprintf(fp, "\n");
14650     }
14651 }
14652
14653 /* Find last component of program's own name, using some heuristics */
14654 void
14655 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14656 {
14657     char *p, *q, c;
14658     int local = (strcmp(host, "localhost") == 0);
14659     while (!local && (p = strchr(prog, ';')) != NULL) {
14660         p++;
14661         while (*p == ' ') p++;
14662         prog = p;
14663     }
14664     if (*prog == '"' || *prog == '\'') {
14665         q = strchr(prog + 1, *prog);
14666     } else {
14667         q = strchr(prog, ' ');
14668     }
14669     if (q == NULL) q = prog + strlen(prog);
14670     p = q;
14671     while (p >= prog && *p != '/' && *p != '\\') p--;
14672     p++;
14673     if(p == prog && *p == '"') p++;
14674     c = *q; *q = 0;
14675     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14676     memcpy(buf, p, q - p);
14677     buf[q - p] = NULLCHAR;
14678     if (!local) {
14679         strcat(buf, "@");
14680         strcat(buf, host);
14681     }
14682 }
14683
14684 char *
14685 TimeControlTagValue ()
14686 {
14687     char buf[MSG_SIZ];
14688     if (!appData.clockMode) {
14689       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14690     } else if (movesPerSession > 0) {
14691       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14692     } else if (timeIncrement == 0) {
14693       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14694     } else {
14695       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14696     }
14697     return StrSave(buf);
14698 }
14699
14700 void
14701 SetGameInfo ()
14702 {
14703     /* This routine is used only for certain modes */
14704     VariantClass v = gameInfo.variant;
14705     ChessMove r = GameUnfinished;
14706     char *p = NULL;
14707
14708     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14709         r = gameInfo.result;
14710         p = gameInfo.resultDetails;
14711         gameInfo.resultDetails = NULL;
14712     }
14713     ClearGameInfo(&gameInfo);
14714     gameInfo.variant = v;
14715
14716     switch (gameMode) {
14717       case MachinePlaysWhite:
14718         gameInfo.event = StrSave( appData.pgnEventHeader );
14719         gameInfo.site = StrSave(HostName());
14720         gameInfo.date = PGNDate();
14721         gameInfo.round = StrSave("-");
14722         gameInfo.white = StrSave(first.tidy);
14723         gameInfo.black = StrSave(UserName());
14724         gameInfo.timeControl = TimeControlTagValue();
14725         break;
14726
14727       case MachinePlaysBlack:
14728         gameInfo.event = StrSave( appData.pgnEventHeader );
14729         gameInfo.site = StrSave(HostName());
14730         gameInfo.date = PGNDate();
14731         gameInfo.round = StrSave("-");
14732         gameInfo.white = StrSave(UserName());
14733         gameInfo.black = StrSave(first.tidy);
14734         gameInfo.timeControl = TimeControlTagValue();
14735         break;
14736
14737       case TwoMachinesPlay:
14738         gameInfo.event = StrSave( appData.pgnEventHeader );
14739         gameInfo.site = StrSave(HostName());
14740         gameInfo.date = PGNDate();
14741         if (roundNr > 0) {
14742             char buf[MSG_SIZ];
14743             snprintf(buf, MSG_SIZ, "%d", roundNr);
14744             gameInfo.round = StrSave(buf);
14745         } else {
14746             gameInfo.round = StrSave("-");
14747         }
14748         if (first.twoMachinesColor[0] == 'w') {
14749             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14750             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14751         } else {
14752             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14753             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14754         }
14755         gameInfo.timeControl = TimeControlTagValue();
14756         break;
14757
14758       case EditGame:
14759         gameInfo.event = StrSave("Edited game");
14760         gameInfo.site = StrSave(HostName());
14761         gameInfo.date = PGNDate();
14762         gameInfo.round = StrSave("-");
14763         gameInfo.white = StrSave("-");
14764         gameInfo.black = StrSave("-");
14765         gameInfo.result = r;
14766         gameInfo.resultDetails = p;
14767         break;
14768
14769       case EditPosition:
14770         gameInfo.event = StrSave("Edited position");
14771         gameInfo.site = StrSave(HostName());
14772         gameInfo.date = PGNDate();
14773         gameInfo.round = StrSave("-");
14774         gameInfo.white = StrSave("-");
14775         gameInfo.black = StrSave("-");
14776         break;
14777
14778       case IcsPlayingWhite:
14779       case IcsPlayingBlack:
14780       case IcsObserving:
14781       case IcsExamining:
14782         break;
14783
14784       case PlayFromGameFile:
14785         gameInfo.event = StrSave("Game from non-PGN file");
14786         gameInfo.site = StrSave(HostName());
14787         gameInfo.date = PGNDate();
14788         gameInfo.round = StrSave("-");
14789         gameInfo.white = StrSave("?");
14790         gameInfo.black = StrSave("?");
14791         break;
14792
14793       default:
14794         break;
14795     }
14796 }
14797
14798 void
14799 ReplaceComment (int index, char *text)
14800 {
14801     int len;
14802     char *p;
14803     float score;
14804
14805     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14806        pvInfoList[index-1].depth == len &&
14807        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14808        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14809     while (*text == '\n') text++;
14810     len = strlen(text);
14811     while (len > 0 && text[len - 1] == '\n') len--;
14812
14813     if (commentList[index] != NULL)
14814       free(commentList[index]);
14815
14816     if (len == 0) {
14817         commentList[index] = NULL;
14818         return;
14819     }
14820   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14821       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14822       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14823     commentList[index] = (char *) malloc(len + 2);
14824     strncpy(commentList[index], text, len);
14825     commentList[index][len] = '\n';
14826     commentList[index][len + 1] = NULLCHAR;
14827   } else {
14828     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14829     char *p;
14830     commentList[index] = (char *) malloc(len + 7);
14831     safeStrCpy(commentList[index], "{\n", 3);
14832     safeStrCpy(commentList[index]+2, text, len+1);
14833     commentList[index][len+2] = NULLCHAR;
14834     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14835     strcat(commentList[index], "\n}\n");
14836   }
14837 }
14838
14839 void
14840 CrushCRs (char *text)
14841 {
14842   char *p = text;
14843   char *q = text;
14844   char ch;
14845
14846   do {
14847     ch = *p++;
14848     if (ch == '\r') continue;
14849     *q++ = ch;
14850   } while (ch != '\0');
14851 }
14852
14853 void
14854 AppendComment (int index, char *text, Boolean addBraces)
14855 /* addBraces  tells if we should add {} */
14856 {
14857     int oldlen, len;
14858     char *old;
14859
14860 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14861     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14862
14863     CrushCRs(text);
14864     while (*text == '\n') text++;
14865     len = strlen(text);
14866     while (len > 0 && text[len - 1] == '\n') len--;
14867     text[len] = NULLCHAR;
14868
14869     if (len == 0) return;
14870
14871     if (commentList[index] != NULL) {
14872       Boolean addClosingBrace = addBraces;
14873         old = commentList[index];
14874         oldlen = strlen(old);
14875         while(commentList[index][oldlen-1] ==  '\n')
14876           commentList[index][--oldlen] = NULLCHAR;
14877         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14878         safeStrCpy(commentList[index], old, oldlen + len + 6);
14879         free(old);
14880         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14881         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14882           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14883           while (*text == '\n') { text++; len--; }
14884           commentList[index][--oldlen] = NULLCHAR;
14885       }
14886         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14887         else          strcat(commentList[index], "\n");
14888         strcat(commentList[index], text);
14889         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14890         else          strcat(commentList[index], "\n");
14891     } else {
14892         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14893         if(addBraces)
14894           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14895         else commentList[index][0] = NULLCHAR;
14896         strcat(commentList[index], text);
14897         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14898         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14899     }
14900 }
14901
14902 static char *
14903 FindStr (char * text, char * sub_text)
14904 {
14905     char * result = strstr( text, sub_text );
14906
14907     if( result != NULL ) {
14908         result += strlen( sub_text );
14909     }
14910
14911     return result;
14912 }
14913
14914 /* [AS] Try to extract PV info from PGN comment */
14915 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14916 char *
14917 GetInfoFromComment (int index, char * text)
14918 {
14919     char * sep = text, *p;
14920
14921     if( text != NULL && index > 0 ) {
14922         int score = 0;
14923         int depth = 0;
14924         int time = -1, sec = 0, deci;
14925         char * s_eval = FindStr( text, "[%eval " );
14926         char * s_emt = FindStr( text, "[%emt " );
14927
14928         if( s_eval != NULL || s_emt != NULL ) {
14929             /* New style */
14930             char delim;
14931
14932             if( s_eval != NULL ) {
14933                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14934                     return text;
14935                 }
14936
14937                 if( delim != ']' ) {
14938                     return text;
14939                 }
14940             }
14941
14942             if( s_emt != NULL ) {
14943             }
14944                 return text;
14945         }
14946         else {
14947             /* We expect something like: [+|-]nnn.nn/dd */
14948             int score_lo = 0;
14949
14950             if(*text != '{') return text; // [HGM] braces: must be normal comment
14951
14952             sep = strchr( text, '/' );
14953             if( sep == NULL || sep < (text+4) ) {
14954                 return text;
14955             }
14956
14957             p = text;
14958             if(p[1] == '(') { // comment starts with PV
14959                p = strchr(p, ')'); // locate end of PV
14960                if(p == NULL || sep < p+5) return text;
14961                // at this point we have something like "{(.*) +0.23/6 ..."
14962                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14963                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14964                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14965             }
14966             time = -1; sec = -1; deci = -1;
14967             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14968                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14969                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14970                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14971                 return text;
14972             }
14973
14974             if( score_lo < 0 || score_lo >= 100 ) {
14975                 return text;
14976             }
14977
14978             if(sec >= 0) time = 600*time + 10*sec; else
14979             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14980
14981             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14982
14983             /* [HGM] PV time: now locate end of PV info */
14984             while( *++sep >= '0' && *sep <= '9'); // strip depth
14985             if(time >= 0)
14986             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14987             if(sec >= 0)
14988             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14989             if(deci >= 0)
14990             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14991             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14992         }
14993
14994         if( depth <= 0 ) {
14995             return text;
14996         }
14997
14998         if( time < 0 ) {
14999             time = -1;
15000         }
15001
15002         pvInfoList[index-1].depth = depth;
15003         pvInfoList[index-1].score = score;
15004         pvInfoList[index-1].time  = 10*time; // centi-sec
15005         if(*sep == '}') *sep = 0; else *--sep = '{';
15006         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15007     }
15008     return sep;
15009 }
15010
15011 void
15012 SendToProgram (char *message, ChessProgramState *cps)
15013 {
15014     int count, outCount, error;
15015     char buf[MSG_SIZ];
15016
15017     if (cps->pr == NoProc) return;
15018     Attention(cps);
15019
15020     if (appData.debugMode) {
15021         TimeMark now;
15022         GetTimeMark(&now);
15023         fprintf(debugFP, "%ld >%-6s: %s",
15024                 SubtractTimeMarks(&now, &programStartTime),
15025                 cps->which, message);
15026     }
15027
15028     count = strlen(message);
15029     outCount = OutputToProcess(cps->pr, message, count, &error);
15030     if (outCount < count && !exiting
15031                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15032       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15033       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15034         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15035             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15036                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15037                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15038                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15039             } else {
15040                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15041                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15042                 gameInfo.result = res;
15043             }
15044             gameInfo.resultDetails = StrSave(buf);
15045         }
15046         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15047         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15048     }
15049 }
15050
15051 void
15052 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15053 {
15054     char *end_str;
15055     char buf[MSG_SIZ];
15056     ChessProgramState *cps = (ChessProgramState *)closure;
15057
15058     if (isr != cps->isr) return; /* Killed intentionally */
15059     if (count <= 0) {
15060         if (count == 0) {
15061             RemoveInputSource(cps->isr);
15062             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15063             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15064                     _(cps->which), cps->program);
15065         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15066                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15067                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15068                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15069                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15070                 } else {
15071                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15072                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15073                     gameInfo.result = res;
15074                 }
15075                 gameInfo.resultDetails = StrSave(buf);
15076             }
15077             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15078             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15079         } else {
15080             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15081                     _(cps->which), cps->program);
15082             RemoveInputSource(cps->isr);
15083
15084             /* [AS] Program is misbehaving badly... kill it */
15085             if( count == -2 ) {
15086                 DestroyChildProcess( cps->pr, 9 );
15087                 cps->pr = NoProc;
15088             }
15089
15090             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15091         }
15092         return;
15093     }
15094
15095     if ((end_str = strchr(message, '\r')) != NULL)
15096       *end_str = NULLCHAR;
15097     if ((end_str = strchr(message, '\n')) != NULL)
15098       *end_str = NULLCHAR;
15099
15100     if (appData.debugMode) {
15101         TimeMark now; int print = 1;
15102         char *quote = ""; char c; int i;
15103
15104         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15105                 char start = message[0];
15106                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15107                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15108                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15109                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15110                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15111                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15112                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15113                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15114                    sscanf(message, "hint: %c", &c)!=1 && 
15115                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15116                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15117                     print = (appData.engineComments >= 2);
15118                 }
15119                 message[0] = start; // restore original message
15120         }
15121         if(print) {
15122                 GetTimeMark(&now);
15123                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15124                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15125                         quote,
15126                         message);
15127         }
15128     }
15129
15130     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15131     if (appData.icsEngineAnalyze) {
15132         if (strstr(message, "whisper") != NULL ||
15133              strstr(message, "kibitz") != NULL ||
15134             strstr(message, "tellics") != NULL) return;
15135     }
15136
15137     HandleMachineMove(message, cps);
15138 }
15139
15140
15141 void
15142 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15143 {
15144     char buf[MSG_SIZ];
15145     int seconds;
15146
15147     if( timeControl_2 > 0 ) {
15148         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15149             tc = timeControl_2;
15150         }
15151     }
15152     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15153     inc /= cps->timeOdds;
15154     st  /= cps->timeOdds;
15155
15156     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15157
15158     if (st > 0) {
15159       /* Set exact time per move, normally using st command */
15160       if (cps->stKludge) {
15161         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15162         seconds = st % 60;
15163         if (seconds == 0) {
15164           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15165         } else {
15166           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15167         }
15168       } else {
15169         snprintf(buf, MSG_SIZ, "st %d\n", st);
15170       }
15171     } else {
15172       /* Set conventional or incremental time control, using level command */
15173       if (seconds == 0) {
15174         /* Note old gnuchess bug -- minutes:seconds used to not work.
15175            Fixed in later versions, but still avoid :seconds
15176            when seconds is 0. */
15177         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15178       } else {
15179         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15180                  seconds, inc/1000.);
15181       }
15182     }
15183     SendToProgram(buf, cps);
15184
15185     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15186     /* Orthogonally, limit search to given depth */
15187     if (sd > 0) {
15188       if (cps->sdKludge) {
15189         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15190       } else {
15191         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15192       }
15193       SendToProgram(buf, cps);
15194     }
15195
15196     if(cps->nps >= 0) { /* [HGM] nps */
15197         if(cps->supportsNPS == FALSE)
15198           cps->nps = -1; // don't use if engine explicitly says not supported!
15199         else {
15200           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15201           SendToProgram(buf, cps);
15202         }
15203     }
15204 }
15205
15206 ChessProgramState *
15207 WhitePlayer ()
15208 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15209 {
15210     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15211        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15212         return &second;
15213     return &first;
15214 }
15215
15216 void
15217 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15218 {
15219     char message[MSG_SIZ];
15220     long time, otime;
15221
15222     /* Note: this routine must be called when the clocks are stopped
15223        or when they have *just* been set or switched; otherwise
15224        it will be off by the time since the current tick started.
15225     */
15226     if (machineWhite) {
15227         time = whiteTimeRemaining / 10;
15228         otime = blackTimeRemaining / 10;
15229     } else {
15230         time = blackTimeRemaining / 10;
15231         otime = whiteTimeRemaining / 10;
15232     }
15233     /* [HGM] translate opponent's time by time-odds factor */
15234     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15235
15236     if (time <= 0) time = 1;
15237     if (otime <= 0) otime = 1;
15238
15239     snprintf(message, MSG_SIZ, "time %ld\n", time);
15240     SendToProgram(message, cps);
15241
15242     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15243     SendToProgram(message, cps);
15244 }
15245
15246 int
15247 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15248 {
15249   char buf[MSG_SIZ];
15250   int len = strlen(name);
15251   int val;
15252
15253   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15254     (*p) += len + 1;
15255     sscanf(*p, "%d", &val);
15256     *loc = (val != 0);
15257     while (**p && **p != ' ')
15258       (*p)++;
15259     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15260     SendToProgram(buf, cps);
15261     return TRUE;
15262   }
15263   return FALSE;
15264 }
15265
15266 int
15267 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15268 {
15269   char buf[MSG_SIZ];
15270   int len = strlen(name);
15271   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15272     (*p) += len + 1;
15273     sscanf(*p, "%d", loc);
15274     while (**p && **p != ' ') (*p)++;
15275     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15276     SendToProgram(buf, cps);
15277     return TRUE;
15278   }
15279   return FALSE;
15280 }
15281
15282 int
15283 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15284 {
15285   char buf[MSG_SIZ];
15286   int len = strlen(name);
15287   if (strncmp((*p), name, len) == 0
15288       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15289     (*p) += len + 2;
15290     sscanf(*p, "%[^\"]", loc);
15291     while (**p && **p != '\"') (*p)++;
15292     if (**p == '\"') (*p)++;
15293     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15294     SendToProgram(buf, cps);
15295     return TRUE;
15296   }
15297   return FALSE;
15298 }
15299
15300 int
15301 ParseOption (Option *opt, ChessProgramState *cps)
15302 // [HGM] options: process the string that defines an engine option, and determine
15303 // name, type, default value, and allowed value range
15304 {
15305         char *p, *q, buf[MSG_SIZ];
15306         int n, min = (-1)<<31, max = 1<<31, def;
15307
15308         if(p = strstr(opt->name, " -spin ")) {
15309             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15310             if(max < min) max = min; // enforce consistency
15311             if(def < min) def = min;
15312             if(def > max) def = max;
15313             opt->value = def;
15314             opt->min = min;
15315             opt->max = max;
15316             opt->type = Spin;
15317         } else if((p = strstr(opt->name, " -slider "))) {
15318             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15319             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15320             if(max < min) max = min; // enforce consistency
15321             if(def < min) def = min;
15322             if(def > max) def = max;
15323             opt->value = def;
15324             opt->min = min;
15325             opt->max = max;
15326             opt->type = Spin; // Slider;
15327         } else if((p = strstr(opt->name, " -string "))) {
15328             opt->textValue = p+9;
15329             opt->type = TextBox;
15330         } else if((p = strstr(opt->name, " -file "))) {
15331             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15332             opt->textValue = p+7;
15333             opt->type = FileName; // FileName;
15334         } else if((p = strstr(opt->name, " -path "))) {
15335             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15336             opt->textValue = p+7;
15337             opt->type = PathName; // PathName;
15338         } else if(p = strstr(opt->name, " -check ")) {
15339             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15340             opt->value = (def != 0);
15341             opt->type = CheckBox;
15342         } else if(p = strstr(opt->name, " -combo ")) {
15343             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15344             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15345             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15346             opt->value = n = 0;
15347             while(q = StrStr(q, " /// ")) {
15348                 n++; *q = 0;    // count choices, and null-terminate each of them
15349                 q += 5;
15350                 if(*q == '*') { // remember default, which is marked with * prefix
15351                     q++;
15352                     opt->value = n;
15353                 }
15354                 cps->comboList[cps->comboCnt++] = q;
15355             }
15356             cps->comboList[cps->comboCnt++] = NULL;
15357             opt->max = n + 1;
15358             opt->type = ComboBox;
15359         } else if(p = strstr(opt->name, " -button")) {
15360             opt->type = Button;
15361         } else if(p = strstr(opt->name, " -save")) {
15362             opt->type = SaveButton;
15363         } else return FALSE;
15364         *p = 0; // terminate option name
15365         // now look if the command-line options define a setting for this engine option.
15366         if(cps->optionSettings && cps->optionSettings[0])
15367             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15368         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15369           snprintf(buf, MSG_SIZ, "option %s", p);
15370                 if(p = strstr(buf, ",")) *p = 0;
15371                 if(q = strchr(buf, '=')) switch(opt->type) {
15372                     case ComboBox:
15373                         for(n=0; n<opt->max; n++)
15374                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15375                         break;
15376                     case TextBox:
15377                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15378                         break;
15379                     case Spin:
15380                     case CheckBox:
15381                         opt->value = atoi(q+1);
15382                     default:
15383                         break;
15384                 }
15385                 strcat(buf, "\n");
15386                 SendToProgram(buf, cps);
15387         }
15388         return TRUE;
15389 }
15390
15391 void
15392 FeatureDone (ChessProgramState *cps, int val)
15393 {
15394   DelayedEventCallback cb = GetDelayedEvent();
15395   if ((cb == InitBackEnd3 && cps == &first) ||
15396       (cb == SettingsMenuIfReady && cps == &second) ||
15397       (cb == LoadEngine) ||
15398       (cb == TwoMachinesEventIfReady)) {
15399     CancelDelayedEvent();
15400     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15401   }
15402   cps->initDone = val;
15403 }
15404
15405 /* Parse feature command from engine */
15406 void
15407 ParseFeatures (char *args, ChessProgramState *cps)
15408 {
15409   char *p = args;
15410   char *q;
15411   int val;
15412   char buf[MSG_SIZ];
15413
15414   for (;;) {
15415     while (*p == ' ') p++;
15416     if (*p == NULLCHAR) return;
15417
15418     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15419     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15420     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15421     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15422     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15423     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15424     if (BoolFeature(&p, "reuse", &val, cps)) {
15425       /* Engine can disable reuse, but can't enable it if user said no */
15426       if (!val) cps->reuse = FALSE;
15427       continue;
15428     }
15429     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15430     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15431       if (gameMode == TwoMachinesPlay) {
15432         DisplayTwoMachinesTitle();
15433       } else {
15434         DisplayTitle("");
15435       }
15436       continue;
15437     }
15438     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15439     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15440     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15441     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15442     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15443     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15444     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15445     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15446     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15447     if (IntFeature(&p, "done", &val, cps)) {
15448       FeatureDone(cps, val);
15449       continue;
15450     }
15451     /* Added by Tord: */
15452     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15453     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15454     /* End of additions by Tord */
15455
15456     /* [HGM] added features: */
15457     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15458     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15459     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15460     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15461     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15462     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15463     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15464         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15465           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15466             SendToProgram(buf, cps);
15467             continue;
15468         }
15469         if(cps->nrOptions >= MAX_OPTIONS) {
15470             cps->nrOptions--;
15471             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15472             DisplayError(buf, 0);
15473         }
15474         continue;
15475     }
15476     /* End of additions by HGM */
15477
15478     /* unknown feature: complain and skip */
15479     q = p;
15480     while (*q && *q != '=') q++;
15481     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15482     SendToProgram(buf, cps);
15483     p = q;
15484     if (*p == '=') {
15485       p++;
15486       if (*p == '\"') {
15487         p++;
15488         while (*p && *p != '\"') p++;
15489         if (*p == '\"') p++;
15490       } else {
15491         while (*p && *p != ' ') p++;
15492       }
15493     }
15494   }
15495
15496 }
15497
15498 void
15499 PeriodicUpdatesEvent (int newState)
15500 {
15501     if (newState == appData.periodicUpdates)
15502       return;
15503
15504     appData.periodicUpdates=newState;
15505
15506     /* Display type changes, so update it now */
15507 //    DisplayAnalysis();
15508
15509     /* Get the ball rolling again... */
15510     if (newState) {
15511         AnalysisPeriodicEvent(1);
15512         StartAnalysisClock();
15513     }
15514 }
15515
15516 void
15517 PonderNextMoveEvent (int newState)
15518 {
15519     if (newState == appData.ponderNextMove) return;
15520     if (gameMode == EditPosition) EditPositionDone(TRUE);
15521     if (newState) {
15522         SendToProgram("hard\n", &first);
15523         if (gameMode == TwoMachinesPlay) {
15524             SendToProgram("hard\n", &second);
15525         }
15526     } else {
15527         SendToProgram("easy\n", &first);
15528         thinkOutput[0] = NULLCHAR;
15529         if (gameMode == TwoMachinesPlay) {
15530             SendToProgram("easy\n", &second);
15531         }
15532     }
15533     appData.ponderNextMove = newState;
15534 }
15535
15536 void
15537 NewSettingEvent (int option, int *feature, char *command, int value)
15538 {
15539     char buf[MSG_SIZ];
15540
15541     if (gameMode == EditPosition) EditPositionDone(TRUE);
15542     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15543     if(feature == NULL || *feature) SendToProgram(buf, &first);
15544     if (gameMode == TwoMachinesPlay) {
15545         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15546     }
15547 }
15548
15549 void
15550 ShowThinkingEvent ()
15551 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15552 {
15553     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15554     int newState = appData.showThinking
15555         // [HGM] thinking: other features now need thinking output as well
15556         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15557
15558     if (oldState == newState) return;
15559     oldState = newState;
15560     if (gameMode == EditPosition) EditPositionDone(TRUE);
15561     if (oldState) {
15562         SendToProgram("post\n", &first);
15563         if (gameMode == TwoMachinesPlay) {
15564             SendToProgram("post\n", &second);
15565         }
15566     } else {
15567         SendToProgram("nopost\n", &first);
15568         thinkOutput[0] = NULLCHAR;
15569         if (gameMode == TwoMachinesPlay) {
15570             SendToProgram("nopost\n", &second);
15571         }
15572     }
15573 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15574 }
15575
15576 void
15577 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15578 {
15579   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15580   if (pr == NoProc) return;
15581   AskQuestion(title, question, replyPrefix, pr);
15582 }
15583
15584 void
15585 TypeInEvent (char firstChar)
15586 {
15587     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15588         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15589         gameMode == AnalyzeMode || gameMode == EditGame || 
15590         gameMode == EditPosition || gameMode == IcsExamining ||
15591         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15592         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15593                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15594                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15595         gameMode == Training) PopUpMoveDialog(firstChar);
15596 }
15597
15598 void
15599 TypeInDoneEvent (char *move)
15600 {
15601         Board board;
15602         int n, fromX, fromY, toX, toY;
15603         char promoChar;
15604         ChessMove moveType;
15605
15606         // [HGM] FENedit
15607         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15608                 EditPositionPasteFEN(move);
15609                 return;
15610         }
15611         // [HGM] movenum: allow move number to be typed in any mode
15612         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15613           ToNrEvent(2*n-1);
15614           return;
15615         }
15616         // undocumented kludge: allow command-line option to be typed in!
15617         // (potentially fatal, and does not implement the effect of the option.)
15618         // should only be used for options that are values on which future decisions will be made,
15619         // and definitely not on options that would be used during initialization.
15620         if(strstr(move, "!!! -") == move) {
15621             ParseArgsFromString(move+4);
15622             return;
15623         }
15624
15625       if (gameMode != EditGame && currentMove != forwardMostMove && 
15626         gameMode != Training) {
15627         DisplayMoveError(_("Displayed move is not current"));
15628       } else {
15629         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15630           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15631         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15632         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15633           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15634           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15635         } else {
15636           DisplayMoveError(_("Could not parse move"));
15637         }
15638       }
15639 }
15640
15641 void
15642 DisplayMove (int moveNumber)
15643 {
15644     char message[MSG_SIZ];
15645     char res[MSG_SIZ];
15646     char cpThinkOutput[MSG_SIZ];
15647
15648     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15649
15650     if (moveNumber == forwardMostMove - 1 ||
15651         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15652
15653         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15654
15655         if (strchr(cpThinkOutput, '\n')) {
15656             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15657         }
15658     } else {
15659         *cpThinkOutput = NULLCHAR;
15660     }
15661
15662     /* [AS] Hide thinking from human user */
15663     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15664         *cpThinkOutput = NULLCHAR;
15665         if( thinkOutput[0] != NULLCHAR ) {
15666             int i;
15667
15668             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15669                 cpThinkOutput[i] = '.';
15670             }
15671             cpThinkOutput[i] = NULLCHAR;
15672             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15673         }
15674     }
15675
15676     if (moveNumber == forwardMostMove - 1 &&
15677         gameInfo.resultDetails != NULL) {
15678         if (gameInfo.resultDetails[0] == NULLCHAR) {
15679           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15680         } else {
15681           snprintf(res, MSG_SIZ, " {%s} %s",
15682                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15683         }
15684     } else {
15685         res[0] = NULLCHAR;
15686     }
15687
15688     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15689         DisplayMessage(res, cpThinkOutput);
15690     } else {
15691       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15692                 WhiteOnMove(moveNumber) ? " " : ".. ",
15693                 parseList[moveNumber], res);
15694         DisplayMessage(message, cpThinkOutput);
15695     }
15696 }
15697
15698 void
15699 DisplayComment (int moveNumber, char *text)
15700 {
15701     char title[MSG_SIZ];
15702
15703     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15704       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15705     } else {
15706       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15707               WhiteOnMove(moveNumber) ? " " : ".. ",
15708               parseList[moveNumber]);
15709     }
15710     if (text != NULL && (appData.autoDisplayComment || commentUp))
15711         CommentPopUp(title, text);
15712 }
15713
15714 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15715  * might be busy thinking or pondering.  It can be omitted if your
15716  * gnuchess is configured to stop thinking immediately on any user
15717  * input.  However, that gnuchess feature depends on the FIONREAD
15718  * ioctl, which does not work properly on some flavors of Unix.
15719  */
15720 void
15721 Attention (ChessProgramState *cps)
15722 {
15723 #if ATTENTION
15724     if (!cps->useSigint) return;
15725     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15726     switch (gameMode) {
15727       case MachinePlaysWhite:
15728       case MachinePlaysBlack:
15729       case TwoMachinesPlay:
15730       case IcsPlayingWhite:
15731       case IcsPlayingBlack:
15732       case AnalyzeMode:
15733       case AnalyzeFile:
15734         /* Skip if we know it isn't thinking */
15735         if (!cps->maybeThinking) return;
15736         if (appData.debugMode)
15737           fprintf(debugFP, "Interrupting %s\n", cps->which);
15738         InterruptChildProcess(cps->pr);
15739         cps->maybeThinking = FALSE;
15740         break;
15741       default:
15742         break;
15743     }
15744 #endif /*ATTENTION*/
15745 }
15746
15747 int
15748 CheckFlags ()
15749 {
15750     if (whiteTimeRemaining <= 0) {
15751         if (!whiteFlag) {
15752             whiteFlag = TRUE;
15753             if (appData.icsActive) {
15754                 if (appData.autoCallFlag &&
15755                     gameMode == IcsPlayingBlack && !blackFlag) {
15756                   SendToICS(ics_prefix);
15757                   SendToICS("flag\n");
15758                 }
15759             } else {
15760                 if (blackFlag) {
15761                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15762                 } else {
15763                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15764                     if (appData.autoCallFlag) {
15765                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15766                         return TRUE;
15767                     }
15768                 }
15769             }
15770         }
15771     }
15772     if (blackTimeRemaining <= 0) {
15773         if (!blackFlag) {
15774             blackFlag = TRUE;
15775             if (appData.icsActive) {
15776                 if (appData.autoCallFlag &&
15777                     gameMode == IcsPlayingWhite && !whiteFlag) {
15778                   SendToICS(ics_prefix);
15779                   SendToICS("flag\n");
15780                 }
15781             } else {
15782                 if (whiteFlag) {
15783                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15784                 } else {
15785                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15786                     if (appData.autoCallFlag) {
15787                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15788                         return TRUE;
15789                     }
15790                 }
15791             }
15792         }
15793     }
15794     return FALSE;
15795 }
15796
15797 void
15798 CheckTimeControl ()
15799 {
15800     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15801         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15802
15803     /*
15804      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15805      */
15806     if ( !WhiteOnMove(forwardMostMove) ) {
15807         /* White made time control */
15808         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15809         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15810         /* [HGM] time odds: correct new time quota for time odds! */
15811                                             / WhitePlayer()->timeOdds;
15812         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15813     } else {
15814         lastBlack -= blackTimeRemaining;
15815         /* Black made time control */
15816         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15817                                             / WhitePlayer()->other->timeOdds;
15818         lastWhite = whiteTimeRemaining;
15819     }
15820 }
15821
15822 void
15823 DisplayBothClocks ()
15824 {
15825     int wom = gameMode == EditPosition ?
15826       !blackPlaysFirst : WhiteOnMove(currentMove);
15827     DisplayWhiteClock(whiteTimeRemaining, wom);
15828     DisplayBlackClock(blackTimeRemaining, !wom);
15829 }
15830
15831
15832 /* Timekeeping seems to be a portability nightmare.  I think everyone
15833    has ftime(), but I'm really not sure, so I'm including some ifdefs
15834    to use other calls if you don't.  Clocks will be less accurate if
15835    you have neither ftime nor gettimeofday.
15836 */
15837
15838 /* VS 2008 requires the #include outside of the function */
15839 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15840 #include <sys/timeb.h>
15841 #endif
15842
15843 /* Get the current time as a TimeMark */
15844 void
15845 GetTimeMark (TimeMark *tm)
15846 {
15847 #if HAVE_GETTIMEOFDAY
15848
15849     struct timeval timeVal;
15850     struct timezone timeZone;
15851
15852     gettimeofday(&timeVal, &timeZone);
15853     tm->sec = (long) timeVal.tv_sec;
15854     tm->ms = (int) (timeVal.tv_usec / 1000L);
15855
15856 #else /*!HAVE_GETTIMEOFDAY*/
15857 #if HAVE_FTIME
15858
15859 // include <sys/timeb.h> / moved to just above start of function
15860     struct timeb timeB;
15861
15862     ftime(&timeB);
15863     tm->sec = (long) timeB.time;
15864     tm->ms = (int) timeB.millitm;
15865
15866 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15867     tm->sec = (long) time(NULL);
15868     tm->ms = 0;
15869 #endif
15870 #endif
15871 }
15872
15873 /* Return the difference in milliseconds between two
15874    time marks.  We assume the difference will fit in a long!
15875 */
15876 long
15877 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15878 {
15879     return 1000L*(tm2->sec - tm1->sec) +
15880            (long) (tm2->ms - tm1->ms);
15881 }
15882
15883
15884 /*
15885  * Code to manage the game clocks.
15886  *
15887  * In tournament play, black starts the clock and then white makes a move.
15888  * We give the human user a slight advantage if he is playing white---the
15889  * clocks don't run until he makes his first move, so it takes zero time.
15890  * Also, we don't account for network lag, so we could get out of sync
15891  * with GNU Chess's clock -- but then, referees are always right.
15892  */
15893
15894 static TimeMark tickStartTM;
15895 static long intendedTickLength;
15896
15897 long
15898 NextTickLength (long timeRemaining)
15899 {
15900     long nominalTickLength, nextTickLength;
15901
15902     if (timeRemaining > 0L && timeRemaining <= 10000L)
15903       nominalTickLength = 100L;
15904     else
15905       nominalTickLength = 1000L;
15906     nextTickLength = timeRemaining % nominalTickLength;
15907     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15908
15909     return nextTickLength;
15910 }
15911
15912 /* Adjust clock one minute up or down */
15913 void
15914 AdjustClock (Boolean which, int dir)
15915 {
15916     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15917     if(which) blackTimeRemaining += 60000*dir;
15918     else      whiteTimeRemaining += 60000*dir;
15919     DisplayBothClocks();
15920     adjustedClock = TRUE;
15921 }
15922
15923 /* Stop clocks and reset to a fresh time control */
15924 void
15925 ResetClocks ()
15926 {
15927     (void) StopClockTimer();
15928     if (appData.icsActive) {
15929         whiteTimeRemaining = blackTimeRemaining = 0;
15930     } else if (searchTime) {
15931         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15932         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15933     } else { /* [HGM] correct new time quote for time odds */
15934         whiteTC = blackTC = fullTimeControlString;
15935         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15936         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15937     }
15938     if (whiteFlag || blackFlag) {
15939         DisplayTitle("");
15940         whiteFlag = blackFlag = FALSE;
15941     }
15942     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15943     DisplayBothClocks();
15944     adjustedClock = FALSE;
15945 }
15946
15947 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15948
15949 /* Decrement running clock by amount of time that has passed */
15950 void
15951 DecrementClocks ()
15952 {
15953     long timeRemaining;
15954     long lastTickLength, fudge;
15955     TimeMark now;
15956
15957     if (!appData.clockMode) return;
15958     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15959
15960     GetTimeMark(&now);
15961
15962     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15963
15964     /* Fudge if we woke up a little too soon */
15965     fudge = intendedTickLength - lastTickLength;
15966     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15967
15968     if (WhiteOnMove(forwardMostMove)) {
15969         if(whiteNPS >= 0) lastTickLength = 0;
15970         timeRemaining = whiteTimeRemaining -= lastTickLength;
15971         if(timeRemaining < 0 && !appData.icsActive) {
15972             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15973             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15974                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15975                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15976             }
15977         }
15978         DisplayWhiteClock(whiteTimeRemaining - fudge,
15979                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15980     } else {
15981         if(blackNPS >= 0) lastTickLength = 0;
15982         timeRemaining = blackTimeRemaining -= lastTickLength;
15983         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15984             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15985             if(suddenDeath) {
15986                 blackStartMove = forwardMostMove;
15987                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15988             }
15989         }
15990         DisplayBlackClock(blackTimeRemaining - fudge,
15991                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15992     }
15993     if (CheckFlags()) return;
15994
15995     tickStartTM = now;
15996     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15997     StartClockTimer(intendedTickLength);
15998
15999     /* if the time remaining has fallen below the alarm threshold, sound the
16000      * alarm. if the alarm has sounded and (due to a takeback or time control
16001      * with increment) the time remaining has increased to a level above the
16002      * threshold, reset the alarm so it can sound again.
16003      */
16004
16005     if (appData.icsActive && appData.icsAlarm) {
16006
16007         /* make sure we are dealing with the user's clock */
16008         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16009                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16010            )) return;
16011
16012         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16013             alarmSounded = FALSE;
16014         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16015             PlayAlarmSound();
16016             alarmSounded = TRUE;
16017         }
16018     }
16019 }
16020
16021
16022 /* A player has just moved, so stop the previously running
16023    clock and (if in clock mode) start the other one.
16024    We redisplay both clocks in case we're in ICS mode, because
16025    ICS gives us an update to both clocks after every move.
16026    Note that this routine is called *after* forwardMostMove
16027    is updated, so the last fractional tick must be subtracted
16028    from the color that is *not* on move now.
16029 */
16030 void
16031 SwitchClocks (int newMoveNr)
16032 {
16033     long lastTickLength;
16034     TimeMark now;
16035     int flagged = FALSE;
16036
16037     GetTimeMark(&now);
16038
16039     if (StopClockTimer() && appData.clockMode) {
16040         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16041         if (!WhiteOnMove(forwardMostMove)) {
16042             if(blackNPS >= 0) lastTickLength = 0;
16043             blackTimeRemaining -= lastTickLength;
16044            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16045 //         if(pvInfoList[forwardMostMove].time == -1)
16046                  pvInfoList[forwardMostMove].time =               // use GUI time
16047                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16048         } else {
16049            if(whiteNPS >= 0) lastTickLength = 0;
16050            whiteTimeRemaining -= lastTickLength;
16051            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16052 //         if(pvInfoList[forwardMostMove].time == -1)
16053                  pvInfoList[forwardMostMove].time =
16054                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16055         }
16056         flagged = CheckFlags();
16057     }
16058     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16059     CheckTimeControl();
16060
16061     if (flagged || !appData.clockMode) return;
16062
16063     switch (gameMode) {
16064       case MachinePlaysBlack:
16065       case MachinePlaysWhite:
16066       case BeginningOfGame:
16067         if (pausing) return;
16068         break;
16069
16070       case EditGame:
16071       case PlayFromGameFile:
16072       case IcsExamining:
16073         return;
16074
16075       default:
16076         break;
16077     }
16078
16079     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16080         if(WhiteOnMove(forwardMostMove))
16081              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16082         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16083     }
16084
16085     tickStartTM = now;
16086     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16087       whiteTimeRemaining : blackTimeRemaining);
16088     StartClockTimer(intendedTickLength);
16089 }
16090
16091
16092 /* Stop both clocks */
16093 void
16094 StopClocks ()
16095 {
16096     long lastTickLength;
16097     TimeMark now;
16098
16099     if (!StopClockTimer()) return;
16100     if (!appData.clockMode) return;
16101
16102     GetTimeMark(&now);
16103
16104     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16105     if (WhiteOnMove(forwardMostMove)) {
16106         if(whiteNPS >= 0) lastTickLength = 0;
16107         whiteTimeRemaining -= lastTickLength;
16108         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16109     } else {
16110         if(blackNPS >= 0) lastTickLength = 0;
16111         blackTimeRemaining -= lastTickLength;
16112         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16113     }
16114     CheckFlags();
16115 }
16116
16117 /* Start clock of player on move.  Time may have been reset, so
16118    if clock is already running, stop and restart it. */
16119 void
16120 StartClocks ()
16121 {
16122     (void) StopClockTimer(); /* in case it was running already */
16123     DisplayBothClocks();
16124     if (CheckFlags()) return;
16125
16126     if (!appData.clockMode) return;
16127     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16128
16129     GetTimeMark(&tickStartTM);
16130     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16131       whiteTimeRemaining : blackTimeRemaining);
16132
16133    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16134     whiteNPS = blackNPS = -1;
16135     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16136        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16137         whiteNPS = first.nps;
16138     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16139        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16140         blackNPS = first.nps;
16141     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16142         whiteNPS = second.nps;
16143     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16144         blackNPS = second.nps;
16145     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16146
16147     StartClockTimer(intendedTickLength);
16148 }
16149
16150 char *
16151 TimeString (long ms)
16152 {
16153     long second, minute, hour, day;
16154     char *sign = "";
16155     static char buf[32];
16156
16157     if (ms > 0 && ms <= 9900) {
16158       /* convert milliseconds to tenths, rounding up */
16159       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16160
16161       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16162       return buf;
16163     }
16164
16165     /* convert milliseconds to seconds, rounding up */
16166     /* use floating point to avoid strangeness of integer division
16167        with negative dividends on many machines */
16168     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16169
16170     if (second < 0) {
16171         sign = "-";
16172         second = -second;
16173     }
16174
16175     day = second / (60 * 60 * 24);
16176     second = second % (60 * 60 * 24);
16177     hour = second / (60 * 60);
16178     second = second % (60 * 60);
16179     minute = second / 60;
16180     second = second % 60;
16181
16182     if (day > 0)
16183       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16184               sign, day, hour, minute, second);
16185     else if (hour > 0)
16186       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16187     else
16188       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16189
16190     return buf;
16191 }
16192
16193
16194 /*
16195  * This is necessary because some C libraries aren't ANSI C compliant yet.
16196  */
16197 char *
16198 StrStr (char *string, char *match)
16199 {
16200     int i, length;
16201
16202     length = strlen(match);
16203
16204     for (i = strlen(string) - length; i >= 0; i--, string++)
16205       if (!strncmp(match, string, length))
16206         return string;
16207
16208     return NULL;
16209 }
16210
16211 char *
16212 StrCaseStr (char *string, char *match)
16213 {
16214     int i, j, length;
16215
16216     length = strlen(match);
16217
16218     for (i = strlen(string) - length; i >= 0; i--, string++) {
16219         for (j = 0; j < length; j++) {
16220             if (ToLower(match[j]) != ToLower(string[j]))
16221               break;
16222         }
16223         if (j == length) return string;
16224     }
16225
16226     return NULL;
16227 }
16228
16229 #ifndef _amigados
16230 int
16231 StrCaseCmp (char *s1, char *s2)
16232 {
16233     char c1, c2;
16234
16235     for (;;) {
16236         c1 = ToLower(*s1++);
16237         c2 = ToLower(*s2++);
16238         if (c1 > c2) return 1;
16239         if (c1 < c2) return -1;
16240         if (c1 == NULLCHAR) return 0;
16241     }
16242 }
16243
16244
16245 int
16246 ToLower (int c)
16247 {
16248     return isupper(c) ? tolower(c) : c;
16249 }
16250
16251
16252 int
16253 ToUpper (int c)
16254 {
16255     return islower(c) ? toupper(c) : c;
16256 }
16257 #endif /* !_amigados    */
16258
16259 char *
16260 StrSave (char *s)
16261 {
16262   char *ret;
16263
16264   if ((ret = (char *) malloc(strlen(s) + 1)))
16265     {
16266       safeStrCpy(ret, s, strlen(s)+1);
16267     }
16268   return ret;
16269 }
16270
16271 char *
16272 StrSavePtr (char *s, char **savePtr)
16273 {
16274     if (*savePtr) {
16275         free(*savePtr);
16276     }
16277     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16278       safeStrCpy(*savePtr, s, strlen(s)+1);
16279     }
16280     return(*savePtr);
16281 }
16282
16283 char *
16284 PGNDate ()
16285 {
16286     time_t clock;
16287     struct tm *tm;
16288     char buf[MSG_SIZ];
16289
16290     clock = time((time_t *)NULL);
16291     tm = localtime(&clock);
16292     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16293             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16294     return StrSave(buf);
16295 }
16296
16297
16298 char *
16299 PositionToFEN (int move, char *overrideCastling)
16300 {
16301     int i, j, fromX, fromY, toX, toY;
16302     int whiteToPlay;
16303     char buf[MSG_SIZ];
16304     char *p, *q;
16305     int emptycount;
16306     ChessSquare piece;
16307
16308     whiteToPlay = (gameMode == EditPosition) ?
16309       !blackPlaysFirst : (move % 2 == 0);
16310     p = buf;
16311
16312     /* Piece placement data */
16313     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16314         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16315         emptycount = 0;
16316         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16317             if (boards[move][i][j] == EmptySquare) {
16318                 emptycount++;
16319             } else { ChessSquare piece = boards[move][i][j];
16320                 if (emptycount > 0) {
16321                     if(emptycount<10) /* [HGM] can be >= 10 */
16322                         *p++ = '0' + emptycount;
16323                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16324                     emptycount = 0;
16325                 }
16326                 if(PieceToChar(piece) == '+') {
16327                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16328                     *p++ = '+';
16329                     piece = (ChessSquare)(DEMOTED piece);
16330                 }
16331                 *p++ = PieceToChar(piece);
16332                 if(p[-1] == '~') {
16333                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16334                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16335                     *p++ = '~';
16336                 }
16337             }
16338         }
16339         if (emptycount > 0) {
16340             if(emptycount<10) /* [HGM] can be >= 10 */
16341                 *p++ = '0' + emptycount;
16342             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16343             emptycount = 0;
16344         }
16345         *p++ = '/';
16346     }
16347     *(p - 1) = ' ';
16348
16349     /* [HGM] print Crazyhouse or Shogi holdings */
16350     if( gameInfo.holdingsWidth ) {
16351         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16352         q = p;
16353         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16354             piece = boards[move][i][BOARD_WIDTH-1];
16355             if( piece != EmptySquare )
16356               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16357                   *p++ = PieceToChar(piece);
16358         }
16359         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16360             piece = boards[move][BOARD_HEIGHT-i-1][0];
16361             if( piece != EmptySquare )
16362               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16363                   *p++ = PieceToChar(piece);
16364         }
16365
16366         if( q == p ) *p++ = '-';
16367         *p++ = ']';
16368         *p++ = ' ';
16369     }
16370
16371     /* Active color */
16372     *p++ = whiteToPlay ? 'w' : 'b';
16373     *p++ = ' ';
16374
16375   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16376     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16377   } else {
16378   if(nrCastlingRights) {
16379      q = p;
16380      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16381        /* [HGM] write directly from rights */
16382            if(boards[move][CASTLING][2] != NoRights &&
16383               boards[move][CASTLING][0] != NoRights   )
16384                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16385            if(boards[move][CASTLING][2] != NoRights &&
16386               boards[move][CASTLING][1] != NoRights   )
16387                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16388            if(boards[move][CASTLING][5] != NoRights &&
16389               boards[move][CASTLING][3] != NoRights   )
16390                 *p++ = boards[move][CASTLING][3] + AAA;
16391            if(boards[move][CASTLING][5] != NoRights &&
16392               boards[move][CASTLING][4] != NoRights   )
16393                 *p++ = boards[move][CASTLING][4] + AAA;
16394      } else {
16395
16396         /* [HGM] write true castling rights */
16397         if( nrCastlingRights == 6 ) {
16398             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16399                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16400             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16401                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16402             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16403                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16404             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16405                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16406         }
16407      }
16408      if (q == p) *p++ = '-'; /* No castling rights */
16409      *p++ = ' ';
16410   }
16411
16412   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16413      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16414     /* En passant target square */
16415     if (move > backwardMostMove) {
16416         fromX = moveList[move - 1][0] - AAA;
16417         fromY = moveList[move - 1][1] - ONE;
16418         toX = moveList[move - 1][2] - AAA;
16419         toY = moveList[move - 1][3] - ONE;
16420         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16421             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16422             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16423             fromX == toX) {
16424             /* 2-square pawn move just happened */
16425             *p++ = toX + AAA;
16426             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16427         } else {
16428             *p++ = '-';
16429         }
16430     } else if(move == backwardMostMove) {
16431         // [HGM] perhaps we should always do it like this, and forget the above?
16432         if((signed char)boards[move][EP_STATUS] >= 0) {
16433             *p++ = boards[move][EP_STATUS] + AAA;
16434             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16435         } else {
16436             *p++ = '-';
16437         }
16438     } else {
16439         *p++ = '-';
16440     }
16441     *p++ = ' ';
16442   }
16443   }
16444
16445     /* [HGM] find reversible plies */
16446     {   int i = 0, j=move;
16447
16448         if (appData.debugMode) { int k;
16449             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16450             for(k=backwardMostMove; k<=forwardMostMove; k++)
16451                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16452
16453         }
16454
16455         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16456         if( j == backwardMostMove ) i += initialRulePlies;
16457         sprintf(p, "%d ", i);
16458         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16459     }
16460     /* Fullmove number */
16461     sprintf(p, "%d", (move / 2) + 1);
16462
16463     return StrSave(buf);
16464 }
16465
16466 Boolean
16467 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16468 {
16469     int i, j;
16470     char *p, c;
16471     int emptycount;
16472     ChessSquare piece;
16473
16474     p = fen;
16475
16476     /* [HGM] by default clear Crazyhouse holdings, if present */
16477     if(gameInfo.holdingsWidth) {
16478        for(i=0; i<BOARD_HEIGHT; i++) {
16479            board[i][0]             = EmptySquare; /* black holdings */
16480            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16481            board[i][1]             = (ChessSquare) 0; /* black counts */
16482            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16483        }
16484     }
16485
16486     /* Piece placement data */
16487     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16488         j = 0;
16489         for (;;) {
16490             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16491                 if (*p == '/') p++;
16492                 emptycount = gameInfo.boardWidth - j;
16493                 while (emptycount--)
16494                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16495                 break;
16496 #if(BOARD_FILES >= 10)
16497             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16498                 p++; emptycount=10;
16499                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16500                 while (emptycount--)
16501                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16502 #endif
16503             } else if (isdigit(*p)) {
16504                 emptycount = *p++ - '0';
16505                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16506                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16507                 while (emptycount--)
16508                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16509             } else if (*p == '+' || isalpha(*p)) {
16510                 if (j >= gameInfo.boardWidth) return FALSE;
16511                 if(*p=='+') {
16512                     piece = CharToPiece(*++p);
16513                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16514                     piece = (ChessSquare) (PROMOTED piece ); p++;
16515                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16516                 } else piece = CharToPiece(*p++);
16517
16518                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16519                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16520                     piece = (ChessSquare) (PROMOTED piece);
16521                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16522                     p++;
16523                 }
16524                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16525             } else {
16526                 return FALSE;
16527             }
16528         }
16529     }
16530     while (*p == '/' || *p == ' ') p++;
16531
16532     /* [HGM] look for Crazyhouse holdings here */
16533     while(*p==' ') p++;
16534     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16535         if(*p == '[') p++;
16536         if(*p == '-' ) p++; /* empty holdings */ else {
16537             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16538             /* if we would allow FEN reading to set board size, we would   */
16539             /* have to add holdings and shift the board read so far here   */
16540             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16541                 p++;
16542                 if((int) piece >= (int) BlackPawn ) {
16543                     i = (int)piece - (int)BlackPawn;
16544                     i = PieceToNumber((ChessSquare)i);
16545                     if( i >= gameInfo.holdingsSize ) return FALSE;
16546                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16547                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16548                 } else {
16549                     i = (int)piece - (int)WhitePawn;
16550                     i = PieceToNumber((ChessSquare)i);
16551                     if( i >= gameInfo.holdingsSize ) return FALSE;
16552                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16553                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16554                 }
16555             }
16556         }
16557         if(*p == ']') p++;
16558     }
16559
16560     while(*p == ' ') p++;
16561
16562     /* Active color */
16563     c = *p++;
16564     if(appData.colorNickNames) {
16565       if( c == appData.colorNickNames[0] ) c = 'w'; else
16566       if( c == appData.colorNickNames[1] ) c = 'b';
16567     }
16568     switch (c) {
16569       case 'w':
16570         *blackPlaysFirst = FALSE;
16571         break;
16572       case 'b':
16573         *blackPlaysFirst = TRUE;
16574         break;
16575       default:
16576         return FALSE;
16577     }
16578
16579     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16580     /* return the extra info in global variiables             */
16581
16582     /* set defaults in case FEN is incomplete */
16583     board[EP_STATUS] = EP_UNKNOWN;
16584     for(i=0; i<nrCastlingRights; i++ ) {
16585         board[CASTLING][i] =
16586             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16587     }   /* assume possible unless obviously impossible */
16588     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16589     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16590     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16591                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16592     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16593     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16594     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16595                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16596     FENrulePlies = 0;
16597
16598     while(*p==' ') p++;
16599     if(nrCastlingRights) {
16600       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16601           /* castling indicator present, so default becomes no castlings */
16602           for(i=0; i<nrCastlingRights; i++ ) {
16603                  board[CASTLING][i] = NoRights;
16604           }
16605       }
16606       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16607              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16608              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16609              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16610         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16611
16612         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16613             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16614             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16615         }
16616         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16617             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16618         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16619                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16620         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16621                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16622         switch(c) {
16623           case'K':
16624               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16625               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16626               board[CASTLING][2] = whiteKingFile;
16627               break;
16628           case'Q':
16629               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16630               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16631               board[CASTLING][2] = whiteKingFile;
16632               break;
16633           case'k':
16634               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16635               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16636               board[CASTLING][5] = blackKingFile;
16637               break;
16638           case'q':
16639               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16640               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16641               board[CASTLING][5] = blackKingFile;
16642           case '-':
16643               break;
16644           default: /* FRC castlings */
16645               if(c >= 'a') { /* black rights */
16646                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16647                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16648                   if(i == BOARD_RGHT) break;
16649                   board[CASTLING][5] = i;
16650                   c -= AAA;
16651                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16652                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16653                   if(c > i)
16654                       board[CASTLING][3] = c;
16655                   else
16656                       board[CASTLING][4] = c;
16657               } else { /* white rights */
16658                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16659                     if(board[0][i] == WhiteKing) break;
16660                   if(i == BOARD_RGHT) break;
16661                   board[CASTLING][2] = i;
16662                   c -= AAA - 'a' + 'A';
16663                   if(board[0][c] >= WhiteKing) break;
16664                   if(c > i)
16665                       board[CASTLING][0] = c;
16666                   else
16667                       board[CASTLING][1] = c;
16668               }
16669         }
16670       }
16671       for(i=0; i<nrCastlingRights; i++)
16672         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16673     if (appData.debugMode) {
16674         fprintf(debugFP, "FEN castling rights:");
16675         for(i=0; i<nrCastlingRights; i++)
16676         fprintf(debugFP, " %d", board[CASTLING][i]);
16677         fprintf(debugFP, "\n");
16678     }
16679
16680       while(*p==' ') p++;
16681     }
16682
16683     /* read e.p. field in games that know e.p. capture */
16684     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16685        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16686       if(*p=='-') {
16687         p++; board[EP_STATUS] = EP_NONE;
16688       } else {
16689          char c = *p++ - AAA;
16690
16691          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16692          if(*p >= '0' && *p <='9') p++;
16693          board[EP_STATUS] = c;
16694       }
16695     }
16696
16697
16698     if(sscanf(p, "%d", &i) == 1) {
16699         FENrulePlies = i; /* 50-move ply counter */
16700         /* (The move number is still ignored)    */
16701     }
16702
16703     return TRUE;
16704 }
16705
16706 void
16707 EditPositionPasteFEN (char *fen)
16708 {
16709   if (fen != NULL) {
16710     Board initial_position;
16711
16712     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16713       DisplayError(_("Bad FEN position in clipboard"), 0);
16714       return ;
16715     } else {
16716       int savedBlackPlaysFirst = blackPlaysFirst;
16717       EditPositionEvent();
16718       blackPlaysFirst = savedBlackPlaysFirst;
16719       CopyBoard(boards[0], initial_position);
16720       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16721       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16722       DisplayBothClocks();
16723       DrawPosition(FALSE, boards[currentMove]);
16724     }
16725   }
16726 }
16727
16728 static char cseq[12] = "\\   ";
16729
16730 Boolean
16731 set_cont_sequence (char *new_seq)
16732 {
16733     int len;
16734     Boolean ret;
16735
16736     // handle bad attempts to set the sequence
16737         if (!new_seq)
16738                 return 0; // acceptable error - no debug
16739
16740     len = strlen(new_seq);
16741     ret = (len > 0) && (len < sizeof(cseq));
16742     if (ret)
16743       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16744     else if (appData.debugMode)
16745       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16746     return ret;
16747 }
16748
16749 /*
16750     reformat a source message so words don't cross the width boundary.  internal
16751     newlines are not removed.  returns the wrapped size (no null character unless
16752     included in source message).  If dest is NULL, only calculate the size required
16753     for the dest buffer.  lp argument indicats line position upon entry, and it's
16754     passed back upon exit.
16755 */
16756 int
16757 wrap (char *dest, char *src, int count, int width, int *lp)
16758 {
16759     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16760
16761     cseq_len = strlen(cseq);
16762     old_line = line = *lp;
16763     ansi = len = clen = 0;
16764
16765     for (i=0; i < count; i++)
16766     {
16767         if (src[i] == '\033')
16768             ansi = 1;
16769
16770         // if we hit the width, back up
16771         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16772         {
16773             // store i & len in case the word is too long
16774             old_i = i, old_len = len;
16775
16776             // find the end of the last word
16777             while (i && src[i] != ' ' && src[i] != '\n')
16778             {
16779                 i--;
16780                 len--;
16781             }
16782
16783             // word too long?  restore i & len before splitting it
16784             if ((old_i-i+clen) >= width)
16785             {
16786                 i = old_i;
16787                 len = old_len;
16788             }
16789
16790             // extra space?
16791             if (i && src[i-1] == ' ')
16792                 len--;
16793
16794             if (src[i] != ' ' && src[i] != '\n')
16795             {
16796                 i--;
16797                 if (len)
16798                     len--;
16799             }
16800
16801             // now append the newline and continuation sequence
16802             if (dest)
16803                 dest[len] = '\n';
16804             len++;
16805             if (dest)
16806                 strncpy(dest+len, cseq, cseq_len);
16807             len += cseq_len;
16808             line = cseq_len;
16809             clen = cseq_len;
16810             continue;
16811         }
16812
16813         if (dest)
16814             dest[len] = src[i];
16815         len++;
16816         if (!ansi)
16817             line++;
16818         if (src[i] == '\n')
16819             line = 0;
16820         if (src[i] == 'm')
16821             ansi = 0;
16822     }
16823     if (dest && appData.debugMode)
16824     {
16825         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16826             count, width, line, len, *lp);
16827         show_bytes(debugFP, src, count);
16828         fprintf(debugFP, "\ndest: ");
16829         show_bytes(debugFP, dest, len);
16830         fprintf(debugFP, "\n");
16831     }
16832     *lp = dest ? line : old_line;
16833
16834     return len;
16835 }
16836
16837 // [HGM] vari: routines for shelving variations
16838 Boolean modeRestore = FALSE;
16839
16840 void
16841 PushInner (int firstMove, int lastMove)
16842 {
16843         int i, j, nrMoves = lastMove - firstMove;
16844
16845         // push current tail of game on stack
16846         savedResult[storedGames] = gameInfo.result;
16847         savedDetails[storedGames] = gameInfo.resultDetails;
16848         gameInfo.resultDetails = NULL;
16849         savedFirst[storedGames] = firstMove;
16850         savedLast [storedGames] = lastMove;
16851         savedFramePtr[storedGames] = framePtr;
16852         framePtr -= nrMoves; // reserve space for the boards
16853         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16854             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16855             for(j=0; j<MOVE_LEN; j++)
16856                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16857             for(j=0; j<2*MOVE_LEN; j++)
16858                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16859             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16860             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16861             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16862             pvInfoList[firstMove+i-1].depth = 0;
16863             commentList[framePtr+i] = commentList[firstMove+i];
16864             commentList[firstMove+i] = NULL;
16865         }
16866
16867         storedGames++;
16868         forwardMostMove = firstMove; // truncate game so we can start variation
16869 }
16870
16871 void
16872 PushTail (int firstMove, int lastMove)
16873 {
16874         if(appData.icsActive) { // only in local mode
16875                 forwardMostMove = currentMove; // mimic old ICS behavior
16876                 return;
16877         }
16878         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16879
16880         PushInner(firstMove, lastMove);
16881         if(storedGames == 1) GreyRevert(FALSE);
16882         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16883 }
16884
16885 void
16886 PopInner (Boolean annotate)
16887 {
16888         int i, j, nrMoves;
16889         char buf[8000], moveBuf[20];
16890
16891         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16892         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16893         nrMoves = savedLast[storedGames] - currentMove;
16894         if(annotate) {
16895                 int cnt = 10;
16896                 if(!WhiteOnMove(currentMove))
16897                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16898                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16899                 for(i=currentMove; i<forwardMostMove; i++) {
16900                         if(WhiteOnMove(i))
16901                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16902                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16903                         strcat(buf, moveBuf);
16904                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16905                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16906                 }
16907                 strcat(buf, ")");
16908         }
16909         for(i=1; i<=nrMoves; i++) { // copy last variation back
16910             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16911             for(j=0; j<MOVE_LEN; j++)
16912                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16913             for(j=0; j<2*MOVE_LEN; j++)
16914                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16915             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16916             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16917             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16918             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16919             commentList[currentMove+i] = commentList[framePtr+i];
16920             commentList[framePtr+i] = NULL;
16921         }
16922         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16923         framePtr = savedFramePtr[storedGames];
16924         gameInfo.result = savedResult[storedGames];
16925         if(gameInfo.resultDetails != NULL) {
16926             free(gameInfo.resultDetails);
16927       }
16928         gameInfo.resultDetails = savedDetails[storedGames];
16929         forwardMostMove = currentMove + nrMoves;
16930 }
16931
16932 Boolean
16933 PopTail (Boolean annotate)
16934 {
16935         if(appData.icsActive) return FALSE; // only in local mode
16936         if(!storedGames) return FALSE; // sanity
16937         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16938
16939         PopInner(annotate);
16940         if(currentMove < forwardMostMove) ForwardEvent(); else
16941         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16942
16943         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16944         return TRUE;
16945 }
16946
16947 void
16948 CleanupTail ()
16949 {       // remove all shelved variations
16950         int i;
16951         for(i=0; i<storedGames; i++) {
16952             if(savedDetails[i])
16953                 free(savedDetails[i]);
16954             savedDetails[i] = NULL;
16955         }
16956         for(i=framePtr; i<MAX_MOVES; i++) {
16957                 if(commentList[i]) free(commentList[i]);
16958                 commentList[i] = NULL;
16959         }
16960         framePtr = MAX_MOVES-1;
16961         storedGames = 0;
16962 }
16963
16964 void
16965 LoadVariation (int index, char *text)
16966 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16967         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16968         int level = 0, move;
16969
16970         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16971         // first find outermost bracketing variation
16972         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16973             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16974                 if(*p == '{') wait = '}'; else
16975                 if(*p == '[') wait = ']'; else
16976                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16977                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16978             }
16979             if(*p == wait) wait = NULLCHAR; // closing ]} found
16980             p++;
16981         }
16982         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16983         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16984         end[1] = NULLCHAR; // clip off comment beyond variation
16985         ToNrEvent(currentMove-1);
16986         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16987         // kludge: use ParsePV() to append variation to game
16988         move = currentMove;
16989         ParsePV(start, TRUE, TRUE);
16990         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16991         ClearPremoveHighlights();
16992         CommentPopDown();
16993         ToNrEvent(currentMove+1);
16994 }
16995