Let second engine move in lockstep during dual analysis
[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 SendToICS P((char *s));
156 void SendToICSDelayed P((char *s, long msdelay));
157 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
158 void HandleMachineMove P((char *message, ChessProgramState *cps));
159 int AutoPlayOneMove P((void));
160 int LoadGameOneMove P((ChessMove readAhead));
161 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
162 int LoadPositionFromFile P((char *filename, int n, char *title));
163 int SavePositionToFile P((char *filename));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 int ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219 void NextMatchGame P((void));
220 int NextTourneyGame P((int nr, int *swap));
221 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
222 FILE *WriteTourneyFile P((char *results, FILE *f));
223 void DisplayTwoMachinesTitle P(());
224 static void ExcludeClick P((int index));
225 void ToggleSecond P((void));
226
227 #ifdef WIN32
228        extern void ConsoleCreate();
229 #endif
230
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
234
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
242 Boolean abortMatch;
243
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 int endPV = -1;
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
251 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
255 Boolean partnerUp;
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
267 int chattingPartner;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273
274 /* States for ics_getting_history */
275 #define H_FALSE 0
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
281
282 /* whosays values for GameEnds */
283 #define GE_ICS 0
284 #define GE_ENGINE 1
285 #define GE_PLAYER 2
286 #define GE_FILE 3
287 #define GE_XBOARD 4
288 #define GE_ENGINE1 5
289 #define GE_ENGINE2 6
290
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
293
294 /* Different types of move when calling RegisterMove */
295 #define CMAIL_MOVE   0
296 #define CMAIL_RESIGN 1
297 #define CMAIL_DRAW   2
298 #define CMAIL_ACCEPT 3
299
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
304
305 /* Telnet protocol constants */
306 #define TN_WILL 0373
307 #define TN_WONT 0374
308 #define TN_DO   0375
309 #define TN_DONT 0376
310 #define TN_IAC  0377
311 #define TN_ECHO 0001
312 #define TN_SGA  0003
313 #define TN_PORT 23
314
315 char*
316 safeStrCpy (char *dst, const char *src, size_t count)
317 { // [HGM] made safe
318   int i;
319   assert( dst != NULL );
320   assert( src != NULL );
321   assert( count > 0 );
322
323   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324   if(  i == count && dst[count-1] != NULLCHAR)
325     {
326       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327       if(appData.debugMode)
328       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
329     }
330
331   return dst;
332 }
333
334 /* Some compiler can't cast u64 to double
335  * This function do the job for us:
336
337  * We use the highest bit for cast, this only
338  * works if the highest bit is not
339  * in use (This should not happen)
340  *
341  * We used this for all compiler
342  */
343 double
344 u64ToDouble (u64 value)
345 {
346   double r;
347   u64 tmp = value & u64Const(0x7fffffffffffffff);
348   r = (double)(s64)tmp;
349   if (value & u64Const(0x8000000000000000))
350        r +=  9.2233720368547758080e18; /* 2^63 */
351  return r;
352 }
353
354 /* Fake up flags for now, as we aren't keeping track of castling
355    availability yet. [HGM] Change of logic: the flag now only
356    indicates the type of castlings allowed by the rule of the game.
357    The actual rights themselves are maintained in the array
358    castlingRights, as part of the game history, and are not probed
359    by this function.
360  */
361 int
362 PosFlags (index)
363 {
364   int flags = F_ALL_CASTLE_OK;
365   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366   switch (gameInfo.variant) {
367   case VariantSuicide:
368     flags &= ~F_ALL_CASTLE_OK;
369   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370     flags |= F_IGNORE_CHECK;
371   case VariantLosers:
372     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
373     break;
374   case VariantAtomic:
375     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376     break;
377   case VariantKriegspiel:
378     flags |= F_KRIEGSPIEL_CAPTURE;
379     break;
380   case VariantCapaRandom:
381   case VariantFischeRandom:
382     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383   case VariantNoCastle:
384   case VariantShatranj:
385   case VariantCourier:
386   case VariantMakruk:
387   case VariantGrand:
388     flags &= ~F_ALL_CASTLE_OK;
389     break;
390   default:
391     break;
392   }
393   return flags;
394 }
395
396 FILE *gameFileFP, *debugFP, *serverFP;
397 char *currentDebugFile; // [HGM] debug split: to remember name
398
399 /*
400     [AS] Note: sometimes, the sscanf() function is used to parse the input
401     into a fixed-size buffer. Because of this, we must be prepared to
402     receive strings as long as the size of the input buffer, which is currently
403     set to 4K for Windows and 8K for the rest.
404     So, we must either allocate sufficiently large buffers here, or
405     reduce the size of the input buffer in the input reading part.
406 */
407
408 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
409 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
410 char thinkOutput1[MSG_SIZ*10];
411
412 ChessProgramState first, second, pairing;
413
414 /* premove variables */
415 int premoveToX = 0;
416 int premoveToY = 0;
417 int premoveFromX = 0;
418 int premoveFromY = 0;
419 int premovePromoChar = 0;
420 int gotPremove = 0;
421 Boolean alarmSounded;
422 /* end premove variables */
423
424 char *ics_prefix = "$";
425 enum ICS_TYPE ics_type = ICS_GENERIC;
426
427 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
428 int pauseExamForwardMostMove = 0;
429 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
430 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
431 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
432 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
433 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
434 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
435 int whiteFlag = FALSE, blackFlag = FALSE;
436 int userOfferedDraw = FALSE;
437 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
438 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
439 int cmailMoveType[CMAIL_MAX_GAMES];
440 long ics_clock_paused = 0;
441 ProcRef icsPR = NoProc, cmailPR = NoProc;
442 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
443 GameMode gameMode = BeginningOfGame;
444 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
445 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
446 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
447 int hiddenThinkOutputState = 0; /* [AS] */
448 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
449 int adjudicateLossPlies = 6;
450 char white_holding[64], black_holding[64];
451 TimeMark lastNodeCountTime;
452 long lastNodeCount=0;
453 int shiftKey, controlKey; // [HGM] set by mouse handler
454
455 int have_sent_ICS_logon = 0;
456 int movesPerSession;
457 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
458 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
459 Boolean adjustedClock;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0, nextGame = 0, roundNr = 0;
464 Boolean waitingForGame = FALSE;
465 TimeMark programStartTime, pauseStart;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
468
469 /* animateTraining preserves the state of appData.animate
470  * when Training mode is activated. This allows the
471  * response to be animated when appData.animate == TRUE and
472  * appData.animateDragging == TRUE.
473  */
474 Boolean animateTraining;
475
476 GameInfo gameInfo;
477
478 AppData appData;
479
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char  initialRights[BOARD_FILES];
484 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int   initialRulePlies, FENrulePlies;
486 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 int loadFlag = 0;
488 Boolean shuffleOpenings;
489 int mute; // mute all sounds
490
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int storedGames = 0;
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
500
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void PushInner P((int firstMove, int lastMove));
504 void PopInner P((Boolean annotate));
505 void CleanupTail P((void));
506
507 ChessSquare  FIDEArray[2][BOARD_FILES] = {
508     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511         BlackKing, BlackBishop, BlackKnight, BlackRook }
512 };
513
514 ChessSquare twoKingsArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackKing, BlackKnight, BlackRook }
519 };
520
521 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
523         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
524     { BlackRook, BlackMan, BlackBishop, BlackQueen,
525         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
526 };
527
528 ChessSquare SpartanArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
532         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
533 };
534
535 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
539         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
540 };
541
542 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
544         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
546         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
547 };
548
549 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
551         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackMan, BlackFerz,
553         BlackKing, BlackMan, BlackKnight, BlackRook }
554 };
555
556
557 #if (BOARD_FILES>=10)
558 ChessSquare ShogiArray[2][BOARD_FILES] = {
559     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
560         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
561     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
562         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
563 };
564
565 ChessSquare XiangqiArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
567         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
569         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
570 };
571
572 ChessSquare CapablancaArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
576         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
577 };
578
579 ChessSquare GreatArray[2][BOARD_FILES] = {
580     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
581         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
582     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
583         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
584 };
585
586 ChessSquare JanusArray[2][BOARD_FILES] = {
587     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
588         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
589     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
590         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
591 };
592
593 ChessSquare GrandArray[2][BOARD_FILES] = {
594     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
595         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
596     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
597         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
598 };
599
600 #ifdef GOTHIC
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
606 };
607 #else // !GOTHIC
608 #define GothicArray CapablancaArray
609 #endif // !GOTHIC
610
611 #ifdef FALCON
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
614         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
616         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !FALCON
619 #define FalconArray CapablancaArray
620 #endif // !FALCON
621
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
628
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 };
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
639
640
641 Board initialPosition;
642
643
644 /* Convert str to a rating. Checks for special cases of "----",
645
646    "++++", etc. Also strips ()'s */
647 int
648 string_to_rating (char *str)
649 {
650   while(*str && !isdigit(*str)) ++str;
651   if (!*str)
652     return 0;   /* One of the special "no rating" cases */
653   else
654     return atoi(str);
655 }
656
657 void
658 ClearProgramStats ()
659 {
660     /* Init programStats */
661     programStats.movelist[0] = 0;
662     programStats.depth = 0;
663     programStats.nr_moves = 0;
664     programStats.moves_left = 0;
665     programStats.nodes = 0;
666     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
667     programStats.score = 0;
668     programStats.got_only_move = 0;
669     programStats.got_fail = 0;
670     programStats.line_is_book = 0;
671 }
672
673 void
674 CommonEngineInit ()
675 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
676     if (appData.firstPlaysBlack) {
677         first.twoMachinesColor = "black\n";
678         second.twoMachinesColor = "white\n";
679     } else {
680         first.twoMachinesColor = "white\n";
681         second.twoMachinesColor = "black\n";
682     }
683
684     first.other = &second;
685     second.other = &first;
686
687     { float norm = 1;
688         if(appData.timeOddsMode) {
689             norm = appData.timeOdds[0];
690             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
691         }
692         first.timeOdds  = appData.timeOdds[0]/norm;
693         second.timeOdds = appData.timeOdds[1]/norm;
694     }
695
696     if(programVersion) free(programVersion);
697     if (appData.noChessProgram) {
698         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
699         sprintf(programVersion, "%s", PACKAGE_STRING);
700     } else {
701       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
702       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
703       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
704     }
705 }
706
707 void
708 UnloadEngine (ChessProgramState *cps)
709 {
710         /* Kill off first chess program */
711         if (cps->isr != NULL)
712           RemoveInputSource(cps->isr);
713         cps->isr = NULL;
714
715         if (cps->pr != NoProc) {
716             ExitAnalyzeMode();
717             DoSleep( appData.delayBeforeQuit );
718             SendToProgram("quit\n", cps);
719             DoSleep( appData.delayAfterQuit );
720             DestroyChildProcess(cps->pr, cps->useSigterm);
721         }
722         cps->pr = NoProc;
723         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
724 }
725
726 void
727 ClearOptions (ChessProgramState *cps)
728 {
729     int i;
730     cps->nrOptions = cps->comboCnt = 0;
731     for(i=0; i<MAX_OPTIONS; i++) {
732         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
733         cps->option[i].textValue = 0;
734     }
735 }
736
737 char *engineNames[] = {
738   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
739      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
740 N_("first"),
741   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
742      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
743 N_("second")
744 };
745
746 void
747 InitEngine (ChessProgramState *cps, int n)
748 {   // [HGM] all engine initialiation put in a function that does one engine
749
750     ClearOptions(cps);
751
752     cps->which = engineNames[n];
753     cps->maybeThinking = FALSE;
754     cps->pr = NoProc;
755     cps->isr = NULL;
756     cps->sendTime = 2;
757     cps->sendDrawOffers = 1;
758
759     cps->program = appData.chessProgram[n];
760     cps->host = appData.host[n];
761     cps->dir = appData.directory[n];
762     cps->initString = appData.engInitString[n];
763     cps->computerString = appData.computerString[n];
764     cps->useSigint  = TRUE;
765     cps->useSigterm = TRUE;
766     cps->reuse = appData.reuse[n];
767     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
768     cps->useSetboard = FALSE;
769     cps->useSAN = FALSE;
770     cps->usePing = FALSE;
771     cps->lastPing = 0;
772     cps->lastPong = 0;
773     cps->usePlayother = FALSE;
774     cps->useColors = TRUE;
775     cps->useUsermove = FALSE;
776     cps->sendICS = FALSE;
777     cps->sendName = appData.icsActive;
778     cps->sdKludge = FALSE;
779     cps->stKludge = FALSE;
780     TidyProgramName(cps->program, cps->host, cps->tidy);
781     cps->matchWins = 0;
782     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
783     cps->analysisSupport = 2; /* detect */
784     cps->analyzing = FALSE;
785     cps->initDone = FALSE;
786
787     /* New features added by Tord: */
788     cps->useFEN960 = FALSE;
789     cps->useOOCastle = TRUE;
790     /* End of new features added by Tord. */
791     cps->fenOverride  = appData.fenOverride[n];
792
793     /* [HGM] time odds: set factor for each machine */
794     cps->timeOdds  = appData.timeOdds[n];
795
796     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797     cps->accumulateTC = appData.accumulateTC[n];
798     cps->maxNrOfSessions = 1;
799
800     /* [HGM] debug */
801     cps->debug = FALSE;
802
803     cps->supportsNPS = UNKNOWN;
804     cps->memSize = FALSE;
805     cps->maxCores = FALSE;
806     cps->egtFormats[0] = NULLCHAR;
807
808     /* [HGM] options */
809     cps->optionSettings  = appData.engOptions[n];
810
811     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
812     cps->isUCI = appData.isUCI[n]; /* [AS] */
813     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
814
815     if (appData.protocolVersion[n] > PROTOVER
816         || appData.protocolVersion[n] < 1)
817       {
818         char buf[MSG_SIZ];
819         int len;
820
821         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
822                        appData.protocolVersion[n]);
823         if( (len >= MSG_SIZ) && appData.debugMode )
824           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
825
826         DisplayFatalError(buf, 0, 2);
827       }
828     else
829       {
830         cps->protocolVersion = appData.protocolVersion[n];
831       }
832
833     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
834     ParseFeatures(appData.featureDefaults, cps);
835 }
836
837 ChessProgramState *savCps;
838
839 void
840 LoadEngine ()
841 {
842     int i;
843     if(WaitForEngine(savCps, LoadEngine)) return;
844     CommonEngineInit(); // recalculate time odds
845     if(gameInfo.variant != StringToVariant(appData.variant)) {
846         // we changed variant when loading the engine; this forces us to reset
847         Reset(TRUE, savCps != &first);
848         EditGameEvent(); // for consistency with other path, as Reset changes mode
849     }
850     InitChessProgram(savCps, FALSE);
851     SendToProgram("force\n", savCps);
852     DisplayMessage("", "");
853     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
854     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
855     ThawUI();
856     SetGNUMode();
857 }
858
859 void
860 ReplaceEngine (ChessProgramState *cps, int n)
861 {
862     EditGameEvent();
863     UnloadEngine(cps);
864     appData.noChessProgram = FALSE;
865     appData.clockMode = TRUE;
866     InitEngine(cps, n);
867     UpdateLogos(TRUE);
868     if(n) return; // only startup first engine immediately; second can wait
869     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
870     LoadEngine();
871 }
872
873 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
874 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
875
876 static char resetOptions[] = 
877         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
878         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
879         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
880         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
881
882 void
883 FloatToFront(char **list, char *engineLine)
884 {
885     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
886     int i=0;
887     if(appData.recentEngines <= 0) return;
888     TidyProgramName(engineLine, "localhost", tidy+1);
889     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
890     strncpy(buf+1, *list, MSG_SIZ-50);
891     if(p = strstr(buf, tidy)) { // tidy name appears in list
892         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
893         while(*p++ = *++q); // squeeze out
894     }
895     strcat(tidy, buf+1); // put list behind tidy name
896     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
897     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
898     ASSIGN(*list, tidy+1);
899 }
900
901 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
902
903 void
904 Load (ChessProgramState *cps, int i)
905 {
906     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
907     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
908         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
909         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
910         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
911         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
912         appData.firstProtocolVersion = PROTOVER;
913         ParseArgsFromString(buf);
914         SwapEngines(i);
915         ReplaceEngine(cps, i);
916         FloatToFront(&appData.recentEngineList, engineLine);
917         return;
918     }
919     p = engineName;
920     while(q = strchr(p, SLASH)) p = q+1;
921     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
922     if(engineDir[0] != NULLCHAR) {
923         ASSIGN(appData.directory[i], engineDir); p = engineName;
924     } else if(p != engineName) { // derive directory from engine path, when not given
925         p[-1] = 0;
926         ASSIGN(appData.directory[i], engineName);
927         p[-1] = SLASH;
928         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
929     } else { ASSIGN(appData.directory[i], "."); }
930     if(params[0]) {
931         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
932         snprintf(command, MSG_SIZ, "%s %s", p, params);
933         p = command;
934     }
935     ASSIGN(appData.chessProgram[i], p);
936     appData.isUCI[i] = isUCI;
937     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
938     appData.hasOwnBookUCI[i] = hasBook;
939     if(!nickName[0]) useNick = FALSE;
940     if(useNick) ASSIGN(appData.pgnName[i], nickName);
941     if(addToList) {
942         int len;
943         char quote;
944         q = firstChessProgramNames;
945         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
946         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
947         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
948                         quote, p, quote, appData.directory[i], 
949                         useNick ? " -fn \"" : "",
950                         useNick ? nickName : "",
951                         useNick ? "\"" : "",
952                         v1 ? " -firstProtocolVersion 1" : "",
953                         hasBook ? "" : " -fNoOwnBookUCI",
954                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
955                         storeVariant ? " -variant " : "",
956                         storeVariant ? VariantName(gameInfo.variant) : "");
957         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
958         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
959         if(insert != q) insert[-1] = NULLCHAR;
960         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
961         if(q)   free(q);
962         FloatToFront(&appData.recentEngineList, buf);
963     }
964     ReplaceEngine(cps, i);
965 }
966
967 void
968 InitTimeControls ()
969 {
970     int matched, min, sec;
971     /*
972      * Parse timeControl resource
973      */
974     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
975                           appData.movesPerSession)) {
976         char buf[MSG_SIZ];
977         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
978         DisplayFatalError(buf, 0, 2);
979     }
980
981     /*
982      * Parse searchTime resource
983      */
984     if (*appData.searchTime != NULLCHAR) {
985         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
986         if (matched == 1) {
987             searchTime = min * 60;
988         } else if (matched == 2) {
989             searchTime = min * 60 + sec;
990         } else {
991             char buf[MSG_SIZ];
992             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
993             DisplayFatalError(buf, 0, 2);
994         }
995     }
996 }
997
998 void
999 InitBackEnd1 ()
1000 {
1001
1002     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1003     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1004
1005     GetTimeMark(&programStartTime);
1006     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1007     appData.seedBase = random() + (random()<<15);
1008     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1009
1010     ClearProgramStats();
1011     programStats.ok_to_send = 1;
1012     programStats.seen_stat = 0;
1013
1014     /*
1015      * Initialize game list
1016      */
1017     ListNew(&gameList);
1018
1019
1020     /*
1021      * Internet chess server status
1022      */
1023     if (appData.icsActive) {
1024         appData.matchMode = FALSE;
1025         appData.matchGames = 0;
1026 #if ZIPPY
1027         appData.noChessProgram = !appData.zippyPlay;
1028 #else
1029         appData.zippyPlay = FALSE;
1030         appData.zippyTalk = FALSE;
1031         appData.noChessProgram = TRUE;
1032 #endif
1033         if (*appData.icsHelper != NULLCHAR) {
1034             appData.useTelnet = TRUE;
1035             appData.telnetProgram = appData.icsHelper;
1036         }
1037     } else {
1038         appData.zippyTalk = appData.zippyPlay = FALSE;
1039     }
1040
1041     /* [AS] Initialize pv info list [HGM] and game state */
1042     {
1043         int i, j;
1044
1045         for( i=0; i<=framePtr; i++ ) {
1046             pvInfoList[i].depth = -1;
1047             boards[i][EP_STATUS] = EP_NONE;
1048             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1049         }
1050     }
1051
1052     InitTimeControls();
1053
1054     /* [AS] Adjudication threshold */
1055     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1056
1057     InitEngine(&first, 0);
1058     InitEngine(&second, 1);
1059     CommonEngineInit();
1060
1061     pairing.which = "pairing"; // pairing engine
1062     pairing.pr = NoProc;
1063     pairing.isr = NULL;
1064     pairing.program = appData.pairingEngine;
1065     pairing.host = "localhost";
1066     pairing.dir = ".";
1067
1068     if (appData.icsActive) {
1069         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1070     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1071         appData.clockMode = FALSE;
1072         first.sendTime = second.sendTime = 0;
1073     }
1074
1075 #if ZIPPY
1076     /* Override some settings from environment variables, for backward
1077        compatibility.  Unfortunately it's not feasible to have the env
1078        vars just set defaults, at least in xboard.  Ugh.
1079     */
1080     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1081       ZippyInit();
1082     }
1083 #endif
1084
1085     if (!appData.icsActive) {
1086       char buf[MSG_SIZ];
1087       int len;
1088
1089       /* Check for variants that are supported only in ICS mode,
1090          or not at all.  Some that are accepted here nevertheless
1091          have bugs; see comments below.
1092       */
1093       VariantClass variant = StringToVariant(appData.variant);
1094       switch (variant) {
1095       case VariantBughouse:     /* need four players and two boards */
1096       case VariantKriegspiel:   /* need to hide pieces and move details */
1097         /* case VariantFischeRandom: (Fabien: moved below) */
1098         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1099         if( (len >= MSG_SIZ) && appData.debugMode )
1100           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1101
1102         DisplayFatalError(buf, 0, 2);
1103         return;
1104
1105       case VariantUnknown:
1106       case VariantLoadable:
1107       case Variant29:
1108       case Variant30:
1109       case Variant31:
1110       case Variant32:
1111       case Variant33:
1112       case Variant34:
1113       case Variant35:
1114       case Variant36:
1115       default:
1116         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1117         if( (len >= MSG_SIZ) && appData.debugMode )
1118           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1119
1120         DisplayFatalError(buf, 0, 2);
1121         return;
1122
1123       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1124       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1125       case VariantGothic:     /* [HGM] should work */
1126       case VariantCapablanca: /* [HGM] should work */
1127       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1128       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1129       case VariantKnightmate: /* [HGM] should work */
1130       case VariantCylinder:   /* [HGM] untested */
1131       case VariantFalcon:     /* [HGM] untested */
1132       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1133                                  offboard interposition not understood */
1134       case VariantNormal:     /* definitely works! */
1135       case VariantWildCastle: /* pieces not automatically shuffled */
1136       case VariantNoCastle:   /* pieces not automatically shuffled */
1137       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1138       case VariantLosers:     /* should work except for win condition,
1139                                  and doesn't know captures are mandatory */
1140       case VariantSuicide:    /* should work except for win condition,
1141                                  and doesn't know captures are mandatory */
1142       case VariantGiveaway:   /* should work except for win condition,
1143                                  and doesn't know captures are mandatory */
1144       case VariantTwoKings:   /* should work */
1145       case VariantAtomic:     /* should work except for win condition */
1146       case Variant3Check:     /* should work except for win condition */
1147       case VariantShatranj:   /* should work except for all win conditions */
1148       case VariantMakruk:     /* should work except for draw countdown */
1149       case VariantBerolina:   /* might work if TestLegality is off */
1150       case VariantCapaRandom: /* should work */
1151       case VariantJanus:      /* should work */
1152       case VariantSuper:      /* experimental */
1153       case VariantGreat:      /* experimental, requires legality testing to be off */
1154       case VariantSChess:     /* S-Chess, should work */
1155       case VariantGrand:      /* should work */
1156       case VariantSpartan:    /* should work */
1157         break;
1158       }
1159     }
1160
1161 }
1162
1163 int
1164 NextIntegerFromString (char ** str, long * value)
1165 {
1166     int result = -1;
1167     char * s = *str;
1168
1169     while( *s == ' ' || *s == '\t' ) {
1170         s++;
1171     }
1172
1173     *value = 0;
1174
1175     if( *s >= '0' && *s <= '9' ) {
1176         while( *s >= '0' && *s <= '9' ) {
1177             *value = *value * 10 + (*s - '0');
1178             s++;
1179         }
1180
1181         result = 0;
1182     }
1183
1184     *str = s;
1185
1186     return result;
1187 }
1188
1189 int
1190 NextTimeControlFromString (char ** str, long * value)
1191 {
1192     long temp;
1193     int result = NextIntegerFromString( str, &temp );
1194
1195     if( result == 0 ) {
1196         *value = temp * 60; /* Minutes */
1197         if( **str == ':' ) {
1198             (*str)++;
1199             result = NextIntegerFromString( str, &temp );
1200             *value += temp; /* Seconds */
1201         }
1202     }
1203
1204     return result;
1205 }
1206
1207 int
1208 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1209 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1210     int result = -1, type = 0; long temp, temp2;
1211
1212     if(**str != ':') return -1; // old params remain in force!
1213     (*str)++;
1214     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1215     if( NextIntegerFromString( str, &temp ) ) return -1;
1216     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1217
1218     if(**str != '/') {
1219         /* time only: incremental or sudden-death time control */
1220         if(**str == '+') { /* increment follows; read it */
1221             (*str)++;
1222             if(**str == '!') type = *(*str)++; // Bronstein TC
1223             if(result = NextIntegerFromString( str, &temp2)) return -1;
1224             *inc = temp2 * 1000;
1225             if(**str == '.') { // read fraction of increment
1226                 char *start = ++(*str);
1227                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1228                 temp2 *= 1000;
1229                 while(start++ < *str) temp2 /= 10;
1230                 *inc += temp2;
1231             }
1232         } else *inc = 0;
1233         *moves = 0; *tc = temp * 1000; *incType = type;
1234         return 0;
1235     }
1236
1237     (*str)++; /* classical time control */
1238     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1239
1240     if(result == 0) {
1241         *moves = temp;
1242         *tc    = temp2 * 1000;
1243         *inc   = 0;
1244         *incType = type;
1245     }
1246     return result;
1247 }
1248
1249 int
1250 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1251 {   /* [HGM] get time to add from the multi-session time-control string */
1252     int incType, moves=1; /* kludge to force reading of first session */
1253     long time, increment;
1254     char *s = tcString;
1255
1256     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1257     do {
1258         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1259         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1260         if(movenr == -1) return time;    /* last move before new session     */
1261         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1262         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1263         if(!moves) return increment;     /* current session is incremental   */
1264         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1265     } while(movenr >= -1);               /* try again for next session       */
1266
1267     return 0; // no new time quota on this move
1268 }
1269
1270 int
1271 ParseTimeControl (char *tc, float ti, int mps)
1272 {
1273   long tc1;
1274   long tc2;
1275   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1276   int min, sec=0;
1277
1278   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1279   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1280       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1281   if(ti > 0) {
1282
1283     if(mps)
1284       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1285     else 
1286       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1287   } else {
1288     if(mps)
1289       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1290     else 
1291       snprintf(buf, MSG_SIZ, ":%s", mytc);
1292   }
1293   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1294   
1295   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1296     return FALSE;
1297   }
1298
1299   if( *tc == '/' ) {
1300     /* Parse second time control */
1301     tc++;
1302
1303     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1304       return FALSE;
1305     }
1306
1307     if( tc2 == 0 ) {
1308       return FALSE;
1309     }
1310
1311     timeControl_2 = tc2 * 1000;
1312   }
1313   else {
1314     timeControl_2 = 0;
1315   }
1316
1317   if( tc1 == 0 ) {
1318     return FALSE;
1319   }
1320
1321   timeControl = tc1 * 1000;
1322
1323   if (ti >= 0) {
1324     timeIncrement = ti * 1000;  /* convert to ms */
1325     movesPerSession = 0;
1326   } else {
1327     timeIncrement = 0;
1328     movesPerSession = mps;
1329   }
1330   return TRUE;
1331 }
1332
1333 void
1334 InitBackEnd2 ()
1335 {
1336     if (appData.debugMode) {
1337         fprintf(debugFP, "%s\n", programVersion);
1338     }
1339     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1340
1341     set_cont_sequence(appData.wrapContSeq);
1342     if (appData.matchGames > 0) {
1343         appData.matchMode = TRUE;
1344     } else if (appData.matchMode) {
1345         appData.matchGames = 1;
1346     }
1347     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1348         appData.matchGames = appData.sameColorGames;
1349     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1350         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1351         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1352     }
1353     Reset(TRUE, FALSE);
1354     if (appData.noChessProgram || first.protocolVersion == 1) {
1355       InitBackEnd3();
1356     } else {
1357       /* kludge: allow timeout for initial "feature" commands */
1358       FreezeUI();
1359       DisplayMessage("", _("Starting chess program"));
1360       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1361     }
1362 }
1363
1364 int
1365 CalculateIndex (int index, int gameNr)
1366 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1367     int res;
1368     if(index > 0) return index; // fixed nmber
1369     if(index == 0) return 1;
1370     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1371     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1372     return res;
1373 }
1374
1375 int
1376 LoadGameOrPosition (int gameNr)
1377 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1378     if (*appData.loadGameFile != NULLCHAR) {
1379         if (!LoadGameFromFile(appData.loadGameFile,
1380                 CalculateIndex(appData.loadGameIndex, gameNr),
1381                               appData.loadGameFile, FALSE)) {
1382             DisplayFatalError(_("Bad game file"), 0, 1);
1383             return 0;
1384         }
1385     } else if (*appData.loadPositionFile != NULLCHAR) {
1386         if (!LoadPositionFromFile(appData.loadPositionFile,
1387                 CalculateIndex(appData.loadPositionIndex, gameNr),
1388                                   appData.loadPositionFile)) {
1389             DisplayFatalError(_("Bad position file"), 0, 1);
1390             return 0;
1391         }
1392     }
1393     return 1;
1394 }
1395
1396 void
1397 ReserveGame (int gameNr, char resChar)
1398 {
1399     FILE *tf = fopen(appData.tourneyFile, "r+");
1400     char *p, *q, c, buf[MSG_SIZ];
1401     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1402     safeStrCpy(buf, lastMsg, MSG_SIZ);
1403     DisplayMessage(_("Pick new game"), "");
1404     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1405     ParseArgsFromFile(tf);
1406     p = q = appData.results;
1407     if(appData.debugMode) {
1408       char *r = appData.participants;
1409       fprintf(debugFP, "results = '%s'\n", p);
1410       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1411       fprintf(debugFP, "\n");
1412     }
1413     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1414     nextGame = q - p;
1415     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1416     safeStrCpy(q, p, strlen(p) + 2);
1417     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1418     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1419     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1420         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1421         q[nextGame] = '*';
1422     }
1423     fseek(tf, -(strlen(p)+4), SEEK_END);
1424     c = fgetc(tf);
1425     if(c != '"') // depending on DOS or Unix line endings we can be one off
1426          fseek(tf, -(strlen(p)+2), SEEK_END);
1427     else fseek(tf, -(strlen(p)+3), SEEK_END);
1428     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1429     DisplayMessage(buf, "");
1430     free(p); appData.results = q;
1431     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1432        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1433       int round = appData.defaultMatchGames * appData.tourneyType;
1434       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1435          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1436         UnloadEngine(&first);  // next game belongs to other pairing;
1437         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1438     }
1439     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1440 }
1441
1442 void
1443 MatchEvent (int mode)
1444 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1445         int dummy;
1446         if(matchMode) { // already in match mode: switch it off
1447             abortMatch = TRUE;
1448             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1449             return;
1450         }
1451 //      if(gameMode != BeginningOfGame) {
1452 //          DisplayError(_("You can only start a match from the initial position."), 0);
1453 //          return;
1454 //      }
1455         abortMatch = FALSE;
1456         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1457         /* Set up machine vs. machine match */
1458         nextGame = 0;
1459         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1460         if(appData.tourneyFile[0]) {
1461             ReserveGame(-1, 0);
1462             if(nextGame > appData.matchGames) {
1463                 char buf[MSG_SIZ];
1464                 if(strchr(appData.results, '*') == NULL) {
1465                     FILE *f;
1466                     appData.tourneyCycles++;
1467                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1468                         fclose(f);
1469                         NextTourneyGame(-1, &dummy);
1470                         ReserveGame(-1, 0);
1471                         if(nextGame <= appData.matchGames) {
1472                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1473                             matchMode = mode;
1474                             ScheduleDelayedEvent(NextMatchGame, 10000);
1475                             return;
1476                         }
1477                     }
1478                 }
1479                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1480                 DisplayError(buf, 0);
1481                 appData.tourneyFile[0] = 0;
1482                 return;
1483             }
1484         } else
1485         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1486             DisplayFatalError(_("Can't have a match with no chess programs"),
1487                               0, 2);
1488             return;
1489         }
1490         matchMode = mode;
1491         matchGame = roundNr = 1;
1492         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1493         NextMatchGame();
1494 }
1495
1496 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1497
1498 void
1499 InitBackEnd3 P((void))
1500 {
1501     GameMode initialMode;
1502     char buf[MSG_SIZ];
1503     int err, len;
1504
1505     InitChessProgram(&first, startedFromSetupPosition);
1506
1507     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1508         free(programVersion);
1509         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1510         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1511         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1512     }
1513
1514     if (appData.icsActive) {
1515 #ifdef WIN32
1516         /* [DM] Make a console window if needed [HGM] merged ifs */
1517         ConsoleCreate();
1518 #endif
1519         err = establish();
1520         if (err != 0)
1521           {
1522             if (*appData.icsCommPort != NULLCHAR)
1523               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1524                              appData.icsCommPort);
1525             else
1526               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1527                         appData.icsHost, appData.icsPort);
1528
1529             if( (len >= MSG_SIZ) && appData.debugMode )
1530               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1531
1532             DisplayFatalError(buf, err, 1);
1533             return;
1534         }
1535         SetICSMode();
1536         telnetISR =
1537           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1538         fromUserISR =
1539           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1540         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1541             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1542     } else if (appData.noChessProgram) {
1543         SetNCPMode();
1544     } else {
1545         SetGNUMode();
1546     }
1547
1548     if (*appData.cmailGameName != NULLCHAR) {
1549         SetCmailMode();
1550         OpenLoopback(&cmailPR);
1551         cmailISR =
1552           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1553     }
1554
1555     ThawUI();
1556     DisplayMessage("", "");
1557     if (StrCaseCmp(appData.initialMode, "") == 0) {
1558       initialMode = BeginningOfGame;
1559       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1560         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1561         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1562         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1563         ModeHighlight();
1564       }
1565     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1566       initialMode = TwoMachinesPlay;
1567     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1568       initialMode = AnalyzeFile;
1569     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1570       initialMode = AnalyzeMode;
1571     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1572       initialMode = MachinePlaysWhite;
1573     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1574       initialMode = MachinePlaysBlack;
1575     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1576       initialMode = EditGame;
1577     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1578       initialMode = EditPosition;
1579     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1580       initialMode = Training;
1581     } else {
1582       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1583       if( (len >= MSG_SIZ) && appData.debugMode )
1584         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1585
1586       DisplayFatalError(buf, 0, 2);
1587       return;
1588     }
1589
1590     if (appData.matchMode) {
1591         if(appData.tourneyFile[0]) { // start tourney from command line
1592             FILE *f;
1593             if(f = fopen(appData.tourneyFile, "r")) {
1594                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1595                 fclose(f);
1596                 appData.clockMode = TRUE;
1597                 SetGNUMode();
1598             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1599         }
1600         MatchEvent(TRUE);
1601     } else if (*appData.cmailGameName != NULLCHAR) {
1602         /* Set up cmail mode */
1603         ReloadCmailMsgEvent(TRUE);
1604     } else {
1605         /* Set up other modes */
1606         if (initialMode == AnalyzeFile) {
1607           if (*appData.loadGameFile == NULLCHAR) {
1608             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1609             return;
1610           }
1611         }
1612         if (*appData.loadGameFile != NULLCHAR) {
1613             (void) LoadGameFromFile(appData.loadGameFile,
1614                                     appData.loadGameIndex,
1615                                     appData.loadGameFile, TRUE);
1616         } else if (*appData.loadPositionFile != NULLCHAR) {
1617             (void) LoadPositionFromFile(appData.loadPositionFile,
1618                                         appData.loadPositionIndex,
1619                                         appData.loadPositionFile);
1620             /* [HGM] try to make self-starting even after FEN load */
1621             /* to allow automatic setup of fairy variants with wtm */
1622             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1623                 gameMode = BeginningOfGame;
1624                 setboardSpoiledMachineBlack = 1;
1625             }
1626             /* [HGM] loadPos: make that every new game uses the setup */
1627             /* from file as long as we do not switch variant          */
1628             if(!blackPlaysFirst) {
1629                 startedFromPositionFile = TRUE;
1630                 CopyBoard(filePosition, boards[0]);
1631             }
1632         }
1633         if (initialMode == AnalyzeMode) {
1634           if (appData.noChessProgram) {
1635             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1636             return;
1637           }
1638           if (appData.icsActive) {
1639             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1640             return;
1641           }
1642           AnalyzeModeEvent();
1643         } else if (initialMode == AnalyzeFile) {
1644           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1645           ShowThinkingEvent();
1646           AnalyzeFileEvent();
1647           AnalysisPeriodicEvent(1);
1648         } else if (initialMode == MachinePlaysWhite) {
1649           if (appData.noChessProgram) {
1650             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1651                               0, 2);
1652             return;
1653           }
1654           if (appData.icsActive) {
1655             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1656                               0, 2);
1657             return;
1658           }
1659           MachineWhiteEvent();
1660         } else if (initialMode == MachinePlaysBlack) {
1661           if (appData.noChessProgram) {
1662             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1663                               0, 2);
1664             return;
1665           }
1666           if (appData.icsActive) {
1667             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1668                               0, 2);
1669             return;
1670           }
1671           MachineBlackEvent();
1672         } else if (initialMode == TwoMachinesPlay) {
1673           if (appData.noChessProgram) {
1674             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1675                               0, 2);
1676             return;
1677           }
1678           if (appData.icsActive) {
1679             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1680                               0, 2);
1681             return;
1682           }
1683           TwoMachinesEvent();
1684         } else if (initialMode == EditGame) {
1685           EditGameEvent();
1686         } else if (initialMode == EditPosition) {
1687           EditPositionEvent();
1688         } else if (initialMode == Training) {
1689           if (*appData.loadGameFile == NULLCHAR) {
1690             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1691             return;
1692           }
1693           TrainingEvent();
1694         }
1695     }
1696 }
1697
1698 void
1699 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1700 {
1701     DisplayBook(current+1);
1702
1703     MoveHistorySet( movelist, first, last, current, pvInfoList );
1704
1705     EvalGraphSet( first, last, current, pvInfoList );
1706
1707     MakeEngineOutputTitle();
1708 }
1709
1710 /*
1711  * Establish will establish a contact to a remote host.port.
1712  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1713  *  used to talk to the host.
1714  * Returns 0 if okay, error code if not.
1715  */
1716 int
1717 establish ()
1718 {
1719     char buf[MSG_SIZ];
1720
1721     if (*appData.icsCommPort != NULLCHAR) {
1722         /* Talk to the host through a serial comm port */
1723         return OpenCommPort(appData.icsCommPort, &icsPR);
1724
1725     } else if (*appData.gateway != NULLCHAR) {
1726         if (*appData.remoteShell == NULLCHAR) {
1727             /* Use the rcmd protocol to run telnet program on a gateway host */
1728             snprintf(buf, sizeof(buf), "%s %s %s",
1729                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1730             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1731
1732         } else {
1733             /* Use the rsh program to run telnet program on a gateway host */
1734             if (*appData.remoteUser == NULLCHAR) {
1735                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1736                         appData.gateway, appData.telnetProgram,
1737                         appData.icsHost, appData.icsPort);
1738             } else {
1739                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1740                         appData.remoteShell, appData.gateway,
1741                         appData.remoteUser, appData.telnetProgram,
1742                         appData.icsHost, appData.icsPort);
1743             }
1744             return StartChildProcess(buf, "", &icsPR);
1745
1746         }
1747     } else if (appData.useTelnet) {
1748         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1749
1750     } else {
1751         /* TCP socket interface differs somewhat between
1752            Unix and NT; handle details in the front end.
1753            */
1754         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1755     }
1756 }
1757
1758 void
1759 EscapeExpand (char *p, char *q)
1760 {       // [HGM] initstring: routine to shape up string arguments
1761         while(*p++ = *q++) if(p[-1] == '\\')
1762             switch(*q++) {
1763                 case 'n': p[-1] = '\n'; break;
1764                 case 'r': p[-1] = '\r'; break;
1765                 case 't': p[-1] = '\t'; break;
1766                 case '\\': p[-1] = '\\'; break;
1767                 case 0: *p = 0; return;
1768                 default: p[-1] = q[-1]; break;
1769             }
1770 }
1771
1772 void
1773 show_bytes (FILE *fp, char *buf, int count)
1774 {
1775     while (count--) {
1776         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1777             fprintf(fp, "\\%03o", *buf & 0xff);
1778         } else {
1779             putc(*buf, fp);
1780         }
1781         buf++;
1782     }
1783     fflush(fp);
1784 }
1785
1786 /* Returns an errno value */
1787 int
1788 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1789 {
1790     char buf[8192], *p, *q, *buflim;
1791     int left, newcount, outcount;
1792
1793     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1794         *appData.gateway != NULLCHAR) {
1795         if (appData.debugMode) {
1796             fprintf(debugFP, ">ICS: ");
1797             show_bytes(debugFP, message, count);
1798             fprintf(debugFP, "\n");
1799         }
1800         return OutputToProcess(pr, message, count, outError);
1801     }
1802
1803     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1804     p = message;
1805     q = buf;
1806     left = count;
1807     newcount = 0;
1808     while (left) {
1809         if (q >= buflim) {
1810             if (appData.debugMode) {
1811                 fprintf(debugFP, ">ICS: ");
1812                 show_bytes(debugFP, buf, newcount);
1813                 fprintf(debugFP, "\n");
1814             }
1815             outcount = OutputToProcess(pr, buf, newcount, outError);
1816             if (outcount < newcount) return -1; /* to be sure */
1817             q = buf;
1818             newcount = 0;
1819         }
1820         if (*p == '\n') {
1821             *q++ = '\r';
1822             newcount++;
1823         } else if (((unsigned char) *p) == TN_IAC) {
1824             *q++ = (char) TN_IAC;
1825             newcount ++;
1826         }
1827         *q++ = *p++;
1828         newcount++;
1829         left--;
1830     }
1831     if (appData.debugMode) {
1832         fprintf(debugFP, ">ICS: ");
1833         show_bytes(debugFP, buf, newcount);
1834         fprintf(debugFP, "\n");
1835     }
1836     outcount = OutputToProcess(pr, buf, newcount, outError);
1837     if (outcount < newcount) return -1; /* to be sure */
1838     return count;
1839 }
1840
1841 void
1842 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1843 {
1844     int outError, outCount;
1845     static int gotEof = 0;
1846
1847     /* Pass data read from player on to ICS */
1848     if (count > 0) {
1849         gotEof = 0;
1850         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1851         if (outCount < count) {
1852             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1853         }
1854     } else if (count < 0) {
1855         RemoveInputSource(isr);
1856         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1857     } else if (gotEof++ > 0) {
1858         RemoveInputSource(isr);
1859         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1860     }
1861 }
1862
1863 void
1864 KeepAlive ()
1865 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1866     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1867     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1868     SendToICS("date\n");
1869     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1870 }
1871
1872 /* added routine for printf style output to ics */
1873 void
1874 ics_printf (char *format, ...)
1875 {
1876     char buffer[MSG_SIZ];
1877     va_list args;
1878
1879     va_start(args, format);
1880     vsnprintf(buffer, sizeof(buffer), format, args);
1881     buffer[sizeof(buffer)-1] = '\0';
1882     SendToICS(buffer);
1883     va_end(args);
1884 }
1885
1886 void
1887 SendToICS (char *s)
1888 {
1889     int count, outCount, outError;
1890
1891     if (icsPR == NoProc) return;
1892
1893     count = strlen(s);
1894     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1895     if (outCount < count) {
1896         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1897     }
1898 }
1899
1900 /* This is used for sending logon scripts to the ICS. Sending
1901    without a delay causes problems when using timestamp on ICC
1902    (at least on my machine). */
1903 void
1904 SendToICSDelayed (char *s, long msdelay)
1905 {
1906     int count, outCount, outError;
1907
1908     if (icsPR == NoProc) return;
1909
1910     count = strlen(s);
1911     if (appData.debugMode) {
1912         fprintf(debugFP, ">ICS: ");
1913         show_bytes(debugFP, s, count);
1914         fprintf(debugFP, "\n");
1915     }
1916     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1917                                       msdelay);
1918     if (outCount < count) {
1919         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1920     }
1921 }
1922
1923
1924 /* Remove all highlighting escape sequences in s
1925    Also deletes any suffix starting with '('
1926    */
1927 char *
1928 StripHighlightAndTitle (char *s)
1929 {
1930     static char retbuf[MSG_SIZ];
1931     char *p = retbuf;
1932
1933     while (*s != NULLCHAR) {
1934         while (*s == '\033') {
1935             while (*s != NULLCHAR && !isalpha(*s)) s++;
1936             if (*s != NULLCHAR) s++;
1937         }
1938         while (*s != NULLCHAR && *s != '\033') {
1939             if (*s == '(' || *s == '[') {
1940                 *p = NULLCHAR;
1941                 return retbuf;
1942             }
1943             *p++ = *s++;
1944         }
1945     }
1946     *p = NULLCHAR;
1947     return retbuf;
1948 }
1949
1950 /* Remove all highlighting escape sequences in s */
1951 char *
1952 StripHighlight (char *s)
1953 {
1954     static char retbuf[MSG_SIZ];
1955     char *p = retbuf;
1956
1957     while (*s != NULLCHAR) {
1958         while (*s == '\033') {
1959             while (*s != NULLCHAR && !isalpha(*s)) s++;
1960             if (*s != NULLCHAR) s++;
1961         }
1962         while (*s != NULLCHAR && *s != '\033') {
1963             *p++ = *s++;
1964         }
1965     }
1966     *p = NULLCHAR;
1967     return retbuf;
1968 }
1969
1970 char *variantNames[] = VARIANT_NAMES;
1971 char *
1972 VariantName (VariantClass v)
1973 {
1974     return variantNames[v];
1975 }
1976
1977
1978 /* Identify a variant from the strings the chess servers use or the
1979    PGN Variant tag names we use. */
1980 VariantClass
1981 StringToVariant (char *e)
1982 {
1983     char *p;
1984     int wnum = -1;
1985     VariantClass v = VariantNormal;
1986     int i, found = FALSE;
1987     char buf[MSG_SIZ];
1988     int len;
1989
1990     if (!e) return v;
1991
1992     /* [HGM] skip over optional board-size prefixes */
1993     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1994         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1995         while( *e++ != '_');
1996     }
1997
1998     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1999         v = VariantNormal;
2000         found = TRUE;
2001     } else
2002     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2003       if (StrCaseStr(e, variantNames[i])) {
2004         v = (VariantClass) i;
2005         found = TRUE;
2006         break;
2007       }
2008     }
2009
2010     if (!found) {
2011       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2012           || StrCaseStr(e, "wild/fr")
2013           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2014         v = VariantFischeRandom;
2015       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2016                  (i = 1, p = StrCaseStr(e, "w"))) {
2017         p += i;
2018         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2019         if (isdigit(*p)) {
2020           wnum = atoi(p);
2021         } else {
2022           wnum = -1;
2023         }
2024         switch (wnum) {
2025         case 0: /* FICS only, actually */
2026         case 1:
2027           /* Castling legal even if K starts on d-file */
2028           v = VariantWildCastle;
2029           break;
2030         case 2:
2031         case 3:
2032         case 4:
2033           /* Castling illegal even if K & R happen to start in
2034              normal positions. */
2035           v = VariantNoCastle;
2036           break;
2037         case 5:
2038         case 7:
2039         case 8:
2040         case 10:
2041         case 11:
2042         case 12:
2043         case 13:
2044         case 14:
2045         case 15:
2046         case 18:
2047         case 19:
2048           /* Castling legal iff K & R start in normal positions */
2049           v = VariantNormal;
2050           break;
2051         case 6:
2052         case 20:
2053         case 21:
2054           /* Special wilds for position setup; unclear what to do here */
2055           v = VariantLoadable;
2056           break;
2057         case 9:
2058           /* Bizarre ICC game */
2059           v = VariantTwoKings;
2060           break;
2061         case 16:
2062           v = VariantKriegspiel;
2063           break;
2064         case 17:
2065           v = VariantLosers;
2066           break;
2067         case 22:
2068           v = VariantFischeRandom;
2069           break;
2070         case 23:
2071           v = VariantCrazyhouse;
2072           break;
2073         case 24:
2074           v = VariantBughouse;
2075           break;
2076         case 25:
2077           v = Variant3Check;
2078           break;
2079         case 26:
2080           /* Not quite the same as FICS suicide! */
2081           v = VariantGiveaway;
2082           break;
2083         case 27:
2084           v = VariantAtomic;
2085           break;
2086         case 28:
2087           v = VariantShatranj;
2088           break;
2089
2090         /* Temporary names for future ICC types.  The name *will* change in
2091            the next xboard/WinBoard release after ICC defines it. */
2092         case 29:
2093           v = Variant29;
2094           break;
2095         case 30:
2096           v = Variant30;
2097           break;
2098         case 31:
2099           v = Variant31;
2100           break;
2101         case 32:
2102           v = Variant32;
2103           break;
2104         case 33:
2105           v = Variant33;
2106           break;
2107         case 34:
2108           v = Variant34;
2109           break;
2110         case 35:
2111           v = Variant35;
2112           break;
2113         case 36:
2114           v = Variant36;
2115           break;
2116         case 37:
2117           v = VariantShogi;
2118           break;
2119         case 38:
2120           v = VariantXiangqi;
2121           break;
2122         case 39:
2123           v = VariantCourier;
2124           break;
2125         case 40:
2126           v = VariantGothic;
2127           break;
2128         case 41:
2129           v = VariantCapablanca;
2130           break;
2131         case 42:
2132           v = VariantKnightmate;
2133           break;
2134         case 43:
2135           v = VariantFairy;
2136           break;
2137         case 44:
2138           v = VariantCylinder;
2139           break;
2140         case 45:
2141           v = VariantFalcon;
2142           break;
2143         case 46:
2144           v = VariantCapaRandom;
2145           break;
2146         case 47:
2147           v = VariantBerolina;
2148           break;
2149         case 48:
2150           v = VariantJanus;
2151           break;
2152         case 49:
2153           v = VariantSuper;
2154           break;
2155         case 50:
2156           v = VariantGreat;
2157           break;
2158         case -1:
2159           /* Found "wild" or "w" in the string but no number;
2160              must assume it's normal chess. */
2161           v = VariantNormal;
2162           break;
2163         default:
2164           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2165           if( (len >= MSG_SIZ) && appData.debugMode )
2166             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2167
2168           DisplayError(buf, 0);
2169           v = VariantUnknown;
2170           break;
2171         }
2172       }
2173     }
2174     if (appData.debugMode) {
2175       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2176               e, wnum, VariantName(v));
2177     }
2178     return v;
2179 }
2180
2181 static int leftover_start = 0, leftover_len = 0;
2182 char star_match[STAR_MATCH_N][MSG_SIZ];
2183
2184 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2185    advance *index beyond it, and set leftover_start to the new value of
2186    *index; else return FALSE.  If pattern contains the character '*', it
2187    matches any sequence of characters not containing '\r', '\n', or the
2188    character following the '*' (if any), and the matched sequence(s) are
2189    copied into star_match.
2190    */
2191 int
2192 looking_at ( char *buf, int *index, char *pattern)
2193 {
2194     char *bufp = &buf[*index], *patternp = pattern;
2195     int star_count = 0;
2196     char *matchp = star_match[0];
2197
2198     for (;;) {
2199         if (*patternp == NULLCHAR) {
2200             *index = leftover_start = bufp - buf;
2201             *matchp = NULLCHAR;
2202             return TRUE;
2203         }
2204         if (*bufp == NULLCHAR) return FALSE;
2205         if (*patternp == '*') {
2206             if (*bufp == *(patternp + 1)) {
2207                 *matchp = NULLCHAR;
2208                 matchp = star_match[++star_count];
2209                 patternp += 2;
2210                 bufp++;
2211                 continue;
2212             } else if (*bufp == '\n' || *bufp == '\r') {
2213                 patternp++;
2214                 if (*patternp == NULLCHAR)
2215                   continue;
2216                 else
2217                   return FALSE;
2218             } else {
2219                 *matchp++ = *bufp++;
2220                 continue;
2221             }
2222         }
2223         if (*patternp != *bufp) return FALSE;
2224         patternp++;
2225         bufp++;
2226     }
2227 }
2228
2229 void
2230 SendToPlayer (char *data, int length)
2231 {
2232     int error, outCount;
2233     outCount = OutputToProcess(NoProc, data, length, &error);
2234     if (outCount < length) {
2235         DisplayFatalError(_("Error writing to display"), error, 1);
2236     }
2237 }
2238
2239 void
2240 PackHolding (char packed[], char *holding)
2241 {
2242     char *p = holding;
2243     char *q = packed;
2244     int runlength = 0;
2245     int curr = 9999;
2246     do {
2247         if (*p == curr) {
2248             runlength++;
2249         } else {
2250             switch (runlength) {
2251               case 0:
2252                 break;
2253               case 1:
2254                 *q++ = curr;
2255                 break;
2256               case 2:
2257                 *q++ = curr;
2258                 *q++ = curr;
2259                 break;
2260               default:
2261                 sprintf(q, "%d", runlength);
2262                 while (*q) q++;
2263                 *q++ = curr;
2264                 break;
2265             }
2266             runlength = 1;
2267             curr = *p;
2268         }
2269     } while (*p++);
2270     *q = NULLCHAR;
2271 }
2272
2273 /* Telnet protocol requests from the front end */
2274 void
2275 TelnetRequest (unsigned char ddww, unsigned char option)
2276 {
2277     unsigned char msg[3];
2278     int outCount, outError;
2279
2280     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2281
2282     if (appData.debugMode) {
2283         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2284         switch (ddww) {
2285           case TN_DO:
2286             ddwwStr = "DO";
2287             break;
2288           case TN_DONT:
2289             ddwwStr = "DONT";
2290             break;
2291           case TN_WILL:
2292             ddwwStr = "WILL";
2293             break;
2294           case TN_WONT:
2295             ddwwStr = "WONT";
2296             break;
2297           default:
2298             ddwwStr = buf1;
2299             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2300             break;
2301         }
2302         switch (option) {
2303           case TN_ECHO:
2304             optionStr = "ECHO";
2305             break;
2306           default:
2307             optionStr = buf2;
2308             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2309             break;
2310         }
2311         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2312     }
2313     msg[0] = TN_IAC;
2314     msg[1] = ddww;
2315     msg[2] = option;
2316     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2317     if (outCount < 3) {
2318         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2319     }
2320 }
2321
2322 void
2323 DoEcho ()
2324 {
2325     if (!appData.icsActive) return;
2326     TelnetRequest(TN_DO, TN_ECHO);
2327 }
2328
2329 void
2330 DontEcho ()
2331 {
2332     if (!appData.icsActive) return;
2333     TelnetRequest(TN_DONT, TN_ECHO);
2334 }
2335
2336 void
2337 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2338 {
2339     /* put the holdings sent to us by the server on the board holdings area */
2340     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2341     char p;
2342     ChessSquare piece;
2343
2344     if(gameInfo.holdingsWidth < 2)  return;
2345     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2346         return; // prevent overwriting by pre-board holdings
2347
2348     if( (int)lowestPiece >= BlackPawn ) {
2349         holdingsColumn = 0;
2350         countsColumn = 1;
2351         holdingsStartRow = BOARD_HEIGHT-1;
2352         direction = -1;
2353     } else {
2354         holdingsColumn = BOARD_WIDTH-1;
2355         countsColumn = BOARD_WIDTH-2;
2356         holdingsStartRow = 0;
2357         direction = 1;
2358     }
2359
2360     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2361         board[i][holdingsColumn] = EmptySquare;
2362         board[i][countsColumn]   = (ChessSquare) 0;
2363     }
2364     while( (p=*holdings++) != NULLCHAR ) {
2365         piece = CharToPiece( ToUpper(p) );
2366         if(piece == EmptySquare) continue;
2367         /*j = (int) piece - (int) WhitePawn;*/
2368         j = PieceToNumber(piece);
2369         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2370         if(j < 0) continue;               /* should not happen */
2371         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2372         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2373         board[holdingsStartRow+j*direction][countsColumn]++;
2374     }
2375 }
2376
2377
2378 void
2379 VariantSwitch (Board board, VariantClass newVariant)
2380 {
2381    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2382    static Board oldBoard;
2383
2384    startedFromPositionFile = FALSE;
2385    if(gameInfo.variant == newVariant) return;
2386
2387    /* [HGM] This routine is called each time an assignment is made to
2388     * gameInfo.variant during a game, to make sure the board sizes
2389     * are set to match the new variant. If that means adding or deleting
2390     * holdings, we shift the playing board accordingly
2391     * This kludge is needed because in ICS observe mode, we get boards
2392     * of an ongoing game without knowing the variant, and learn about the
2393     * latter only later. This can be because of the move list we requested,
2394     * in which case the game history is refilled from the beginning anyway,
2395     * but also when receiving holdings of a crazyhouse game. In the latter
2396     * case we want to add those holdings to the already received position.
2397     */
2398
2399
2400    if (appData.debugMode) {
2401      fprintf(debugFP, "Switch board from %s to %s\n",
2402              VariantName(gameInfo.variant), VariantName(newVariant));
2403      setbuf(debugFP, NULL);
2404    }
2405    shuffleOpenings = 0;       /* [HGM] shuffle */
2406    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2407    switch(newVariant)
2408      {
2409      case VariantShogi:
2410        newWidth = 9;  newHeight = 9;
2411        gameInfo.holdingsSize = 7;
2412      case VariantBughouse:
2413      case VariantCrazyhouse:
2414        newHoldingsWidth = 2; break;
2415      case VariantGreat:
2416        newWidth = 10;
2417      case VariantSuper:
2418        newHoldingsWidth = 2;
2419        gameInfo.holdingsSize = 8;
2420        break;
2421      case VariantGothic:
2422      case VariantCapablanca:
2423      case VariantCapaRandom:
2424        newWidth = 10;
2425      default:
2426        newHoldingsWidth = gameInfo.holdingsSize = 0;
2427      };
2428
2429    if(newWidth  != gameInfo.boardWidth  ||
2430       newHeight != gameInfo.boardHeight ||
2431       newHoldingsWidth != gameInfo.holdingsWidth ) {
2432
2433      /* shift position to new playing area, if needed */
2434      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2435        for(i=0; i<BOARD_HEIGHT; i++)
2436          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2437            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2438              board[i][j];
2439        for(i=0; i<newHeight; i++) {
2440          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2441          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2442        }
2443      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2444        for(i=0; i<BOARD_HEIGHT; i++)
2445          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2446            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2447              board[i][j];
2448      }
2449      board[HOLDINGS_SET] = 0;
2450      gameInfo.boardWidth  = newWidth;
2451      gameInfo.boardHeight = newHeight;
2452      gameInfo.holdingsWidth = newHoldingsWidth;
2453      gameInfo.variant = newVariant;
2454      InitDrawingSizes(-2, 0);
2455    } else gameInfo.variant = newVariant;
2456    CopyBoard(oldBoard, board);   // remember correctly formatted board
2457      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2458    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2459 }
2460
2461 static int loggedOn = FALSE;
2462
2463 /*-- Game start info cache: --*/
2464 int gs_gamenum;
2465 char gs_kind[MSG_SIZ];
2466 static char player1Name[128] = "";
2467 static char player2Name[128] = "";
2468 static char cont_seq[] = "\n\\   ";
2469 static int player1Rating = -1;
2470 static int player2Rating = -1;
2471 /*----------------------------*/
2472
2473 ColorClass curColor = ColorNormal;
2474 int suppressKibitz = 0;
2475
2476 // [HGM] seekgraph
2477 Boolean soughtPending = FALSE;
2478 Boolean seekGraphUp;
2479 #define MAX_SEEK_ADS 200
2480 #define SQUARE 0x80
2481 char *seekAdList[MAX_SEEK_ADS];
2482 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2483 float tcList[MAX_SEEK_ADS];
2484 char colorList[MAX_SEEK_ADS];
2485 int nrOfSeekAds = 0;
2486 int minRating = 1010, maxRating = 2800;
2487 int hMargin = 10, vMargin = 20, h, w;
2488 extern int squareSize, lineGap;
2489
2490 void
2491 PlotSeekAd (int i)
2492 {
2493         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2494         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2495         if(r < minRating+100 && r >=0 ) r = minRating+100;
2496         if(r > maxRating) r = maxRating;
2497         if(tc < 1.f) tc = 1.f;
2498         if(tc > 95.f) tc = 95.f;
2499         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2500         y = ((double)r - minRating)/(maxRating - minRating)
2501             * (h-vMargin-squareSize/8-1) + vMargin;
2502         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2503         if(strstr(seekAdList[i], " u ")) color = 1;
2504         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2505            !strstr(seekAdList[i], "bullet") &&
2506            !strstr(seekAdList[i], "blitz") &&
2507            !strstr(seekAdList[i], "standard") ) color = 2;
2508         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2509         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2510 }
2511
2512 void
2513 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2514 {
2515         char buf[MSG_SIZ], *ext = "";
2516         VariantClass v = StringToVariant(type);
2517         if(strstr(type, "wild")) {
2518             ext = type + 4; // append wild number
2519             if(v == VariantFischeRandom) type = "chess960"; else
2520             if(v == VariantLoadable) type = "setup"; else
2521             type = VariantName(v);
2522         }
2523         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2524         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2525             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2526             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2527             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2528             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2529             seekNrList[nrOfSeekAds] = nr;
2530             zList[nrOfSeekAds] = 0;
2531             seekAdList[nrOfSeekAds++] = StrSave(buf);
2532             if(plot) PlotSeekAd(nrOfSeekAds-1);
2533         }
2534 }
2535
2536 void
2537 EraseSeekDot (int i)
2538 {
2539     int x = xList[i], y = yList[i], d=squareSize/4, k;
2540     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2541     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2542     // now replot every dot that overlapped
2543     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2544         int xx = xList[k], yy = yList[k];
2545         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2546             DrawSeekDot(xx, yy, colorList[k]);
2547     }
2548 }
2549
2550 void
2551 RemoveSeekAd (int nr)
2552 {
2553         int i;
2554         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2555             EraseSeekDot(i);
2556             if(seekAdList[i]) free(seekAdList[i]);
2557             seekAdList[i] = seekAdList[--nrOfSeekAds];
2558             seekNrList[i] = seekNrList[nrOfSeekAds];
2559             ratingList[i] = ratingList[nrOfSeekAds];
2560             colorList[i]  = colorList[nrOfSeekAds];
2561             tcList[i] = tcList[nrOfSeekAds];
2562             xList[i]  = xList[nrOfSeekAds];
2563             yList[i]  = yList[nrOfSeekAds];
2564             zList[i]  = zList[nrOfSeekAds];
2565             seekAdList[nrOfSeekAds] = NULL;
2566             break;
2567         }
2568 }
2569
2570 Boolean
2571 MatchSoughtLine (char *line)
2572 {
2573     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2574     int nr, base, inc, u=0; char dummy;
2575
2576     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2577        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2578        (u=1) &&
2579        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2580         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2581         // match: compact and save the line
2582         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2583         return TRUE;
2584     }
2585     return FALSE;
2586 }
2587
2588 int
2589 DrawSeekGraph ()
2590 {
2591     int i;
2592     if(!seekGraphUp) return FALSE;
2593     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2594     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2595
2596     DrawSeekBackground(0, 0, w, h);
2597     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2598     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2599     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2600         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2601         yy = h-1-yy;
2602         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2603         if(i%500 == 0) {
2604             char buf[MSG_SIZ];
2605             snprintf(buf, MSG_SIZ, "%d", i);
2606             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2607         }
2608     }
2609     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2610     for(i=1; i<100; i+=(i<10?1:5)) {
2611         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2612         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2613         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2614             char buf[MSG_SIZ];
2615             snprintf(buf, MSG_SIZ, "%d", i);
2616             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2617         }
2618     }
2619     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2620     return TRUE;
2621 }
2622
2623 int
2624 SeekGraphClick (ClickType click, int x, int y, int moving)
2625 {
2626     static int lastDown = 0, displayed = 0, lastSecond;
2627     if(y < 0) return FALSE;
2628     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2629         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2630         if(!seekGraphUp) return FALSE;
2631         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2632         DrawPosition(TRUE, NULL);
2633         return TRUE;
2634     }
2635     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2636         if(click == Release || moving) return FALSE;
2637         nrOfSeekAds = 0;
2638         soughtPending = TRUE;
2639         SendToICS(ics_prefix);
2640         SendToICS("sought\n"); // should this be "sought all"?
2641     } else { // issue challenge based on clicked ad
2642         int dist = 10000; int i, closest = 0, second = 0;
2643         for(i=0; i<nrOfSeekAds; i++) {
2644             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2645             if(d < dist) { dist = d; closest = i; }
2646             second += (d - zList[i] < 120); // count in-range ads
2647             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2648         }
2649         if(dist < 120) {
2650             char buf[MSG_SIZ];
2651             second = (second > 1);
2652             if(displayed != closest || second != lastSecond) {
2653                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2654                 lastSecond = second; displayed = closest;
2655             }
2656             if(click == Press) {
2657                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2658                 lastDown = closest;
2659                 return TRUE;
2660             } // on press 'hit', only show info
2661             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2662             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2663             SendToICS(ics_prefix);
2664             SendToICS(buf);
2665             return TRUE; // let incoming board of started game pop down the graph
2666         } else if(click == Release) { // release 'miss' is ignored
2667             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2668             if(moving == 2) { // right up-click
2669                 nrOfSeekAds = 0; // refresh graph
2670                 soughtPending = TRUE;
2671                 SendToICS(ics_prefix);
2672                 SendToICS("sought\n"); // should this be "sought all"?
2673             }
2674             return TRUE;
2675         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2676         // press miss or release hit 'pop down' seek graph
2677         seekGraphUp = FALSE;
2678         DrawPosition(TRUE, NULL);
2679     }
2680     return TRUE;
2681 }
2682
2683 void
2684 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2685 {
2686 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2687 #define STARTED_NONE 0
2688 #define STARTED_MOVES 1
2689 #define STARTED_BOARD 2
2690 #define STARTED_OBSERVE 3
2691 #define STARTED_HOLDINGS 4
2692 #define STARTED_CHATTER 5
2693 #define STARTED_COMMENT 6
2694 #define STARTED_MOVES_NOHIDE 7
2695
2696     static int started = STARTED_NONE;
2697     static char parse[20000];
2698     static int parse_pos = 0;
2699     static char buf[BUF_SIZE + 1];
2700     static int firstTime = TRUE, intfSet = FALSE;
2701     static ColorClass prevColor = ColorNormal;
2702     static int savingComment = FALSE;
2703     static int cmatch = 0; // continuation sequence match
2704     char *bp;
2705     char str[MSG_SIZ];
2706     int i, oldi;
2707     int buf_len;
2708     int next_out;
2709     int tkind;
2710     int backup;    /* [DM] For zippy color lines */
2711     char *p;
2712     char talker[MSG_SIZ]; // [HGM] chat
2713     int channel;
2714
2715     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2716
2717     if (appData.debugMode) {
2718       if (!error) {
2719         fprintf(debugFP, "<ICS: ");
2720         show_bytes(debugFP, data, count);
2721         fprintf(debugFP, "\n");
2722       }
2723     }
2724
2725     if (appData.debugMode) { int f = forwardMostMove;
2726         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2727                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2728                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2729     }
2730     if (count > 0) {
2731         /* If last read ended with a partial line that we couldn't parse,
2732            prepend it to the new read and try again. */
2733         if (leftover_len > 0) {
2734             for (i=0; i<leftover_len; i++)
2735               buf[i] = buf[leftover_start + i];
2736         }
2737
2738     /* copy new characters into the buffer */
2739     bp = buf + leftover_len;
2740     buf_len=leftover_len;
2741     for (i=0; i<count; i++)
2742     {
2743         // ignore these
2744         if (data[i] == '\r')
2745             continue;
2746
2747         // join lines split by ICS?
2748         if (!appData.noJoin)
2749         {
2750             /*
2751                 Joining just consists of finding matches against the
2752                 continuation sequence, and discarding that sequence
2753                 if found instead of copying it.  So, until a match
2754                 fails, there's nothing to do since it might be the
2755                 complete sequence, and thus, something we don't want
2756                 copied.
2757             */
2758             if (data[i] == cont_seq[cmatch])
2759             {
2760                 cmatch++;
2761                 if (cmatch == strlen(cont_seq))
2762                 {
2763                     cmatch = 0; // complete match.  just reset the counter
2764
2765                     /*
2766                         it's possible for the ICS to not include the space
2767                         at the end of the last word, making our [correct]
2768                         join operation fuse two separate words.  the server
2769                         does this when the space occurs at the width setting.
2770                     */
2771                     if (!buf_len || buf[buf_len-1] != ' ')
2772                     {
2773                         *bp++ = ' ';
2774                         buf_len++;
2775                     }
2776                 }
2777                 continue;
2778             }
2779             else if (cmatch)
2780             {
2781                 /*
2782                     match failed, so we have to copy what matched before
2783                     falling through and copying this character.  In reality,
2784                     this will only ever be just the newline character, but
2785                     it doesn't hurt to be precise.
2786                 */
2787                 strncpy(bp, cont_seq, cmatch);
2788                 bp += cmatch;
2789                 buf_len += cmatch;
2790                 cmatch = 0;
2791             }
2792         }
2793
2794         // copy this char
2795         *bp++ = data[i];
2796         buf_len++;
2797     }
2798
2799         buf[buf_len] = NULLCHAR;
2800 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2801         next_out = 0;
2802         leftover_start = 0;
2803
2804         i = 0;
2805         while (i < buf_len) {
2806             /* Deal with part of the TELNET option negotiation
2807                protocol.  We refuse to do anything beyond the
2808                defaults, except that we allow the WILL ECHO option,
2809                which ICS uses to turn off password echoing when we are
2810                directly connected to it.  We reject this option
2811                if localLineEditing mode is on (always on in xboard)
2812                and we are talking to port 23, which might be a real
2813                telnet server that will try to keep WILL ECHO on permanently.
2814              */
2815             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2816                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2817                 unsigned char option;
2818                 oldi = i;
2819                 switch ((unsigned char) buf[++i]) {
2820                   case TN_WILL:
2821                     if (appData.debugMode)
2822                       fprintf(debugFP, "\n<WILL ");
2823                     switch (option = (unsigned char) buf[++i]) {
2824                       case TN_ECHO:
2825                         if (appData.debugMode)
2826                           fprintf(debugFP, "ECHO ");
2827                         /* Reply only if this is a change, according
2828                            to the protocol rules. */
2829                         if (remoteEchoOption) break;
2830                         if (appData.localLineEditing &&
2831                             atoi(appData.icsPort) == TN_PORT) {
2832                             TelnetRequest(TN_DONT, TN_ECHO);
2833                         } else {
2834                             EchoOff();
2835                             TelnetRequest(TN_DO, TN_ECHO);
2836                             remoteEchoOption = TRUE;
2837                         }
2838                         break;
2839                       default:
2840                         if (appData.debugMode)
2841                           fprintf(debugFP, "%d ", option);
2842                         /* Whatever this is, we don't want it. */
2843                         TelnetRequest(TN_DONT, option);
2844                         break;
2845                     }
2846                     break;
2847                   case TN_WONT:
2848                     if (appData.debugMode)
2849                       fprintf(debugFP, "\n<WONT ");
2850                     switch (option = (unsigned char) buf[++i]) {
2851                       case TN_ECHO:
2852                         if (appData.debugMode)
2853                           fprintf(debugFP, "ECHO ");
2854                         /* Reply only if this is a change, according
2855                            to the protocol rules. */
2856                         if (!remoteEchoOption) break;
2857                         EchoOn();
2858                         TelnetRequest(TN_DONT, TN_ECHO);
2859                         remoteEchoOption = FALSE;
2860                         break;
2861                       default:
2862                         if (appData.debugMode)
2863                           fprintf(debugFP, "%d ", (unsigned char) option);
2864                         /* Whatever this is, it must already be turned
2865                            off, because we never agree to turn on
2866                            anything non-default, so according to the
2867                            protocol rules, we don't reply. */
2868                         break;
2869                     }
2870                     break;
2871                   case TN_DO:
2872                     if (appData.debugMode)
2873                       fprintf(debugFP, "\n<DO ");
2874                     switch (option = (unsigned char) buf[++i]) {
2875                       default:
2876                         /* Whatever this is, we refuse to do it. */
2877                         if (appData.debugMode)
2878                           fprintf(debugFP, "%d ", option);
2879                         TelnetRequest(TN_WONT, option);
2880                         break;
2881                     }
2882                     break;
2883                   case TN_DONT:
2884                     if (appData.debugMode)
2885                       fprintf(debugFP, "\n<DONT ");
2886                     switch (option = (unsigned char) buf[++i]) {
2887                       default:
2888                         if (appData.debugMode)
2889                           fprintf(debugFP, "%d ", option);
2890                         /* Whatever this is, we are already not doing
2891                            it, because we never agree to do anything
2892                            non-default, so according to the protocol
2893                            rules, we don't reply. */
2894                         break;
2895                     }
2896                     break;
2897                   case TN_IAC:
2898                     if (appData.debugMode)
2899                       fprintf(debugFP, "\n<IAC ");
2900                     /* Doubled IAC; pass it through */
2901                     i--;
2902                     break;
2903                   default:
2904                     if (appData.debugMode)
2905                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2906                     /* Drop all other telnet commands on the floor */
2907                     break;
2908                 }
2909                 if (oldi > next_out)
2910                   SendToPlayer(&buf[next_out], oldi - next_out);
2911                 if (++i > next_out)
2912                   next_out = i;
2913                 continue;
2914             }
2915
2916             /* OK, this at least will *usually* work */
2917             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2918                 loggedOn = TRUE;
2919             }
2920
2921             if (loggedOn && !intfSet) {
2922                 if (ics_type == ICS_ICC) {
2923                   snprintf(str, MSG_SIZ,
2924                           "/set-quietly interface %s\n/set-quietly style 12\n",
2925                           programVersion);
2926                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2927                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2928                 } else if (ics_type == ICS_CHESSNET) {
2929                   snprintf(str, MSG_SIZ, "/style 12\n");
2930                 } else {
2931                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2932                   strcat(str, programVersion);
2933                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2934                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2935                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2936 #ifdef WIN32
2937                   strcat(str, "$iset nohighlight 1\n");
2938 #endif
2939                   strcat(str, "$iset lock 1\n$style 12\n");
2940                 }
2941                 SendToICS(str);
2942                 NotifyFrontendLogin();
2943                 intfSet = TRUE;
2944             }
2945
2946             if (started == STARTED_COMMENT) {
2947                 /* Accumulate characters in comment */
2948                 parse[parse_pos++] = buf[i];
2949                 if (buf[i] == '\n') {
2950                     parse[parse_pos] = NULLCHAR;
2951                     if(chattingPartner>=0) {
2952                         char mess[MSG_SIZ];
2953                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2954                         OutputChatMessage(chattingPartner, mess);
2955                         chattingPartner = -1;
2956                         next_out = i+1; // [HGM] suppress printing in ICS window
2957                     } else
2958                     if(!suppressKibitz) // [HGM] kibitz
2959                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2960                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2961                         int nrDigit = 0, nrAlph = 0, j;
2962                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2963                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2964                         parse[parse_pos] = NULLCHAR;
2965                         // try to be smart: if it does not look like search info, it should go to
2966                         // ICS interaction window after all, not to engine-output window.
2967                         for(j=0; j<parse_pos; j++) { // count letters and digits
2968                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2969                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2970                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2971                         }
2972                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2973                             int depth=0; float score;
2974                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2975                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2976                                 pvInfoList[forwardMostMove-1].depth = depth;
2977                                 pvInfoList[forwardMostMove-1].score = 100*score;
2978                             }
2979                             OutputKibitz(suppressKibitz, parse);
2980                         } else {
2981                             char tmp[MSG_SIZ];
2982                             if(gameMode == IcsObserving) // restore original ICS messages
2983                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2984                             else
2985                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2986                             SendToPlayer(tmp, strlen(tmp));
2987                         }
2988                         next_out = i+1; // [HGM] suppress printing in ICS window
2989                     }
2990                     started = STARTED_NONE;
2991                 } else {
2992                     /* Don't match patterns against characters in comment */
2993                     i++;
2994                     continue;
2995                 }
2996             }
2997             if (started == STARTED_CHATTER) {
2998                 if (buf[i] != '\n') {
2999                     /* Don't match patterns against characters in chatter */
3000                     i++;
3001                     continue;
3002                 }
3003                 started = STARTED_NONE;
3004                 if(suppressKibitz) next_out = i+1;
3005             }
3006
3007             /* Kludge to deal with rcmd protocol */
3008             if (firstTime && looking_at(buf, &i, "\001*")) {
3009                 DisplayFatalError(&buf[1], 0, 1);
3010                 continue;
3011             } else {
3012                 firstTime = FALSE;
3013             }
3014
3015             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3016                 ics_type = ICS_ICC;
3017                 ics_prefix = "/";
3018                 if (appData.debugMode)
3019                   fprintf(debugFP, "ics_type %d\n", ics_type);
3020                 continue;
3021             }
3022             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3023                 ics_type = ICS_FICS;
3024                 ics_prefix = "$";
3025                 if (appData.debugMode)
3026                   fprintf(debugFP, "ics_type %d\n", ics_type);
3027                 continue;
3028             }
3029             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3030                 ics_type = ICS_CHESSNET;
3031                 ics_prefix = "/";
3032                 if (appData.debugMode)
3033                   fprintf(debugFP, "ics_type %d\n", ics_type);
3034                 continue;
3035             }
3036
3037             if (!loggedOn &&
3038                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3039                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3040                  looking_at(buf, &i, "will be \"*\""))) {
3041               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3042               continue;
3043             }
3044
3045             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3046               char buf[MSG_SIZ];
3047               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3048               DisplayIcsInteractionTitle(buf);
3049               have_set_title = TRUE;
3050             }
3051
3052             /* skip finger notes */
3053             if (started == STARTED_NONE &&
3054                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3055                  (buf[i] == '1' && buf[i+1] == '0')) &&
3056                 buf[i+2] == ':' && buf[i+3] == ' ') {
3057               started = STARTED_CHATTER;
3058               i += 3;
3059               continue;
3060             }
3061
3062             oldi = i;
3063             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3064             if(appData.seekGraph) {
3065                 if(soughtPending && MatchSoughtLine(buf+i)) {
3066                     i = strstr(buf+i, "rated") - buf;
3067                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3068                     next_out = leftover_start = i;
3069                     started = STARTED_CHATTER;
3070                     suppressKibitz = TRUE;
3071                     continue;
3072                 }
3073                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3074                         && looking_at(buf, &i, "* ads displayed")) {
3075                     soughtPending = FALSE;
3076                     seekGraphUp = TRUE;
3077                     DrawSeekGraph();
3078                     continue;
3079                 }
3080                 if(appData.autoRefresh) {
3081                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3082                         int s = (ics_type == ICS_ICC); // ICC format differs
3083                         if(seekGraphUp)
3084                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3085                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3086                         looking_at(buf, &i, "*% "); // eat prompt
3087                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3088                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3089                         next_out = i; // suppress
3090                         continue;
3091                     }
3092                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3093                         char *p = star_match[0];
3094                         while(*p) {
3095                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3096                             while(*p && *p++ != ' '); // next
3097                         }
3098                         looking_at(buf, &i, "*% "); // eat prompt
3099                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3100                         next_out = i;
3101                         continue;
3102                     }
3103                 }
3104             }
3105
3106             /* skip formula vars */
3107             if (started == STARTED_NONE &&
3108                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3109               started = STARTED_CHATTER;
3110               i += 3;
3111               continue;
3112             }
3113
3114             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3115             if (appData.autoKibitz && started == STARTED_NONE &&
3116                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3117                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3118                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3119                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3120                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3121                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3122                         suppressKibitz = TRUE;
3123                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3124                         next_out = i;
3125                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3126                                 && (gameMode == IcsPlayingWhite)) ||
3127                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3128                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3129                             started = STARTED_CHATTER; // own kibitz we simply discard
3130                         else {
3131                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3132                             parse_pos = 0; parse[0] = NULLCHAR;
3133                             savingComment = TRUE;
3134                             suppressKibitz = gameMode != IcsObserving ? 2 :
3135                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3136                         }
3137                         continue;
3138                 } else
3139                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3140                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3141                          && atoi(star_match[0])) {
3142                     // suppress the acknowledgements of our own autoKibitz
3143                     char *p;
3144                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3145                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3146                     SendToPlayer(star_match[0], strlen(star_match[0]));
3147                     if(looking_at(buf, &i, "*% ")) // eat prompt
3148                         suppressKibitz = FALSE;
3149                     next_out = i;
3150                     continue;
3151                 }
3152             } // [HGM] kibitz: end of patch
3153
3154             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3155
3156             // [HGM] chat: intercept tells by users for which we have an open chat window
3157             channel = -1;
3158             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3159                                            looking_at(buf, &i, "* whispers:") ||
3160                                            looking_at(buf, &i, "* kibitzes:") ||
3161                                            looking_at(buf, &i, "* shouts:") ||
3162                                            looking_at(buf, &i, "* c-shouts:") ||
3163                                            looking_at(buf, &i, "--> * ") ||
3164                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3165                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3166                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3167                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3168                 int p;
3169                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3170                 chattingPartner = -1;
3171
3172                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3173                 for(p=0; p<MAX_CHAT; p++) {
3174                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3175                     talker[0] = '['; strcat(talker, "] ");
3176                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3177                     chattingPartner = p; break;
3178                     }
3179                 } else
3180                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3181                 for(p=0; p<MAX_CHAT; p++) {
3182                     if(!strcmp("kibitzes", chatPartner[p])) {
3183                         talker[0] = '['; strcat(talker, "] ");
3184                         chattingPartner = p; break;
3185                     }
3186                 } else
3187                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3188                 for(p=0; p<MAX_CHAT; p++) {
3189                     if(!strcmp("whispers", chatPartner[p])) {
3190                         talker[0] = '['; strcat(talker, "] ");
3191                         chattingPartner = p; break;
3192                     }
3193                 } else
3194                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3195                   if(buf[i-8] == '-' && buf[i-3] == 't')
3196                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3197                     if(!strcmp("c-shouts", chatPartner[p])) {
3198                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3199                         chattingPartner = p; break;
3200                     }
3201                   }
3202                   if(chattingPartner < 0)
3203                   for(p=0; p<MAX_CHAT; p++) {
3204                     if(!strcmp("shouts", chatPartner[p])) {
3205                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3206                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3207                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3208                         chattingPartner = p; break;
3209                     }
3210                   }
3211                 }
3212                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3213                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3214                     talker[0] = 0; Colorize(ColorTell, FALSE);
3215                     chattingPartner = p; break;
3216                 }
3217                 if(chattingPartner<0) i = oldi; else {
3218                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3219                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3220                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221                     started = STARTED_COMMENT;
3222                     parse_pos = 0; parse[0] = NULLCHAR;
3223                     savingComment = 3 + chattingPartner; // counts as TRUE
3224                     suppressKibitz = TRUE;
3225                     continue;
3226                 }
3227             } // [HGM] chat: end of patch
3228
3229           backup = i;
3230             if (appData.zippyTalk || appData.zippyPlay) {
3231                 /* [DM] Backup address for color zippy lines */
3232 #if ZIPPY
3233                if (loggedOn == TRUE)
3234                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3235                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3236 #endif
3237             } // [DM] 'else { ' deleted
3238                 if (
3239                     /* Regular tells and says */
3240                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3241                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3242                     looking_at(buf, &i, "* says: ") ||
3243                     /* Don't color "message" or "messages" output */
3244                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3245                     looking_at(buf, &i, "*. * at *:*: ") ||
3246                     looking_at(buf, &i, "--* (*:*): ") ||
3247                     /* Message notifications (same color as tells) */
3248                     looking_at(buf, &i, "* has left a message ") ||
3249                     looking_at(buf, &i, "* just sent you a message:\n") ||
3250                     /* Whispers and kibitzes */
3251                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3252                     looking_at(buf, &i, "* kibitzes: ") ||
3253                     /* Channel tells */
3254                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3255
3256                   if (tkind == 1 && strchr(star_match[0], ':')) {
3257                       /* Avoid "tells you:" spoofs in channels */
3258                      tkind = 3;
3259                   }
3260                   if (star_match[0][0] == NULLCHAR ||
3261                       strchr(star_match[0], ' ') ||
3262                       (tkind == 3 && strchr(star_match[1], ' '))) {
3263                     /* Reject bogus matches */
3264                     i = oldi;
3265                   } else {
3266                     if (appData.colorize) {
3267                       if (oldi > next_out) {
3268                         SendToPlayer(&buf[next_out], oldi - next_out);
3269                         next_out = oldi;
3270                       }
3271                       switch (tkind) {
3272                       case 1:
3273                         Colorize(ColorTell, FALSE);
3274                         curColor = ColorTell;
3275                         break;
3276                       case 2:
3277                         Colorize(ColorKibitz, FALSE);
3278                         curColor = ColorKibitz;
3279                         break;
3280                       case 3:
3281                         p = strrchr(star_match[1], '(');
3282                         if (p == NULL) {
3283                           p = star_match[1];
3284                         } else {
3285                           p++;
3286                         }
3287                         if (atoi(p) == 1) {
3288                           Colorize(ColorChannel1, FALSE);
3289                           curColor = ColorChannel1;
3290                         } else {
3291                           Colorize(ColorChannel, FALSE);
3292                           curColor = ColorChannel;
3293                         }
3294                         break;
3295                       case 5:
3296                         curColor = ColorNormal;
3297                         break;
3298                       }
3299                     }
3300                     if (started == STARTED_NONE && appData.autoComment &&
3301                         (gameMode == IcsObserving ||
3302                          gameMode == IcsPlayingWhite ||
3303                          gameMode == IcsPlayingBlack)) {
3304                       parse_pos = i - oldi;
3305                       memcpy(parse, &buf[oldi], parse_pos);
3306                       parse[parse_pos] = NULLCHAR;
3307                       started = STARTED_COMMENT;
3308                       savingComment = TRUE;
3309                     } else {
3310                       started = STARTED_CHATTER;
3311                       savingComment = FALSE;
3312                     }
3313                     loggedOn = TRUE;
3314                     continue;
3315                   }
3316                 }
3317
3318                 if (looking_at(buf, &i, "* s-shouts: ") ||
3319                     looking_at(buf, &i, "* c-shouts: ")) {
3320                     if (appData.colorize) {
3321                         if (oldi > next_out) {
3322                             SendToPlayer(&buf[next_out], oldi - next_out);
3323                             next_out = oldi;
3324                         }
3325                         Colorize(ColorSShout, FALSE);
3326                         curColor = ColorSShout;
3327                     }
3328                     loggedOn = TRUE;
3329                     started = STARTED_CHATTER;
3330                     continue;
3331                 }
3332
3333                 if (looking_at(buf, &i, "--->")) {
3334                     loggedOn = TRUE;
3335                     continue;
3336                 }
3337
3338                 if (looking_at(buf, &i, "* shouts: ") ||
3339                     looking_at(buf, &i, "--> ")) {
3340                     if (appData.colorize) {
3341                         if (oldi > next_out) {
3342                             SendToPlayer(&buf[next_out], oldi - next_out);
3343                             next_out = oldi;
3344                         }
3345                         Colorize(ColorShout, FALSE);
3346                         curColor = ColorShout;
3347                     }
3348                     loggedOn = TRUE;
3349                     started = STARTED_CHATTER;
3350                     continue;
3351                 }
3352
3353                 if (looking_at( buf, &i, "Challenge:")) {
3354                     if (appData.colorize) {
3355                         if (oldi > next_out) {
3356                             SendToPlayer(&buf[next_out], oldi - next_out);
3357                             next_out = oldi;
3358                         }
3359                         Colorize(ColorChallenge, FALSE);
3360                         curColor = ColorChallenge;
3361                     }
3362                     loggedOn = TRUE;
3363                     continue;
3364                 }
3365
3366                 if (looking_at(buf, &i, "* offers you") ||
3367                     looking_at(buf, &i, "* offers to be") ||
3368                     looking_at(buf, &i, "* would like to") ||
3369                     looking_at(buf, &i, "* requests to") ||
3370                     looking_at(buf, &i, "Your opponent offers") ||
3371                     looking_at(buf, &i, "Your opponent requests")) {
3372
3373                     if (appData.colorize) {
3374                         if (oldi > next_out) {
3375                             SendToPlayer(&buf[next_out], oldi - next_out);
3376                             next_out = oldi;
3377                         }
3378                         Colorize(ColorRequest, FALSE);
3379                         curColor = ColorRequest;
3380                     }
3381                     continue;
3382                 }
3383
3384                 if (looking_at(buf, &i, "* (*) seeking")) {
3385                     if (appData.colorize) {
3386                         if (oldi > next_out) {
3387                             SendToPlayer(&buf[next_out], oldi - next_out);
3388                             next_out = oldi;
3389                         }
3390                         Colorize(ColorSeek, FALSE);
3391                         curColor = ColorSeek;
3392                     }
3393                     continue;
3394             }
3395
3396           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3397
3398             if (looking_at(buf, &i, "\\   ")) {
3399                 if (prevColor != ColorNormal) {
3400                     if (oldi > next_out) {
3401                         SendToPlayer(&buf[next_out], oldi - next_out);
3402                         next_out = oldi;
3403                     }
3404                     Colorize(prevColor, TRUE);
3405                     curColor = prevColor;
3406                 }
3407                 if (savingComment) {
3408                     parse_pos = i - oldi;
3409                     memcpy(parse, &buf[oldi], parse_pos);
3410                     parse[parse_pos] = NULLCHAR;
3411                     started = STARTED_COMMENT;
3412                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3413                         chattingPartner = savingComment - 3; // kludge to remember the box
3414                 } else {
3415                     started = STARTED_CHATTER;
3416                 }
3417                 continue;
3418             }
3419
3420             if (looking_at(buf, &i, "Black Strength :") ||
3421                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3422                 looking_at(buf, &i, "<10>") ||
3423                 looking_at(buf, &i, "#@#")) {
3424                 /* Wrong board style */
3425                 loggedOn = TRUE;
3426                 SendToICS(ics_prefix);
3427                 SendToICS("set style 12\n");
3428                 SendToICS(ics_prefix);
3429                 SendToICS("refresh\n");
3430                 continue;
3431             }
3432
3433             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3434                 ICSInitScript();
3435                 have_sent_ICS_logon = 1;
3436                 continue;
3437             }
3438
3439             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3440                 (looking_at(buf, &i, "\n<12> ") ||
3441                  looking_at(buf, &i, "<12> "))) {
3442                 loggedOn = TRUE;
3443                 if (oldi > next_out) {
3444                     SendToPlayer(&buf[next_out], oldi - next_out);
3445                 }
3446                 next_out = i;
3447                 started = STARTED_BOARD;
3448                 parse_pos = 0;
3449                 continue;
3450             }
3451
3452             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3453                 looking_at(buf, &i, "<b1> ")) {
3454                 if (oldi > next_out) {
3455                     SendToPlayer(&buf[next_out], oldi - next_out);
3456                 }
3457                 next_out = i;
3458                 started = STARTED_HOLDINGS;
3459                 parse_pos = 0;
3460                 continue;
3461             }
3462
3463             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3464                 loggedOn = TRUE;
3465                 /* Header for a move list -- first line */
3466
3467                 switch (ics_getting_history) {
3468                   case H_FALSE:
3469                     switch (gameMode) {
3470                       case IcsIdle:
3471                       case BeginningOfGame:
3472                         /* User typed "moves" or "oldmoves" while we
3473                            were idle.  Pretend we asked for these
3474                            moves and soak them up so user can step
3475                            through them and/or save them.
3476                            */
3477                         Reset(FALSE, TRUE);
3478                         gameMode = IcsObserving;
3479                         ModeHighlight();
3480                         ics_gamenum = -1;
3481                         ics_getting_history = H_GOT_UNREQ_HEADER;
3482                         break;
3483                       case EditGame: /*?*/
3484                       case EditPosition: /*?*/
3485                         /* Should above feature work in these modes too? */
3486                         /* For now it doesn't */
3487                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3488                         break;
3489                       default:
3490                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3491                         break;
3492                     }
3493                     break;
3494                   case H_REQUESTED:
3495                     /* Is this the right one? */
3496                     if (gameInfo.white && gameInfo.black &&
3497                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3498                         strcmp(gameInfo.black, star_match[2]) == 0) {
3499                         /* All is well */
3500                         ics_getting_history = H_GOT_REQ_HEADER;
3501                     }
3502                     break;
3503                   case H_GOT_REQ_HEADER:
3504                   case H_GOT_UNREQ_HEADER:
3505                   case H_GOT_UNWANTED_HEADER:
3506                   case H_GETTING_MOVES:
3507                     /* Should not happen */
3508                     DisplayError(_("Error gathering move list: two headers"), 0);
3509                     ics_getting_history = H_FALSE;
3510                     break;
3511                 }
3512
3513                 /* Save player ratings into gameInfo if needed */
3514                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3515                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3516                     (gameInfo.whiteRating == -1 ||
3517                      gameInfo.blackRating == -1)) {
3518
3519                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3520                     gameInfo.blackRating = string_to_rating(star_match[3]);
3521                     if (appData.debugMode)
3522                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3523                               gameInfo.whiteRating, gameInfo.blackRating);
3524                 }
3525                 continue;
3526             }
3527
3528             if (looking_at(buf, &i,
3529               "* * match, initial time: * minute*, increment: * second")) {
3530                 /* Header for a move list -- second line */
3531                 /* Initial board will follow if this is a wild game */
3532                 if (gameInfo.event != NULL) free(gameInfo.event);
3533                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3534                 gameInfo.event = StrSave(str);
3535                 /* [HGM] we switched variant. Translate boards if needed. */
3536                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3537                 continue;
3538             }
3539
3540             if (looking_at(buf, &i, "Move  ")) {
3541                 /* Beginning of a move list */
3542                 switch (ics_getting_history) {
3543                   case H_FALSE:
3544                     /* Normally should not happen */
3545                     /* Maybe user hit reset while we were parsing */
3546                     break;
3547                   case H_REQUESTED:
3548                     /* Happens if we are ignoring a move list that is not
3549                      * the one we just requested.  Common if the user
3550                      * tries to observe two games without turning off
3551                      * getMoveList */
3552                     break;
3553                   case H_GETTING_MOVES:
3554                     /* Should not happen */
3555                     DisplayError(_("Error gathering move list: nested"), 0);
3556                     ics_getting_history = H_FALSE;
3557                     break;
3558                   case H_GOT_REQ_HEADER:
3559                     ics_getting_history = H_GETTING_MOVES;
3560                     started = STARTED_MOVES;
3561                     parse_pos = 0;
3562                     if (oldi > next_out) {
3563                         SendToPlayer(&buf[next_out], oldi - next_out);
3564                     }
3565                     break;
3566                   case H_GOT_UNREQ_HEADER:
3567                     ics_getting_history = H_GETTING_MOVES;
3568                     started = STARTED_MOVES_NOHIDE;
3569                     parse_pos = 0;
3570                     break;
3571                   case H_GOT_UNWANTED_HEADER:
3572                     ics_getting_history = H_FALSE;
3573                     break;
3574                 }
3575                 continue;
3576             }
3577
3578             if (looking_at(buf, &i, "% ") ||
3579                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3580                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3581                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3582                     soughtPending = FALSE;
3583                     seekGraphUp = TRUE;
3584                     DrawSeekGraph();
3585                 }
3586                 if(suppressKibitz) next_out = i;
3587                 savingComment = FALSE;
3588                 suppressKibitz = 0;
3589                 switch (started) {
3590                   case STARTED_MOVES:
3591                   case STARTED_MOVES_NOHIDE:
3592                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3593                     parse[parse_pos + i - oldi] = NULLCHAR;
3594                     ParseGameHistory(parse);
3595 #if ZIPPY
3596                     if (appData.zippyPlay && first.initDone) {
3597                         FeedMovesToProgram(&first, forwardMostMove);
3598                         if (gameMode == IcsPlayingWhite) {
3599                             if (WhiteOnMove(forwardMostMove)) {
3600                                 if (first.sendTime) {
3601                                   if (first.useColors) {
3602                                     SendToProgram("black\n", &first);
3603                                   }
3604                                   SendTimeRemaining(&first, TRUE);
3605                                 }
3606                                 if (first.useColors) {
3607                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3608                                 }
3609                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3610                                 first.maybeThinking = TRUE;
3611                             } else {
3612                                 if (first.usePlayother) {
3613                                   if (first.sendTime) {
3614                                     SendTimeRemaining(&first, TRUE);
3615                                   }
3616                                   SendToProgram("playother\n", &first);
3617                                   firstMove = FALSE;
3618                                 } else {
3619                                   firstMove = TRUE;
3620                                 }
3621                             }
3622                         } else if (gameMode == IcsPlayingBlack) {
3623                             if (!WhiteOnMove(forwardMostMove)) {
3624                                 if (first.sendTime) {
3625                                   if (first.useColors) {
3626                                     SendToProgram("white\n", &first);
3627                                   }
3628                                   SendTimeRemaining(&first, FALSE);
3629                                 }
3630                                 if (first.useColors) {
3631                                   SendToProgram("black\n", &first);
3632                                 }
3633                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3634                                 first.maybeThinking = TRUE;
3635                             } else {
3636                                 if (first.usePlayother) {
3637                                   if (first.sendTime) {
3638                                     SendTimeRemaining(&first, FALSE);
3639                                   }
3640                                   SendToProgram("playother\n", &first);
3641                                   firstMove = FALSE;
3642                                 } else {
3643                                   firstMove = TRUE;
3644                                 }
3645                             }
3646                         }
3647                     }
3648 #endif
3649                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3650                         /* Moves came from oldmoves or moves command
3651                            while we weren't doing anything else.
3652                            */
3653                         currentMove = forwardMostMove;
3654                         ClearHighlights();/*!!could figure this out*/
3655                         flipView = appData.flipView;
3656                         DrawPosition(TRUE, boards[currentMove]);
3657                         DisplayBothClocks();
3658                         snprintf(str, MSG_SIZ, "%s %s %s",
3659                                 gameInfo.white, _("vs."),  gameInfo.black);
3660                         DisplayTitle(str);
3661                         gameMode = IcsIdle;
3662                     } else {
3663                         /* Moves were history of an active game */
3664                         if (gameInfo.resultDetails != NULL) {
3665                             free(gameInfo.resultDetails);
3666                             gameInfo.resultDetails = NULL;
3667                         }
3668                     }
3669                     HistorySet(parseList, backwardMostMove,
3670                                forwardMostMove, currentMove-1);
3671                     DisplayMove(currentMove - 1);
3672                     if (started == STARTED_MOVES) next_out = i;
3673                     started = STARTED_NONE;
3674                     ics_getting_history = H_FALSE;
3675                     break;
3676
3677                   case STARTED_OBSERVE:
3678                     started = STARTED_NONE;
3679                     SendToICS(ics_prefix);
3680                     SendToICS("refresh\n");
3681                     break;
3682
3683                   default:
3684                     break;
3685                 }
3686                 if(bookHit) { // [HGM] book: simulate book reply
3687                     static char bookMove[MSG_SIZ]; // a bit generous?
3688
3689                     programStats.nodes = programStats.depth = programStats.time =
3690                     programStats.score = programStats.got_only_move = 0;
3691                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3692
3693                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3694                     strcat(bookMove, bookHit);
3695                     HandleMachineMove(bookMove, &first);
3696                 }
3697                 continue;
3698             }
3699
3700             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3701                  started == STARTED_HOLDINGS ||
3702                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3703                 /* Accumulate characters in move list or board */
3704                 parse[parse_pos++] = buf[i];
3705             }
3706
3707             /* Start of game messages.  Mostly we detect start of game
3708                when the first board image arrives.  On some versions
3709                of the ICS, though, we need to do a "refresh" after starting
3710                to observe in order to get the current board right away. */
3711             if (looking_at(buf, &i, "Adding game * to observation list")) {
3712                 started = STARTED_OBSERVE;
3713                 continue;
3714             }
3715
3716             /* Handle auto-observe */
3717             if (appData.autoObserve &&
3718                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3719                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3720                 char *player;
3721                 /* Choose the player that was highlighted, if any. */
3722                 if (star_match[0][0] == '\033' ||
3723                     star_match[1][0] != '\033') {
3724                     player = star_match[0];
3725                 } else {
3726                     player = star_match[2];
3727                 }
3728                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3729                         ics_prefix, StripHighlightAndTitle(player));
3730                 SendToICS(str);
3731
3732                 /* Save ratings from notify string */
3733                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3734                 player1Rating = string_to_rating(star_match[1]);
3735                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3736                 player2Rating = string_to_rating(star_match[3]);
3737
3738                 if (appData.debugMode)
3739                   fprintf(debugFP,
3740                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3741                           player1Name, player1Rating,
3742                           player2Name, player2Rating);
3743
3744                 continue;
3745             }
3746
3747             /* Deal with automatic examine mode after a game,
3748                and with IcsObserving -> IcsExamining transition */
3749             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3750                 looking_at(buf, &i, "has made you an examiner of game *")) {
3751
3752                 int gamenum = atoi(star_match[0]);
3753                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3754                     gamenum == ics_gamenum) {
3755                     /* We were already playing or observing this game;
3756                        no need to refetch history */
3757                     gameMode = IcsExamining;
3758                     if (pausing) {
3759                         pauseExamForwardMostMove = forwardMostMove;
3760                     } else if (currentMove < forwardMostMove) {
3761                         ForwardInner(forwardMostMove);
3762                     }
3763                 } else {
3764                     /* I don't think this case really can happen */
3765                     SendToICS(ics_prefix);
3766                     SendToICS("refresh\n");
3767                 }
3768                 continue;
3769             }
3770
3771             /* Error messages */
3772 //          if (ics_user_moved) {
3773             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3774                 if (looking_at(buf, &i, "Illegal move") ||
3775                     looking_at(buf, &i, "Not a legal move") ||
3776                     looking_at(buf, &i, "Your king is in check") ||
3777                     looking_at(buf, &i, "It isn't your turn") ||
3778                     looking_at(buf, &i, "It is not your move")) {
3779                     /* Illegal move */
3780                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3781                         currentMove = forwardMostMove-1;
3782                         DisplayMove(currentMove - 1); /* before DMError */
3783                         DrawPosition(FALSE, boards[currentMove]);
3784                         SwitchClocks(forwardMostMove-1); // [HGM] race
3785                         DisplayBothClocks();
3786                     }
3787                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3788                     ics_user_moved = 0;
3789                     continue;
3790                 }
3791             }
3792
3793             if (looking_at(buf, &i, "still have time") ||
3794                 looking_at(buf, &i, "not out of time") ||
3795                 looking_at(buf, &i, "either player is out of time") ||
3796                 looking_at(buf, &i, "has timeseal; checking")) {
3797                 /* We must have called his flag a little too soon */
3798                 whiteFlag = blackFlag = FALSE;
3799                 continue;
3800             }
3801
3802             if (looking_at(buf, &i, "added * seconds to") ||
3803                 looking_at(buf, &i, "seconds were added to")) {
3804                 /* Update the clocks */
3805                 SendToICS(ics_prefix);
3806                 SendToICS("refresh\n");
3807                 continue;
3808             }
3809
3810             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3811                 ics_clock_paused = TRUE;
3812                 StopClocks();
3813                 continue;
3814             }
3815
3816             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3817                 ics_clock_paused = FALSE;
3818                 StartClocks();
3819                 continue;
3820             }
3821
3822             /* Grab player ratings from the Creating: message.
3823                Note we have to check for the special case when
3824                the ICS inserts things like [white] or [black]. */
3825             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3826                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3827                 /* star_matches:
3828                    0    player 1 name (not necessarily white)
3829                    1    player 1 rating
3830                    2    empty, white, or black (IGNORED)
3831                    3    player 2 name (not necessarily black)
3832                    4    player 2 rating
3833
3834                    The names/ratings are sorted out when the game
3835                    actually starts (below).
3836                 */
3837                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3838                 player1Rating = string_to_rating(star_match[1]);
3839                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3840                 player2Rating = string_to_rating(star_match[4]);
3841
3842                 if (appData.debugMode)
3843                   fprintf(debugFP,
3844                           "Ratings from 'Creating:' %s %d, %s %d\n",
3845                           player1Name, player1Rating,
3846                           player2Name, player2Rating);
3847
3848                 continue;
3849             }
3850
3851             /* Improved generic start/end-of-game messages */
3852             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3853                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3854                 /* If tkind == 0: */
3855                 /* star_match[0] is the game number */
3856                 /*           [1] is the white player's name */
3857                 /*           [2] is the black player's name */
3858                 /* For end-of-game: */
3859                 /*           [3] is the reason for the game end */
3860                 /*           [4] is a PGN end game-token, preceded by " " */
3861                 /* For start-of-game: */
3862                 /*           [3] begins with "Creating" or "Continuing" */
3863                 /*           [4] is " *" or empty (don't care). */
3864                 int gamenum = atoi(star_match[0]);
3865                 char *whitename, *blackname, *why, *endtoken;
3866                 ChessMove endtype = EndOfFile;
3867
3868                 if (tkind == 0) {
3869                   whitename = star_match[1];
3870                   blackname = star_match[2];
3871                   why = star_match[3];
3872                   endtoken = star_match[4];
3873                 } else {
3874                   whitename = star_match[1];
3875                   blackname = star_match[3];
3876                   why = star_match[5];
3877                   endtoken = star_match[6];
3878                 }
3879
3880                 /* Game start messages */
3881                 if (strncmp(why, "Creating ", 9) == 0 ||
3882                     strncmp(why, "Continuing ", 11) == 0) {
3883                     gs_gamenum = gamenum;
3884                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3885                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3886                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3887 #if ZIPPY
3888                     if (appData.zippyPlay) {
3889                         ZippyGameStart(whitename, blackname);
3890                     }
3891 #endif /*ZIPPY*/
3892                     partnerBoardValid = FALSE; // [HGM] bughouse
3893                     continue;
3894                 }
3895
3896                 /* Game end messages */
3897                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3898                     ics_gamenum != gamenum) {
3899                     continue;
3900                 }
3901                 while (endtoken[0] == ' ') endtoken++;
3902                 switch (endtoken[0]) {
3903                   case '*':
3904                   default:
3905                     endtype = GameUnfinished;
3906                     break;
3907                   case '0':
3908                     endtype = BlackWins;
3909                     break;
3910                   case '1':
3911                     if (endtoken[1] == '/')
3912                       endtype = GameIsDrawn;
3913                     else
3914                       endtype = WhiteWins;
3915                     break;
3916                 }
3917                 GameEnds(endtype, why, GE_ICS);
3918 #if ZIPPY
3919                 if (appData.zippyPlay && first.initDone) {
3920                     ZippyGameEnd(endtype, why);
3921                     if (first.pr == NoProc) {
3922                       /* Start the next process early so that we'll
3923                          be ready for the next challenge */
3924                       StartChessProgram(&first);
3925                     }
3926                     /* Send "new" early, in case this command takes
3927                        a long time to finish, so that we'll be ready
3928                        for the next challenge. */
3929                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3930                     Reset(TRUE, TRUE);
3931                 }
3932 #endif /*ZIPPY*/
3933                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3934                 continue;
3935             }
3936
3937             if (looking_at(buf, &i, "Removing game * from observation") ||
3938                 looking_at(buf, &i, "no longer observing game *") ||
3939                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3940                 if (gameMode == IcsObserving &&
3941                     atoi(star_match[0]) == ics_gamenum)
3942                   {
3943                       /* icsEngineAnalyze */
3944                       if (appData.icsEngineAnalyze) {
3945                             ExitAnalyzeMode();
3946                             ModeHighlight();
3947                       }
3948                       StopClocks();
3949                       gameMode = IcsIdle;
3950                       ics_gamenum = -1;
3951                       ics_user_moved = FALSE;
3952                   }
3953                 continue;
3954             }
3955
3956             if (looking_at(buf, &i, "no longer examining game *")) {
3957                 if (gameMode == IcsExamining &&
3958                     atoi(star_match[0]) == ics_gamenum)
3959                   {
3960                       gameMode = IcsIdle;
3961                       ics_gamenum = -1;
3962                       ics_user_moved = FALSE;
3963                   }
3964                 continue;
3965             }
3966
3967             /* Advance leftover_start past any newlines we find,
3968                so only partial lines can get reparsed */
3969             if (looking_at(buf, &i, "\n")) {
3970                 prevColor = curColor;
3971                 if (curColor != ColorNormal) {
3972                     if (oldi > next_out) {
3973                         SendToPlayer(&buf[next_out], oldi - next_out);
3974                         next_out = oldi;
3975                     }
3976                     Colorize(ColorNormal, FALSE);
3977                     curColor = ColorNormal;
3978                 }
3979                 if (started == STARTED_BOARD) {
3980                     started = STARTED_NONE;
3981                     parse[parse_pos] = NULLCHAR;
3982                     ParseBoard12(parse);
3983                     ics_user_moved = 0;
3984
3985                     /* Send premove here */
3986                     if (appData.premove) {
3987                       char str[MSG_SIZ];
3988                       if (currentMove == 0 &&
3989                           gameMode == IcsPlayingWhite &&
3990                           appData.premoveWhite) {
3991                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3992                         if (appData.debugMode)
3993                           fprintf(debugFP, "Sending premove:\n");
3994                         SendToICS(str);
3995                       } else if (currentMove == 1 &&
3996                                  gameMode == IcsPlayingBlack &&
3997                                  appData.premoveBlack) {
3998                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3999                         if (appData.debugMode)
4000                           fprintf(debugFP, "Sending premove:\n");
4001                         SendToICS(str);
4002                       } else if (gotPremove) {
4003                         gotPremove = 0;
4004                         ClearPremoveHighlights();
4005                         if (appData.debugMode)
4006                           fprintf(debugFP, "Sending premove:\n");
4007                           UserMoveEvent(premoveFromX, premoveFromY,
4008                                         premoveToX, premoveToY,
4009                                         premovePromoChar);
4010                       }
4011                     }
4012
4013                     /* Usually suppress following prompt */
4014                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4015                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4016                         if (looking_at(buf, &i, "*% ")) {
4017                             savingComment = FALSE;
4018                             suppressKibitz = 0;
4019                         }
4020                     }
4021                     next_out = i;
4022                 } else if (started == STARTED_HOLDINGS) {
4023                     int gamenum;
4024                     char new_piece[MSG_SIZ];
4025                     started = STARTED_NONE;
4026                     parse[parse_pos] = NULLCHAR;
4027                     if (appData.debugMode)
4028                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4029                                                         parse, currentMove);
4030                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4031                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4032                         if (gameInfo.variant == VariantNormal) {
4033                           /* [HGM] We seem to switch variant during a game!
4034                            * Presumably no holdings were displayed, so we have
4035                            * to move the position two files to the right to
4036                            * create room for them!
4037                            */
4038                           VariantClass newVariant;
4039                           switch(gameInfo.boardWidth) { // base guess on board width
4040                                 case 9:  newVariant = VariantShogi; break;
4041                                 case 10: newVariant = VariantGreat; break;
4042                                 default: newVariant = VariantCrazyhouse; break;
4043                           }
4044                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4045                           /* Get a move list just to see the header, which
4046                              will tell us whether this is really bug or zh */
4047                           if (ics_getting_history == H_FALSE) {
4048                             ics_getting_history = H_REQUESTED;
4049                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4050                             SendToICS(str);
4051                           }
4052                         }
4053                         new_piece[0] = NULLCHAR;
4054                         sscanf(parse, "game %d white [%s black [%s <- %s",
4055                                &gamenum, white_holding, black_holding,
4056                                new_piece);
4057                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4058                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4059                         /* [HGM] copy holdings to board holdings area */
4060                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4061                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4062                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4063 #if ZIPPY
4064                         if (appData.zippyPlay && first.initDone) {
4065                             ZippyHoldings(white_holding, black_holding,
4066                                           new_piece);
4067                         }
4068 #endif /*ZIPPY*/
4069                         if (tinyLayout || smallLayout) {
4070                             char wh[16], bh[16];
4071                             PackHolding(wh, white_holding);
4072                             PackHolding(bh, black_holding);
4073                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4074                                     gameInfo.white, gameInfo.black);
4075                         } else {
4076                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4077                                     gameInfo.white, white_holding, _("vs."),
4078                                     gameInfo.black, black_holding);
4079                         }
4080                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4081                         DrawPosition(FALSE, boards[currentMove]);
4082                         DisplayTitle(str);
4083                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4084                         sscanf(parse, "game %d white [%s black [%s <- %s",
4085                                &gamenum, white_holding, black_holding,
4086                                new_piece);
4087                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4088                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4089                         /* [HGM] copy holdings to partner-board holdings area */
4090                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4091                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4092                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4093                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4094                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4095                       }
4096                     }
4097                     /* Suppress following prompt */
4098                     if (looking_at(buf, &i, "*% ")) {
4099                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4100                         savingComment = FALSE;
4101                         suppressKibitz = 0;
4102                     }
4103                     next_out = i;
4104                 }
4105                 continue;
4106             }
4107
4108             i++;                /* skip unparsed character and loop back */
4109         }
4110
4111         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4112 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4113 //          SendToPlayer(&buf[next_out], i - next_out);
4114             started != STARTED_HOLDINGS && leftover_start > next_out) {
4115             SendToPlayer(&buf[next_out], leftover_start - next_out);
4116             next_out = i;
4117         }
4118
4119         leftover_len = buf_len - leftover_start;
4120         /* if buffer ends with something we couldn't parse,
4121            reparse it after appending the next read */
4122
4123     } else if (count == 0) {
4124         RemoveInputSource(isr);
4125         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4126     } else {
4127         DisplayFatalError(_("Error reading from ICS"), error, 1);
4128     }
4129 }
4130
4131
4132 /* Board style 12 looks like this:
4133
4134    <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
4135
4136  * The "<12> " is stripped before it gets to this routine.  The two
4137  * trailing 0's (flip state and clock ticking) are later addition, and
4138  * some chess servers may not have them, or may have only the first.
4139  * Additional trailing fields may be added in the future.
4140  */
4141
4142 #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"
4143
4144 #define RELATION_OBSERVING_PLAYED    0
4145 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4146 #define RELATION_PLAYING_MYMOVE      1
4147 #define RELATION_PLAYING_NOTMYMOVE  -1
4148 #define RELATION_EXAMINING           2
4149 #define RELATION_ISOLATED_BOARD     -3
4150 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4151
4152 void
4153 ParseBoard12 (char *string)
4154 {
4155     GameMode newGameMode;
4156     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4157     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4158     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4159     char to_play, board_chars[200];
4160     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4161     char black[32], white[32];
4162     Board board;
4163     int prevMove = currentMove;
4164     int ticking = 2;
4165     ChessMove moveType;
4166     int fromX, fromY, toX, toY;
4167     char promoChar;
4168     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4169     char *bookHit = NULL; // [HGM] book
4170     Boolean weird = FALSE, reqFlag = FALSE;
4171
4172     fromX = fromY = toX = toY = -1;
4173
4174     newGame = FALSE;
4175
4176     if (appData.debugMode)
4177       fprintf(debugFP, _("Parsing board: %s\n"), string);
4178
4179     move_str[0] = NULLCHAR;
4180     elapsed_time[0] = NULLCHAR;
4181     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4182         int  i = 0, j;
4183         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4184             if(string[i] == ' ') { ranks++; files = 0; }
4185             else files++;
4186             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4187             i++;
4188         }
4189         for(j = 0; j <i; j++) board_chars[j] = string[j];
4190         board_chars[i] = '\0';
4191         string += i + 1;
4192     }
4193     n = sscanf(string, PATTERN, &to_play, &double_push,
4194                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4195                &gamenum, white, black, &relation, &basetime, &increment,
4196                &white_stren, &black_stren, &white_time, &black_time,
4197                &moveNum, str, elapsed_time, move_str, &ics_flip,
4198                &ticking);
4199
4200     if (n < 21) {
4201         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4202         DisplayError(str, 0);
4203         return;
4204     }
4205
4206     /* Convert the move number to internal form */
4207     moveNum = (moveNum - 1) * 2;
4208     if (to_play == 'B') moveNum++;
4209     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4210       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4211                         0, 1);
4212       return;
4213     }
4214
4215     switch (relation) {
4216       case RELATION_OBSERVING_PLAYED:
4217       case RELATION_OBSERVING_STATIC:
4218         if (gamenum == -1) {
4219             /* Old ICC buglet */
4220             relation = RELATION_OBSERVING_STATIC;
4221         }
4222         newGameMode = IcsObserving;
4223         break;
4224       case RELATION_PLAYING_MYMOVE:
4225       case RELATION_PLAYING_NOTMYMOVE:
4226         newGameMode =
4227           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4228             IcsPlayingWhite : IcsPlayingBlack;
4229         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4230         break;
4231       case RELATION_EXAMINING:
4232         newGameMode = IcsExamining;
4233         break;
4234       case RELATION_ISOLATED_BOARD:
4235       default:
4236         /* Just display this board.  If user was doing something else,
4237            we will forget about it until the next board comes. */
4238         newGameMode = IcsIdle;
4239         break;
4240       case RELATION_STARTING_POSITION:
4241         newGameMode = gameMode;
4242         break;
4243     }
4244
4245     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4246         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4247          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4248       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4249       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4250       static int lastBgGame = -1;
4251       char *toSqr;
4252       for (k = 0; k < ranks; k++) {
4253         for (j = 0; j < files; j++)
4254           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4255         if(gameInfo.holdingsWidth > 1) {
4256              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4257              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4258         }
4259       }
4260       CopyBoard(partnerBoard, board);
4261       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4262         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4263         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4264       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4265       if(toSqr = strchr(str, '-')) {
4266         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4267         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4268       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4269       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4270       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4271       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4272       if(twoBoards) {
4273           DisplayWhiteClock(white_time*fac, to_play == 'W');
4274           DisplayBlackClock(black_time*fac, to_play != 'W');
4275           activePartner = to_play;
4276           if(gamenum != lastBgGame) {
4277               char buf[MSG_SIZ];
4278               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4279               DisplayTitle(buf);
4280           }
4281           lastBgGame = gamenum;
4282           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4283                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4284       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4285                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4286       DisplayMessage(partnerStatus, "");
4287         partnerBoardValid = TRUE;
4288       return;
4289     }
4290
4291     if(appData.dualBoard && appData.bgObserve) {
4292         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4293             SendToICS(ics_prefix), SendToICS("pobserve\n");
4294         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4295             char buf[MSG_SIZ];
4296             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4297             SendToICS(buf);
4298         }
4299     }
4300
4301     /* Modify behavior for initial board display on move listing
4302        of wild games.
4303        */
4304     switch (ics_getting_history) {
4305       case H_FALSE:
4306       case H_REQUESTED:
4307         break;
4308       case H_GOT_REQ_HEADER:
4309       case H_GOT_UNREQ_HEADER:
4310         /* This is the initial position of the current game */
4311         gamenum = ics_gamenum;
4312         moveNum = 0;            /* old ICS bug workaround */
4313         if (to_play == 'B') {
4314           startedFromSetupPosition = TRUE;
4315           blackPlaysFirst = TRUE;
4316           moveNum = 1;
4317           if (forwardMostMove == 0) forwardMostMove = 1;
4318           if (backwardMostMove == 0) backwardMostMove = 1;
4319           if (currentMove == 0) currentMove = 1;
4320         }
4321         newGameMode = gameMode;
4322         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4323         break;
4324       case H_GOT_UNWANTED_HEADER:
4325         /* This is an initial board that we don't want */
4326         return;
4327       case H_GETTING_MOVES:
4328         /* Should not happen */
4329         DisplayError(_("Error gathering move list: extra board"), 0);
4330         ics_getting_history = H_FALSE;
4331         return;
4332     }
4333
4334    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4335                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4336                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4337      /* [HGM] We seem to have switched variant unexpectedly
4338       * Try to guess new variant from board size
4339       */
4340           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4341           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4342           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4343           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4344           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4345           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4346           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4347           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4348           /* Get a move list just to see the header, which
4349              will tell us whether this is really bug or zh */
4350           if (ics_getting_history == H_FALSE) {
4351             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4352             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4353             SendToICS(str);
4354           }
4355     }
4356
4357     /* Take action if this is the first board of a new game, or of a
4358        different game than is currently being displayed.  */
4359     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4360         relation == RELATION_ISOLATED_BOARD) {
4361
4362         /* Forget the old game and get the history (if any) of the new one */
4363         if (gameMode != BeginningOfGame) {
4364           Reset(TRUE, TRUE);
4365         }
4366         newGame = TRUE;
4367         if (appData.autoRaiseBoard) BoardToTop();
4368         prevMove = -3;
4369         if (gamenum == -1) {
4370             newGameMode = IcsIdle;
4371         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4372                    appData.getMoveList && !reqFlag) {
4373             /* Need to get game history */
4374             ics_getting_history = H_REQUESTED;
4375             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4376             SendToICS(str);
4377         }
4378
4379         /* Initially flip the board to have black on the bottom if playing
4380            black or if the ICS flip flag is set, but let the user change
4381            it with the Flip View button. */
4382         flipView = appData.autoFlipView ?
4383           (newGameMode == IcsPlayingBlack) || ics_flip :
4384           appData.flipView;
4385
4386         /* Done with values from previous mode; copy in new ones */
4387         gameMode = newGameMode;
4388         ModeHighlight();
4389         ics_gamenum = gamenum;
4390         if (gamenum == gs_gamenum) {
4391             int klen = strlen(gs_kind);
4392             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4393             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4394             gameInfo.event = StrSave(str);
4395         } else {
4396             gameInfo.event = StrSave("ICS game");
4397         }
4398         gameInfo.site = StrSave(appData.icsHost);
4399         gameInfo.date = PGNDate();
4400         gameInfo.round = StrSave("-");
4401         gameInfo.white = StrSave(white);
4402         gameInfo.black = StrSave(black);
4403         timeControl = basetime * 60 * 1000;
4404         timeControl_2 = 0;
4405         timeIncrement = increment * 1000;
4406         movesPerSession = 0;
4407         gameInfo.timeControl = TimeControlTagValue();
4408         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4409   if (appData.debugMode) {
4410     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4411     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4412     setbuf(debugFP, NULL);
4413   }
4414
4415         gameInfo.outOfBook = NULL;
4416
4417         /* Do we have the ratings? */
4418         if (strcmp(player1Name, white) == 0 &&
4419             strcmp(player2Name, black) == 0) {
4420             if (appData.debugMode)
4421               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4422                       player1Rating, player2Rating);
4423             gameInfo.whiteRating = player1Rating;
4424             gameInfo.blackRating = player2Rating;
4425         } else if (strcmp(player2Name, white) == 0 &&
4426                    strcmp(player1Name, black) == 0) {
4427             if (appData.debugMode)
4428               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4429                       player2Rating, player1Rating);
4430             gameInfo.whiteRating = player2Rating;
4431             gameInfo.blackRating = player1Rating;
4432         }
4433         player1Name[0] = player2Name[0] = NULLCHAR;
4434
4435         /* Silence shouts if requested */
4436         if (appData.quietPlay &&
4437             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4438             SendToICS(ics_prefix);
4439             SendToICS("set shout 0\n");
4440         }
4441     }
4442
4443     /* Deal with midgame name changes */
4444     if (!newGame) {
4445         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4446             if (gameInfo.white) free(gameInfo.white);
4447             gameInfo.white = StrSave(white);
4448         }
4449         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4450             if (gameInfo.black) free(gameInfo.black);
4451             gameInfo.black = StrSave(black);
4452         }
4453     }
4454
4455     /* Throw away game result if anything actually changes in examine mode */
4456     if (gameMode == IcsExamining && !newGame) {
4457         gameInfo.result = GameUnfinished;
4458         if (gameInfo.resultDetails != NULL) {
4459             free(gameInfo.resultDetails);
4460             gameInfo.resultDetails = NULL;
4461         }
4462     }
4463
4464     /* In pausing && IcsExamining mode, we ignore boards coming
4465        in if they are in a different variation than we are. */
4466     if (pauseExamInvalid) return;
4467     if (pausing && gameMode == IcsExamining) {
4468         if (moveNum <= pauseExamForwardMostMove) {
4469             pauseExamInvalid = TRUE;
4470             forwardMostMove = pauseExamForwardMostMove;
4471             return;
4472         }
4473     }
4474
4475   if (appData.debugMode) {
4476     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4477   }
4478     /* Parse the board */
4479     for (k = 0; k < ranks; k++) {
4480       for (j = 0; j < files; j++)
4481         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4482       if(gameInfo.holdingsWidth > 1) {
4483            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4484            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4485       }
4486     }
4487     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4488       board[5][BOARD_RGHT+1] = WhiteAngel;
4489       board[6][BOARD_RGHT+1] = WhiteMarshall;
4490       board[1][0] = BlackMarshall;
4491       board[2][0] = BlackAngel;
4492       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4493     }
4494     CopyBoard(boards[moveNum], board);
4495     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4496     if (moveNum == 0) {
4497         startedFromSetupPosition =
4498           !CompareBoards(board, initialPosition);
4499         if(startedFromSetupPosition)
4500             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4501     }
4502
4503     /* [HGM] Set castling rights. Take the outermost Rooks,
4504        to make it also work for FRC opening positions. Note that board12
4505        is really defective for later FRC positions, as it has no way to
4506        indicate which Rook can castle if they are on the same side of King.
4507        For the initial position we grant rights to the outermost Rooks,
4508        and remember thos rights, and we then copy them on positions
4509        later in an FRC game. This means WB might not recognize castlings with
4510        Rooks that have moved back to their original position as illegal,
4511        but in ICS mode that is not its job anyway.
4512     */
4513     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4514     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4515
4516         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4517             if(board[0][i] == WhiteRook) j = i;
4518         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4519         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4520             if(board[0][i] == WhiteRook) j = i;
4521         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4522         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4523             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4524         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4525         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4526             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4527         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4528
4529         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4530         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4531         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4532             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4533         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4534             if(board[BOARD_HEIGHT-1][k] == bKing)
4535                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4536         if(gameInfo.variant == VariantTwoKings) {
4537             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4538             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4539             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4540         }
4541     } else { int r;
4542         r = boards[moveNum][CASTLING][0] = initialRights[0];
4543         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4544         r = boards[moveNum][CASTLING][1] = initialRights[1];
4545         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4546         r = boards[moveNum][CASTLING][3] = initialRights[3];
4547         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4548         r = boards[moveNum][CASTLING][4] = initialRights[4];
4549         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4550         /* wildcastle kludge: always assume King has rights */
4551         r = boards[moveNum][CASTLING][2] = initialRights[2];
4552         r = boards[moveNum][CASTLING][5] = initialRights[5];
4553     }
4554     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4555     boards[moveNum][EP_STATUS] = EP_NONE;
4556     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4557     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4558     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4559
4560
4561     if (ics_getting_history == H_GOT_REQ_HEADER ||
4562         ics_getting_history == H_GOT_UNREQ_HEADER) {
4563         /* This was an initial position from a move list, not
4564            the current position */
4565         return;
4566     }
4567
4568     /* Update currentMove and known move number limits */
4569     newMove = newGame || moveNum > forwardMostMove;
4570
4571     if (newGame) {
4572         forwardMostMove = backwardMostMove = currentMove = moveNum;
4573         if (gameMode == IcsExamining && moveNum == 0) {
4574           /* Workaround for ICS limitation: we are not told the wild
4575              type when starting to examine a game.  But if we ask for
4576              the move list, the move list header will tell us */
4577             ics_getting_history = H_REQUESTED;
4578             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4579             SendToICS(str);
4580         }
4581     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4582                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4583 #if ZIPPY
4584         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4585         /* [HGM] applied this also to an engine that is silently watching        */
4586         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4587             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4588             gameInfo.variant == currentlyInitializedVariant) {
4589           takeback = forwardMostMove - moveNum;
4590           for (i = 0; i < takeback; i++) {
4591             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4592             SendToProgram("undo\n", &first);
4593           }
4594         }
4595 #endif
4596
4597         forwardMostMove = moveNum;
4598         if (!pausing || currentMove > forwardMostMove)
4599           currentMove = forwardMostMove;
4600     } else {
4601         /* New part of history that is not contiguous with old part */
4602         if (pausing && gameMode == IcsExamining) {
4603             pauseExamInvalid = TRUE;
4604             forwardMostMove = pauseExamForwardMostMove;
4605             return;
4606         }
4607         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4608 #if ZIPPY
4609             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4610                 // [HGM] when we will receive the move list we now request, it will be
4611                 // fed to the engine from the first move on. So if the engine is not
4612                 // in the initial position now, bring it there.
4613                 InitChessProgram(&first, 0);
4614             }
4615 #endif
4616             ics_getting_history = H_REQUESTED;
4617             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4618             SendToICS(str);
4619         }
4620         forwardMostMove = backwardMostMove = currentMove = moveNum;
4621     }
4622
4623     /* Update the clocks */
4624     if (strchr(elapsed_time, '.')) {
4625       /* Time is in ms */
4626       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4627       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4628     } else {
4629       /* Time is in seconds */
4630       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4631       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4632     }
4633
4634
4635 #if ZIPPY
4636     if (appData.zippyPlay && newGame &&
4637         gameMode != IcsObserving && gameMode != IcsIdle &&
4638         gameMode != IcsExamining)
4639       ZippyFirstBoard(moveNum, basetime, increment);
4640 #endif
4641
4642     /* Put the move on the move list, first converting
4643        to canonical algebraic form. */
4644     if (moveNum > 0) {
4645   if (appData.debugMode) {
4646     if (appData.debugMode) { int f = forwardMostMove;
4647         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4648                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4649                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4650     }
4651     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4652     fprintf(debugFP, "moveNum = %d\n", moveNum);
4653     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4654     setbuf(debugFP, NULL);
4655   }
4656         if (moveNum <= backwardMostMove) {
4657             /* We don't know what the board looked like before
4658                this move.  Punt. */
4659           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4660             strcat(parseList[moveNum - 1], " ");
4661             strcat(parseList[moveNum - 1], elapsed_time);
4662             moveList[moveNum - 1][0] = NULLCHAR;
4663         } else if (strcmp(move_str, "none") == 0) {
4664             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4665             /* Again, we don't know what the board looked like;
4666                this is really the start of the game. */
4667             parseList[moveNum - 1][0] = NULLCHAR;
4668             moveList[moveNum - 1][0] = NULLCHAR;
4669             backwardMostMove = moveNum;
4670             startedFromSetupPosition = TRUE;
4671             fromX = fromY = toX = toY = -1;
4672         } else {
4673           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4674           //                 So we parse the long-algebraic move string in stead of the SAN move
4675           int valid; char buf[MSG_SIZ], *prom;
4676
4677           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4678                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4679           // str looks something like "Q/a1-a2"; kill the slash
4680           if(str[1] == '/')
4681             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4682           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4683           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4684                 strcat(buf, prom); // long move lacks promo specification!
4685           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4686                 if(appData.debugMode)
4687                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4688                 safeStrCpy(move_str, buf, MSG_SIZ);
4689           }
4690           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4691                                 &fromX, &fromY, &toX, &toY, &promoChar)
4692                || ParseOneMove(buf, moveNum - 1, &moveType,
4693                                 &fromX, &fromY, &toX, &toY, &promoChar);
4694           // end of long SAN patch
4695           if (valid) {
4696             (void) CoordsToAlgebraic(boards[moveNum - 1],
4697                                      PosFlags(moveNum - 1),
4698                                      fromY, fromX, toY, toX, promoChar,
4699                                      parseList[moveNum-1]);
4700             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4701               case MT_NONE:
4702               case MT_STALEMATE:
4703               default:
4704                 break;
4705               case MT_CHECK:
4706                 if(gameInfo.variant != VariantShogi)
4707                     strcat(parseList[moveNum - 1], "+");
4708                 break;
4709               case MT_CHECKMATE:
4710               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4711                 strcat(parseList[moveNum - 1], "#");
4712                 break;
4713             }
4714             strcat(parseList[moveNum - 1], " ");
4715             strcat(parseList[moveNum - 1], elapsed_time);
4716             /* currentMoveString is set as a side-effect of ParseOneMove */
4717             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4718             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4719             strcat(moveList[moveNum - 1], "\n");
4720
4721             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4722                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4723               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4724                 ChessSquare old, new = boards[moveNum][k][j];
4725                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4726                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4727                   if(old == new) continue;
4728                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4729                   else if(new == WhiteWazir || new == BlackWazir) {
4730                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4731                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4732                       else boards[moveNum][k][j] = old; // preserve type of Gold
4733                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4734                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4735               }
4736           } else {
4737             /* Move from ICS was illegal!?  Punt. */
4738             if (appData.debugMode) {
4739               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4740               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4741             }
4742             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4743             strcat(parseList[moveNum - 1], " ");
4744             strcat(parseList[moveNum - 1], elapsed_time);
4745             moveList[moveNum - 1][0] = NULLCHAR;
4746             fromX = fromY = toX = toY = -1;
4747           }
4748         }
4749   if (appData.debugMode) {
4750     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4751     setbuf(debugFP, NULL);
4752   }
4753
4754 #if ZIPPY
4755         /* Send move to chess program (BEFORE animating it). */
4756         if (appData.zippyPlay && !newGame && newMove &&
4757            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4758
4759             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4760                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4761                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4762                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4763                             move_str);
4764                     DisplayError(str, 0);
4765                 } else {
4766                     if (first.sendTime) {
4767                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4768                     }
4769                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4770                     if (firstMove && !bookHit) {
4771                         firstMove = FALSE;
4772                         if (first.useColors) {
4773                           SendToProgram(gameMode == IcsPlayingWhite ?
4774                                         "white\ngo\n" :
4775                                         "black\ngo\n", &first);
4776                         } else {
4777                           SendToProgram("go\n", &first);
4778                         }
4779                         first.maybeThinking = TRUE;
4780                     }
4781                 }
4782             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4783               if (moveList[moveNum - 1][0] == NULLCHAR) {
4784                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4785                 DisplayError(str, 0);
4786               } else {
4787                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4788                 SendMoveToProgram(moveNum - 1, &first);
4789               }
4790             }
4791         }
4792 #endif
4793     }
4794
4795     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4796         /* If move comes from a remote source, animate it.  If it
4797            isn't remote, it will have already been animated. */
4798         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4799             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4800         }
4801         if (!pausing && appData.highlightLastMove) {
4802             SetHighlights(fromX, fromY, toX, toY);
4803         }
4804     }
4805
4806     /* Start the clocks */
4807     whiteFlag = blackFlag = FALSE;
4808     appData.clockMode = !(basetime == 0 && increment == 0);
4809     if (ticking == 0) {
4810       ics_clock_paused = TRUE;
4811       StopClocks();
4812     } else if (ticking == 1) {
4813       ics_clock_paused = FALSE;
4814     }
4815     if (gameMode == IcsIdle ||
4816         relation == RELATION_OBSERVING_STATIC ||
4817         relation == RELATION_EXAMINING ||
4818         ics_clock_paused)
4819       DisplayBothClocks();
4820     else
4821       StartClocks();
4822
4823     /* Display opponents and material strengths */
4824     if (gameInfo.variant != VariantBughouse &&
4825         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4826         if (tinyLayout || smallLayout) {
4827             if(gameInfo.variant == VariantNormal)
4828               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4829                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4830                     basetime, increment);
4831             else
4832               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4833                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4834                     basetime, increment, (int) gameInfo.variant);
4835         } else {
4836             if(gameInfo.variant == VariantNormal)
4837               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4838                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4839                     basetime, increment);
4840             else
4841               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4842                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4843                     basetime, increment, VariantName(gameInfo.variant));
4844         }
4845         DisplayTitle(str);
4846   if (appData.debugMode) {
4847     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4848   }
4849     }
4850
4851
4852     /* Display the board */
4853     if (!pausing && !appData.noGUI) {
4854
4855       if (appData.premove)
4856           if (!gotPremove ||
4857              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4858              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4859               ClearPremoveHighlights();
4860
4861       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4862         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4863       DrawPosition(j, boards[currentMove]);
4864
4865       DisplayMove(moveNum - 1);
4866       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4867             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4868               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4869         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4870       }
4871     }
4872
4873     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4874 #if ZIPPY
4875     if(bookHit) { // [HGM] book: simulate book reply
4876         static char bookMove[MSG_SIZ]; // a bit generous?
4877
4878         programStats.nodes = programStats.depth = programStats.time =
4879         programStats.score = programStats.got_only_move = 0;
4880         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4881
4882         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4883         strcat(bookMove, bookHit);
4884         HandleMachineMove(bookMove, &first);
4885     }
4886 #endif
4887 }
4888
4889 void
4890 GetMoveListEvent ()
4891 {
4892     char buf[MSG_SIZ];
4893     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4894         ics_getting_history = H_REQUESTED;
4895         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4896         SendToICS(buf);
4897     }
4898 }
4899
4900 void
4901 SendToBoth (char *msg)
4902 {   // to make it easy to keep two engines in step in dual analysis
4903     SendToProgram(msg, &first);
4904     if(second.analyzing) SendToProgram(msg, &second);
4905 }
4906
4907 void
4908 AnalysisPeriodicEvent (int force)
4909 {
4910     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4911          && !force) || !appData.periodicUpdates)
4912       return;
4913
4914     /* Send . command to Crafty to collect stats */
4915     SendToBoth(".\n");
4916
4917     /* Don't send another until we get a response (this makes
4918        us stop sending to old Crafty's which don't understand
4919        the "." command (sending illegal cmds resets node count & time,
4920        which looks bad)) */
4921     programStats.ok_to_send = 0;
4922 }
4923
4924 void
4925 ics_update_width (int new_width)
4926 {
4927         ics_printf("set width %d\n", new_width);
4928 }
4929
4930 void
4931 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4932 {
4933     char buf[MSG_SIZ];
4934
4935     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4936         // null move in variant where engine does not understand it (for analysis purposes)
4937         SendBoard(cps, moveNum + 1); // send position after move in stead.
4938         return;
4939     }
4940     if (cps->useUsermove) {
4941       SendToProgram("usermove ", cps);
4942     }
4943     if (cps->useSAN) {
4944       char *space;
4945       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4946         int len = space - parseList[moveNum];
4947         memcpy(buf, parseList[moveNum], len);
4948         buf[len++] = '\n';
4949         buf[len] = NULLCHAR;
4950       } else {
4951         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4952       }
4953       SendToProgram(buf, cps);
4954     } else {
4955       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4956         AlphaRank(moveList[moveNum], 4);
4957         SendToProgram(moveList[moveNum], cps);
4958         AlphaRank(moveList[moveNum], 4); // and back
4959       } else
4960       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4961        * the engine. It would be nice to have a better way to identify castle
4962        * moves here. */
4963       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4964                                                                          && cps->useOOCastle) {
4965         int fromX = moveList[moveNum][0] - AAA;
4966         int fromY = moveList[moveNum][1] - ONE;
4967         int toX = moveList[moveNum][2] - AAA;
4968         int toY = moveList[moveNum][3] - ONE;
4969         if((boards[moveNum][fromY][fromX] == WhiteKing
4970             && boards[moveNum][toY][toX] == WhiteRook)
4971            || (boards[moveNum][fromY][fromX] == BlackKing
4972                && boards[moveNum][toY][toX] == BlackRook)) {
4973           if(toX > fromX) SendToProgram("O-O\n", cps);
4974           else SendToProgram("O-O-O\n", cps);
4975         }
4976         else SendToProgram(moveList[moveNum], cps);
4977       } else
4978       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4979         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4980           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4981           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4982                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4983         } else
4984           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4985                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4986         SendToProgram(buf, cps);
4987       }
4988       else SendToProgram(moveList[moveNum], cps);
4989       /* End of additions by Tord */
4990     }
4991
4992     /* [HGM] setting up the opening has brought engine in force mode! */
4993     /*       Send 'go' if we are in a mode where machine should play. */
4994     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4995         (gameMode == TwoMachinesPlay   ||
4996 #if ZIPPY
4997          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4998 #endif
4999          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5000         SendToProgram("go\n", cps);
5001   if (appData.debugMode) {
5002     fprintf(debugFP, "(extra)\n");
5003   }
5004     }
5005     setboardSpoiledMachineBlack = 0;
5006 }
5007
5008 void
5009 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5010 {
5011     char user_move[MSG_SIZ];
5012     char suffix[4];
5013
5014     if(gameInfo.variant == VariantSChess && promoChar) {
5015         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5016         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5017     } else suffix[0] = NULLCHAR;
5018
5019     switch (moveType) {
5020       default:
5021         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5022                 (int)moveType, fromX, fromY, toX, toY);
5023         DisplayError(user_move + strlen("say "), 0);
5024         break;
5025       case WhiteKingSideCastle:
5026       case BlackKingSideCastle:
5027       case WhiteQueenSideCastleWild:
5028       case BlackQueenSideCastleWild:
5029       /* PUSH Fabien */
5030       case WhiteHSideCastleFR:
5031       case BlackHSideCastleFR:
5032       /* POP Fabien */
5033         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5034         break;
5035       case WhiteQueenSideCastle:
5036       case BlackQueenSideCastle:
5037       case WhiteKingSideCastleWild:
5038       case BlackKingSideCastleWild:
5039       /* PUSH Fabien */
5040       case WhiteASideCastleFR:
5041       case BlackASideCastleFR:
5042       /* POP Fabien */
5043         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5044         break;
5045       case WhiteNonPromotion:
5046       case BlackNonPromotion:
5047         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5048         break;
5049       case WhitePromotion:
5050       case BlackPromotion:
5051         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5052           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5053                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5054                 PieceToChar(WhiteFerz));
5055         else if(gameInfo.variant == VariantGreat)
5056           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5057                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5058                 PieceToChar(WhiteMan));
5059         else
5060           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5061                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5062                 promoChar);
5063         break;
5064       case WhiteDrop:
5065       case BlackDrop:
5066       drop:
5067         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5068                  ToUpper(PieceToChar((ChessSquare) fromX)),
5069                  AAA + toX, ONE + toY);
5070         break;
5071       case IllegalMove:  /* could be a variant we don't quite understand */
5072         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5073       case NormalMove:
5074       case WhiteCapturesEnPassant:
5075       case BlackCapturesEnPassant:
5076         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5077                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5078         break;
5079     }
5080     SendToICS(user_move);
5081     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5082         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5083 }
5084
5085 void
5086 UploadGameEvent ()
5087 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5088     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5089     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5090     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5091       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5092       return;
5093     }
5094     if(gameMode != IcsExamining) { // is this ever not the case?
5095         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5096
5097         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5098           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5099         } else { // on FICS we must first go to general examine mode
5100           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5101         }
5102         if(gameInfo.variant != VariantNormal) {
5103             // try figure out wild number, as xboard names are not always valid on ICS
5104             for(i=1; i<=36; i++) {
5105               snprintf(buf, MSG_SIZ, "wild/%d", i);
5106                 if(StringToVariant(buf) == gameInfo.variant) break;
5107             }
5108             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5109             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5110             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5111         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5112         SendToICS(ics_prefix);
5113         SendToICS(buf);
5114         if(startedFromSetupPosition || backwardMostMove != 0) {
5115           fen = PositionToFEN(backwardMostMove, NULL);
5116           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5117             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5118             SendToICS(buf);
5119           } else { // FICS: everything has to set by separate bsetup commands
5120             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5121             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5122             SendToICS(buf);
5123             if(!WhiteOnMove(backwardMostMove)) {
5124                 SendToICS("bsetup tomove black\n");
5125             }
5126             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5127             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5128             SendToICS(buf);
5129             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5130             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5131             SendToICS(buf);
5132             i = boards[backwardMostMove][EP_STATUS];
5133             if(i >= 0) { // set e.p.
5134               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5135                 SendToICS(buf);
5136             }
5137             bsetup++;
5138           }
5139         }
5140       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5141             SendToICS("bsetup done\n"); // switch to normal examining.
5142     }
5143     for(i = backwardMostMove; i<last; i++) {
5144         char buf[20];
5145         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5146         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5147             int len = strlen(moveList[i]);
5148             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5149             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5150         }
5151         SendToICS(buf);
5152     }
5153     SendToICS(ics_prefix);
5154     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5155 }
5156
5157 void
5158 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5159 {
5160     if (rf == DROP_RANK) {
5161       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5162       sprintf(move, "%c@%c%c\n",
5163                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5164     } else {
5165         if (promoChar == 'x' || promoChar == NULLCHAR) {
5166           sprintf(move, "%c%c%c%c\n",
5167                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5168         } else {
5169             sprintf(move, "%c%c%c%c%c\n",
5170                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5171         }
5172     }
5173 }
5174
5175 void
5176 ProcessICSInitScript (FILE *f)
5177 {
5178     char buf[MSG_SIZ];
5179
5180     while (fgets(buf, MSG_SIZ, f)) {
5181         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5182     }
5183
5184     fclose(f);
5185 }
5186
5187
5188 static int lastX, lastY, selectFlag, dragging;
5189
5190 void
5191 Sweep (int step)
5192 {
5193     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5194     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5195     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5196     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5197     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5198     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5199     do {
5200         promoSweep -= step;
5201         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5202         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5203         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5204         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5205         if(!step) step = -1;
5206     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5207             appData.testLegality && (promoSweep == king ||
5208             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5209     if(toX >= 0) {
5210         int victim = boards[currentMove][toY][toX];
5211         boards[currentMove][toY][toX] = promoSweep;
5212         DrawPosition(FALSE, boards[currentMove]);
5213         boards[currentMove][toY][toX] = victim;
5214     } else
5215     ChangeDragPiece(promoSweep);
5216 }
5217
5218 int
5219 PromoScroll (int x, int y)
5220 {
5221   int step = 0;
5222
5223   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5224   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5225   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5226   if(!step) return FALSE;
5227   lastX = x; lastY = y;
5228   if((promoSweep < BlackPawn) == flipView) step = -step;
5229   if(step > 0) selectFlag = 1;
5230   if(!selectFlag) Sweep(step);
5231   return FALSE;
5232 }
5233
5234 void
5235 NextPiece (int step)
5236 {
5237     ChessSquare piece = boards[currentMove][toY][toX];
5238     do {
5239         pieceSweep -= step;
5240         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5241         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5242         if(!step) step = -1;
5243     } while(PieceToChar(pieceSweep) == '.');
5244     boards[currentMove][toY][toX] = pieceSweep;
5245     DrawPosition(FALSE, boards[currentMove]);
5246     boards[currentMove][toY][toX] = piece;
5247 }
5248 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5249 void
5250 AlphaRank (char *move, int n)
5251 {
5252 //    char *p = move, c; int x, y;
5253
5254     if (appData.debugMode) {
5255         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5256     }
5257
5258     if(move[1]=='*' &&
5259        move[2]>='0' && move[2]<='9' &&
5260        move[3]>='a' && move[3]<='x'    ) {
5261         move[1] = '@';
5262         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5263         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5264     } else
5265     if(move[0]>='0' && move[0]<='9' &&
5266        move[1]>='a' && move[1]<='x' &&
5267        move[2]>='0' && move[2]<='9' &&
5268        move[3]>='a' && move[3]<='x'    ) {
5269         /* input move, Shogi -> normal */
5270         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5271         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5272         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5273         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5274     } else
5275     if(move[1]=='@' &&
5276        move[3]>='0' && move[3]<='9' &&
5277        move[2]>='a' && move[2]<='x'    ) {
5278         move[1] = '*';
5279         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5280         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5281     } else
5282     if(
5283        move[0]>='a' && move[0]<='x' &&
5284        move[3]>='0' && move[3]<='9' &&
5285        move[2]>='a' && move[2]<='x'    ) {
5286          /* output move, normal -> Shogi */
5287         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5288         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5289         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5290         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5291         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5292     }
5293     if (appData.debugMode) {
5294         fprintf(debugFP, "   out = '%s'\n", move);
5295     }
5296 }
5297
5298 char yy_textstr[8000];
5299
5300 /* Parser for moves from gnuchess, ICS, or user typein box */
5301 Boolean
5302 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5303 {
5304     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5305
5306     switch (*moveType) {
5307       case WhitePromotion:
5308       case BlackPromotion:
5309       case WhiteNonPromotion:
5310       case BlackNonPromotion:
5311       case NormalMove:
5312       case WhiteCapturesEnPassant:
5313       case BlackCapturesEnPassant:
5314       case WhiteKingSideCastle:
5315       case WhiteQueenSideCastle:
5316       case BlackKingSideCastle:
5317       case BlackQueenSideCastle:
5318       case WhiteKingSideCastleWild:
5319       case WhiteQueenSideCastleWild:
5320       case BlackKingSideCastleWild:
5321       case BlackQueenSideCastleWild:
5322       /* Code added by Tord: */
5323       case WhiteHSideCastleFR:
5324       case WhiteASideCastleFR:
5325       case BlackHSideCastleFR:
5326       case BlackASideCastleFR:
5327       /* End of code added by Tord */
5328       case IllegalMove:         /* bug or odd chess variant */
5329         *fromX = currentMoveString[0] - AAA;
5330         *fromY = currentMoveString[1] - ONE;
5331         *toX = currentMoveString[2] - AAA;
5332         *toY = currentMoveString[3] - ONE;
5333         *promoChar = currentMoveString[4];
5334         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5335             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5336     if (appData.debugMode) {
5337         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5338     }
5339             *fromX = *fromY = *toX = *toY = 0;
5340             return FALSE;
5341         }
5342         if (appData.testLegality) {
5343           return (*moveType != IllegalMove);
5344         } else {
5345           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5346                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5347         }
5348
5349       case WhiteDrop:
5350       case BlackDrop:
5351         *fromX = *moveType == WhiteDrop ?
5352           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5353           (int) CharToPiece(ToLower(currentMoveString[0]));
5354         *fromY = DROP_RANK;
5355         *toX = currentMoveString[2] - AAA;
5356         *toY = currentMoveString[3] - ONE;
5357         *promoChar = NULLCHAR;
5358         return TRUE;
5359
5360       case AmbiguousMove:
5361       case ImpossibleMove:
5362       case EndOfFile:
5363       case ElapsedTime:
5364       case Comment:
5365       case PGNTag:
5366       case NAG:
5367       case WhiteWins:
5368       case BlackWins:
5369       case GameIsDrawn:
5370       default:
5371     if (appData.debugMode) {
5372         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5373     }
5374         /* bug? */
5375         *fromX = *fromY = *toX = *toY = 0;
5376         *promoChar = NULLCHAR;
5377         return FALSE;
5378     }
5379 }
5380
5381 Boolean pushed = FALSE;
5382 char *lastParseAttempt;
5383
5384 void
5385 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5386 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5387   int fromX, fromY, toX, toY; char promoChar;
5388   ChessMove moveType;
5389   Boolean valid;
5390   int nr = 0;
5391
5392   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5393     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5394     pushed = TRUE;
5395   }
5396   endPV = forwardMostMove;
5397   do {
5398     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5399     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5400     lastParseAttempt = pv;
5401     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5402     if(!valid && nr == 0 &&
5403        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5404         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5405         // Hande case where played move is different from leading PV move
5406         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5407         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5408         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5409         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5410           endPV += 2; // if position different, keep this
5411           moveList[endPV-1][0] = fromX + AAA;
5412           moveList[endPV-1][1] = fromY + ONE;
5413           moveList[endPV-1][2] = toX + AAA;
5414           moveList[endPV-1][3] = toY + ONE;
5415           parseList[endPV-1][0] = NULLCHAR;
5416           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5417         }
5418       }
5419     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5420     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5421     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5422     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5423         valid++; // allow comments in PV
5424         continue;
5425     }
5426     nr++;
5427     if(endPV+1 > framePtr) break; // no space, truncate
5428     if(!valid) break;
5429     endPV++;
5430     CopyBoard(boards[endPV], boards[endPV-1]);
5431     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5432     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5433     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5434     CoordsToAlgebraic(boards[endPV - 1],
5435                              PosFlags(endPV - 1),
5436                              fromY, fromX, toY, toX, promoChar,
5437                              parseList[endPV - 1]);
5438   } while(valid);
5439   if(atEnd == 2) return; // used hidden, for PV conversion
5440   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5441   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5442   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5443                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5444   DrawPosition(TRUE, boards[currentMove]);
5445 }
5446
5447 int
5448 MultiPV (ChessProgramState *cps)
5449 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5450         int i;
5451         for(i=0; i<cps->nrOptions; i++)
5452             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5453                 return i;
5454         return -1;
5455 }
5456
5457 Boolean
5458 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5459 {
5460         int startPV, multi, lineStart, origIndex = index;
5461         char *p, buf2[MSG_SIZ];
5462         ChessProgramState *cps = (pane ? &second : &first);
5463
5464         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5465         lastX = x; lastY = y;
5466         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5467         lineStart = startPV = index;
5468         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5469         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5470         index = startPV;
5471         do{ while(buf[index] && buf[index] != '\n') index++;
5472         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5473         buf[index] = 0;
5474         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5475                 int n = cps->option[multi].value;
5476                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5477                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5478                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5479                 cps->option[multi].value = n;
5480                 *start = *end = 0;
5481                 return FALSE;
5482         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5483                 ExcludeClick(origIndex - lineStart);
5484                 return FALSE;
5485         }
5486         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5487         *start = startPV; *end = index-1;
5488         return TRUE;
5489 }
5490
5491 char *
5492 PvToSAN (char *pv)
5493 {
5494         static char buf[10*MSG_SIZ];
5495         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5496         *buf = NULLCHAR;
5497         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5498         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5499         for(i = forwardMostMove; i<endPV; i++){
5500             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5501             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5502             k += strlen(buf+k);
5503         }
5504         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5505         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5506         endPV = savedEnd;
5507         return buf;
5508 }
5509
5510 Boolean
5511 LoadPV (int x, int y)
5512 { // called on right mouse click to load PV
5513   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5514   lastX = x; lastY = y;
5515   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5516   return TRUE;
5517 }
5518
5519 void
5520 UnLoadPV ()
5521 {
5522   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5523   if(endPV < 0) return;
5524   if(appData.autoCopyPV) CopyFENToClipboard();
5525   endPV = -1;
5526   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5527         Boolean saveAnimate = appData.animate;
5528         if(pushed) {
5529             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5530                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5531             } else storedGames--; // abandon shelved tail of original game
5532         }
5533         pushed = FALSE;
5534         forwardMostMove = currentMove;
5535         currentMove = oldFMM;
5536         appData.animate = FALSE;
5537         ToNrEvent(forwardMostMove);
5538         appData.animate = saveAnimate;
5539   }
5540   currentMove = forwardMostMove;
5541   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5542   ClearPremoveHighlights();
5543   DrawPosition(TRUE, boards[currentMove]);
5544 }
5545
5546 void
5547 MovePV (int x, int y, int h)
5548 { // step through PV based on mouse coordinates (called on mouse move)
5549   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5550
5551   // we must somehow check if right button is still down (might be released off board!)
5552   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5553   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5554   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5555   if(!step) return;
5556   lastX = x; lastY = y;
5557
5558   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5559   if(endPV < 0) return;
5560   if(y < margin) step = 1; else
5561   if(y > h - margin) step = -1;
5562   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5563   currentMove += step;
5564   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5565   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5566                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5567   DrawPosition(FALSE, boards[currentMove]);
5568 }
5569
5570
5571 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5572 // All positions will have equal probability, but the current method will not provide a unique
5573 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5574 #define DARK 1
5575 #define LITE 2
5576 #define ANY 3
5577
5578 int squaresLeft[4];
5579 int piecesLeft[(int)BlackPawn];
5580 int seed, nrOfShuffles;
5581
5582 void
5583 GetPositionNumber ()
5584 {       // sets global variable seed
5585         int i;
5586
5587         seed = appData.defaultFrcPosition;
5588         if(seed < 0) { // randomize based on time for negative FRC position numbers
5589                 for(i=0; i<50; i++) seed += random();
5590                 seed = random() ^ random() >> 8 ^ random() << 8;
5591                 if(seed<0) seed = -seed;
5592         }
5593 }
5594
5595 int
5596 put (Board board, int pieceType, int rank, int n, int shade)
5597 // put the piece on the (n-1)-th empty squares of the given shade
5598 {
5599         int i;
5600
5601         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5602                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5603                         board[rank][i] = (ChessSquare) pieceType;
5604                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5605                         squaresLeft[ANY]--;
5606                         piecesLeft[pieceType]--;
5607                         return i;
5608                 }
5609         }
5610         return -1;
5611 }
5612
5613
5614 void
5615 AddOnePiece (Board board, int pieceType, int rank, int shade)
5616 // calculate where the next piece goes, (any empty square), and put it there
5617 {
5618         int i;
5619
5620         i = seed % squaresLeft[shade];
5621         nrOfShuffles *= squaresLeft[shade];
5622         seed /= squaresLeft[shade];
5623         put(board, pieceType, rank, i, shade);
5624 }
5625
5626 void
5627 AddTwoPieces (Board board, int pieceType, int rank)
5628 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5629 {
5630         int i, n=squaresLeft[ANY], j=n-1, k;
5631
5632         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5633         i = seed % k;  // pick one
5634         nrOfShuffles *= k;
5635         seed /= k;
5636         while(i >= j) i -= j--;
5637         j = n - 1 - j; i += j;
5638         put(board, pieceType, rank, j, ANY);
5639         put(board, pieceType, rank, i, ANY);
5640 }
5641
5642 void
5643 SetUpShuffle (Board board, int number)
5644 {
5645         int i, p, first=1;
5646
5647         GetPositionNumber(); nrOfShuffles = 1;
5648
5649         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5650         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5651         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5652
5653         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5654
5655         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5656             p = (int) board[0][i];
5657             if(p < (int) BlackPawn) piecesLeft[p] ++;
5658             board[0][i] = EmptySquare;
5659         }
5660
5661         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5662             // shuffles restricted to allow normal castling put KRR first
5663             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5664                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5665             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5666                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5667             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5668                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5669             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5670                 put(board, WhiteRook, 0, 0, ANY);
5671             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5672         }
5673
5674         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5675             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5676             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5677                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5678                 while(piecesLeft[p] >= 2) {
5679                     AddOnePiece(board, p, 0, LITE);
5680                     AddOnePiece(board, p, 0, DARK);
5681                 }
5682                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5683             }
5684
5685         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5686             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5687             // but we leave King and Rooks for last, to possibly obey FRC restriction
5688             if(p == (int)WhiteRook) continue;
5689             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5690             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5691         }
5692
5693         // now everything is placed, except perhaps King (Unicorn) and Rooks
5694
5695         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5696             // Last King gets castling rights
5697             while(piecesLeft[(int)WhiteUnicorn]) {
5698                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5699                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5700             }
5701
5702             while(piecesLeft[(int)WhiteKing]) {
5703                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5704                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5705             }
5706
5707
5708         } else {
5709             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5710             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5711         }
5712
5713         // Only Rooks can be left; simply place them all
5714         while(piecesLeft[(int)WhiteRook]) {
5715                 i = put(board, WhiteRook, 0, 0, ANY);
5716                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5717                         if(first) {
5718                                 first=0;
5719                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5720                         }
5721                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5722                 }
5723         }
5724         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5725             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5726         }
5727
5728         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5729 }
5730
5731 int
5732 SetCharTable (char *table, const char * map)
5733 /* [HGM] moved here from winboard.c because of its general usefulness */
5734 /*       Basically a safe strcpy that uses the last character as King */
5735 {
5736     int result = FALSE; int NrPieces;
5737
5738     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5739                     && NrPieces >= 12 && !(NrPieces&1)) {
5740         int i; /* [HGM] Accept even length from 12 to 34 */
5741
5742         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5743         for( i=0; i<NrPieces/2-1; i++ ) {
5744             table[i] = map[i];
5745             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5746         }
5747         table[(int) WhiteKing]  = map[NrPieces/2-1];
5748         table[(int) BlackKing]  = map[NrPieces-1];
5749
5750         result = TRUE;
5751     }
5752
5753     return result;
5754 }
5755
5756 void
5757 Prelude (Board board)
5758 {       // [HGM] superchess: random selection of exo-pieces
5759         int i, j, k; ChessSquare p;
5760         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5761
5762         GetPositionNumber(); // use FRC position number
5763
5764         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5765             SetCharTable(pieceToChar, appData.pieceToCharTable);
5766             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5767                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5768         }
5769
5770         j = seed%4;                 seed /= 4;
5771         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5772         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5773         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5774         j = seed%3 + (seed%3 >= j); seed /= 3;
5775         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5776         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5777         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5778         j = seed%3;                 seed /= 3;
5779         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5780         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5781         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5782         j = seed%2 + (seed%2 >= j); seed /= 2;
5783         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5784         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5785         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5786         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5787         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5788         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5789         put(board, exoPieces[0],    0, 0, ANY);
5790         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5791 }
5792
5793 void
5794 InitPosition (int redraw)
5795 {
5796     ChessSquare (* pieces)[BOARD_FILES];
5797     int i, j, pawnRow, overrule,
5798     oldx = gameInfo.boardWidth,
5799     oldy = gameInfo.boardHeight,
5800     oldh = gameInfo.holdingsWidth;
5801     static int oldv;
5802
5803     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5804
5805     /* [AS] Initialize pv info list [HGM] and game status */
5806     {
5807         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5808             pvInfoList[i].depth = 0;
5809             boards[i][EP_STATUS] = EP_NONE;
5810             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5811         }
5812
5813         initialRulePlies = 0; /* 50-move counter start */
5814
5815         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5816         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5817     }
5818
5819
5820     /* [HGM] logic here is completely changed. In stead of full positions */
5821     /* the initialized data only consist of the two backranks. The switch */
5822     /* selects which one we will use, which is than copied to the Board   */
5823     /* initialPosition, which for the rest is initialized by Pawns and    */
5824     /* empty squares. This initial position is then copied to boards[0],  */
5825     /* possibly after shuffling, so that it remains available.            */
5826
5827     gameInfo.holdingsWidth = 0; /* default board sizes */
5828     gameInfo.boardWidth    = 8;
5829     gameInfo.boardHeight   = 8;
5830     gameInfo.holdingsSize  = 0;
5831     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5832     for(i=0; i<BOARD_FILES-2; i++)
5833       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5834     initialPosition[EP_STATUS] = EP_NONE;
5835     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5836     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5837          SetCharTable(pieceNickName, appData.pieceNickNames);
5838     else SetCharTable(pieceNickName, "............");
5839     pieces = FIDEArray;
5840
5841     switch (gameInfo.variant) {
5842     case VariantFischeRandom:
5843       shuffleOpenings = TRUE;
5844     default:
5845       break;
5846     case VariantShatranj:
5847       pieces = ShatranjArray;
5848       nrCastlingRights = 0;
5849       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5850       break;
5851     case VariantMakruk:
5852       pieces = makrukArray;
5853       nrCastlingRights = 0;
5854       startedFromSetupPosition = TRUE;
5855       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5856       break;
5857     case VariantTwoKings:
5858       pieces = twoKingsArray;
5859       break;
5860     case VariantGrand:
5861       pieces = GrandArray;
5862       nrCastlingRights = 0;
5863       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5864       gameInfo.boardWidth = 10;
5865       gameInfo.boardHeight = 10;
5866       gameInfo.holdingsSize = 7;
5867       break;
5868     case VariantCapaRandom:
5869       shuffleOpenings = TRUE;
5870     case VariantCapablanca:
5871       pieces = CapablancaArray;
5872       gameInfo.boardWidth = 10;
5873       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5874       break;
5875     case VariantGothic:
5876       pieces = GothicArray;
5877       gameInfo.boardWidth = 10;
5878       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5879       break;
5880     case VariantSChess:
5881       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5882       gameInfo.holdingsSize = 7;
5883       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5884       break;
5885     case VariantJanus:
5886       pieces = JanusArray;
5887       gameInfo.boardWidth = 10;
5888       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5889       nrCastlingRights = 6;
5890         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5891         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5892         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5893         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5894         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5895         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5896       break;
5897     case VariantFalcon:
5898       pieces = FalconArray;
5899       gameInfo.boardWidth = 10;
5900       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5901       break;
5902     case VariantXiangqi:
5903       pieces = XiangqiArray;
5904       gameInfo.boardWidth  = 9;
5905       gameInfo.boardHeight = 10;
5906       nrCastlingRights = 0;
5907       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5908       break;
5909     case VariantShogi:
5910       pieces = ShogiArray;
5911       gameInfo.boardWidth  = 9;
5912       gameInfo.boardHeight = 9;
5913       gameInfo.holdingsSize = 7;
5914       nrCastlingRights = 0;
5915       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5916       break;
5917     case VariantCourier:
5918       pieces = CourierArray;
5919       gameInfo.boardWidth  = 12;
5920       nrCastlingRights = 0;
5921       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5922       break;
5923     case VariantKnightmate:
5924       pieces = KnightmateArray;
5925       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5926       break;
5927     case VariantSpartan:
5928       pieces = SpartanArray;
5929       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5930       break;
5931     case VariantFairy:
5932       pieces = fairyArray;
5933       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5934       break;
5935     case VariantGreat:
5936       pieces = GreatArray;
5937       gameInfo.boardWidth = 10;
5938       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5939       gameInfo.holdingsSize = 8;
5940       break;
5941     case VariantSuper:
5942       pieces = FIDEArray;
5943       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5944       gameInfo.holdingsSize = 8;
5945       startedFromSetupPosition = TRUE;
5946       break;
5947     case VariantCrazyhouse:
5948     case VariantBughouse:
5949       pieces = FIDEArray;
5950       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5951       gameInfo.holdingsSize = 5;
5952       break;
5953     case VariantWildCastle:
5954       pieces = FIDEArray;
5955       /* !!?shuffle with kings guaranteed to be on d or e file */
5956       shuffleOpenings = 1;
5957       break;
5958     case VariantNoCastle:
5959       pieces = FIDEArray;
5960       nrCastlingRights = 0;
5961       /* !!?unconstrained back-rank shuffle */
5962       shuffleOpenings = 1;
5963       break;
5964     }
5965
5966     overrule = 0;
5967     if(appData.NrFiles >= 0) {
5968         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5969         gameInfo.boardWidth = appData.NrFiles;
5970     }
5971     if(appData.NrRanks >= 0) {
5972         gameInfo.boardHeight = appData.NrRanks;
5973     }
5974     if(appData.holdingsSize >= 0) {
5975         i = appData.holdingsSize;
5976         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5977         gameInfo.holdingsSize = i;
5978     }
5979     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5980     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5981         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5982
5983     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5984     if(pawnRow < 1) pawnRow = 1;
5985     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5986
5987     /* User pieceToChar list overrules defaults */
5988     if(appData.pieceToCharTable != NULL)
5989         SetCharTable(pieceToChar, appData.pieceToCharTable);
5990
5991     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5992
5993         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5994             s = (ChessSquare) 0; /* account holding counts in guard band */
5995         for( i=0; i<BOARD_HEIGHT; i++ )
5996             initialPosition[i][j] = s;
5997
5998         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5999         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6000         initialPosition[pawnRow][j] = WhitePawn;
6001         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6002         if(gameInfo.variant == VariantXiangqi) {
6003             if(j&1) {
6004                 initialPosition[pawnRow][j] =
6005                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6006                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6007                    initialPosition[2][j] = WhiteCannon;
6008                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6009                 }
6010             }
6011         }
6012         if(gameInfo.variant == VariantGrand) {
6013             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6014                initialPosition[0][j] = WhiteRook;
6015                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6016             }
6017         }
6018         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6019     }
6020     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6021
6022             j=BOARD_LEFT+1;
6023             initialPosition[1][j] = WhiteBishop;
6024             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6025             j=BOARD_RGHT-2;
6026             initialPosition[1][j] = WhiteRook;
6027             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6028     }
6029
6030     if( nrCastlingRights == -1) {
6031         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6032         /*       This sets default castling rights from none to normal corners   */
6033         /* Variants with other castling rights must set them themselves above    */
6034         nrCastlingRights = 6;
6035
6036         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6037         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6038         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6039         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6040         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6041         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6042      }
6043
6044      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6045      if(gameInfo.variant == VariantGreat) { // promotion commoners
6046         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6047         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6048         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6049         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6050      }
6051      if( gameInfo.variant == VariantSChess ) {
6052       initialPosition[1][0] = BlackMarshall;
6053       initialPosition[2][0] = BlackAngel;
6054       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6055       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6056       initialPosition[1][1] = initialPosition[2][1] = 
6057       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6058      }
6059   if (appData.debugMode) {
6060     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6061   }
6062     if(shuffleOpenings) {
6063         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6064         startedFromSetupPosition = TRUE;
6065     }
6066     if(startedFromPositionFile) {
6067       /* [HGM] loadPos: use PositionFile for every new game */
6068       CopyBoard(initialPosition, filePosition);
6069       for(i=0; i<nrCastlingRights; i++)
6070           initialRights[i] = filePosition[CASTLING][i];
6071       startedFromSetupPosition = TRUE;
6072     }
6073
6074     CopyBoard(boards[0], initialPosition);
6075
6076     if(oldx != gameInfo.boardWidth ||
6077        oldy != gameInfo.boardHeight ||
6078        oldv != gameInfo.variant ||
6079        oldh != gameInfo.holdingsWidth
6080                                          )
6081             InitDrawingSizes(-2 ,0);
6082
6083     oldv = gameInfo.variant;
6084     if (redraw)
6085       DrawPosition(TRUE, boards[currentMove]);
6086 }
6087
6088 void
6089 SendBoard (ChessProgramState *cps, int moveNum)
6090 {
6091     char message[MSG_SIZ];
6092
6093     if (cps->useSetboard) {
6094       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6095       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6096       SendToProgram(message, cps);
6097       free(fen);
6098
6099     } else {
6100       ChessSquare *bp;
6101       int i, j, left=0, right=BOARD_WIDTH;
6102       /* Kludge to set black to move, avoiding the troublesome and now
6103        * deprecated "black" command.
6104        */
6105       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6106         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6107
6108       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6109
6110       SendToProgram("edit\n", cps);
6111       SendToProgram("#\n", cps);
6112       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6113         bp = &boards[moveNum][i][left];
6114         for (j = left; j < right; j++, bp++) {
6115           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6116           if ((int) *bp < (int) BlackPawn) {
6117             if(j == BOARD_RGHT+1)
6118                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6119             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6120             if(message[0] == '+' || message[0] == '~') {
6121               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6122                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6123                         AAA + j, ONE + i);
6124             }
6125             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6126                 message[1] = BOARD_RGHT   - 1 - j + '1';
6127                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6128             }
6129             SendToProgram(message, cps);
6130           }
6131         }
6132       }
6133
6134       SendToProgram("c\n", cps);
6135       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6136         bp = &boards[moveNum][i][left];
6137         for (j = left; j < right; j++, bp++) {
6138           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6139           if (((int) *bp != (int) EmptySquare)
6140               && ((int) *bp >= (int) BlackPawn)) {
6141             if(j == BOARD_LEFT-2)
6142                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6143             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6144                     AAA + j, ONE + i);
6145             if(message[0] == '+' || message[0] == '~') {
6146               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6147                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6148                         AAA + j, ONE + i);
6149             }
6150             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6151                 message[1] = BOARD_RGHT   - 1 - j + '1';
6152                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6153             }
6154             SendToProgram(message, cps);
6155           }
6156         }
6157       }
6158
6159       SendToProgram(".\n", cps);
6160     }
6161     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6162 }
6163
6164 char exclusionHeader[MSG_SIZ];
6165 int exCnt, excludePtr;
6166 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6167 static Exclusion excluTab[200];
6168 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6169
6170 static void
6171 WriteMap (int s)
6172 {
6173     int j;
6174     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6175     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6176 }
6177
6178 static void
6179 ClearMap ()
6180 {
6181     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6182     excludePtr = 24; exCnt = 0;
6183     WriteMap(0);
6184 }
6185
6186 static void
6187 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6188 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6189     char buf[2*MOVE_LEN], *p;
6190     Exclusion *e = excluTab;
6191     int i;
6192     for(i=0; i<exCnt; i++)
6193         if(e[i].ff == fromX && e[i].fr == fromY &&
6194            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6195     if(i == exCnt) { // was not in exclude list; add it
6196         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6197         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6198             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6199             return; // abort
6200         }
6201         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6202         excludePtr++; e[i].mark = excludePtr++;
6203         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6204         exCnt++;
6205     }
6206     exclusionHeader[e[i].mark] = state;
6207 }
6208
6209 static int
6210 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6211 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6212     char buf[MSG_SIZ];
6213     int j, k;
6214     ChessMove moveType;
6215     if((signed char)promoChar == -1) { // kludge to indicate best move
6216         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6217             return 1; // if unparsable, abort
6218     }
6219     // update exclusion map (resolving toggle by consulting existing state)
6220     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6221     j = k%8; k >>= 3;
6222     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6223     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6224          excludeMap[k] |=   1<<j;
6225     else excludeMap[k] &= ~(1<<j);
6226     // update header
6227     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6228     // inform engine
6229     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6230     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6231     SendToBoth(buf);
6232     return (state == '+');
6233 }
6234
6235 static void
6236 ExcludeClick (int index)
6237 {
6238     int i, j;
6239     Exclusion *e = excluTab;
6240     if(index < 25) { // none, best or tail clicked
6241         if(index < 13) { // none: include all
6242             WriteMap(0); // clear map
6243             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6244             SendToBoth("include all\n"); // and inform engine
6245         } else if(index > 18) { // tail
6246             if(exclusionHeader[19] == '-') { // tail was excluded
6247                 SendToBoth("include all\n");
6248                 WriteMap(0); // clear map completely
6249                 // now re-exclude selected moves
6250                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6251                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6252             } else { // tail was included or in mixed state
6253                 SendToBoth("exclude all\n");
6254                 WriteMap(0xFF); // fill map completely
6255                 // now re-include selected moves
6256                 j = 0; // count them
6257                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6258                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6259                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6260             }
6261         } else { // best
6262             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6263         }
6264     } else {
6265         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6266             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6267             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6268             break;
6269         }
6270     }
6271 }
6272
6273 ChessSquare
6274 DefaultPromoChoice (int white)
6275 {
6276     ChessSquare result;
6277     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6278         result = WhiteFerz; // no choice
6279     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6280         result= WhiteKing; // in Suicide Q is the last thing we want
6281     else if(gameInfo.variant == VariantSpartan)
6282         result = white ? WhiteQueen : WhiteAngel;
6283     else result = WhiteQueen;
6284     if(!white) result = WHITE_TO_BLACK result;
6285     return result;
6286 }
6287
6288 static int autoQueen; // [HGM] oneclick
6289
6290 int
6291 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6292 {
6293     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6294     /* [HGM] add Shogi promotions */
6295     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6296     ChessSquare piece;
6297     ChessMove moveType;
6298     Boolean premove;
6299
6300     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6301     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6302
6303     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6304       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6305         return FALSE;
6306
6307     piece = boards[currentMove][fromY][fromX];
6308     if(gameInfo.variant == VariantShogi) {
6309         promotionZoneSize = BOARD_HEIGHT/3;
6310         highestPromotingPiece = (int)WhiteFerz;
6311     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6312         promotionZoneSize = 3;
6313     }
6314
6315     // Treat Lance as Pawn when it is not representing Amazon
6316     if(gameInfo.variant != VariantSuper) {
6317         if(piece == WhiteLance) piece = WhitePawn; else
6318         if(piece == BlackLance) piece = BlackPawn;
6319     }
6320
6321     // next weed out all moves that do not touch the promotion zone at all
6322     if((int)piece >= BlackPawn) {
6323         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6324              return FALSE;
6325         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6326     } else {
6327         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6328            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6329     }
6330
6331     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6332
6333     // weed out mandatory Shogi promotions
6334     if(gameInfo.variant == VariantShogi) {
6335         if(piece >= BlackPawn) {
6336             if(toY == 0 && piece == BlackPawn ||
6337                toY == 0 && piece == BlackQueen ||
6338                toY <= 1 && piece == BlackKnight) {
6339                 *promoChoice = '+';
6340                 return FALSE;
6341             }
6342         } else {
6343             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6344                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6345                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6346                 *promoChoice = '+';
6347                 return FALSE;
6348             }
6349         }
6350     }
6351
6352     // weed out obviously illegal Pawn moves
6353     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6354         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6355         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6356         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6357         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6358         // note we are not allowed to test for valid (non-)capture, due to premove
6359     }
6360
6361     // we either have a choice what to promote to, or (in Shogi) whether to promote
6362     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6363         *promoChoice = PieceToChar(BlackFerz);  // no choice
6364         return FALSE;
6365     }
6366     // no sense asking what we must promote to if it is going to explode...
6367     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6368         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6369         return FALSE;
6370     }
6371     // give caller the default choice even if we will not make it
6372     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6373     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6374     if(        sweepSelect && gameInfo.variant != VariantGreat
6375                            && gameInfo.variant != VariantGrand
6376                            && gameInfo.variant != VariantSuper) return FALSE;
6377     if(autoQueen) return FALSE; // predetermined
6378
6379     // suppress promotion popup on illegal moves that are not premoves
6380     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6381               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6382     if(appData.testLegality && !premove) {
6383         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6384                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6385         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6386             return FALSE;
6387     }
6388
6389     return TRUE;
6390 }
6391
6392 int
6393 InPalace (int row, int column)
6394 {   /* [HGM] for Xiangqi */
6395     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6396          column < (BOARD_WIDTH + 4)/2 &&
6397          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6398     return FALSE;
6399 }
6400
6401 int
6402 PieceForSquare (int x, int y)
6403 {
6404   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6405      return -1;
6406   else
6407      return boards[currentMove][y][x];
6408 }
6409
6410 int
6411 OKToStartUserMove (int x, int y)
6412 {
6413     ChessSquare from_piece;
6414     int white_piece;
6415
6416     if (matchMode) return FALSE;
6417     if (gameMode == EditPosition) return TRUE;
6418
6419     if (x >= 0 && y >= 0)
6420       from_piece = boards[currentMove][y][x];
6421     else
6422       from_piece = EmptySquare;
6423
6424     if (from_piece == EmptySquare) return FALSE;
6425
6426     white_piece = (int)from_piece >= (int)WhitePawn &&
6427       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6428
6429     switch (gameMode) {
6430       case AnalyzeFile:
6431       case TwoMachinesPlay:
6432       case EndOfGame:
6433         return FALSE;
6434
6435       case IcsObserving:
6436       case IcsIdle:
6437         return FALSE;
6438
6439       case MachinePlaysWhite:
6440       case IcsPlayingBlack:
6441         if (appData.zippyPlay) return FALSE;
6442         if (white_piece) {
6443             DisplayMoveError(_("You are playing Black"));
6444             return FALSE;
6445         }
6446         break;
6447
6448       case MachinePlaysBlack:
6449       case IcsPlayingWhite:
6450         if (appData.zippyPlay) return FALSE;
6451         if (!white_piece) {
6452             DisplayMoveError(_("You are playing White"));
6453             return FALSE;
6454         }
6455         break;
6456
6457       case PlayFromGameFile:
6458             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6459       case EditGame:
6460         if (!white_piece && WhiteOnMove(currentMove)) {
6461             DisplayMoveError(_("It is White's turn"));
6462             return FALSE;
6463         }
6464         if (white_piece && !WhiteOnMove(currentMove)) {
6465             DisplayMoveError(_("It is Black's turn"));
6466             return FALSE;
6467         }
6468         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6469             /* Editing correspondence game history */
6470             /* Could disallow this or prompt for confirmation */
6471             cmailOldMove = -1;
6472         }
6473         break;
6474
6475       case BeginningOfGame:
6476         if (appData.icsActive) return FALSE;
6477         if (!appData.noChessProgram) {
6478             if (!white_piece) {
6479                 DisplayMoveError(_("You are playing White"));
6480                 return FALSE;
6481             }
6482         }
6483         break;
6484
6485       case Training:
6486         if (!white_piece && WhiteOnMove(currentMove)) {
6487             DisplayMoveError(_("It is White's turn"));
6488             return FALSE;
6489         }
6490         if (white_piece && !WhiteOnMove(currentMove)) {
6491             DisplayMoveError(_("It is Black's turn"));
6492             return FALSE;
6493         }
6494         break;
6495
6496       default:
6497       case IcsExamining:
6498         break;
6499     }
6500     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6501         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6502         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6503         && gameMode != AnalyzeFile && gameMode != Training) {
6504         DisplayMoveError(_("Displayed position is not current"));
6505         return FALSE;
6506     }
6507     return TRUE;
6508 }
6509
6510 Boolean
6511 OnlyMove (int *x, int *y, Boolean captures) 
6512 {
6513     DisambiguateClosure cl;
6514     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6515     switch(gameMode) {
6516       case MachinePlaysBlack:
6517       case IcsPlayingWhite:
6518       case BeginningOfGame:
6519         if(!WhiteOnMove(currentMove)) return FALSE;
6520         break;
6521       case MachinePlaysWhite:
6522       case IcsPlayingBlack:
6523         if(WhiteOnMove(currentMove)) return FALSE;
6524         break;
6525       case EditGame:
6526         break;
6527       default:
6528         return FALSE;
6529     }
6530     cl.pieceIn = EmptySquare;
6531     cl.rfIn = *y;
6532     cl.ffIn = *x;
6533     cl.rtIn = -1;
6534     cl.ftIn = -1;
6535     cl.promoCharIn = NULLCHAR;
6536     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6537     if( cl.kind == NormalMove ||
6538         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6539         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6540         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6541       fromX = cl.ff;
6542       fromY = cl.rf;
6543       *x = cl.ft;
6544       *y = cl.rt;
6545       return TRUE;
6546     }
6547     if(cl.kind != ImpossibleMove) return FALSE;
6548     cl.pieceIn = EmptySquare;
6549     cl.rfIn = -1;
6550     cl.ffIn = -1;
6551     cl.rtIn = *y;
6552     cl.ftIn = *x;
6553     cl.promoCharIn = NULLCHAR;
6554     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6555     if( cl.kind == NormalMove ||
6556         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6557         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6558         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6559       fromX = cl.ff;
6560       fromY = cl.rf;
6561       *x = cl.ft;
6562       *y = cl.rt;
6563       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6564       return TRUE;
6565     }
6566     return FALSE;
6567 }
6568
6569 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6570 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6571 int lastLoadGameUseList = FALSE;
6572 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6573 ChessMove lastLoadGameStart = EndOfFile;
6574 int doubleClick;
6575
6576 void
6577 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6578 {
6579     ChessMove moveType;
6580     ChessSquare pup;
6581     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6582
6583     /* Check if the user is playing in turn.  This is complicated because we
6584        let the user "pick up" a piece before it is his turn.  So the piece he
6585        tried to pick up may have been captured by the time he puts it down!
6586        Therefore we use the color the user is supposed to be playing in this
6587        test, not the color of the piece that is currently on the starting
6588        square---except in EditGame mode, where the user is playing both
6589        sides; fortunately there the capture race can't happen.  (It can
6590        now happen in IcsExamining mode, but that's just too bad.  The user
6591        will get a somewhat confusing message in that case.)
6592        */
6593
6594     switch (gameMode) {
6595       case AnalyzeFile:
6596       case TwoMachinesPlay:
6597       case EndOfGame:
6598       case IcsObserving:
6599       case IcsIdle:
6600         /* We switched into a game mode where moves are not accepted,
6601            perhaps while the mouse button was down. */
6602         return;
6603
6604       case MachinePlaysWhite:
6605         /* User is moving for Black */
6606         if (WhiteOnMove(currentMove)) {
6607             DisplayMoveError(_("It is White's turn"));
6608             return;
6609         }
6610         break;
6611
6612       case MachinePlaysBlack:
6613         /* User is moving for White */
6614         if (!WhiteOnMove(currentMove)) {
6615             DisplayMoveError(_("It is Black's turn"));
6616             return;
6617         }
6618         break;
6619
6620       case PlayFromGameFile:
6621             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6622       case EditGame:
6623       case IcsExamining:
6624       case BeginningOfGame:
6625       case AnalyzeMode:
6626       case Training:
6627         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6628         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6629             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6630             /* User is moving for Black */
6631             if (WhiteOnMove(currentMove)) {
6632                 DisplayMoveError(_("It is White's turn"));
6633                 return;
6634             }
6635         } else {
6636             /* User is moving for White */
6637             if (!WhiteOnMove(currentMove)) {
6638                 DisplayMoveError(_("It is Black's turn"));
6639                 return;
6640             }
6641         }
6642         break;
6643
6644       case IcsPlayingBlack:
6645         /* User is moving for Black */
6646         if (WhiteOnMove(currentMove)) {
6647             if (!appData.premove) {
6648                 DisplayMoveError(_("It is White's turn"));
6649             } else if (toX >= 0 && toY >= 0) {
6650                 premoveToX = toX;
6651                 premoveToY = toY;
6652                 premoveFromX = fromX;
6653                 premoveFromY = fromY;
6654                 premovePromoChar = promoChar;
6655                 gotPremove = 1;
6656                 if (appData.debugMode)
6657                     fprintf(debugFP, "Got premove: fromX %d,"
6658                             "fromY %d, toX %d, toY %d\n",
6659                             fromX, fromY, toX, toY);
6660             }
6661             return;
6662         }
6663         break;
6664
6665       case IcsPlayingWhite:
6666         /* User is moving for White */
6667         if (!WhiteOnMove(currentMove)) {
6668             if (!appData.premove) {
6669                 DisplayMoveError(_("It is Black's turn"));
6670             } else if (toX >= 0 && toY >= 0) {
6671                 premoveToX = toX;
6672                 premoveToY = toY;
6673                 premoveFromX = fromX;
6674                 premoveFromY = fromY;
6675                 premovePromoChar = promoChar;
6676                 gotPremove = 1;
6677                 if (appData.debugMode)
6678                     fprintf(debugFP, "Got premove: fromX %d,"
6679                             "fromY %d, toX %d, toY %d\n",
6680                             fromX, fromY, toX, toY);
6681             }
6682             return;
6683         }
6684         break;
6685
6686       default:
6687         break;
6688
6689       case EditPosition:
6690         /* EditPosition, empty square, or different color piece;
6691            click-click move is possible */
6692         if (toX == -2 || toY == -2) {
6693             boards[0][fromY][fromX] = EmptySquare;
6694             DrawPosition(FALSE, boards[currentMove]);
6695             return;
6696         } else if (toX >= 0 && toY >= 0) {
6697             boards[0][toY][toX] = boards[0][fromY][fromX];
6698             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6699                 if(boards[0][fromY][0] != EmptySquare) {
6700                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6701                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6702                 }
6703             } else
6704             if(fromX == BOARD_RGHT+1) {
6705                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6706                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6707                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6708                 }
6709             } else
6710             boards[0][fromY][fromX] = gatingPiece;
6711             DrawPosition(FALSE, boards[currentMove]);
6712             return;
6713         }
6714         return;
6715     }
6716
6717     if(toX < 0 || toY < 0) return;
6718     pup = boards[currentMove][toY][toX];
6719
6720     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6721     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6722          if( pup != EmptySquare ) return;
6723          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6724            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6725                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6726            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6727            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6728            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6729            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6730          fromY = DROP_RANK;
6731     }
6732
6733     /* [HGM] always test for legality, to get promotion info */
6734     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6735                                          fromY, fromX, toY, toX, promoChar);
6736
6737     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6738
6739     /* [HGM] but possibly ignore an IllegalMove result */
6740     if (appData.testLegality) {
6741         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6742             DisplayMoveError(_("Illegal move"));
6743             return;
6744         }
6745     }
6746
6747     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6748         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6749              ClearPremoveHighlights(); // was included
6750         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6751         return;
6752     }
6753
6754     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6755 }
6756
6757 /* Common tail of UserMoveEvent and DropMenuEvent */
6758 int
6759 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6760 {
6761     char *bookHit = 0;
6762
6763     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6764         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6765         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6766         if(WhiteOnMove(currentMove)) {
6767             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6768         } else {
6769             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6770         }
6771     }
6772
6773     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6774        move type in caller when we know the move is a legal promotion */
6775     if(moveType == NormalMove && promoChar)
6776         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6777
6778     /* [HGM] <popupFix> The following if has been moved here from
6779        UserMoveEvent(). Because it seemed to belong here (why not allow
6780        piece drops in training games?), and because it can only be
6781        performed after it is known to what we promote. */
6782     if (gameMode == Training) {
6783       /* compare the move played on the board to the next move in the
6784        * game. If they match, display the move and the opponent's response.
6785        * If they don't match, display an error message.
6786        */
6787       int saveAnimate;
6788       Board testBoard;
6789       CopyBoard(testBoard, boards[currentMove]);
6790       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6791
6792       if (CompareBoards(testBoard, boards[currentMove+1])) {
6793         ForwardInner(currentMove+1);
6794
6795         /* Autoplay the opponent's response.
6796          * if appData.animate was TRUE when Training mode was entered,
6797          * the response will be animated.
6798          */
6799         saveAnimate = appData.animate;
6800         appData.animate = animateTraining;
6801         ForwardInner(currentMove+1);
6802         appData.animate = saveAnimate;
6803
6804         /* check for the end of the game */
6805         if (currentMove >= forwardMostMove) {
6806           gameMode = PlayFromGameFile;
6807           ModeHighlight();
6808           SetTrainingModeOff();
6809           DisplayInformation(_("End of game"));
6810         }
6811       } else {
6812         DisplayError(_("Incorrect move"), 0);
6813       }
6814       return 1;
6815     }
6816
6817   /* Ok, now we know that the move is good, so we can kill
6818      the previous line in Analysis Mode */
6819   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6820                                 && currentMove < forwardMostMove) {
6821     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6822     else forwardMostMove = currentMove;
6823   }
6824
6825   ClearMap();
6826
6827   /* If we need the chess program but it's dead, restart it */
6828   ResurrectChessProgram();
6829
6830   /* A user move restarts a paused game*/
6831   if (pausing)
6832     PauseEvent();
6833
6834   thinkOutput[0] = NULLCHAR;
6835
6836   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6837
6838   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6839     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6840     return 1;
6841   }
6842
6843   if (gameMode == BeginningOfGame) {
6844     if (appData.noChessProgram) {
6845       gameMode = EditGame;
6846       SetGameInfo();
6847     } else {
6848       char buf[MSG_SIZ];
6849       gameMode = MachinePlaysBlack;
6850       StartClocks();
6851       SetGameInfo();
6852       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6853       DisplayTitle(buf);
6854       if (first.sendName) {
6855         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6856         SendToProgram(buf, &first);
6857       }
6858       StartClocks();
6859     }
6860     ModeHighlight();
6861   }
6862
6863   /* Relay move to ICS or chess engine */
6864   if (appData.icsActive) {
6865     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6866         gameMode == IcsExamining) {
6867       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6868         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6869         SendToICS("draw ");
6870         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6871       }
6872       // also send plain move, in case ICS does not understand atomic claims
6873       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6874       ics_user_moved = 1;
6875     }
6876   } else {
6877     if (first.sendTime && (gameMode == BeginningOfGame ||
6878                            gameMode == MachinePlaysWhite ||
6879                            gameMode == MachinePlaysBlack)) {
6880       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6881     }
6882     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
6883          // [HGM] book: if program might be playing, let it use book
6884         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6885         first.maybeThinking = TRUE;
6886     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6887         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6888         SendBoard(&first, currentMove+1);
6889         if(second.analyzing) {
6890             if(!second.useSetboard) SendToProgram("undo\n", &second);
6891             SendBoard(&second, currentMove+1);
6892         }
6893     } else {
6894         SendMoveToProgram(forwardMostMove-1, &first);
6895         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
6896     }
6897     if (currentMove == cmailOldMove + 1) {
6898       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6899     }
6900   }
6901
6902   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6903
6904   switch (gameMode) {
6905   case EditGame:
6906     if(appData.testLegality)
6907     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6908     case MT_NONE:
6909     case MT_CHECK:
6910       break;
6911     case MT_CHECKMATE:
6912     case MT_STAINMATE:
6913       if (WhiteOnMove(currentMove)) {
6914         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6915       } else {
6916         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6917       }
6918       break;
6919     case MT_STALEMATE:
6920       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6921       break;
6922     }
6923     break;
6924
6925   case MachinePlaysBlack:
6926   case MachinePlaysWhite:
6927     /* disable certain menu options while machine is thinking */
6928     SetMachineThinkingEnables();
6929     break;
6930
6931   default:
6932     break;
6933   }
6934
6935   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6936   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6937
6938   if(bookHit) { // [HGM] book: simulate book reply
6939         static char bookMove[MSG_SIZ]; // a bit generous?
6940
6941         programStats.nodes = programStats.depth = programStats.time =
6942         programStats.score = programStats.got_only_move = 0;
6943         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6944
6945         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6946         strcat(bookMove, bookHit);
6947         HandleMachineMove(bookMove, &first);
6948   }
6949   return 1;
6950 }
6951
6952 void
6953 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6954 {
6955     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6956     Markers *m = (Markers *) closure;
6957     if(rf == fromY && ff == fromX)
6958         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6959                          || kind == WhiteCapturesEnPassant
6960                          || kind == BlackCapturesEnPassant);
6961     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6962 }
6963
6964 void
6965 MarkTargetSquares (int clear)
6966 {
6967   int x, y;
6968   if(clear) // no reason to ever suppress clearing
6969     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6970   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6971      !appData.testLegality || gameMode == EditPosition) return;
6972   if(!clear) {
6973     int capt = 0;
6974     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6975     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6976       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6977       if(capt)
6978       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6979     }
6980   }
6981   DrawPosition(FALSE, NULL);
6982 }
6983
6984 int
6985 Explode (Board board, int fromX, int fromY, int toX, int toY)
6986 {
6987     if(gameInfo.variant == VariantAtomic &&
6988        (board[toY][toX] != EmptySquare ||                     // capture?
6989         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6990                          board[fromY][fromX] == BlackPawn   )
6991       )) {
6992         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6993         return TRUE;
6994     }
6995     return FALSE;
6996 }
6997
6998 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6999
7000 int
7001 CanPromote (ChessSquare piece, int y)
7002 {
7003         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7004         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7005         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
7006            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7007            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7008                                                   gameInfo.variant == VariantMakruk) return FALSE;
7009         return (piece == BlackPawn && y == 1 ||
7010                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7011                 piece == BlackLance && y == 1 ||
7012                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7013 }
7014
7015 void
7016 LeftClick (ClickType clickType, int xPix, int yPix)
7017 {
7018     int x, y;
7019     Boolean saveAnimate;
7020     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7021     char promoChoice = NULLCHAR;
7022     ChessSquare piece;
7023     static TimeMark lastClickTime, prevClickTime;
7024
7025     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7026
7027     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7028
7029     if (clickType == Press) ErrorPopDown();
7030
7031     x = EventToSquare(xPix, BOARD_WIDTH);
7032     y = EventToSquare(yPix, BOARD_HEIGHT);
7033     if (!flipView && y >= 0) {
7034         y = BOARD_HEIGHT - 1 - y;
7035     }
7036     if (flipView && x >= 0) {
7037         x = BOARD_WIDTH - 1 - x;
7038     }
7039
7040     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7041         defaultPromoChoice = promoSweep;
7042         promoSweep = EmptySquare;   // terminate sweep
7043         promoDefaultAltered = TRUE;
7044         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7045     }
7046
7047     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7048         if(clickType == Release) return; // ignore upclick of click-click destination
7049         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7050         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7051         if(gameInfo.holdingsWidth &&
7052                 (WhiteOnMove(currentMove)
7053                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7054                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7055             // click in right holdings, for determining promotion piece
7056             ChessSquare p = boards[currentMove][y][x];
7057             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7058             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7059             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7060                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7061                 fromX = fromY = -1;
7062                 return;
7063             }
7064         }
7065         DrawPosition(FALSE, boards[currentMove]);
7066         return;
7067     }
7068
7069     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7070     if(clickType == Press
7071             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7072               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7073               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7074         return;
7075
7076     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7077         // could be static click on premove from-square: abort premove
7078         gotPremove = 0;
7079         ClearPremoveHighlights();
7080     }
7081
7082     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7083         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7084
7085     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7086         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7087                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7088         defaultPromoChoice = DefaultPromoChoice(side);
7089     }
7090
7091     autoQueen = appData.alwaysPromoteToQueen;
7092
7093     if (fromX == -1) {
7094       int originalY = y;
7095       gatingPiece = EmptySquare;
7096       if (clickType != Press) {
7097         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7098             DragPieceEnd(xPix, yPix); dragging = 0;
7099             DrawPosition(FALSE, NULL);
7100         }
7101         return;
7102       }
7103       doubleClick = FALSE;
7104       fromX = x; fromY = y; toX = toY = -1;
7105       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7106          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7107          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7108             /* First square */
7109             if (OKToStartUserMove(fromX, fromY)) {
7110                 second = 0;
7111                 MarkTargetSquares(0);
7112                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7113                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7114                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7115                     promoSweep = defaultPromoChoice;
7116                     selectFlag = 0; lastX = xPix; lastY = yPix;
7117                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7118                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7119                 }
7120                 if (appData.highlightDragging) {
7121                     SetHighlights(fromX, fromY, -1, -1);
7122                 } else {
7123                     ClearHighlights();
7124                 }
7125             } else fromX = fromY = -1;
7126             return;
7127         }
7128     }
7129
7130     /* fromX != -1 */
7131     if (clickType == Press && gameMode != EditPosition) {
7132         ChessSquare fromP;
7133         ChessSquare toP;
7134         int frc;
7135
7136         // ignore off-board to clicks
7137         if(y < 0 || x < 0) return;
7138
7139         /* Check if clicking again on the same color piece */
7140         fromP = boards[currentMove][fromY][fromX];
7141         toP = boards[currentMove][y][x];
7142         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7143         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7144              WhitePawn <= toP && toP <= WhiteKing &&
7145              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7146              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7147             (BlackPawn <= fromP && fromP <= BlackKing &&
7148              BlackPawn <= toP && toP <= BlackKing &&
7149              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7150              !(fromP == BlackKing && toP == BlackRook && frc))) {
7151             /* Clicked again on same color piece -- changed his mind */
7152             second = (x == fromX && y == fromY);
7153             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7154                 second = FALSE; // first double-click rather than scond click
7155                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7156             }
7157             promoDefaultAltered = FALSE;
7158             MarkTargetSquares(1);
7159            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7160             if (appData.highlightDragging) {
7161                 SetHighlights(x, y, -1, -1);
7162             } else {
7163                 ClearHighlights();
7164             }
7165             if (OKToStartUserMove(x, y)) {
7166                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7167                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7168                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7169                  gatingPiece = boards[currentMove][fromY][fromX];
7170                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7171                 fromX = x;
7172                 fromY = y; dragging = 1;
7173                 MarkTargetSquares(0);
7174                 DragPieceBegin(xPix, yPix, FALSE);
7175                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7176                     promoSweep = defaultPromoChoice;
7177                     selectFlag = 0; lastX = xPix; lastY = yPix;
7178                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7179                 }
7180             }
7181            }
7182            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7183            second = FALSE; 
7184         }
7185         // ignore clicks on holdings
7186         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7187     }
7188
7189     if (clickType == Release && x == fromX && y == fromY) {
7190         DragPieceEnd(xPix, yPix); dragging = 0;
7191         if(clearFlag) {
7192             // a deferred attempt to click-click move an empty square on top of a piece
7193             boards[currentMove][y][x] = EmptySquare;
7194             ClearHighlights();
7195             DrawPosition(FALSE, boards[currentMove]);
7196             fromX = fromY = -1; clearFlag = 0;
7197             return;
7198         }
7199         if (appData.animateDragging) {
7200             /* Undo animation damage if any */
7201             DrawPosition(FALSE, NULL);
7202         }
7203         if (second || sweepSelecting) {
7204             /* Second up/down in same square; just abort move */
7205             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7206             second = sweepSelecting = 0;
7207             fromX = fromY = -1;
7208             gatingPiece = EmptySquare;
7209             ClearHighlights();
7210             gotPremove = 0;
7211             ClearPremoveHighlights();
7212         } else {
7213             /* First upclick in same square; start click-click mode */
7214             SetHighlights(x, y, -1, -1);
7215         }
7216         return;
7217     }
7218
7219     clearFlag = 0;
7220
7221     /* we now have a different from- and (possibly off-board) to-square */
7222     /* Completed move */
7223     if(!sweepSelecting) {
7224         toX = x;
7225         toY = y;
7226     } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
7227
7228     saveAnimate = appData.animate;
7229     if (clickType == Press) {
7230         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7231             // must be Edit Position mode with empty-square selected
7232             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7233             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7234             return;
7235         }
7236         if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7237           if(appData.sweepSelect) {
7238             ChessSquare piece = boards[currentMove][fromY][fromX];
7239             promoSweep = defaultPromoChoice;
7240             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7241             selectFlag = 0; lastX = xPix; lastY = yPix;
7242             Sweep(0); // Pawn that is going to promote: preview promotion piece
7243             sweepSelecting = 1;
7244             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7245             MarkTargetSquares(1);
7246           }
7247           return; // promo popup appears on up-click
7248         }
7249         /* Finish clickclick move */
7250         if (appData.animate || appData.highlightLastMove) {
7251             SetHighlights(fromX, fromY, toX, toY);
7252         } else {
7253             ClearHighlights();
7254         }
7255     } else {
7256         /* Finish drag move */
7257         if (appData.highlightLastMove) {
7258             SetHighlights(fromX, fromY, toX, toY);
7259         } else {
7260             ClearHighlights();
7261         }
7262         DragPieceEnd(xPix, yPix); dragging = 0;
7263         /* Don't animate move and drag both */
7264         appData.animate = FALSE;
7265     }
7266     MarkTargetSquares(1);
7267
7268     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7269     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7270         ChessSquare piece = boards[currentMove][fromY][fromX];
7271         if(gameMode == EditPosition && piece != EmptySquare &&
7272            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7273             int n;
7274
7275             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7276                 n = PieceToNumber(piece - (int)BlackPawn);
7277                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7278                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7279                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7280             } else
7281             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7282                 n = PieceToNumber(piece);
7283                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7284                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7285                 boards[currentMove][n][BOARD_WIDTH-2]++;
7286             }
7287             boards[currentMove][fromY][fromX] = EmptySquare;
7288         }
7289         ClearHighlights();
7290         fromX = fromY = -1;
7291         DrawPosition(TRUE, boards[currentMove]);
7292         return;
7293     }
7294
7295     // off-board moves should not be highlighted
7296     if(x < 0 || y < 0) ClearHighlights();
7297
7298     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7299
7300     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7301         SetHighlights(fromX, fromY, toX, toY);
7302         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7303             // [HGM] super: promotion to captured piece selected from holdings
7304             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7305             promotionChoice = TRUE;
7306             // kludge follows to temporarily execute move on display, without promoting yet
7307             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7308             boards[currentMove][toY][toX] = p;
7309             DrawPosition(FALSE, boards[currentMove]);
7310             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7311             boards[currentMove][toY][toX] = q;
7312             DisplayMessage("Click in holdings to choose piece", "");
7313             return;
7314         }
7315         PromotionPopUp();
7316     } else {
7317         int oldMove = currentMove;
7318         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7319         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7320         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7321         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7322            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7323             DrawPosition(TRUE, boards[currentMove]);
7324         fromX = fromY = -1;
7325     }
7326     appData.animate = saveAnimate;
7327     if (appData.animate || appData.animateDragging) {
7328         /* Undo animation damage if needed */
7329         DrawPosition(FALSE, NULL);
7330     }
7331 }
7332
7333 int
7334 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7335 {   // front-end-free part taken out of PieceMenuPopup
7336     int whichMenu; int xSqr, ySqr;
7337
7338     if(seekGraphUp) { // [HGM] seekgraph
7339         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7340         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7341         return -2;
7342     }
7343
7344     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7345          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7346         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7347         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7348         if(action == Press)   {
7349             originalFlip = flipView;
7350             flipView = !flipView; // temporarily flip board to see game from partners perspective
7351             DrawPosition(TRUE, partnerBoard);
7352             DisplayMessage(partnerStatus, "");
7353             partnerUp = TRUE;
7354         } else if(action == Release) {
7355             flipView = originalFlip;
7356             DrawPosition(TRUE, boards[currentMove]);
7357             partnerUp = FALSE;
7358         }
7359         return -2;
7360     }
7361
7362     xSqr = EventToSquare(x, BOARD_WIDTH);
7363     ySqr = EventToSquare(y, BOARD_HEIGHT);
7364     if (action == Release) {
7365         if(pieceSweep != EmptySquare) {
7366             EditPositionMenuEvent(pieceSweep, toX, toY);
7367             pieceSweep = EmptySquare;
7368         } else UnLoadPV(); // [HGM] pv
7369     }
7370     if (action != Press) return -2; // return code to be ignored
7371     switch (gameMode) {
7372       case IcsExamining:
7373         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7374       case EditPosition:
7375         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7376         if (xSqr < 0 || ySqr < 0) return -1;
7377         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7378         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7379         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7380         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7381         NextPiece(0);
7382         return 2; // grab
7383       case IcsObserving:
7384         if(!appData.icsEngineAnalyze) return -1;
7385       case IcsPlayingWhite:
7386       case IcsPlayingBlack:
7387         if(!appData.zippyPlay) goto noZip;
7388       case AnalyzeMode:
7389       case AnalyzeFile:
7390       case MachinePlaysWhite:
7391       case MachinePlaysBlack:
7392       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7393         if (!appData.dropMenu) {
7394           LoadPV(x, y);
7395           return 2; // flag front-end to grab mouse events
7396         }
7397         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7398            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7399       case EditGame:
7400       noZip:
7401         if (xSqr < 0 || ySqr < 0) return -1;
7402         if (!appData.dropMenu || appData.testLegality &&
7403             gameInfo.variant != VariantBughouse &&
7404             gameInfo.variant != VariantCrazyhouse) return -1;
7405         whichMenu = 1; // drop menu
7406         break;
7407       default:
7408         return -1;
7409     }
7410
7411     if (((*fromX = xSqr) < 0) ||
7412         ((*fromY = ySqr) < 0)) {
7413         *fromX = *fromY = -1;
7414         return -1;
7415     }
7416     if (flipView)
7417       *fromX = BOARD_WIDTH - 1 - *fromX;
7418     else
7419       *fromY = BOARD_HEIGHT - 1 - *fromY;
7420
7421     return whichMenu;
7422 }
7423
7424 void
7425 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7426 {
7427 //    char * hint = lastHint;
7428     FrontEndProgramStats stats;
7429
7430     stats.which = cps == &first ? 0 : 1;
7431     stats.depth = cpstats->depth;
7432     stats.nodes = cpstats->nodes;
7433     stats.score = cpstats->score;
7434     stats.time = cpstats->time;
7435     stats.pv = cpstats->movelist;
7436     stats.hint = lastHint;
7437     stats.an_move_index = 0;
7438     stats.an_move_count = 0;
7439
7440     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7441         stats.hint = cpstats->move_name;
7442         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7443         stats.an_move_count = cpstats->nr_moves;
7444     }
7445
7446     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
7447
7448     SetProgramStats( &stats );
7449 }
7450
7451 void
7452 ClearEngineOutputPane (int which)
7453 {
7454     static FrontEndProgramStats dummyStats;
7455     dummyStats.which = which;
7456     dummyStats.pv = "#";
7457     SetProgramStats( &dummyStats );
7458 }
7459
7460 #define MAXPLAYERS 500
7461
7462 char *
7463 TourneyStandings (int display)
7464 {
7465     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7466     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7467     char result, *p, *names[MAXPLAYERS];
7468
7469     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7470         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7471     names[0] = p = strdup(appData.participants);
7472     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7473
7474     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7475
7476     while(result = appData.results[nr]) {
7477         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7478         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7479         wScore = bScore = 0;
7480         switch(result) {
7481           case '+': wScore = 2; break;
7482           case '-': bScore = 2; break;
7483           case '=': wScore = bScore = 1; break;
7484           case ' ':
7485           case '*': return strdup("busy"); // tourney not finished
7486         }
7487         score[w] += wScore;
7488         score[b] += bScore;
7489         games[w]++;
7490         games[b]++;
7491         nr++;
7492     }
7493     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7494     for(w=0; w<nPlayers; w++) {
7495         bScore = -1;
7496         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7497         ranking[w] = b; points[w] = bScore; score[b] = -2;
7498     }
7499     p = malloc(nPlayers*34+1);
7500     for(w=0; w<nPlayers && w<display; w++)
7501         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7502     free(names[0]);
7503     return p;
7504 }
7505
7506 void
7507 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7508 {       // count all piece types
7509         int p, f, r;
7510         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7511         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7512         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7513                 p = board[r][f];
7514                 pCnt[p]++;
7515                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7516                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7517                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7518                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7519                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7520                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7521         }
7522 }
7523
7524 int
7525 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7526 {
7527         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7528         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7529
7530         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7531         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7532         if(myPawns == 2 && nMine == 3) // KPP
7533             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7534         if(myPawns == 1 && nMine == 2) // KP
7535             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7536         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7537             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7538         if(myPawns) return FALSE;
7539         if(pCnt[WhiteRook+side])
7540             return pCnt[BlackRook-side] ||
7541                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7542                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7543                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7544         if(pCnt[WhiteCannon+side]) {
7545             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7546             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7547         }
7548         if(pCnt[WhiteKnight+side])
7549             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7550         return FALSE;
7551 }
7552
7553 int
7554 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7555 {
7556         VariantClass v = gameInfo.variant;
7557
7558         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7559         if(v == VariantShatranj) return TRUE; // always winnable through baring
7560         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7561         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7562
7563         if(v == VariantXiangqi) {
7564                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7565
7566                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7567                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7568                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7569                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7570                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7571                 if(stale) // we have at least one last-rank P plus perhaps C
7572                     return majors // KPKX
7573                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7574                 else // KCA*E*
7575                     return pCnt[WhiteFerz+side] // KCAK
7576                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7577                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7578                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7579
7580         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7581                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7582
7583                 if(nMine == 1) return FALSE; // bare King
7584                 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
7585                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7586                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7587                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7588                 if(pCnt[WhiteKnight+side])
7589                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7590                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7591                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7592                 if(nBishops)
7593                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7594                 if(pCnt[WhiteAlfil+side])
7595                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7596                 if(pCnt[WhiteWazir+side])
7597                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7598         }
7599
7600         return TRUE;
7601 }
7602
7603 int
7604 CompareWithRights (Board b1, Board b2)
7605 {
7606     int rights = 0;
7607     if(!CompareBoards(b1, b2)) return FALSE;
7608     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7609     /* compare castling rights */
7610     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7611            rights++; /* King lost rights, while rook still had them */
7612     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7613         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7614            rights++; /* but at least one rook lost them */
7615     }
7616     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7617            rights++;
7618     if( b1[CASTLING][5] != NoRights ) {
7619         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7620            rights++;
7621     }
7622     return rights == 0;
7623 }
7624
7625 int
7626 Adjudicate (ChessProgramState *cps)
7627 {       // [HGM] some adjudications useful with buggy engines
7628         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7629         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7630         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7631         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7632         int k, count = 0; static int bare = 1;
7633         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7634         Boolean canAdjudicate = !appData.icsActive;
7635
7636         // most tests only when we understand the game, i.e. legality-checking on
7637             if( appData.testLegality )
7638             {   /* [HGM] Some more adjudications for obstinate engines */
7639                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7640                 static int moveCount = 6;
7641                 ChessMove result;
7642                 char *reason = NULL;
7643
7644                 /* Count what is on board. */
7645                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7646
7647                 /* Some material-based adjudications that have to be made before stalemate test */
7648                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7649                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7650                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7651                      if(canAdjudicate && appData.checkMates) {
7652                          if(engineOpponent)
7653                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7654                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7655                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7656                          return 1;
7657                      }
7658                 }
7659
7660                 /* Bare King in Shatranj (loses) or Losers (wins) */
7661                 if( nrW == 1 || nrB == 1) {
7662                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7663                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7664                      if(canAdjudicate && appData.checkMates) {
7665                          if(engineOpponent)
7666                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7667                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7668                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7669                          return 1;
7670                      }
7671                   } else
7672                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7673                   {    /* bare King */
7674                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7675                         if(canAdjudicate && appData.checkMates) {
7676                             /* but only adjudicate if adjudication enabled */
7677                             if(engineOpponent)
7678                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7679                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7680                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7681                             return 1;
7682                         }
7683                   }
7684                 } else bare = 1;
7685
7686
7687             // don't wait for engine to announce game end if we can judge ourselves
7688             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7689               case MT_CHECK:
7690                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7691                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7692                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7693                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7694                             checkCnt++;
7695                         if(checkCnt >= 2) {
7696                             reason = "Xboard adjudication: 3rd check";
7697                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7698                             break;
7699                         }
7700                     }
7701                 }
7702               case MT_NONE:
7703               default:
7704                 break;
7705               case MT_STALEMATE:
7706               case MT_STAINMATE:
7707                 reason = "Xboard adjudication: Stalemate";
7708                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7709                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7710                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7711                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7712                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7713                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7714                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7715                                                                         EP_CHECKMATE : EP_WINS);
7716                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7717                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7718                 }
7719                 break;
7720               case MT_CHECKMATE:
7721                 reason = "Xboard adjudication: Checkmate";
7722                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7723                 break;
7724             }
7725
7726                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7727                     case EP_STALEMATE:
7728                         result = GameIsDrawn; break;
7729                     case EP_CHECKMATE:
7730                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7731                     case EP_WINS:
7732                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7733                     default:
7734                         result = EndOfFile;
7735                 }
7736                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7737                     if(engineOpponent)
7738                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7739                     GameEnds( result, reason, GE_XBOARD );
7740                     return 1;
7741                 }
7742
7743                 /* Next absolutely insufficient mating material. */
7744                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7745                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7746                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7747
7748                      /* always flag draws, for judging claims */
7749                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7750
7751                      if(canAdjudicate && appData.materialDraws) {
7752                          /* but only adjudicate them if adjudication enabled */
7753                          if(engineOpponent) {
7754                            SendToProgram("force\n", engineOpponent); // suppress reply
7755                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7756                          }
7757                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7758                          return 1;
7759                      }
7760                 }
7761
7762                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7763                 if(gameInfo.variant == VariantXiangqi ?
7764                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7765                  : nrW + nrB == 4 &&
7766                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7767                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7768                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7769                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7770                    ) ) {
7771                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7772                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7773                           if(engineOpponent) {
7774                             SendToProgram("force\n", engineOpponent); // suppress reply
7775                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7776                           }
7777                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7778                           return 1;
7779                      }
7780                 } else moveCount = 6;
7781             }
7782
7783         // Repetition draws and 50-move rule can be applied independently of legality testing
7784
7785                 /* Check for rep-draws */
7786                 count = 0;
7787                 for(k = forwardMostMove-2;
7788                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7789                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7790                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7791                     k-=2)
7792                 {   int rights=0;
7793                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7794                         /* compare castling rights */
7795                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7796                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7797                                 rights++; /* King lost rights, while rook still had them */
7798                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7799                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7800                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7801                                    rights++; /* but at least one rook lost them */
7802                         }
7803                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7804                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7805                                 rights++;
7806                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7807                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7808                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7809                                    rights++;
7810                         }
7811                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7812                             && appData.drawRepeats > 1) {
7813                              /* adjudicate after user-specified nr of repeats */
7814                              int result = GameIsDrawn;
7815                              char *details = "XBoard adjudication: repetition draw";
7816                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7817                                 // [HGM] xiangqi: check for forbidden perpetuals
7818                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7819                                 for(m=forwardMostMove; m>k; m-=2) {
7820                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7821                                         ourPerpetual = 0; // the current mover did not always check
7822                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7823                                         hisPerpetual = 0; // the opponent did not always check
7824                                 }
7825                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7826                                                                         ourPerpetual, hisPerpetual);
7827                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7828                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7829                                     details = "Xboard adjudication: perpetual checking";
7830                                 } else
7831                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7832                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7833                                 } else
7834                                 // Now check for perpetual chases
7835                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7836                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7837                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7838                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7839                                         static char resdet[MSG_SIZ];
7840                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7841                                         details = resdet;
7842                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7843                                     } else
7844                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7845                                         break; // Abort repetition-checking loop.
7846                                 }
7847                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7848                              }
7849                              if(engineOpponent) {
7850                                SendToProgram("force\n", engineOpponent); // suppress reply
7851                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7852                              }
7853                              GameEnds( result, details, GE_XBOARD );
7854                              return 1;
7855                         }
7856                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7857                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7858                     }
7859                 }
7860
7861                 /* Now we test for 50-move draws. Determine ply count */
7862                 count = forwardMostMove;
7863                 /* look for last irreversble move */
7864                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7865                     count--;
7866                 /* if we hit starting position, add initial plies */
7867                 if( count == backwardMostMove )
7868                     count -= initialRulePlies;
7869                 count = forwardMostMove - count;
7870                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7871                         // adjust reversible move counter for checks in Xiangqi
7872                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7873                         if(i < backwardMostMove) i = backwardMostMove;
7874                         while(i <= forwardMostMove) {
7875                                 lastCheck = inCheck; // check evasion does not count
7876                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7877                                 if(inCheck || lastCheck) count--; // check does not count
7878                                 i++;
7879                         }
7880                 }
7881                 if( count >= 100)
7882                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7883                          /* this is used to judge if draw claims are legal */
7884                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7885                          if(engineOpponent) {
7886                            SendToProgram("force\n", engineOpponent); // suppress reply
7887                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7888                          }
7889                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7890                          return 1;
7891                 }
7892
7893                 /* if draw offer is pending, treat it as a draw claim
7894                  * when draw condition present, to allow engines a way to
7895                  * claim draws before making their move to avoid a race
7896                  * condition occurring after their move
7897                  */
7898                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7899                          char *p = NULL;
7900                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7901                              p = "Draw claim: 50-move rule";
7902                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7903                              p = "Draw claim: 3-fold repetition";
7904                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7905                              p = "Draw claim: insufficient mating material";
7906                          if( p != NULL && canAdjudicate) {
7907                              if(engineOpponent) {
7908                                SendToProgram("force\n", engineOpponent); // suppress reply
7909                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7910                              }
7911                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7912                              return 1;
7913                          }
7914                 }
7915
7916                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7917                     if(engineOpponent) {
7918                       SendToProgram("force\n", engineOpponent); // suppress reply
7919                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7920                     }
7921                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7922                     return 1;
7923                 }
7924         return 0;
7925 }
7926
7927 char *
7928 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7929 {   // [HGM] book: this routine intercepts moves to simulate book replies
7930     char *bookHit = NULL;
7931
7932     //first determine if the incoming move brings opponent into his book
7933     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7934         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7935     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7936     if(bookHit != NULL && !cps->bookSuspend) {
7937         // make sure opponent is not going to reply after receiving move to book position
7938         SendToProgram("force\n", cps);
7939         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7940     }
7941     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7942     // now arrange restart after book miss
7943     if(bookHit) {
7944         // after a book hit we never send 'go', and the code after the call to this routine
7945         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7946         char buf[MSG_SIZ], *move = bookHit;
7947         if(cps->useSAN) {
7948             int fromX, fromY, toX, toY;
7949             char promoChar;
7950             ChessMove moveType;
7951             move = buf + 30;
7952             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7953                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7954                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7955                                     PosFlags(forwardMostMove),
7956                                     fromY, fromX, toY, toX, promoChar, move);
7957             } else {
7958                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7959                 bookHit = NULL;
7960             }
7961         }
7962         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7963         SendToProgram(buf, cps);
7964         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7965     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7966         SendToProgram("go\n", cps);
7967         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7968     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7969         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7970             SendToProgram("go\n", cps);
7971         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7972     }
7973     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7974 }
7975
7976 int
7977 LoadError (char *errmess, ChessProgramState *cps)
7978 {   // unloads engine and switches back to -ncp mode if it was first
7979     if(cps->initDone) return FALSE;
7980     cps->isr = NULL; // this should suppress further error popups from breaking pipes
7981     DestroyChildProcess(cps->pr, 9 ); // just to be sure
7982     cps->pr = NoProc; 
7983     if(cps == &first) {
7984         appData.noChessProgram = TRUE;
7985         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
7986         gameMode = BeginningOfGame; ModeHighlight();
7987         SetNCPMode();
7988     }
7989     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
7990     DisplayMessage("", ""); // erase waiting message
7991     if(errmess) DisplayError(errmess, 0); // announce reason, if given
7992     return TRUE;
7993 }
7994
7995 char *savedMessage;
7996 ChessProgramState *savedState;
7997 void
7998 DeferredBookMove (void)
7999 {
8000         if(savedState->lastPing != savedState->lastPong)
8001                     ScheduleDelayedEvent(DeferredBookMove, 10);
8002         else
8003         HandleMachineMove(savedMessage, savedState);
8004 }
8005
8006 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8007
8008 void
8009 HandleMachineMove (char *message, ChessProgramState *cps)
8010 {
8011     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8012     char realname[MSG_SIZ];
8013     int fromX, fromY, toX, toY;
8014     ChessMove moveType;
8015     char promoChar;
8016     char *p, *pv=buf1;
8017     int machineWhite, oldError;
8018     char *bookHit;
8019
8020     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8021         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8022         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8023             DisplayError(_("Invalid pairing from pairing engine"), 0);
8024             return;
8025         }
8026         pairingReceived = 1;
8027         NextMatchGame();
8028         return; // Skim the pairing messages here.
8029     }
8030
8031     oldError = cps->userError; cps->userError = 0;
8032
8033 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8034     /*
8035      * Kludge to ignore BEL characters
8036      */
8037     while (*message == '\007') message++;
8038
8039     /*
8040      * [HGM] engine debug message: ignore lines starting with '#' character
8041      */
8042     if(cps->debug && *message == '#') return;
8043
8044     /*
8045      * Look for book output
8046      */
8047     if (cps == &first && bookRequested) {
8048         if (message[0] == '\t' || message[0] == ' ') {
8049             /* Part of the book output is here; append it */
8050             strcat(bookOutput, message);
8051             strcat(bookOutput, "  \n");
8052             return;
8053         } else if (bookOutput[0] != NULLCHAR) {
8054             /* All of book output has arrived; display it */
8055             char *p = bookOutput;
8056             while (*p != NULLCHAR) {
8057                 if (*p == '\t') *p = ' ';
8058                 p++;
8059             }
8060             DisplayInformation(bookOutput);
8061             bookRequested = FALSE;
8062             /* Fall through to parse the current output */
8063         }
8064     }
8065
8066     /*
8067      * Look for machine move.
8068      */
8069     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8070         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8071     {
8072         /* This method is only useful on engines that support ping */
8073         if (cps->lastPing != cps->lastPong) {
8074           if (gameMode == BeginningOfGame) {
8075             /* Extra move from before last new; ignore */
8076             if (appData.debugMode) {
8077                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8078             }
8079           } else {
8080             if (appData.debugMode) {
8081                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8082                         cps->which, gameMode);
8083             }
8084
8085             SendToProgram("undo\n", cps);
8086           }
8087           return;
8088         }
8089
8090         switch (gameMode) {
8091           case BeginningOfGame:
8092             /* Extra move from before last reset; ignore */
8093             if (appData.debugMode) {
8094                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8095             }
8096             return;
8097
8098           case EndOfGame:
8099           case IcsIdle:
8100           default:
8101             /* Extra move after we tried to stop.  The mode test is
8102                not a reliable way of detecting this problem, but it's
8103                the best we can do on engines that don't support ping.
8104             */
8105             if (appData.debugMode) {
8106                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8107                         cps->which, gameMode);
8108             }
8109             SendToProgram("undo\n", cps);
8110             return;
8111
8112           case MachinePlaysWhite:
8113           case IcsPlayingWhite:
8114             machineWhite = TRUE;
8115             break;
8116
8117           case MachinePlaysBlack:
8118           case IcsPlayingBlack:
8119             machineWhite = FALSE;
8120             break;
8121
8122           case TwoMachinesPlay:
8123             machineWhite = (cps->twoMachinesColor[0] == 'w');
8124             break;
8125         }
8126         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8127             if (appData.debugMode) {
8128                 fprintf(debugFP,
8129                         "Ignoring move out of turn by %s, gameMode %d"
8130                         ", forwardMost %d\n",
8131                         cps->which, gameMode, forwardMostMove);
8132             }
8133             return;
8134         }
8135
8136         if(cps->alphaRank) AlphaRank(machineMove, 4);
8137         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8138                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8139             /* Machine move could not be parsed; ignore it. */
8140           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8141                     machineMove, _(cps->which));
8142             DisplayError(buf1, 0);
8143             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8144                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8145             if (gameMode == TwoMachinesPlay) {
8146               GameEnds(machineWhite ? BlackWins : WhiteWins,
8147                        buf1, GE_XBOARD);
8148             }
8149             return;
8150         }
8151
8152         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8153         /* So we have to redo legality test with true e.p. status here,  */
8154         /* to make sure an illegal e.p. capture does not slip through,   */
8155         /* to cause a forfeit on a justified illegal-move complaint      */
8156         /* of the opponent.                                              */
8157         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8158            ChessMove moveType;
8159            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8160                              fromY, fromX, toY, toX, promoChar);
8161             if(moveType == IllegalMove) {
8162               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8163                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8164                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8165                            buf1, GE_XBOARD);
8166                 return;
8167            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8168            /* [HGM] Kludge to handle engines that send FRC-style castling
8169               when they shouldn't (like TSCP-Gothic) */
8170            switch(moveType) {
8171              case WhiteASideCastleFR:
8172              case BlackASideCastleFR:
8173                toX+=2;
8174                currentMoveString[2]++;
8175                break;
8176              case WhiteHSideCastleFR:
8177              case BlackHSideCastleFR:
8178                toX--;
8179                currentMoveString[2]--;
8180                break;
8181              default: ; // nothing to do, but suppresses warning of pedantic compilers
8182            }
8183         }
8184         hintRequested = FALSE;
8185         lastHint[0] = NULLCHAR;
8186         bookRequested = FALSE;
8187         /* Program may be pondering now */
8188         cps->maybeThinking = TRUE;
8189         if (cps->sendTime == 2) cps->sendTime = 1;
8190         if (cps->offeredDraw) cps->offeredDraw--;
8191
8192         /* [AS] Save move info*/
8193         pvInfoList[ forwardMostMove ].score = programStats.score;
8194         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8195         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8196
8197         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8198
8199         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8200         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8201             int count = 0;
8202
8203             while( count < adjudicateLossPlies ) {
8204                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8205
8206                 if( count & 1 ) {
8207                     score = -score; /* Flip score for winning side */
8208                 }
8209
8210                 if( score > adjudicateLossThreshold ) {
8211                     break;
8212                 }
8213
8214                 count++;
8215             }
8216
8217             if( count >= adjudicateLossPlies ) {
8218                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8219
8220                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8221                     "Xboard adjudication",
8222                     GE_XBOARD );
8223
8224                 return;
8225             }
8226         }
8227
8228         if(Adjudicate(cps)) {
8229             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8230             return; // [HGM] adjudicate: for all automatic game ends
8231         }
8232
8233 #if ZIPPY
8234         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8235             first.initDone) {
8236           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8237                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8238                 SendToICS("draw ");
8239                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8240           }
8241           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8242           ics_user_moved = 1;
8243           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8244                 char buf[3*MSG_SIZ];
8245
8246                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8247                         programStats.score / 100.,
8248                         programStats.depth,
8249                         programStats.time / 100.,
8250                         (unsigned int)programStats.nodes,
8251                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8252                         programStats.movelist);
8253                 SendToICS(buf);
8254 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8255           }
8256         }
8257 #endif
8258
8259         /* [AS] Clear stats for next move */
8260         ClearProgramStats();
8261         thinkOutput[0] = NULLCHAR;
8262         hiddenThinkOutputState = 0;
8263
8264         bookHit = NULL;
8265         if (gameMode == TwoMachinesPlay) {
8266             /* [HGM] relaying draw offers moved to after reception of move */
8267             /* and interpreting offer as claim if it brings draw condition */
8268             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8269                 SendToProgram("draw\n", cps->other);
8270             }
8271             if (cps->other->sendTime) {
8272                 SendTimeRemaining(cps->other,
8273                                   cps->other->twoMachinesColor[0] == 'w');
8274             }
8275             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8276             if (firstMove && !bookHit) {
8277                 firstMove = FALSE;
8278                 if (cps->other->useColors) {
8279                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8280                 }
8281                 SendToProgram("go\n", cps->other);
8282             }
8283             cps->other->maybeThinking = TRUE;
8284         }
8285
8286         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8287
8288         if (!pausing && appData.ringBellAfterMoves) {
8289             RingBell();
8290         }
8291
8292         /*
8293          * Reenable menu items that were disabled while
8294          * machine was thinking
8295          */
8296         if (gameMode != TwoMachinesPlay)
8297             SetUserThinkingEnables();
8298
8299         // [HGM] book: after book hit opponent has received move and is now in force mode
8300         // force the book reply into it, and then fake that it outputted this move by jumping
8301         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8302         if(bookHit) {
8303                 static char bookMove[MSG_SIZ]; // a bit generous?
8304
8305                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8306                 strcat(bookMove, bookHit);
8307                 message = bookMove;
8308                 cps = cps->other;
8309                 programStats.nodes = programStats.depth = programStats.time =
8310                 programStats.score = programStats.got_only_move = 0;
8311                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8312
8313                 if(cps->lastPing != cps->lastPong) {
8314                     savedMessage = message; // args for deferred call
8315                     savedState = cps;
8316                     ScheduleDelayedEvent(DeferredBookMove, 10);
8317                     return;
8318                 }
8319                 goto FakeBookMove;
8320         }
8321
8322         return;
8323     }
8324
8325     /* Set special modes for chess engines.  Later something general
8326      *  could be added here; for now there is just one kludge feature,
8327      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8328      *  when "xboard" is given as an interactive command.
8329      */
8330     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8331         cps->useSigint = FALSE;
8332         cps->useSigterm = FALSE;
8333     }
8334     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8335       ParseFeatures(message+8, cps);
8336       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8337     }
8338
8339     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8340                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8341       int dummy, s=6; char buf[MSG_SIZ];
8342       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8343       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8344       if(startedFromSetupPosition) return;
8345       ParseFEN(boards[0], &dummy, message+s);
8346       DrawPosition(TRUE, boards[0]);
8347       startedFromSetupPosition = TRUE;
8348       return;
8349     }
8350     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8351      * want this, I was asked to put it in, and obliged.
8352      */
8353     if (!strncmp(message, "setboard ", 9)) {
8354         Board initial_position;
8355
8356         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8357
8358         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8359             DisplayError(_("Bad FEN received from engine"), 0);
8360             return ;
8361         } else {
8362            Reset(TRUE, FALSE);
8363            CopyBoard(boards[0], initial_position);
8364            initialRulePlies = FENrulePlies;
8365            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8366            else gameMode = MachinePlaysBlack;
8367            DrawPosition(FALSE, boards[currentMove]);
8368         }
8369         return;
8370     }
8371
8372     /*
8373      * Look for communication commands
8374      */
8375     if (!strncmp(message, "telluser ", 9)) {
8376         if(message[9] == '\\' && message[10] == '\\')
8377             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8378         PlayTellSound();
8379         DisplayNote(message + 9);
8380         return;
8381     }
8382     if (!strncmp(message, "tellusererror ", 14)) {
8383         cps->userError = 1;
8384         if(message[14] == '\\' && message[15] == '\\')
8385             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8386         PlayTellSound();
8387         DisplayError(message + 14, 0);
8388         return;
8389     }
8390     if (!strncmp(message, "tellopponent ", 13)) {
8391       if (appData.icsActive) {
8392         if (loggedOn) {
8393           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8394           SendToICS(buf1);
8395         }
8396       } else {
8397         DisplayNote(message + 13);
8398       }
8399       return;
8400     }
8401     if (!strncmp(message, "tellothers ", 11)) {
8402       if (appData.icsActive) {
8403         if (loggedOn) {
8404           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8405           SendToICS(buf1);
8406         }
8407       }
8408       return;
8409     }
8410     if (!strncmp(message, "tellall ", 8)) {
8411       if (appData.icsActive) {
8412         if (loggedOn) {
8413           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8414           SendToICS(buf1);
8415         }
8416       } else {
8417         DisplayNote(message + 8);
8418       }
8419       return;
8420     }
8421     if (strncmp(message, "warning", 7) == 0) {
8422         /* Undocumented feature, use tellusererror in new code */
8423         DisplayError(message, 0);
8424         return;
8425     }
8426     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8427         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8428         strcat(realname, " query");
8429         AskQuestion(realname, buf2, buf1, cps->pr);
8430         return;
8431     }
8432     /* Commands from the engine directly to ICS.  We don't allow these to be
8433      *  sent until we are logged on. Crafty kibitzes have been known to
8434      *  interfere with the login process.
8435      */
8436     if (loggedOn) {
8437         if (!strncmp(message, "tellics ", 8)) {
8438             SendToICS(message + 8);
8439             SendToICS("\n");
8440             return;
8441         }
8442         if (!strncmp(message, "tellicsnoalias ", 15)) {
8443             SendToICS(ics_prefix);
8444             SendToICS(message + 15);
8445             SendToICS("\n");
8446             return;
8447         }
8448         /* The following are for backward compatibility only */
8449         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8450             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8451             SendToICS(ics_prefix);
8452             SendToICS(message);
8453             SendToICS("\n");
8454             return;
8455         }
8456     }
8457     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8458         return;
8459     }
8460     /*
8461      * If the move is illegal, cancel it and redraw the board.
8462      * Also deal with other error cases.  Matching is rather loose
8463      * here to accommodate engines written before the spec.
8464      */
8465     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8466         strncmp(message, "Error", 5) == 0) {
8467         if (StrStr(message, "name") ||
8468             StrStr(message, "rating") || StrStr(message, "?") ||
8469             StrStr(message, "result") || StrStr(message, "board") ||
8470             StrStr(message, "bk") || StrStr(message, "computer") ||
8471             StrStr(message, "variant") || StrStr(message, "hint") ||
8472             StrStr(message, "random") || StrStr(message, "depth") ||
8473             StrStr(message, "accepted")) {
8474             return;
8475         }
8476         if (StrStr(message, "protover")) {
8477           /* Program is responding to input, so it's apparently done
8478              initializing, and this error message indicates it is
8479              protocol version 1.  So we don't need to wait any longer
8480              for it to initialize and send feature commands. */
8481           FeatureDone(cps, 1);
8482           cps->protocolVersion = 1;
8483           return;
8484         }
8485         cps->maybeThinking = FALSE;
8486
8487         if (StrStr(message, "draw")) {
8488             /* Program doesn't have "draw" command */
8489             cps->sendDrawOffers = 0;
8490             return;
8491         }
8492         if (cps->sendTime != 1 &&
8493             (StrStr(message, "time") || StrStr(message, "otim"))) {
8494           /* Program apparently doesn't have "time" or "otim" command */
8495           cps->sendTime = 0;
8496           return;
8497         }
8498         if (StrStr(message, "analyze")) {
8499             cps->analysisSupport = FALSE;
8500             cps->analyzing = FALSE;
8501 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8502             EditGameEvent(); // [HGM] try to preserve loaded game
8503             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8504             DisplayError(buf2, 0);
8505             return;
8506         }
8507         if (StrStr(message, "(no matching move)st")) {
8508           /* Special kludge for GNU Chess 4 only */
8509           cps->stKludge = TRUE;
8510           SendTimeControl(cps, movesPerSession, timeControl,
8511                           timeIncrement, appData.searchDepth,
8512                           searchTime);
8513           return;
8514         }
8515         if (StrStr(message, "(no matching move)sd")) {
8516           /* Special kludge for GNU Chess 4 only */
8517           cps->sdKludge = TRUE;
8518           SendTimeControl(cps, movesPerSession, timeControl,
8519                           timeIncrement, appData.searchDepth,
8520                           searchTime);
8521           return;
8522         }
8523         if (!StrStr(message, "llegal")) {
8524             return;
8525         }
8526         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8527             gameMode == IcsIdle) return;
8528         if (forwardMostMove <= backwardMostMove) return;
8529         if (pausing) PauseEvent();
8530       if(appData.forceIllegal) {
8531             // [HGM] illegal: machine refused move; force position after move into it
8532           SendToProgram("force\n", cps);
8533           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8534                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8535                 // when black is to move, while there might be nothing on a2 or black
8536                 // might already have the move. So send the board as if white has the move.
8537                 // But first we must change the stm of the engine, as it refused the last move
8538                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8539                 if(WhiteOnMove(forwardMostMove)) {
8540                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8541                     SendBoard(cps, forwardMostMove); // kludgeless board
8542                 } else {
8543                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8544                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8545                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8546                 }
8547           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8548             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8549                  gameMode == TwoMachinesPlay)
8550               SendToProgram("go\n", cps);
8551             return;
8552       } else
8553         if (gameMode == PlayFromGameFile) {
8554             /* Stop reading this game file */
8555             gameMode = EditGame;
8556             ModeHighlight();
8557         }
8558         /* [HGM] illegal-move claim should forfeit game when Xboard */
8559         /* only passes fully legal moves                            */
8560         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8561             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8562                                 "False illegal-move claim", GE_XBOARD );
8563             return; // do not take back move we tested as valid
8564         }
8565         currentMove = forwardMostMove-1;
8566         DisplayMove(currentMove-1); /* before DisplayMoveError */
8567         SwitchClocks(forwardMostMove-1); // [HGM] race
8568         DisplayBothClocks();
8569         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8570                 parseList[currentMove], _(cps->which));
8571         DisplayMoveError(buf1);
8572         DrawPosition(FALSE, boards[currentMove]);
8573
8574         SetUserThinkingEnables();
8575         return;
8576     }
8577     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8578         /* Program has a broken "time" command that
8579            outputs a string not ending in newline.
8580            Don't use it. */
8581         cps->sendTime = 0;
8582     }
8583
8584     /*
8585      * If chess program startup fails, exit with an error message.
8586      * Attempts to recover here are futile. [HGM] Well, we try anyway
8587      */
8588     if ((StrStr(message, "unknown host") != NULL)
8589         || (StrStr(message, "No remote directory") != NULL)
8590         || (StrStr(message, "not found") != NULL)
8591         || (StrStr(message, "No such file") != NULL)
8592         || (StrStr(message, "can't alloc") != NULL)
8593         || (StrStr(message, "Permission denied") != NULL)) {
8594
8595         cps->maybeThinking = FALSE;
8596         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8597                 _(cps->which), cps->program, cps->host, message);
8598         RemoveInputSource(cps->isr);
8599         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8600             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8601             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8602         }
8603         return;
8604     }
8605
8606     /*
8607      * Look for hint output
8608      */
8609     if (sscanf(message, "Hint: %s", buf1) == 1) {
8610         if (cps == &first && hintRequested) {
8611             hintRequested = FALSE;
8612             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8613                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8614                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8615                                     PosFlags(forwardMostMove),
8616                                     fromY, fromX, toY, toX, promoChar, buf1);
8617                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8618                 DisplayInformation(buf2);
8619             } else {
8620                 /* Hint move could not be parsed!? */
8621               snprintf(buf2, sizeof(buf2),
8622                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8623                         buf1, _(cps->which));
8624                 DisplayError(buf2, 0);
8625             }
8626         } else {
8627           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8628         }
8629         return;
8630     }
8631
8632     /*
8633      * Ignore other messages if game is not in progress
8634      */
8635     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8636         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8637
8638     /*
8639      * look for win, lose, draw, or draw offer
8640      */
8641     if (strncmp(message, "1-0", 3) == 0) {
8642         char *p, *q, *r = "";
8643         p = strchr(message, '{');
8644         if (p) {
8645             q = strchr(p, '}');
8646             if (q) {
8647                 *q = NULLCHAR;
8648                 r = p + 1;
8649             }
8650         }
8651         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8652         return;
8653     } else if (strncmp(message, "0-1", 3) == 0) {
8654         char *p, *q, *r = "";
8655         p = strchr(message, '{');
8656         if (p) {
8657             q = strchr(p, '}');
8658             if (q) {
8659                 *q = NULLCHAR;
8660                 r = p + 1;
8661             }
8662         }
8663         /* Kludge for Arasan 4.1 bug */
8664         if (strcmp(r, "Black resigns") == 0) {
8665             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8666             return;
8667         }
8668         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8669         return;
8670     } else if (strncmp(message, "1/2", 3) == 0) {
8671         char *p, *q, *r = "";
8672         p = strchr(message, '{');
8673         if (p) {
8674             q = strchr(p, '}');
8675             if (q) {
8676                 *q = NULLCHAR;
8677                 r = p + 1;
8678             }
8679         }
8680
8681         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8682         return;
8683
8684     } else if (strncmp(message, "White resign", 12) == 0) {
8685         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8686         return;
8687     } else if (strncmp(message, "Black resign", 12) == 0) {
8688         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8689         return;
8690     } else if (strncmp(message, "White matches", 13) == 0 ||
8691                strncmp(message, "Black matches", 13) == 0   ) {
8692         /* [HGM] ignore GNUShogi noises */
8693         return;
8694     } else if (strncmp(message, "White", 5) == 0 &&
8695                message[5] != '(' &&
8696                StrStr(message, "Black") == NULL) {
8697         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8698         return;
8699     } else if (strncmp(message, "Black", 5) == 0 &&
8700                message[5] != '(') {
8701         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8702         return;
8703     } else if (strcmp(message, "resign") == 0 ||
8704                strcmp(message, "computer resigns") == 0) {
8705         switch (gameMode) {
8706           case MachinePlaysBlack:
8707           case IcsPlayingBlack:
8708             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8709             break;
8710           case MachinePlaysWhite:
8711           case IcsPlayingWhite:
8712             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8713             break;
8714           case TwoMachinesPlay:
8715             if (cps->twoMachinesColor[0] == 'w')
8716               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8717             else
8718               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8719             break;
8720           default:
8721             /* can't happen */
8722             break;
8723         }
8724         return;
8725     } else if (strncmp(message, "opponent mates", 14) == 0) {
8726         switch (gameMode) {
8727           case MachinePlaysBlack:
8728           case IcsPlayingBlack:
8729             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8730             break;
8731           case MachinePlaysWhite:
8732           case IcsPlayingWhite:
8733             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8734             break;
8735           case TwoMachinesPlay:
8736             if (cps->twoMachinesColor[0] == 'w')
8737               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8738             else
8739               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8740             break;
8741           default:
8742             /* can't happen */
8743             break;
8744         }
8745         return;
8746     } else if (strncmp(message, "computer mates", 14) == 0) {
8747         switch (gameMode) {
8748           case MachinePlaysBlack:
8749           case IcsPlayingBlack:
8750             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8751             break;
8752           case MachinePlaysWhite:
8753           case IcsPlayingWhite:
8754             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8755             break;
8756           case TwoMachinesPlay:
8757             if (cps->twoMachinesColor[0] == 'w')
8758               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8759             else
8760               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8761             break;
8762           default:
8763             /* can't happen */
8764             break;
8765         }
8766         return;
8767     } else if (strncmp(message, "checkmate", 9) == 0) {
8768         if (WhiteOnMove(forwardMostMove)) {
8769             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8770         } else {
8771             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8772         }
8773         return;
8774     } else if (strstr(message, "Draw") != NULL ||
8775                strstr(message, "game is a draw") != NULL) {
8776         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8777         return;
8778     } else if (strstr(message, "offer") != NULL &&
8779                strstr(message, "draw") != NULL) {
8780 #if ZIPPY
8781         if (appData.zippyPlay && first.initDone) {
8782             /* Relay offer to ICS */
8783             SendToICS(ics_prefix);
8784             SendToICS("draw\n");
8785         }
8786 #endif
8787         cps->offeredDraw = 2; /* valid until this engine moves twice */
8788         if (gameMode == TwoMachinesPlay) {
8789             if (cps->other->offeredDraw) {
8790                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8791             /* [HGM] in two-machine mode we delay relaying draw offer      */
8792             /* until after we also have move, to see if it is really claim */
8793             }
8794         } else if (gameMode == MachinePlaysWhite ||
8795                    gameMode == MachinePlaysBlack) {
8796           if (userOfferedDraw) {
8797             DisplayInformation(_("Machine accepts your draw offer"));
8798             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8799           } else {
8800             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8801           }
8802         }
8803     }
8804
8805
8806     /*
8807      * Look for thinking output
8808      */
8809     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8810           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8811                                 ) {
8812         int plylev, mvleft, mvtot, curscore, time;
8813         char mvname[MOVE_LEN];
8814         u64 nodes; // [DM]
8815         char plyext;
8816         int ignore = FALSE;
8817         int prefixHint = FALSE;
8818         mvname[0] = NULLCHAR;
8819
8820         switch (gameMode) {
8821           case MachinePlaysBlack:
8822           case IcsPlayingBlack:
8823             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8824             break;
8825           case MachinePlaysWhite:
8826           case IcsPlayingWhite:
8827             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8828             break;
8829           case AnalyzeMode:
8830           case AnalyzeFile:
8831             break;
8832           case IcsObserving: /* [DM] icsEngineAnalyze */
8833             if (!appData.icsEngineAnalyze) ignore = TRUE;
8834             break;
8835           case TwoMachinesPlay:
8836             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8837                 ignore = TRUE;
8838             }
8839             break;
8840           default:
8841             ignore = TRUE;
8842             break;
8843         }
8844
8845         if (!ignore) {
8846             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8847             buf1[0] = NULLCHAR;
8848             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8849                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8850
8851                 if (plyext != ' ' && plyext != '\t') {
8852                     time *= 100;
8853                 }
8854
8855                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8856                 if( cps->scoreIsAbsolute &&
8857                     ( gameMode == MachinePlaysBlack ||
8858                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8859                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8860                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8861                      !WhiteOnMove(currentMove)
8862                     ) )
8863                 {
8864                     curscore = -curscore;
8865                 }
8866
8867                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8868
8869                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8870                         char buf[MSG_SIZ];
8871                         FILE *f;
8872                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8873                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8874                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8875                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8876                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8877                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8878                                 fclose(f);
8879                         } else DisplayError(_("failed writing PV"), 0);
8880                 }
8881
8882                 tempStats.depth = plylev;
8883                 tempStats.nodes = nodes;
8884                 tempStats.time = time;
8885                 tempStats.score = curscore;
8886                 tempStats.got_only_move = 0;
8887
8888                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8889                         int ticklen;
8890
8891                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8892                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8893                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8894                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8895                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8896                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8897                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8898                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8899                 }
8900
8901                 /* Buffer overflow protection */
8902                 if (pv[0] != NULLCHAR) {
8903                     if (strlen(pv) >= sizeof(tempStats.movelist)
8904                         && appData.debugMode) {
8905                         fprintf(debugFP,
8906                                 "PV is too long; using the first %u bytes.\n",
8907                                 (unsigned) sizeof(tempStats.movelist) - 1);
8908                     }
8909
8910                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8911                 } else {
8912                     sprintf(tempStats.movelist, " no PV\n");
8913                 }
8914
8915                 if (tempStats.seen_stat) {
8916                     tempStats.ok_to_send = 1;
8917                 }
8918
8919                 if (strchr(tempStats.movelist, '(') != NULL) {
8920                     tempStats.line_is_book = 1;
8921                     tempStats.nr_moves = 0;
8922                     tempStats.moves_left = 0;
8923                 } else {
8924                     tempStats.line_is_book = 0;
8925                 }
8926
8927                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8928                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8929
8930                 SendProgramStatsToFrontend( cps, &tempStats );
8931
8932                 /*
8933                     [AS] Protect the thinkOutput buffer from overflow... this
8934                     is only useful if buf1 hasn't overflowed first!
8935                 */
8936                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8937                          plylev,
8938                          (gameMode == TwoMachinesPlay ?
8939                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8940                          ((double) curscore) / 100.0,
8941                          prefixHint ? lastHint : "",
8942                          prefixHint ? " " : "" );
8943
8944                 if( buf1[0] != NULLCHAR ) {
8945                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8946
8947                     if( strlen(pv) > max_len ) {
8948                         if( appData.debugMode) {
8949                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8950                         }
8951                         pv[max_len+1] = '\0';
8952                     }
8953
8954                     strcat( thinkOutput, pv);
8955                 }
8956
8957                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8958                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8959                     DisplayMove(currentMove - 1);
8960                 }
8961                 return;
8962
8963             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8964                 /* crafty (9.25+) says "(only move) <move>"
8965                  * if there is only 1 legal move
8966                  */
8967                 sscanf(p, "(only move) %s", buf1);
8968                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8969                 sprintf(programStats.movelist, "%s (only move)", buf1);
8970                 programStats.depth = 1;
8971                 programStats.nr_moves = 1;
8972                 programStats.moves_left = 1;
8973                 programStats.nodes = 1;
8974                 programStats.time = 1;
8975                 programStats.got_only_move = 1;
8976
8977                 /* Not really, but we also use this member to
8978                    mean "line isn't going to change" (Crafty
8979                    isn't searching, so stats won't change) */
8980                 programStats.line_is_book = 1;
8981
8982                 SendProgramStatsToFrontend( cps, &programStats );
8983
8984                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8985                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8986                     DisplayMove(currentMove - 1);
8987                 }
8988                 return;
8989             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8990                               &time, &nodes, &plylev, &mvleft,
8991                               &mvtot, mvname) >= 5) {
8992                 /* The stat01: line is from Crafty (9.29+) in response
8993                    to the "." command */
8994                 programStats.seen_stat = 1;
8995                 cps->maybeThinking = TRUE;
8996
8997                 if (programStats.got_only_move || !appData.periodicUpdates)
8998                   return;
8999
9000                 programStats.depth = plylev;
9001                 programStats.time = time;
9002                 programStats.nodes = nodes;
9003                 programStats.moves_left = mvleft;
9004                 programStats.nr_moves = mvtot;
9005                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9006                 programStats.ok_to_send = 1;
9007                 programStats.movelist[0] = '\0';
9008
9009                 SendProgramStatsToFrontend( cps, &programStats );
9010
9011                 return;
9012
9013             } else if (strncmp(message,"++",2) == 0) {
9014                 /* Crafty 9.29+ outputs this */
9015                 programStats.got_fail = 2;
9016                 return;
9017
9018             } else if (strncmp(message,"--",2) == 0) {
9019                 /* Crafty 9.29+ outputs this */
9020                 programStats.got_fail = 1;
9021                 return;
9022
9023             } else if (thinkOutput[0] != NULLCHAR &&
9024                        strncmp(message, "    ", 4) == 0) {
9025                 unsigned message_len;
9026
9027                 p = message;
9028                 while (*p && *p == ' ') p++;
9029
9030                 message_len = strlen( p );
9031
9032                 /* [AS] Avoid buffer overflow */
9033                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9034                     strcat(thinkOutput, " ");
9035                     strcat(thinkOutput, p);
9036                 }
9037
9038                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9039                     strcat(programStats.movelist, " ");
9040                     strcat(programStats.movelist, p);
9041                 }
9042
9043                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9044                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9045                     DisplayMove(currentMove - 1);
9046                 }
9047                 return;
9048             }
9049         }
9050         else {
9051             buf1[0] = NULLCHAR;
9052
9053             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9054                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9055             {
9056                 ChessProgramStats cpstats;
9057
9058                 if (plyext != ' ' && plyext != '\t') {
9059                     time *= 100;
9060                 }
9061
9062                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9063                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9064                     curscore = -curscore;
9065                 }
9066
9067                 cpstats.depth = plylev;
9068                 cpstats.nodes = nodes;
9069                 cpstats.time = time;
9070                 cpstats.score = curscore;
9071                 cpstats.got_only_move = 0;
9072                 cpstats.movelist[0] = '\0';
9073
9074                 if (buf1[0] != NULLCHAR) {
9075                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9076                 }
9077
9078                 cpstats.ok_to_send = 0;
9079                 cpstats.line_is_book = 0;
9080                 cpstats.nr_moves = 0;
9081                 cpstats.moves_left = 0;
9082
9083                 SendProgramStatsToFrontend( cps, &cpstats );
9084             }
9085         }
9086     }
9087 }
9088
9089
9090 /* Parse a game score from the character string "game", and
9091    record it as the history of the current game.  The game
9092    score is NOT assumed to start from the standard position.
9093    The display is not updated in any way.
9094    */
9095 void
9096 ParseGameHistory (char *game)
9097 {
9098     ChessMove moveType;
9099     int fromX, fromY, toX, toY, boardIndex;
9100     char promoChar;
9101     char *p, *q;
9102     char buf[MSG_SIZ];
9103
9104     if (appData.debugMode)
9105       fprintf(debugFP, "Parsing game history: %s\n", game);
9106
9107     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9108     gameInfo.site = StrSave(appData.icsHost);
9109     gameInfo.date = PGNDate();
9110     gameInfo.round = StrSave("-");
9111
9112     /* Parse out names of players */
9113     while (*game == ' ') game++;
9114     p = buf;
9115     while (*game != ' ') *p++ = *game++;
9116     *p = NULLCHAR;
9117     gameInfo.white = StrSave(buf);
9118     while (*game == ' ') game++;
9119     p = buf;
9120     while (*game != ' ' && *game != '\n') *p++ = *game++;
9121     *p = NULLCHAR;
9122     gameInfo.black = StrSave(buf);
9123
9124     /* Parse moves */
9125     boardIndex = blackPlaysFirst ? 1 : 0;
9126     yynewstr(game);
9127     for (;;) {
9128         yyboardindex = boardIndex;
9129         moveType = (ChessMove) Myylex();
9130         switch (moveType) {
9131           case IllegalMove:             /* maybe suicide chess, etc. */
9132   if (appData.debugMode) {
9133     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9134     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9135     setbuf(debugFP, NULL);
9136   }
9137           case WhitePromotion:
9138           case BlackPromotion:
9139           case WhiteNonPromotion:
9140           case BlackNonPromotion:
9141           case NormalMove:
9142           case WhiteCapturesEnPassant:
9143           case BlackCapturesEnPassant:
9144           case WhiteKingSideCastle:
9145           case WhiteQueenSideCastle:
9146           case BlackKingSideCastle:
9147           case BlackQueenSideCastle:
9148           case WhiteKingSideCastleWild:
9149           case WhiteQueenSideCastleWild:
9150           case BlackKingSideCastleWild:
9151           case BlackQueenSideCastleWild:
9152           /* PUSH Fabien */
9153           case WhiteHSideCastleFR:
9154           case WhiteASideCastleFR:
9155           case BlackHSideCastleFR:
9156           case BlackASideCastleFR:
9157           /* POP Fabien */
9158             fromX = currentMoveString[0] - AAA;
9159             fromY = currentMoveString[1] - ONE;
9160             toX = currentMoveString[2] - AAA;
9161             toY = currentMoveString[3] - ONE;
9162             promoChar = currentMoveString[4];
9163             break;
9164           case WhiteDrop:
9165           case BlackDrop:
9166             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9167             fromX = moveType == WhiteDrop ?
9168               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9169             (int) CharToPiece(ToLower(currentMoveString[0]));
9170             fromY = DROP_RANK;
9171             toX = currentMoveString[2] - AAA;
9172             toY = currentMoveString[3] - ONE;
9173             promoChar = NULLCHAR;
9174             break;
9175           case AmbiguousMove:
9176             /* bug? */
9177             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9178   if (appData.debugMode) {
9179     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9180     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9181     setbuf(debugFP, NULL);
9182   }
9183             DisplayError(buf, 0);
9184             return;
9185           case ImpossibleMove:
9186             /* bug? */
9187             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9188   if (appData.debugMode) {
9189     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9190     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9191     setbuf(debugFP, NULL);
9192   }
9193             DisplayError(buf, 0);
9194             return;
9195           case EndOfFile:
9196             if (boardIndex < backwardMostMove) {
9197                 /* Oops, gap.  How did that happen? */
9198                 DisplayError(_("Gap in move list"), 0);
9199                 return;
9200             }
9201             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9202             if (boardIndex > forwardMostMove) {
9203                 forwardMostMove = boardIndex;
9204             }
9205             return;
9206           case ElapsedTime:
9207             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9208                 strcat(parseList[boardIndex-1], " ");
9209                 strcat(parseList[boardIndex-1], yy_text);
9210             }
9211             continue;
9212           case Comment:
9213           case PGNTag:
9214           case NAG:
9215           default:
9216             /* ignore */
9217             continue;
9218           case WhiteWins:
9219           case BlackWins:
9220           case GameIsDrawn:
9221           case GameUnfinished:
9222             if (gameMode == IcsExamining) {
9223                 if (boardIndex < backwardMostMove) {
9224                     /* Oops, gap.  How did that happen? */
9225                     return;
9226                 }
9227                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9228                 return;
9229             }
9230             gameInfo.result = moveType;
9231             p = strchr(yy_text, '{');
9232             if (p == NULL) p = strchr(yy_text, '(');
9233             if (p == NULL) {
9234                 p = yy_text;
9235                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9236             } else {
9237                 q = strchr(p, *p == '{' ? '}' : ')');
9238                 if (q != NULL) *q = NULLCHAR;
9239                 p++;
9240             }
9241             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9242             gameInfo.resultDetails = StrSave(p);
9243             continue;
9244         }
9245         if (boardIndex >= forwardMostMove &&
9246             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9247             backwardMostMove = blackPlaysFirst ? 1 : 0;
9248             return;
9249         }
9250         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9251                                  fromY, fromX, toY, toX, promoChar,
9252                                  parseList[boardIndex]);
9253         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9254         /* currentMoveString is set as a side-effect of yylex */
9255         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9256         strcat(moveList[boardIndex], "\n");
9257         boardIndex++;
9258         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9259         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9260           case MT_NONE:
9261           case MT_STALEMATE:
9262           default:
9263             break;
9264           case MT_CHECK:
9265             if(gameInfo.variant != VariantShogi)
9266                 strcat(parseList[boardIndex - 1], "+");
9267             break;
9268           case MT_CHECKMATE:
9269           case MT_STAINMATE:
9270             strcat(parseList[boardIndex - 1], "#");
9271             break;
9272         }
9273     }
9274 }
9275
9276
9277 /* Apply a move to the given board  */
9278 void
9279 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9280 {
9281   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9282   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9283
9284     /* [HGM] compute & store e.p. status and castling rights for new position */
9285     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9286
9287       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9288       oldEP = (signed char)board[EP_STATUS];
9289       board[EP_STATUS] = EP_NONE;
9290
9291   if (fromY == DROP_RANK) {
9292         /* must be first */
9293         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9294             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9295             return;
9296         }
9297         piece = board[toY][toX] = (ChessSquare) fromX;
9298   } else {
9299       int i;
9300
9301       if( board[toY][toX] != EmptySquare )
9302            board[EP_STATUS] = EP_CAPTURE;
9303
9304       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9305            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9306                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9307       } else
9308       if( board[fromY][fromX] == WhitePawn ) {
9309            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9310                board[EP_STATUS] = EP_PAWN_MOVE;
9311            if( toY-fromY==2) {
9312                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9313                         gameInfo.variant != VariantBerolina || toX < fromX)
9314                       board[EP_STATUS] = toX | berolina;
9315                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9316                         gameInfo.variant != VariantBerolina || toX > fromX)
9317                       board[EP_STATUS] = toX;
9318            }
9319       } else
9320       if( board[fromY][fromX] == BlackPawn ) {
9321            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9322                board[EP_STATUS] = EP_PAWN_MOVE;
9323            if( toY-fromY== -2) {
9324                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9325                         gameInfo.variant != VariantBerolina || toX < fromX)
9326                       board[EP_STATUS] = toX | berolina;
9327                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9328                         gameInfo.variant != VariantBerolina || toX > fromX)
9329                       board[EP_STATUS] = toX;
9330            }
9331        }
9332
9333        for(i=0; i<nrCastlingRights; i++) {
9334            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9335               board[CASTLING][i] == toX   && castlingRank[i] == toY
9336              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9337        }
9338
9339        if(gameInfo.variant == VariantSChess) { // update virginity
9340            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9341            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9342            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9343            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9344        }
9345
9346      if (fromX == toX && fromY == toY) return;
9347
9348      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9349      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9350      if(gameInfo.variant == VariantKnightmate)
9351          king += (int) WhiteUnicorn - (int) WhiteKing;
9352
9353     /* Code added by Tord: */
9354     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9355     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9356         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9357       board[fromY][fromX] = EmptySquare;
9358       board[toY][toX] = EmptySquare;
9359       if((toX > fromX) != (piece == WhiteRook)) {
9360         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9361       } else {
9362         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9363       }
9364     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9365                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9366       board[fromY][fromX] = EmptySquare;
9367       board[toY][toX] = EmptySquare;
9368       if((toX > fromX) != (piece == BlackRook)) {
9369         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9370       } else {
9371         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9372       }
9373     /* End of code added by Tord */
9374
9375     } else if (board[fromY][fromX] == king
9376         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9377         && toY == fromY && toX > fromX+1) {
9378         board[fromY][fromX] = EmptySquare;
9379         board[toY][toX] = king;
9380         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9381         board[fromY][BOARD_RGHT-1] = EmptySquare;
9382     } else if (board[fromY][fromX] == king
9383         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9384                && toY == fromY && toX < fromX-1) {
9385         board[fromY][fromX] = EmptySquare;
9386         board[toY][toX] = king;
9387         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9388         board[fromY][BOARD_LEFT] = EmptySquare;
9389     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9390                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9391                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9392                ) {
9393         /* white pawn promotion */
9394         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9395         if(gameInfo.variant==VariantBughouse ||
9396            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9397             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9398         board[fromY][fromX] = EmptySquare;
9399     } else if ((fromY >= BOARD_HEIGHT>>1)
9400                && (toX != fromX)
9401                && gameInfo.variant != VariantXiangqi
9402                && gameInfo.variant != VariantBerolina
9403                && (board[fromY][fromX] == WhitePawn)
9404                && (board[toY][toX] == EmptySquare)) {
9405         board[fromY][fromX] = EmptySquare;
9406         board[toY][toX] = WhitePawn;
9407         captured = board[toY - 1][toX];
9408         board[toY - 1][toX] = EmptySquare;
9409     } else if ((fromY == BOARD_HEIGHT-4)
9410                && (toX == fromX)
9411                && gameInfo.variant == VariantBerolina
9412                && (board[fromY][fromX] == WhitePawn)
9413                && (board[toY][toX] == EmptySquare)) {
9414         board[fromY][fromX] = EmptySquare;
9415         board[toY][toX] = WhitePawn;
9416         if(oldEP & EP_BEROLIN_A) {
9417                 captured = board[fromY][fromX-1];
9418                 board[fromY][fromX-1] = EmptySquare;
9419         }else{  captured = board[fromY][fromX+1];
9420                 board[fromY][fromX+1] = EmptySquare;
9421         }
9422     } else if (board[fromY][fromX] == king
9423         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9424                && toY == fromY && toX > fromX+1) {
9425         board[fromY][fromX] = EmptySquare;
9426         board[toY][toX] = king;
9427         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9428         board[fromY][BOARD_RGHT-1] = EmptySquare;
9429     } else if (board[fromY][fromX] == king
9430         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9431                && toY == fromY && toX < fromX-1) {
9432         board[fromY][fromX] = EmptySquare;
9433         board[toY][toX] = king;
9434         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9435         board[fromY][BOARD_LEFT] = EmptySquare;
9436     } else if (fromY == 7 && fromX == 3
9437                && board[fromY][fromX] == BlackKing
9438                && toY == 7 && toX == 5) {
9439         board[fromY][fromX] = EmptySquare;
9440         board[toY][toX] = BlackKing;
9441         board[fromY][7] = EmptySquare;
9442         board[toY][4] = BlackRook;
9443     } else if (fromY == 7 && fromX == 3
9444                && board[fromY][fromX] == BlackKing
9445                && toY == 7 && toX == 1) {
9446         board[fromY][fromX] = EmptySquare;
9447         board[toY][toX] = BlackKing;
9448         board[fromY][0] = EmptySquare;
9449         board[toY][2] = BlackRook;
9450     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9451                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9452                && toY < promoRank && promoChar
9453                ) {
9454         /* black pawn promotion */
9455         board[toY][toX] = CharToPiece(ToLower(promoChar));
9456         if(gameInfo.variant==VariantBughouse ||
9457            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9458             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9459         board[fromY][fromX] = EmptySquare;
9460     } else if ((fromY < BOARD_HEIGHT>>1)
9461                && (toX != fromX)
9462                && gameInfo.variant != VariantXiangqi
9463                && gameInfo.variant != VariantBerolina
9464                && (board[fromY][fromX] == BlackPawn)
9465                && (board[toY][toX] == EmptySquare)) {
9466         board[fromY][fromX] = EmptySquare;
9467         board[toY][toX] = BlackPawn;
9468         captured = board[toY + 1][toX];
9469         board[toY + 1][toX] = EmptySquare;
9470     } else if ((fromY == 3)
9471                && (toX == fromX)
9472                && gameInfo.variant == VariantBerolina
9473                && (board[fromY][fromX] == BlackPawn)
9474                && (board[toY][toX] == EmptySquare)) {
9475         board[fromY][fromX] = EmptySquare;
9476         board[toY][toX] = BlackPawn;
9477         if(oldEP & EP_BEROLIN_A) {
9478                 captured = board[fromY][fromX-1];
9479                 board[fromY][fromX-1] = EmptySquare;
9480         }else{  captured = board[fromY][fromX+1];
9481                 board[fromY][fromX+1] = EmptySquare;
9482         }
9483     } else {
9484         board[toY][toX] = board[fromY][fromX];
9485         board[fromY][fromX] = EmptySquare;
9486     }
9487   }
9488
9489     if (gameInfo.holdingsWidth != 0) {
9490
9491       /* !!A lot more code needs to be written to support holdings  */
9492       /* [HGM] OK, so I have written it. Holdings are stored in the */
9493       /* penultimate board files, so they are automaticlly stored   */
9494       /* in the game history.                                       */
9495       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9496                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9497         /* Delete from holdings, by decreasing count */
9498         /* and erasing image if necessary            */
9499         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9500         if(p < (int) BlackPawn) { /* white drop */
9501              p -= (int)WhitePawn;
9502                  p = PieceToNumber((ChessSquare)p);
9503              if(p >= gameInfo.holdingsSize) p = 0;
9504              if(--board[p][BOARD_WIDTH-2] <= 0)
9505                   board[p][BOARD_WIDTH-1] = EmptySquare;
9506              if((int)board[p][BOARD_WIDTH-2] < 0)
9507                         board[p][BOARD_WIDTH-2] = 0;
9508         } else {                  /* black drop */
9509              p -= (int)BlackPawn;
9510                  p = PieceToNumber((ChessSquare)p);
9511              if(p >= gameInfo.holdingsSize) p = 0;
9512              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9513                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9514              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9515                         board[BOARD_HEIGHT-1-p][1] = 0;
9516         }
9517       }
9518       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9519           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9520         /* [HGM] holdings: Add to holdings, if holdings exist */
9521         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9522                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9523                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9524         }
9525         p = (int) captured;
9526         if (p >= (int) BlackPawn) {
9527           p -= (int)BlackPawn;
9528           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9529                   /* in Shogi restore piece to its original  first */
9530                   captured = (ChessSquare) (DEMOTED captured);
9531                   p = DEMOTED p;
9532           }
9533           p = PieceToNumber((ChessSquare)p);
9534           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9535           board[p][BOARD_WIDTH-2]++;
9536           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9537         } else {
9538           p -= (int)WhitePawn;
9539           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9540                   captured = (ChessSquare) (DEMOTED captured);
9541                   p = DEMOTED p;
9542           }
9543           p = PieceToNumber((ChessSquare)p);
9544           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9545           board[BOARD_HEIGHT-1-p][1]++;
9546           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9547         }
9548       }
9549     } else if (gameInfo.variant == VariantAtomic) {
9550       if (captured != EmptySquare) {
9551         int y, x;
9552         for (y = toY-1; y <= toY+1; y++) {
9553           for (x = toX-1; x <= toX+1; x++) {
9554             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9555                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9556               board[y][x] = EmptySquare;
9557             }
9558           }
9559         }
9560         board[toY][toX] = EmptySquare;
9561       }
9562     }
9563     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9564         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9565     } else
9566     if(promoChar == '+') {
9567         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9568         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9569     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9570         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9571         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9572            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9573         board[toY][toX] = newPiece;
9574     }
9575     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9576                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9577         // [HGM] superchess: take promotion piece out of holdings
9578         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9579         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9580             if(!--board[k][BOARD_WIDTH-2])
9581                 board[k][BOARD_WIDTH-1] = EmptySquare;
9582         } else {
9583             if(!--board[BOARD_HEIGHT-1-k][1])
9584                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9585         }
9586     }
9587
9588 }
9589
9590 /* Updates forwardMostMove */
9591 void
9592 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9593 {
9594 //    forwardMostMove++; // [HGM] bare: moved downstream
9595
9596     (void) CoordsToAlgebraic(boards[forwardMostMove],
9597                              PosFlags(forwardMostMove),
9598                              fromY, fromX, toY, toX, promoChar,
9599                              parseList[forwardMostMove]);
9600
9601     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9602         int timeLeft; static int lastLoadFlag=0; int king, piece;
9603         piece = boards[forwardMostMove][fromY][fromX];
9604         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9605         if(gameInfo.variant == VariantKnightmate)
9606             king += (int) WhiteUnicorn - (int) WhiteKing;
9607         if(forwardMostMove == 0) {
9608             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9609                 fprintf(serverMoves, "%s;", UserName());
9610             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9611                 fprintf(serverMoves, "%s;", second.tidy);
9612             fprintf(serverMoves, "%s;", first.tidy);
9613             if(gameMode == MachinePlaysWhite)
9614                 fprintf(serverMoves, "%s;", UserName());
9615             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9616                 fprintf(serverMoves, "%s;", second.tidy);
9617         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9618         lastLoadFlag = loadFlag;
9619         // print base move
9620         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9621         // print castling suffix
9622         if( toY == fromY && piece == king ) {
9623             if(toX-fromX > 1)
9624                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9625             if(fromX-toX >1)
9626                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9627         }
9628         // e.p. suffix
9629         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9630              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9631              boards[forwardMostMove][toY][toX] == EmptySquare
9632              && fromX != toX && fromY != toY)
9633                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9634         // promotion suffix
9635         if(promoChar != NULLCHAR) {
9636             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
9637                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
9638                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
9639             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9640         }
9641         if(!loadFlag) {
9642                 char buf[MOVE_LEN*2], *p; int len;
9643             fprintf(serverMoves, "/%d/%d",
9644                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9645             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9646             else                      timeLeft = blackTimeRemaining/1000;
9647             fprintf(serverMoves, "/%d", timeLeft);
9648                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9649                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
9650                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9651                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9652             fprintf(serverMoves, "/%s", buf);
9653         }
9654         fflush(serverMoves);
9655     }
9656
9657     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9658         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9659       return;
9660     }
9661     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9662     if (commentList[forwardMostMove+1] != NULL) {
9663         free(commentList[forwardMostMove+1]);
9664         commentList[forwardMostMove+1] = NULL;
9665     }
9666     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9667     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9668     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9669     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9670     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9671     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9672     adjustedClock = FALSE;
9673     gameInfo.result = GameUnfinished;
9674     if (gameInfo.resultDetails != NULL) {
9675         free(gameInfo.resultDetails);
9676         gameInfo.resultDetails = NULL;
9677     }
9678     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9679                               moveList[forwardMostMove - 1]);
9680     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9681       case MT_NONE:
9682       case MT_STALEMATE:
9683       default:
9684         break;
9685       case MT_CHECK:
9686         if(gameInfo.variant != VariantShogi)
9687             strcat(parseList[forwardMostMove - 1], "+");
9688         break;
9689       case MT_CHECKMATE:
9690       case MT_STAINMATE:
9691         strcat(parseList[forwardMostMove - 1], "#");
9692         break;
9693     }
9694
9695 }
9696
9697 /* Updates currentMove if not pausing */
9698 void
9699 ShowMove (int fromX, int fromY, int toX, int toY)
9700 {
9701     int instant = (gameMode == PlayFromGameFile) ?
9702         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9703     if(appData.noGUI) return;
9704     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9705         if (!instant) {
9706             if (forwardMostMove == currentMove + 1) {
9707                 AnimateMove(boards[forwardMostMove - 1],
9708                             fromX, fromY, toX, toY);
9709             }
9710             if (appData.highlightLastMove) {
9711                 SetHighlights(fromX, fromY, toX, toY);
9712             }
9713         }
9714         currentMove = forwardMostMove;
9715     }
9716
9717     if (instant) return;
9718
9719     DisplayMove(currentMove - 1);
9720     DrawPosition(FALSE, boards[currentMove]);
9721     DisplayBothClocks();
9722     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9723 }
9724
9725 void
9726 SendEgtPath (ChessProgramState *cps)
9727 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9728         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9729
9730         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9731
9732         while(*p) {
9733             char c, *q = name+1, *r, *s;
9734
9735             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9736             while(*p && *p != ',') *q++ = *p++;
9737             *q++ = ':'; *q = 0;
9738             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9739                 strcmp(name, ",nalimov:") == 0 ) {
9740                 // take nalimov path from the menu-changeable option first, if it is defined
9741               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9742                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9743             } else
9744             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9745                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9746                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9747                 s = r = StrStr(s, ":") + 1; // beginning of path info
9748                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9749                 c = *r; *r = 0;             // temporarily null-terminate path info
9750                     *--q = 0;               // strip of trailig ':' from name
9751                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9752                 *r = c;
9753                 SendToProgram(buf,cps);     // send egtbpath command for this format
9754             }
9755             if(*p == ',') p++; // read away comma to position for next format name
9756         }
9757 }
9758
9759 void
9760 InitChessProgram (ChessProgramState *cps, int setup)
9761 /* setup needed to setup FRC opening position */
9762 {
9763     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9764     if (appData.noChessProgram) return;
9765     hintRequested = FALSE;
9766     bookRequested = FALSE;
9767
9768     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9769     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9770     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9771     if(cps->memSize) { /* [HGM] memory */
9772       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9773         SendToProgram(buf, cps);
9774     }
9775     SendEgtPath(cps); /* [HGM] EGT */
9776     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9777       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9778         SendToProgram(buf, cps);
9779     }
9780
9781     SendToProgram(cps->initString, cps);
9782     if (gameInfo.variant != VariantNormal &&
9783         gameInfo.variant != VariantLoadable
9784         /* [HGM] also send variant if board size non-standard */
9785         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9786                                             ) {
9787       char *v = VariantName(gameInfo.variant);
9788       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9789         /* [HGM] in protocol 1 we have to assume all variants valid */
9790         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9791         DisplayFatalError(buf, 0, 1);
9792         return;
9793       }
9794
9795       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9796       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9797       if( gameInfo.variant == VariantXiangqi )
9798            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9799       if( gameInfo.variant == VariantShogi )
9800            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9801       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9802            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9803       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9804           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9805            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9806       if( gameInfo.variant == VariantCourier )
9807            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9808       if( gameInfo.variant == VariantSuper )
9809            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9810       if( gameInfo.variant == VariantGreat )
9811            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9812       if( gameInfo.variant == VariantSChess )
9813            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9814       if( gameInfo.variant == VariantGrand )
9815            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9816
9817       if(overruled) {
9818         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9819                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9820            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9821            if(StrStr(cps->variants, b) == NULL) {
9822                // specific sized variant not known, check if general sizing allowed
9823                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9824                    if(StrStr(cps->variants, "boardsize") == NULL) {
9825                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9826                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9827                        DisplayFatalError(buf, 0, 1);
9828                        return;
9829                    }
9830                    /* [HGM] here we really should compare with the maximum supported board size */
9831                }
9832            }
9833       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9834       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9835       SendToProgram(buf, cps);
9836     }
9837     currentlyInitializedVariant = gameInfo.variant;
9838
9839     /* [HGM] send opening position in FRC to first engine */
9840     if(setup) {
9841           SendToProgram("force\n", cps);
9842           SendBoard(cps, 0);
9843           /* engine is now in force mode! Set flag to wake it up after first move. */
9844           setboardSpoiledMachineBlack = 1;
9845     }
9846
9847     if (cps->sendICS) {
9848       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9849       SendToProgram(buf, cps);
9850     }
9851     cps->maybeThinking = FALSE;
9852     cps->offeredDraw = 0;
9853     if (!appData.icsActive) {
9854         SendTimeControl(cps, movesPerSession, timeControl,
9855                         timeIncrement, appData.searchDepth,
9856                         searchTime);
9857     }
9858     if (appData.showThinking
9859         // [HGM] thinking: four options require thinking output to be sent
9860         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9861                                 ) {
9862         SendToProgram("post\n", cps);
9863     }
9864     SendToProgram("hard\n", cps);
9865     if (!appData.ponderNextMove) {
9866         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9867            it without being sure what state we are in first.  "hard"
9868            is not a toggle, so that one is OK.
9869          */
9870         SendToProgram("easy\n", cps);
9871     }
9872     if (cps->usePing) {
9873       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9874       SendToProgram(buf, cps);
9875     }
9876     cps->initDone = TRUE;
9877     ClearEngineOutputPane(cps == &second);
9878 }
9879
9880
9881 void
9882 StartChessProgram (ChessProgramState *cps)
9883 {
9884     char buf[MSG_SIZ];
9885     int err;
9886
9887     if (appData.noChessProgram) return;
9888     cps->initDone = FALSE;
9889
9890     if (strcmp(cps->host, "localhost") == 0) {
9891         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9892     } else if (*appData.remoteShell == NULLCHAR) {
9893         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9894     } else {
9895         if (*appData.remoteUser == NULLCHAR) {
9896           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9897                     cps->program);
9898         } else {
9899           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9900                     cps->host, appData.remoteUser, cps->program);
9901         }
9902         err = StartChildProcess(buf, "", &cps->pr);
9903     }
9904
9905     if (err != 0) {
9906       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9907         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9908         if(cps != &first) return;
9909         appData.noChessProgram = TRUE;
9910         ThawUI();
9911         SetNCPMode();
9912 //      DisplayFatalError(buf, err, 1);
9913 //      cps->pr = NoProc;
9914 //      cps->isr = NULL;
9915         return;
9916     }
9917
9918     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9919     if (cps->protocolVersion > 1) {
9920       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9921       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9922       cps->comboCnt = 0;  //                and values of combo boxes
9923       SendToProgram(buf, cps);
9924     } else {
9925       SendToProgram("xboard\n", cps);
9926     }
9927 }
9928
9929 void
9930 TwoMachinesEventIfReady P((void))
9931 {
9932   static int curMess = 0;
9933   if (first.lastPing != first.lastPong) {
9934     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9935     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9936     return;
9937   }
9938   if (second.lastPing != second.lastPong) {
9939     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9940     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9941     return;
9942   }
9943   DisplayMessage("", ""); curMess = 0;
9944   ThawUI();
9945   TwoMachinesEvent();
9946 }
9947
9948 char *
9949 MakeName (char *template)
9950 {
9951     time_t clock;
9952     struct tm *tm;
9953     static char buf[MSG_SIZ];
9954     char *p = buf;
9955     int i;
9956
9957     clock = time((time_t *)NULL);
9958     tm = localtime(&clock);
9959
9960     while(*p++ = *template++) if(p[-1] == '%') {
9961         switch(*template++) {
9962           case 0:   *p = 0; return buf;
9963           case 'Y': i = tm->tm_year+1900; break;
9964           case 'y': i = tm->tm_year-100; break;
9965           case 'M': i = tm->tm_mon+1; break;
9966           case 'd': i = tm->tm_mday; break;
9967           case 'h': i = tm->tm_hour; break;
9968           case 'm': i = tm->tm_min; break;
9969           case 's': i = tm->tm_sec; break;
9970           default:  i = 0;
9971         }
9972         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9973     }
9974     return buf;
9975 }
9976
9977 int
9978 CountPlayers (char *p)
9979 {
9980     int n = 0;
9981     while(p = strchr(p, '\n')) p++, n++; // count participants
9982     return n;
9983 }
9984
9985 FILE *
9986 WriteTourneyFile (char *results, FILE *f)
9987 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9988     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9989     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9990         // create a file with tournament description
9991         fprintf(f, "-participants {%s}\n", appData.participants);
9992         fprintf(f, "-seedBase %d\n", appData.seedBase);
9993         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9994         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9995         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9996         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9997         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9998         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9999         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10000         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10001         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10002         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10003         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10004         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10005         if(searchTime > 0)
10006                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10007         else {
10008                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10009                 fprintf(f, "-tc %s\n", appData.timeControl);
10010                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10011         }
10012         fprintf(f, "-results \"%s\"\n", results);
10013     }
10014     return f;
10015 }
10016
10017 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10018
10019 void
10020 Substitute (char *participants, int expunge)
10021 {
10022     int i, changed, changes=0, nPlayers=0;
10023     char *p, *q, *r, buf[MSG_SIZ];
10024     if(participants == NULL) return;
10025     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10026     r = p = participants; q = appData.participants;
10027     while(*p && *p == *q) {
10028         if(*p == '\n') r = p+1, nPlayers++;
10029         p++; q++;
10030     }
10031     if(*p) { // difference
10032         while(*p && *p++ != '\n');
10033         while(*q && *q++ != '\n');
10034       changed = nPlayers;
10035         changes = 1 + (strcmp(p, q) != 0);
10036     }
10037     if(changes == 1) { // a single engine mnemonic was changed
10038         q = r; while(*q) nPlayers += (*q++ == '\n');
10039         p = buf; while(*r && (*p = *r++) != '\n') p++;
10040         *p = NULLCHAR;
10041         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10042         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10043         if(mnemonic[i]) { // The substitute is valid
10044             FILE *f;
10045             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10046                 flock(fileno(f), LOCK_EX);
10047                 ParseArgsFromFile(f);
10048                 fseek(f, 0, SEEK_SET);
10049                 FREE(appData.participants); appData.participants = participants;
10050                 if(expunge) { // erase results of replaced engine
10051                     int len = strlen(appData.results), w, b, dummy;
10052                     for(i=0; i<len; i++) {
10053                         Pairing(i, nPlayers, &w, &b, &dummy);
10054                         if((w == changed || b == changed) && appData.results[i] == '*') {
10055                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10056                             fclose(f);
10057                             return;
10058                         }
10059                     }
10060                     for(i=0; i<len; i++) {
10061                         Pairing(i, nPlayers, &w, &b, &dummy);
10062                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10063                     }
10064                 }
10065                 WriteTourneyFile(appData.results, f);
10066                 fclose(f); // release lock
10067                 return;
10068             }
10069         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10070     }
10071     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10072     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10073     free(participants);
10074     return;
10075 }
10076
10077 int
10078 CheckPlayers (char *participants)
10079 {
10080         int i;
10081         char buf[MSG_SIZ], *p;
10082         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10083         while(p = strchr(participants, '\n')) {
10084             *p = NULLCHAR;
10085             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10086             if(!mnemonic[i]) {
10087                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10088                 *p = '\n';
10089                 DisplayError(buf, 0);
10090                 return 1;
10091             }
10092             *p = '\n';
10093             participants = p + 1;
10094         }
10095         return 0;
10096 }
10097
10098 int
10099 CreateTourney (char *name)
10100 {
10101         FILE *f;
10102         if(matchMode && strcmp(name, appData.tourneyFile)) {
10103              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10104         }
10105         if(name[0] == NULLCHAR) {
10106             if(appData.participants[0])
10107                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10108             return 0;
10109         }
10110         f = fopen(name, "r");
10111         if(f) { // file exists
10112             ASSIGN(appData.tourneyFile, name);
10113             ParseArgsFromFile(f); // parse it
10114         } else {
10115             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10116             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10117                 DisplayError(_("Not enough participants"), 0);
10118                 return 0;
10119             }
10120             if(CheckPlayers(appData.participants)) return 0;
10121             ASSIGN(appData.tourneyFile, name);
10122             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10123             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10124         }
10125         fclose(f);
10126         appData.noChessProgram = FALSE;
10127         appData.clockMode = TRUE;
10128         SetGNUMode();
10129         return 1;
10130 }
10131
10132 int
10133 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10134 {
10135     char buf[MSG_SIZ], *p, *q;
10136     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10137     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10138     skip = !all && group[0]; // if group requested, we start in skip mode
10139     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10140         p = names; q = buf; header = 0;
10141         while(*p && *p != '\n') *q++ = *p++;
10142         *q = 0;
10143         if(*p == '\n') p++;
10144         if(buf[0] == '#') {
10145             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10146             depth++; // we must be entering a new group
10147             if(all) continue; // suppress printing group headers when complete list requested
10148             header = 1;
10149             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10150         }
10151         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10152         if(engineList[i]) free(engineList[i]);
10153         engineList[i] = strdup(buf);
10154         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10155         if(engineMnemonic[i]) free(engineMnemonic[i]);
10156         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10157             strcat(buf, " (");
10158             sscanf(q + 8, "%s", buf + strlen(buf));
10159             strcat(buf, ")");
10160         }
10161         engineMnemonic[i] = strdup(buf);
10162         i++;
10163     }
10164     engineList[i] = engineMnemonic[i] = NULL;
10165     return i;
10166 }
10167
10168 // following implemented as macro to avoid type limitations
10169 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10170
10171 void
10172 SwapEngines (int n)
10173 {   // swap settings for first engine and other engine (so far only some selected options)
10174     int h;
10175     char *p;
10176     if(n == 0) return;
10177     SWAP(directory, p)
10178     SWAP(chessProgram, p)
10179     SWAP(isUCI, h)
10180     SWAP(hasOwnBookUCI, h)
10181     SWAP(protocolVersion, h)
10182     SWAP(reuse, h)
10183     SWAP(scoreIsAbsolute, h)
10184     SWAP(timeOdds, h)
10185     SWAP(logo, p)
10186     SWAP(pgnName, p)
10187     SWAP(pvSAN, h)
10188     SWAP(engOptions, p)
10189     SWAP(engInitString, p)
10190     SWAP(computerString, p)
10191     SWAP(features, p)
10192     SWAP(fenOverride, p)
10193     SWAP(NPS, h)
10194     SWAP(accumulateTC, h)
10195     SWAP(host, p)
10196 }
10197
10198 int
10199 GetEngineLine (char *s, int n)
10200 {
10201     int i;
10202     char buf[MSG_SIZ];
10203     extern char *icsNames;
10204     if(!s || !*s) return 0;
10205     NamesToList(n == 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10206     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10207     if(!mnemonic[i]) return 0;
10208     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10209     if(n == 1) SwapEngines(n);
10210     ParseArgsFromString(buf);
10211     if(n == 1) SwapEngines(n);
10212     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10213         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10214         ParseArgsFromString(buf);
10215     }
10216     return 1;
10217 }
10218
10219 int
10220 SetPlayer (int player, char *p)
10221 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10222     int i;
10223     char buf[MSG_SIZ], *engineName;
10224     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10225     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10226     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10227     if(mnemonic[i]) {
10228         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10229         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10230         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10231         ParseArgsFromString(buf);
10232     }
10233     free(engineName);
10234     return i;
10235 }
10236
10237 char *recentEngines;
10238
10239 void
10240 RecentEngineEvent (int nr)
10241 {
10242     int n;
10243 //    SwapEngines(1); // bump first to second
10244 //    ReplaceEngine(&second, 1); // and load it there
10245     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10246     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10247     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10248         ReplaceEngine(&first, 0);
10249         FloatToFront(&appData.recentEngineList, command[n]);
10250     }
10251 }
10252
10253 int
10254 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10255 {   // determine players from game number
10256     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10257
10258     if(appData.tourneyType == 0) {
10259         roundsPerCycle = (nPlayers - 1) | 1;
10260         pairingsPerRound = nPlayers / 2;
10261     } else if(appData.tourneyType > 0) {
10262         roundsPerCycle = nPlayers - appData.tourneyType;
10263         pairingsPerRound = appData.tourneyType;
10264     }
10265     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10266     gamesPerCycle = gamesPerRound * roundsPerCycle;
10267     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10268     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10269     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10270     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10271     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10272     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10273
10274     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10275     if(appData.roundSync) *syncInterval = gamesPerRound;
10276
10277     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10278
10279     if(appData.tourneyType == 0) {
10280         if(curPairing == (nPlayers-1)/2 ) {
10281             *whitePlayer = curRound;
10282             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10283         } else {
10284             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10285             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10286             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10287             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10288         }
10289     } else if(appData.tourneyType > 1) {
10290         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10291         *whitePlayer = curRound + appData.tourneyType;
10292     } else if(appData.tourneyType > 0) {
10293         *whitePlayer = curPairing;
10294         *blackPlayer = curRound + appData.tourneyType;
10295     }
10296
10297     // take care of white/black alternation per round. 
10298     // For cycles and games this is already taken care of by default, derived from matchGame!
10299     return curRound & 1;
10300 }
10301
10302 int
10303 NextTourneyGame (int nr, int *swapColors)
10304 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10305     char *p, *q;
10306     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10307     FILE *tf;
10308     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10309     tf = fopen(appData.tourneyFile, "r");
10310     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10311     ParseArgsFromFile(tf); fclose(tf);
10312     InitTimeControls(); // TC might be altered from tourney file
10313
10314     nPlayers = CountPlayers(appData.participants); // count participants
10315     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10316     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10317
10318     if(syncInterval) {
10319         p = q = appData.results;
10320         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10321         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10322             DisplayMessage(_("Waiting for other game(s)"),"");
10323             waitingForGame = TRUE;
10324             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10325             return 0;
10326         }
10327         waitingForGame = FALSE;
10328     }
10329
10330     if(appData.tourneyType < 0) {
10331         if(nr>=0 && !pairingReceived) {
10332             char buf[1<<16];
10333             if(pairing.pr == NoProc) {
10334                 if(!appData.pairingEngine[0]) {
10335                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10336                     return 0;
10337                 }
10338                 StartChessProgram(&pairing); // starts the pairing engine
10339             }
10340             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10341             SendToProgram(buf, &pairing);
10342             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10343             SendToProgram(buf, &pairing);
10344             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10345         }
10346         pairingReceived = 0;                              // ... so we continue here 
10347         *swapColors = 0;
10348         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10349         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10350         matchGame = 1; roundNr = nr / syncInterval + 1;
10351     }
10352
10353     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10354
10355     // redefine engines, engine dir, etc.
10356     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10357     if(first.pr == NoProc) {
10358       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10359       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10360     }
10361     if(second.pr == NoProc) {
10362       SwapEngines(1);
10363       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10364       SwapEngines(1);         // and make that valid for second engine by swapping
10365       InitEngine(&second, 1);
10366     }
10367     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10368     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10369     return 1;
10370 }
10371
10372 void
10373 NextMatchGame ()
10374 {   // performs game initialization that does not invoke engines, and then tries to start the game
10375     int res, firstWhite, swapColors = 0;
10376     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10377     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
10378         char buf[MSG_SIZ];
10379         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10380         if(strcmp(buf, currentDebugFile)) { // name has changed
10381             FILE *f = fopen(buf, "w");
10382             if(f) { // if opening the new file failed, just keep using the old one
10383                 ASSIGN(currentDebugFile, buf);
10384                 fclose(debugFP);
10385                 debugFP = f;
10386             }
10387             if(appData.serverFileName) {
10388                 if(serverFP) fclose(serverFP);
10389                 serverFP = fopen(appData.serverFileName, "w");
10390                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10391                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10392             }
10393         }
10394     }
10395     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10396     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10397     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10398     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10399     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10400     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10401     Reset(FALSE, first.pr != NoProc);
10402     res = LoadGameOrPosition(matchGame); // setup game
10403     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10404     if(!res) return; // abort when bad game/pos file
10405     TwoMachinesEvent();
10406 }
10407
10408 void
10409 UserAdjudicationEvent (int result)
10410 {
10411     ChessMove gameResult = GameIsDrawn;
10412
10413     if( result > 0 ) {
10414         gameResult = WhiteWins;
10415     }
10416     else if( result < 0 ) {
10417         gameResult = BlackWins;
10418     }
10419
10420     if( gameMode == TwoMachinesPlay ) {
10421         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10422     }
10423 }
10424
10425
10426 // [HGM] save: calculate checksum of game to make games easily identifiable
10427 int
10428 StringCheckSum (char *s)
10429 {
10430         int i = 0;
10431         if(s==NULL) return 0;
10432         while(*s) i = i*259 + *s++;
10433         return i;
10434 }
10435
10436 int
10437 GameCheckSum ()
10438 {
10439         int i, sum=0;
10440         for(i=backwardMostMove; i<forwardMostMove; i++) {
10441                 sum += pvInfoList[i].depth;
10442                 sum += StringCheckSum(parseList[i]);
10443                 sum += StringCheckSum(commentList[i]);
10444                 sum *= 261;
10445         }
10446         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10447         return sum + StringCheckSum(commentList[i]);
10448 } // end of save patch
10449
10450 void
10451 GameEnds (ChessMove result, char *resultDetails, int whosays)
10452 {
10453     GameMode nextGameMode;
10454     int isIcsGame;
10455     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10456
10457     if(endingGame) return; /* [HGM] crash: forbid recursion */
10458     endingGame = 1;
10459     if(twoBoards) { // [HGM] dual: switch back to one board
10460         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10461         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10462     }
10463     if (appData.debugMode) {
10464       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10465               result, resultDetails ? resultDetails : "(null)", whosays);
10466     }
10467
10468     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10469
10470     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10471         /* If we are playing on ICS, the server decides when the
10472            game is over, but the engine can offer to draw, claim
10473            a draw, or resign.
10474          */
10475 #if ZIPPY
10476         if (appData.zippyPlay && first.initDone) {
10477             if (result == GameIsDrawn) {
10478                 /* In case draw still needs to be claimed */
10479                 SendToICS(ics_prefix);
10480                 SendToICS("draw\n");
10481             } else if (StrCaseStr(resultDetails, "resign")) {
10482                 SendToICS(ics_prefix);
10483                 SendToICS("resign\n");
10484             }
10485         }
10486 #endif
10487         endingGame = 0; /* [HGM] crash */
10488         return;
10489     }
10490
10491     /* If we're loading the game from a file, stop */
10492     if (whosays == GE_FILE) {
10493       (void) StopLoadGameTimer();
10494       gameFileFP = NULL;
10495     }
10496
10497     /* Cancel draw offers */
10498     first.offeredDraw = second.offeredDraw = 0;
10499
10500     /* If this is an ICS game, only ICS can really say it's done;
10501        if not, anyone can. */
10502     isIcsGame = (gameMode == IcsPlayingWhite ||
10503                  gameMode == IcsPlayingBlack ||
10504                  gameMode == IcsObserving    ||
10505                  gameMode == IcsExamining);
10506
10507     if (!isIcsGame || whosays == GE_ICS) {
10508         /* OK -- not an ICS game, or ICS said it was done */
10509         StopClocks();
10510         if (!isIcsGame && !appData.noChessProgram)
10511           SetUserThinkingEnables();
10512
10513         /* [HGM] if a machine claims the game end we verify this claim */
10514         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10515             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10516                 char claimer;
10517                 ChessMove trueResult = (ChessMove) -1;
10518
10519                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10520                                             first.twoMachinesColor[0] :
10521                                             second.twoMachinesColor[0] ;
10522
10523                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10524                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10525                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10526                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10527                 } else
10528                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10529                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10530                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10531                 } else
10532                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10533                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10534                 }
10535
10536                 // now verify win claims, but not in drop games, as we don't understand those yet
10537                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10538                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10539                     (result == WhiteWins && claimer == 'w' ||
10540                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10541                       if (appData.debugMode) {
10542                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10543                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10544                       }
10545                       if(result != trueResult) {
10546                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10547                               result = claimer == 'w' ? BlackWins : WhiteWins;
10548                               resultDetails = buf;
10549                       }
10550                 } else
10551                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10552                     && (forwardMostMove <= backwardMostMove ||
10553                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10554                         (claimer=='b')==(forwardMostMove&1))
10555                                                                                   ) {
10556                       /* [HGM] verify: draws that were not flagged are false claims */
10557                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10558                       result = claimer == 'w' ? BlackWins : WhiteWins;
10559                       resultDetails = buf;
10560                 }
10561                 /* (Claiming a loss is accepted no questions asked!) */
10562             }
10563             /* [HGM] bare: don't allow bare King to win */
10564             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10565                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10566                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10567                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10568                && result != GameIsDrawn)
10569             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10570                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10571                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10572                         if(p >= 0 && p <= (int)WhiteKing) k++;
10573                 }
10574                 if (appData.debugMode) {
10575                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10576                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10577                 }
10578                 if(k <= 1) {
10579                         result = GameIsDrawn;
10580                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10581                         resultDetails = buf;
10582                 }
10583             }
10584         }
10585
10586
10587         if(serverMoves != NULL && !loadFlag) { char c = '=';
10588             if(result==WhiteWins) c = '+';
10589             if(result==BlackWins) c = '-';
10590             if(resultDetails != NULL)
10591                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10592         }
10593         if (resultDetails != NULL) {
10594             gameInfo.result = result;
10595             gameInfo.resultDetails = StrSave(resultDetails);
10596
10597             /* display last move only if game was not loaded from file */
10598             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10599                 DisplayMove(currentMove - 1);
10600
10601             if (forwardMostMove != 0) {
10602                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10603                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10604                                                                 ) {
10605                     if (*appData.saveGameFile != NULLCHAR) {
10606                         SaveGameToFile(appData.saveGameFile, TRUE);
10607                     } else if (appData.autoSaveGames) {
10608                         AutoSaveGame();
10609                     }
10610                     if (*appData.savePositionFile != NULLCHAR) {
10611                         SavePositionToFile(appData.savePositionFile);
10612                     }
10613                 }
10614             }
10615
10616             /* Tell program how game ended in case it is learning */
10617             /* [HGM] Moved this to after saving the PGN, just in case */
10618             /* engine died and we got here through time loss. In that */
10619             /* case we will get a fatal error writing the pipe, which */
10620             /* would otherwise lose us the PGN.                       */
10621             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10622             /* output during GameEnds should never be fatal anymore   */
10623             if (gameMode == MachinePlaysWhite ||
10624                 gameMode == MachinePlaysBlack ||
10625                 gameMode == TwoMachinesPlay ||
10626                 gameMode == IcsPlayingWhite ||
10627                 gameMode == IcsPlayingBlack ||
10628                 gameMode == BeginningOfGame) {
10629                 char buf[MSG_SIZ];
10630                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10631                         resultDetails);
10632                 if (first.pr != NoProc) {
10633                     SendToProgram(buf, &first);
10634                 }
10635                 if (second.pr != NoProc &&
10636                     gameMode == TwoMachinesPlay) {
10637                     SendToProgram(buf, &second);
10638                 }
10639             }
10640         }
10641
10642         if (appData.icsActive) {
10643             if (appData.quietPlay &&
10644                 (gameMode == IcsPlayingWhite ||
10645                  gameMode == IcsPlayingBlack)) {
10646                 SendToICS(ics_prefix);
10647                 SendToICS("set shout 1\n");
10648             }
10649             nextGameMode = IcsIdle;
10650             ics_user_moved = FALSE;
10651             /* clean up premove.  It's ugly when the game has ended and the
10652              * premove highlights are still on the board.
10653              */
10654             if (gotPremove) {
10655               gotPremove = FALSE;
10656               ClearPremoveHighlights();
10657               DrawPosition(FALSE, boards[currentMove]);
10658             }
10659             if (whosays == GE_ICS) {
10660                 switch (result) {
10661                 case WhiteWins:
10662                     if (gameMode == IcsPlayingWhite)
10663                         PlayIcsWinSound();
10664                     else if(gameMode == IcsPlayingBlack)
10665                         PlayIcsLossSound();
10666                     break;
10667                 case BlackWins:
10668                     if (gameMode == IcsPlayingBlack)
10669                         PlayIcsWinSound();
10670                     else if(gameMode == IcsPlayingWhite)
10671                         PlayIcsLossSound();
10672                     break;
10673                 case GameIsDrawn:
10674                     PlayIcsDrawSound();
10675                     break;
10676                 default:
10677                     PlayIcsUnfinishedSound();
10678                 }
10679             }
10680         } else if (gameMode == EditGame ||
10681                    gameMode == PlayFromGameFile ||
10682                    gameMode == AnalyzeMode ||
10683                    gameMode == AnalyzeFile) {
10684             nextGameMode = gameMode;
10685         } else {
10686             nextGameMode = EndOfGame;
10687         }
10688         pausing = FALSE;
10689         ModeHighlight();
10690     } else {
10691         nextGameMode = gameMode;
10692     }
10693
10694     if (appData.noChessProgram) {
10695         gameMode = nextGameMode;
10696         ModeHighlight();
10697         endingGame = 0; /* [HGM] crash */
10698         return;
10699     }
10700
10701     if (first.reuse) {
10702         /* Put first chess program into idle state */
10703         if (first.pr != NoProc &&
10704             (gameMode == MachinePlaysWhite ||
10705              gameMode == MachinePlaysBlack ||
10706              gameMode == TwoMachinesPlay ||
10707              gameMode == IcsPlayingWhite ||
10708              gameMode == IcsPlayingBlack ||
10709              gameMode == BeginningOfGame)) {
10710             SendToProgram("force\n", &first);
10711             if (first.usePing) {
10712               char buf[MSG_SIZ];
10713               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10714               SendToProgram(buf, &first);
10715             }
10716         }
10717     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10718         /* Kill off first chess program */
10719         if (first.isr != NULL)
10720           RemoveInputSource(first.isr);
10721         first.isr = NULL;
10722
10723         if (first.pr != NoProc) {
10724             ExitAnalyzeMode();
10725             DoSleep( appData.delayBeforeQuit );
10726             SendToProgram("quit\n", &first);
10727             DoSleep( appData.delayAfterQuit );
10728             DestroyChildProcess(first.pr, first.useSigterm);
10729         }
10730         first.pr = NoProc;
10731     }
10732     if (second.reuse) {
10733         /* Put second chess program into idle state */
10734         if (second.pr != NoProc &&
10735             gameMode == TwoMachinesPlay) {
10736             SendToProgram("force\n", &second);
10737             if (second.usePing) {
10738               char buf[MSG_SIZ];
10739               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10740               SendToProgram(buf, &second);
10741             }
10742         }
10743     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10744         /* Kill off second chess program */
10745         if (second.isr != NULL)
10746           RemoveInputSource(second.isr);
10747         second.isr = NULL;
10748
10749         if (second.pr != NoProc) {
10750             DoSleep( appData.delayBeforeQuit );
10751             SendToProgram("quit\n", &second);
10752             DoSleep( appData.delayAfterQuit );
10753             DestroyChildProcess(second.pr, second.useSigterm);
10754         }
10755         second.pr = NoProc;
10756     }
10757
10758     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10759         char resChar = '=';
10760         switch (result) {
10761         case WhiteWins:
10762           resChar = '+';
10763           if (first.twoMachinesColor[0] == 'w') {
10764             first.matchWins++;
10765           } else {
10766             second.matchWins++;
10767           }
10768           break;
10769         case BlackWins:
10770           resChar = '-';
10771           if (first.twoMachinesColor[0] == 'b') {
10772             first.matchWins++;
10773           } else {
10774             second.matchWins++;
10775           }
10776           break;
10777         case GameUnfinished:
10778           resChar = ' ';
10779         default:
10780           break;
10781         }
10782
10783         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10784         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10785             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10786             ReserveGame(nextGame, resChar); // sets nextGame
10787             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10788             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10789         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10790
10791         if (nextGame <= appData.matchGames && !abortMatch) {
10792             gameMode = nextGameMode;
10793             matchGame = nextGame; // this will be overruled in tourney mode!
10794             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10795             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10796             endingGame = 0; /* [HGM] crash */
10797             return;
10798         } else {
10799             gameMode = nextGameMode;
10800             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10801                      first.tidy, second.tidy,
10802                      first.matchWins, second.matchWins,
10803                      appData.matchGames - (first.matchWins + second.matchWins));
10804             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10805             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10806             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10807             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10808                 first.twoMachinesColor = "black\n";
10809                 second.twoMachinesColor = "white\n";
10810             } else {
10811                 first.twoMachinesColor = "white\n";
10812                 second.twoMachinesColor = "black\n";
10813             }
10814         }
10815     }
10816     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10817         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10818       ExitAnalyzeMode();
10819     gameMode = nextGameMode;
10820     ModeHighlight();
10821     endingGame = 0;  /* [HGM] crash */
10822     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10823         if(matchMode == TRUE) { // match through command line: exit with or without popup
10824             if(ranking) {
10825                 ToNrEvent(forwardMostMove);
10826                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10827                 else ExitEvent(0);
10828             } else DisplayFatalError(buf, 0, 0);
10829         } else { // match through menu; just stop, with or without popup
10830             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10831             ModeHighlight();
10832             if(ranking){
10833                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10834             } else DisplayNote(buf);
10835       }
10836       if(ranking) free(ranking);
10837     }
10838 }
10839
10840 /* Assumes program was just initialized (initString sent).
10841    Leaves program in force mode. */
10842 void
10843 FeedMovesToProgram (ChessProgramState *cps, int upto)
10844 {
10845     int i;
10846
10847     if (appData.debugMode)
10848       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10849               startedFromSetupPosition ? "position and " : "",
10850               backwardMostMove, upto, cps->which);
10851     if(currentlyInitializedVariant != gameInfo.variant) {
10852       char buf[MSG_SIZ];
10853         // [HGM] variantswitch: make engine aware of new variant
10854         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10855                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10856         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10857         SendToProgram(buf, cps);
10858         currentlyInitializedVariant = gameInfo.variant;
10859     }
10860     SendToProgram("force\n", cps);
10861     if (startedFromSetupPosition) {
10862         SendBoard(cps, backwardMostMove);
10863     if (appData.debugMode) {
10864         fprintf(debugFP, "feedMoves\n");
10865     }
10866     }
10867     for (i = backwardMostMove; i < upto; i++) {
10868         SendMoveToProgram(i, cps);
10869     }
10870 }
10871
10872
10873 int
10874 ResurrectChessProgram ()
10875 {
10876      /* The chess program may have exited.
10877         If so, restart it and feed it all the moves made so far. */
10878     static int doInit = 0;
10879
10880     if (appData.noChessProgram) return 1;
10881
10882     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10883         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10884         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10885         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10886     } else {
10887         if (first.pr != NoProc) return 1;
10888         StartChessProgram(&first);
10889     }
10890     InitChessProgram(&first, FALSE);
10891     FeedMovesToProgram(&first, currentMove);
10892
10893     if (!first.sendTime) {
10894         /* can't tell gnuchess what its clock should read,
10895            so we bow to its notion. */
10896         ResetClocks();
10897         timeRemaining[0][currentMove] = whiteTimeRemaining;
10898         timeRemaining[1][currentMove] = blackTimeRemaining;
10899     }
10900
10901     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10902                 appData.icsEngineAnalyze) && first.analysisSupport) {
10903       SendToProgram("analyze\n", &first);
10904       first.analyzing = TRUE;
10905     }
10906     return 1;
10907 }
10908
10909 /*
10910  * Button procedures
10911  */
10912 void
10913 Reset (int redraw, int init)
10914 {
10915     int i;
10916
10917     if (appData.debugMode) {
10918         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10919                 redraw, init, gameMode);
10920     }
10921     CleanupTail(); // [HGM] vari: delete any stored variations
10922     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10923     pausing = pauseExamInvalid = FALSE;
10924     startedFromSetupPosition = blackPlaysFirst = FALSE;
10925     firstMove = TRUE;
10926     whiteFlag = blackFlag = FALSE;
10927     userOfferedDraw = FALSE;
10928     hintRequested = bookRequested = FALSE;
10929     first.maybeThinking = FALSE;
10930     second.maybeThinking = FALSE;
10931     first.bookSuspend = FALSE; // [HGM] book
10932     second.bookSuspend = FALSE;
10933     thinkOutput[0] = NULLCHAR;
10934     lastHint[0] = NULLCHAR;
10935     ClearGameInfo(&gameInfo);
10936     gameInfo.variant = StringToVariant(appData.variant);
10937     ics_user_moved = ics_clock_paused = FALSE;
10938     ics_getting_history = H_FALSE;
10939     ics_gamenum = -1;
10940     white_holding[0] = black_holding[0] = NULLCHAR;
10941     ClearProgramStats();
10942     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10943
10944     ResetFrontEnd();
10945     ClearHighlights();
10946     flipView = appData.flipView;
10947     ClearPremoveHighlights();
10948     gotPremove = FALSE;
10949     alarmSounded = FALSE;
10950
10951     GameEnds(EndOfFile, NULL, GE_PLAYER);
10952     if(appData.serverMovesName != NULL) {
10953         /* [HGM] prepare to make moves file for broadcasting */
10954         clock_t t = clock();
10955         if(serverMoves != NULL) fclose(serverMoves);
10956         serverMoves = fopen(appData.serverMovesName, "r");
10957         if(serverMoves != NULL) {
10958             fclose(serverMoves);
10959             /* delay 15 sec before overwriting, so all clients can see end */
10960             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10961         }
10962         serverMoves = fopen(appData.serverMovesName, "w");
10963     }
10964
10965     ExitAnalyzeMode();
10966     gameMode = BeginningOfGame;
10967     ModeHighlight();
10968     if(appData.icsActive) gameInfo.variant = VariantNormal;
10969     currentMove = forwardMostMove = backwardMostMove = 0;
10970     MarkTargetSquares(1);
10971     InitPosition(redraw);
10972     for (i = 0; i < MAX_MOVES; i++) {
10973         if (commentList[i] != NULL) {
10974             free(commentList[i]);
10975             commentList[i] = NULL;
10976         }
10977     }
10978     ResetClocks();
10979     timeRemaining[0][0] = whiteTimeRemaining;
10980     timeRemaining[1][0] = blackTimeRemaining;
10981
10982     if (first.pr == NoProc) {
10983         StartChessProgram(&first);
10984     }
10985     if (init) {
10986             InitChessProgram(&first, startedFromSetupPosition);
10987     }
10988     DisplayTitle("");
10989     DisplayMessage("", "");
10990     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10991     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10992     ClearMap();        // [HGM] exclude: invalidate map
10993 }
10994
10995 void
10996 AutoPlayGameLoop ()
10997 {
10998     for (;;) {
10999         if (!AutoPlayOneMove())
11000           return;
11001         if (matchMode || appData.timeDelay == 0)
11002           continue;
11003         if (appData.timeDelay < 0)
11004           return;
11005         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11006         break;
11007     }
11008 }
11009
11010
11011 int
11012 AutoPlayOneMove ()
11013 {
11014     int fromX, fromY, toX, toY;
11015
11016     if (appData.debugMode) {
11017       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11018     }
11019
11020     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11021       return FALSE;
11022
11023     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
11024       pvInfoList[currentMove].depth = programStats.depth;
11025       pvInfoList[currentMove].score = programStats.score;
11026       pvInfoList[currentMove].time  = 0;
11027       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11028     }
11029
11030     if (currentMove >= forwardMostMove) {
11031       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
11032 //      gameMode = EndOfGame;
11033 //      ModeHighlight();
11034
11035       /* [AS] Clear current move marker at the end of a game */
11036       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11037
11038       return FALSE;
11039     }
11040
11041     toX = moveList[currentMove][2] - AAA;
11042     toY = moveList[currentMove][3] - ONE;
11043
11044     if (moveList[currentMove][1] == '@') {
11045         if (appData.highlightLastMove) {
11046             SetHighlights(-1, -1, toX, toY);
11047         }
11048     } else {
11049         fromX = moveList[currentMove][0] - AAA;
11050         fromY = moveList[currentMove][1] - ONE;
11051
11052         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11053
11054         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11055
11056         if (appData.highlightLastMove) {
11057             SetHighlights(fromX, fromY, toX, toY);
11058         }
11059     }
11060     DisplayMove(currentMove);
11061     SendMoveToProgram(currentMove++, &first);
11062     DisplayBothClocks();
11063     DrawPosition(FALSE, boards[currentMove]);
11064     // [HGM] PV info: always display, routine tests if empty
11065     DisplayComment(currentMove - 1, commentList[currentMove]);
11066     return TRUE;
11067 }
11068
11069
11070 int
11071 LoadGameOneMove (ChessMove readAhead)
11072 {
11073     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11074     char promoChar = NULLCHAR;
11075     ChessMove moveType;
11076     char move[MSG_SIZ];
11077     char *p, *q;
11078
11079     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11080         gameMode != AnalyzeMode && gameMode != Training) {
11081         gameFileFP = NULL;
11082         return FALSE;
11083     }
11084
11085     yyboardindex = forwardMostMove;
11086     if (readAhead != EndOfFile) {
11087       moveType = readAhead;
11088     } else {
11089       if (gameFileFP == NULL)
11090           return FALSE;
11091       moveType = (ChessMove) Myylex();
11092     }
11093
11094     done = FALSE;
11095     switch (moveType) {
11096       case Comment:
11097         if (appData.debugMode)
11098           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11099         p = yy_text;
11100
11101         /* append the comment but don't display it */
11102         AppendComment(currentMove, p, FALSE);
11103         return TRUE;
11104
11105       case WhiteCapturesEnPassant:
11106       case BlackCapturesEnPassant:
11107       case WhitePromotion:
11108       case BlackPromotion:
11109       case WhiteNonPromotion:
11110       case BlackNonPromotion:
11111       case NormalMove:
11112       case WhiteKingSideCastle:
11113       case WhiteQueenSideCastle:
11114       case BlackKingSideCastle:
11115       case BlackQueenSideCastle:
11116       case WhiteKingSideCastleWild:
11117       case WhiteQueenSideCastleWild:
11118       case BlackKingSideCastleWild:
11119       case BlackQueenSideCastleWild:
11120       /* PUSH Fabien */
11121       case WhiteHSideCastleFR:
11122       case WhiteASideCastleFR:
11123       case BlackHSideCastleFR:
11124       case BlackASideCastleFR:
11125       /* POP Fabien */
11126         if (appData.debugMode)
11127           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11128         fromX = currentMoveString[0] - AAA;
11129         fromY = currentMoveString[1] - ONE;
11130         toX = currentMoveString[2] - AAA;
11131         toY = currentMoveString[3] - ONE;
11132         promoChar = currentMoveString[4];
11133         break;
11134
11135       case WhiteDrop:
11136       case BlackDrop:
11137         if (appData.debugMode)
11138           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11139         fromX = moveType == WhiteDrop ?
11140           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11141         (int) CharToPiece(ToLower(currentMoveString[0]));
11142         fromY = DROP_RANK;
11143         toX = currentMoveString[2] - AAA;
11144         toY = currentMoveString[3] - ONE;
11145         break;
11146
11147       case WhiteWins:
11148       case BlackWins:
11149       case GameIsDrawn:
11150       case GameUnfinished:
11151         if (appData.debugMode)
11152           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11153         p = strchr(yy_text, '{');
11154         if (p == NULL) p = strchr(yy_text, '(');
11155         if (p == NULL) {
11156             p = yy_text;
11157             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11158         } else {
11159             q = strchr(p, *p == '{' ? '}' : ')');
11160             if (q != NULL) *q = NULLCHAR;
11161             p++;
11162         }
11163         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11164         GameEnds(moveType, p, GE_FILE);
11165         done = TRUE;
11166         if (cmailMsgLoaded) {
11167             ClearHighlights();
11168             flipView = WhiteOnMove(currentMove);
11169             if (moveType == GameUnfinished) flipView = !flipView;
11170             if (appData.debugMode)
11171               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11172         }
11173         break;
11174
11175       case EndOfFile:
11176         if (appData.debugMode)
11177           fprintf(debugFP, "Parser hit end of file\n");
11178         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11179           case MT_NONE:
11180           case MT_CHECK:
11181             break;
11182           case MT_CHECKMATE:
11183           case MT_STAINMATE:
11184             if (WhiteOnMove(currentMove)) {
11185                 GameEnds(BlackWins, "Black mates", GE_FILE);
11186             } else {
11187                 GameEnds(WhiteWins, "White mates", GE_FILE);
11188             }
11189             break;
11190           case MT_STALEMATE:
11191             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11192             break;
11193         }
11194         done = TRUE;
11195         break;
11196
11197       case MoveNumberOne:
11198         if (lastLoadGameStart == GNUChessGame) {
11199             /* GNUChessGames have numbers, but they aren't move numbers */
11200             if (appData.debugMode)
11201               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11202                       yy_text, (int) moveType);
11203             return LoadGameOneMove(EndOfFile); /* tail recursion */
11204         }
11205         /* else fall thru */
11206
11207       case XBoardGame:
11208       case GNUChessGame:
11209       case PGNTag:
11210         /* Reached start of next game in file */
11211         if (appData.debugMode)
11212           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11213         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11214           case MT_NONE:
11215           case MT_CHECK:
11216             break;
11217           case MT_CHECKMATE:
11218           case MT_STAINMATE:
11219             if (WhiteOnMove(currentMove)) {
11220                 GameEnds(BlackWins, "Black mates", GE_FILE);
11221             } else {
11222                 GameEnds(WhiteWins, "White mates", GE_FILE);
11223             }
11224             break;
11225           case MT_STALEMATE:
11226             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11227             break;
11228         }
11229         done = TRUE;
11230         break;
11231
11232       case PositionDiagram:     /* should not happen; ignore */
11233       case ElapsedTime:         /* ignore */
11234       case NAG:                 /* ignore */
11235         if (appData.debugMode)
11236           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11237                   yy_text, (int) moveType);
11238         return LoadGameOneMove(EndOfFile); /* tail recursion */
11239
11240       case IllegalMove:
11241         if (appData.testLegality) {
11242             if (appData.debugMode)
11243               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11244             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11245                     (forwardMostMove / 2) + 1,
11246                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11247             DisplayError(move, 0);
11248             done = TRUE;
11249         } else {
11250             if (appData.debugMode)
11251               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11252                       yy_text, currentMoveString);
11253             fromX = currentMoveString[0] - AAA;
11254             fromY = currentMoveString[1] - ONE;
11255             toX = currentMoveString[2] - AAA;
11256             toY = currentMoveString[3] - ONE;
11257             promoChar = currentMoveString[4];
11258         }
11259         break;
11260
11261       case AmbiguousMove:
11262         if (appData.debugMode)
11263           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11264         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11265                 (forwardMostMove / 2) + 1,
11266                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11267         DisplayError(move, 0);
11268         done = TRUE;
11269         break;
11270
11271       default:
11272       case ImpossibleMove:
11273         if (appData.debugMode)
11274           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11275         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11276                 (forwardMostMove / 2) + 1,
11277                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11278         DisplayError(move, 0);
11279         done = TRUE;
11280         break;
11281     }
11282
11283     if (done) {
11284         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11285             DrawPosition(FALSE, boards[currentMove]);
11286             DisplayBothClocks();
11287             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11288               DisplayComment(currentMove - 1, commentList[currentMove]);
11289         }
11290         (void) StopLoadGameTimer();
11291         gameFileFP = NULL;
11292         cmailOldMove = forwardMostMove;
11293         return FALSE;
11294     } else {
11295         /* currentMoveString is set as a side-effect of yylex */
11296
11297         thinkOutput[0] = NULLCHAR;
11298         MakeMove(fromX, fromY, toX, toY, promoChar);
11299         currentMove = forwardMostMove;
11300         return TRUE;
11301     }
11302 }
11303
11304 /* Load the nth game from the given file */
11305 int
11306 LoadGameFromFile (char *filename, int n, char *title, int useList)
11307 {
11308     FILE *f;
11309     char buf[MSG_SIZ];
11310
11311     if (strcmp(filename, "-") == 0) {
11312         f = stdin;
11313         title = "stdin";
11314     } else {
11315         f = fopen(filename, "rb");
11316         if (f == NULL) {
11317           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11318             DisplayError(buf, errno);
11319             return FALSE;
11320         }
11321     }
11322     if (fseek(f, 0, 0) == -1) {
11323         /* f is not seekable; probably a pipe */
11324         useList = FALSE;
11325     }
11326     if (useList && n == 0) {
11327         int error = GameListBuild(f);
11328         if (error) {
11329             DisplayError(_("Cannot build game list"), error);
11330         } else if (!ListEmpty(&gameList) &&
11331                    ((ListGame *) gameList.tailPred)->number > 1) {
11332             GameListPopUp(f, title);
11333             return TRUE;
11334         }
11335         GameListDestroy();
11336         n = 1;
11337     }
11338     if (n == 0) n = 1;
11339     return LoadGame(f, n, title, FALSE);
11340 }
11341
11342
11343 void
11344 MakeRegisteredMove ()
11345 {
11346     int fromX, fromY, toX, toY;
11347     char promoChar;
11348     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11349         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11350           case CMAIL_MOVE:
11351           case CMAIL_DRAW:
11352             if (appData.debugMode)
11353               fprintf(debugFP, "Restoring %s for game %d\n",
11354                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11355
11356             thinkOutput[0] = NULLCHAR;
11357             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11358             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11359             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11360             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11361             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11362             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11363             MakeMove(fromX, fromY, toX, toY, promoChar);
11364             ShowMove(fromX, fromY, toX, toY);
11365
11366             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11367               case MT_NONE:
11368               case MT_CHECK:
11369                 break;
11370
11371               case MT_CHECKMATE:
11372               case MT_STAINMATE:
11373                 if (WhiteOnMove(currentMove)) {
11374                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11375                 } else {
11376                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11377                 }
11378                 break;
11379
11380               case MT_STALEMATE:
11381                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11382                 break;
11383             }
11384
11385             break;
11386
11387           case CMAIL_RESIGN:
11388             if (WhiteOnMove(currentMove)) {
11389                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11390             } else {
11391                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11392             }
11393             break;
11394
11395           case CMAIL_ACCEPT:
11396             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11397             break;
11398
11399           default:
11400             break;
11401         }
11402     }
11403
11404     return;
11405 }
11406
11407 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11408 int
11409 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11410 {
11411     int retVal;
11412
11413     if (gameNumber > nCmailGames) {
11414         DisplayError(_("No more games in this message"), 0);
11415         return FALSE;
11416     }
11417     if (f == lastLoadGameFP) {
11418         int offset = gameNumber - lastLoadGameNumber;
11419         if (offset == 0) {
11420             cmailMsg[0] = NULLCHAR;
11421             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11422                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11423                 nCmailMovesRegistered--;
11424             }
11425             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11426             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11427                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11428             }
11429         } else {
11430             if (! RegisterMove()) return FALSE;
11431         }
11432     }
11433
11434     retVal = LoadGame(f, gameNumber, title, useList);
11435
11436     /* Make move registered during previous look at this game, if any */
11437     MakeRegisteredMove();
11438
11439     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11440         commentList[currentMove]
11441           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11442         DisplayComment(currentMove - 1, commentList[currentMove]);
11443     }
11444
11445     return retVal;
11446 }
11447
11448 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11449 int
11450 ReloadGame (int offset)
11451 {
11452     int gameNumber = lastLoadGameNumber + offset;
11453     if (lastLoadGameFP == NULL) {
11454         DisplayError(_("No game has been loaded yet"), 0);
11455         return FALSE;
11456     }
11457     if (gameNumber <= 0) {
11458         DisplayError(_("Can't back up any further"), 0);
11459         return FALSE;
11460     }
11461     if (cmailMsgLoaded) {
11462         return CmailLoadGame(lastLoadGameFP, gameNumber,
11463                              lastLoadGameTitle, lastLoadGameUseList);
11464     } else {
11465         return LoadGame(lastLoadGameFP, gameNumber,
11466                         lastLoadGameTitle, lastLoadGameUseList);
11467     }
11468 }
11469
11470 int keys[EmptySquare+1];
11471
11472 int
11473 PositionMatches (Board b1, Board b2)
11474 {
11475     int r, f, sum=0;
11476     switch(appData.searchMode) {
11477         case 1: return CompareWithRights(b1, b2);
11478         case 2:
11479             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11480                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11481             }
11482             return TRUE;
11483         case 3:
11484             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11485               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11486                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11487             }
11488             return sum==0;
11489         case 4:
11490             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11491                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11492             }
11493             return sum==0;
11494     }
11495     return TRUE;
11496 }
11497
11498 #define Q_PROMO  4
11499 #define Q_EP     3
11500 #define Q_BCASTL 2
11501 #define Q_WCASTL 1
11502
11503 int pieceList[256], quickBoard[256];
11504 ChessSquare pieceType[256] = { EmptySquare };
11505 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11506 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11507 int soughtTotal, turn;
11508 Boolean epOK, flipSearch;
11509
11510 typedef struct {
11511     unsigned char piece, to;
11512 } Move;
11513
11514 #define DSIZE (250000)
11515
11516 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11517 Move *moveDatabase = initialSpace;
11518 unsigned int movePtr, dataSize = DSIZE;
11519
11520 int
11521 MakePieceList (Board board, int *counts)
11522 {
11523     int r, f, n=Q_PROMO, total=0;
11524     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11525     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11526         int sq = f + (r<<4);
11527         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11528             quickBoard[sq] = ++n;
11529             pieceList[n] = sq;
11530             pieceType[n] = board[r][f];
11531             counts[board[r][f]]++;
11532             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11533             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11534             total++;
11535         }
11536     }
11537     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11538     return total;
11539 }
11540
11541 void
11542 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11543 {
11544     int sq = fromX + (fromY<<4);
11545     int piece = quickBoard[sq];
11546     quickBoard[sq] = 0;
11547     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11548     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11549         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11550         moveDatabase[movePtr++].piece = Q_WCASTL;
11551         quickBoard[sq] = piece;
11552         piece = quickBoard[from]; quickBoard[from] = 0;
11553         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11554     } else
11555     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11556         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11557         moveDatabase[movePtr++].piece = Q_BCASTL;
11558         quickBoard[sq] = piece;
11559         piece = quickBoard[from]; quickBoard[from] = 0;
11560         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11561     } else
11562     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11563         quickBoard[(fromY<<4)+toX] = 0;
11564         moveDatabase[movePtr].piece = Q_EP;
11565         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11566         moveDatabase[movePtr].to = sq;
11567     } else
11568     if(promoPiece != pieceType[piece]) {
11569         moveDatabase[movePtr++].piece = Q_PROMO;
11570         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11571     }
11572     moveDatabase[movePtr].piece = piece;
11573     quickBoard[sq] = piece;
11574     movePtr++;
11575 }
11576
11577 int
11578 PackGame (Board board)
11579 {
11580     Move *newSpace = NULL;
11581     moveDatabase[movePtr].piece = 0; // terminate previous game
11582     if(movePtr > dataSize) {
11583         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11584         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11585         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11586         if(newSpace) {
11587             int i;
11588             Move *p = moveDatabase, *q = newSpace;
11589             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11590             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11591             moveDatabase = newSpace;
11592         } else { // calloc failed, we must be out of memory. Too bad...
11593             dataSize = 0; // prevent calloc events for all subsequent games
11594             return 0;     // and signal this one isn't cached
11595         }
11596     }
11597     movePtr++;
11598     MakePieceList(board, counts);
11599     return movePtr;
11600 }
11601
11602 int
11603 QuickCompare (Board board, int *minCounts, int *maxCounts)
11604 {   // compare according to search mode
11605     int r, f;
11606     switch(appData.searchMode)
11607     {
11608       case 1: // exact position match
11609         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11610         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11611             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11612         }
11613         break;
11614       case 2: // can have extra material on empty squares
11615         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11616             if(board[r][f] == EmptySquare) continue;
11617             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11618         }
11619         break;
11620       case 3: // material with exact Pawn structure
11621         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11622             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11623             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11624         } // fall through to material comparison
11625       case 4: // exact material
11626         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11627         break;
11628       case 6: // material range with given imbalance
11629         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11630         // fall through to range comparison
11631       case 5: // material range
11632         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11633     }
11634     return TRUE;
11635 }
11636
11637 int
11638 QuickScan (Board board, Move *move)
11639 {   // reconstruct game,and compare all positions in it
11640     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11641     do {
11642         int piece = move->piece;
11643         int to = move->to, from = pieceList[piece];
11644         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11645           if(!piece) return -1;
11646           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11647             piece = (++move)->piece;
11648             from = pieceList[piece];
11649             counts[pieceType[piece]]--;
11650             pieceType[piece] = (ChessSquare) move->to;
11651             counts[move->to]++;
11652           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11653             counts[pieceType[quickBoard[to]]]--;
11654             quickBoard[to] = 0; total--;
11655             move++;
11656             continue;
11657           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11658             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11659             from  = pieceList[piece]; // so this must be King
11660             quickBoard[from] = 0;
11661             pieceList[piece] = to;
11662             from = pieceList[(++move)->piece]; // for FRC this has to be done here
11663             quickBoard[from] = 0; // rook
11664             quickBoard[to] = piece;
11665             to = move->to; piece = move->piece;
11666             goto aftercastle;
11667           }
11668         }
11669         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11670         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11671         quickBoard[from] = 0;
11672       aftercastle:
11673         quickBoard[to] = piece;
11674         pieceList[piece] = to;
11675         cnt++; turn ^= 3;
11676         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11677            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11678            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11679                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11680           ) {
11681             static int lastCounts[EmptySquare+1];
11682             int i;
11683             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11684             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11685         } else stretch = 0;
11686         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11687         move++;
11688     } while(1);
11689 }
11690
11691 void
11692 InitSearch ()
11693 {
11694     int r, f;
11695     flipSearch = FALSE;
11696     CopyBoard(soughtBoard, boards[currentMove]);
11697     soughtTotal = MakePieceList(soughtBoard, maxSought);
11698     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11699     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11700     CopyBoard(reverseBoard, boards[currentMove]);
11701     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11702         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11703         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11704         reverseBoard[r][f] = piece;
11705     }
11706     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11707     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11708     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11709                  || (boards[currentMove][CASTLING][2] == NoRights || 
11710                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11711                  && (boards[currentMove][CASTLING][5] == NoRights || 
11712                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11713       ) {
11714         flipSearch = TRUE;
11715         CopyBoard(flipBoard, soughtBoard);
11716         CopyBoard(rotateBoard, reverseBoard);
11717         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11718             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11719             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11720         }
11721     }
11722     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11723     if(appData.searchMode >= 5) {
11724         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11725         MakePieceList(soughtBoard, minSought);
11726         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11727     }
11728     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11729         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11730 }
11731
11732 GameInfo dummyInfo;
11733
11734 int
11735 GameContainsPosition (FILE *f, ListGame *lg)
11736 {
11737     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11738     int fromX, fromY, toX, toY;
11739     char promoChar;
11740     static int initDone=FALSE;
11741
11742     // weed out games based on numerical tag comparison
11743     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11744     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11745     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11746     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11747     if(!initDone) {
11748         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11749         initDone = TRUE;
11750     }
11751     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11752     else CopyBoard(boards[scratch], initialPosition); // default start position
11753     if(lg->moves) {
11754         turn = btm + 1;
11755         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11756         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11757     }
11758     if(btm) plyNr++;
11759     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11760     fseek(f, lg->offset, 0);
11761     yynewfile(f);
11762     while(1) {
11763         yyboardindex = scratch;
11764         quickFlag = plyNr+1;
11765         next = Myylex();
11766         quickFlag = 0;
11767         switch(next) {
11768             case PGNTag:
11769                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11770             default:
11771                 continue;
11772
11773             case XBoardGame:
11774             case GNUChessGame:
11775                 if(plyNr) return -1; // after we have seen moves, this is for new game
11776               continue;
11777
11778             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11779             case ImpossibleMove:
11780             case WhiteWins: // game ends here with these four
11781             case BlackWins:
11782             case GameIsDrawn:
11783             case GameUnfinished:
11784                 return -1;
11785
11786             case IllegalMove:
11787                 if(appData.testLegality) return -1;
11788             case WhiteCapturesEnPassant:
11789             case BlackCapturesEnPassant:
11790             case WhitePromotion:
11791             case BlackPromotion:
11792             case WhiteNonPromotion:
11793             case BlackNonPromotion:
11794             case NormalMove:
11795             case WhiteKingSideCastle:
11796             case WhiteQueenSideCastle:
11797             case BlackKingSideCastle:
11798             case BlackQueenSideCastle:
11799             case WhiteKingSideCastleWild:
11800             case WhiteQueenSideCastleWild:
11801             case BlackKingSideCastleWild:
11802             case BlackQueenSideCastleWild:
11803             case WhiteHSideCastleFR:
11804             case WhiteASideCastleFR:
11805             case BlackHSideCastleFR:
11806             case BlackASideCastleFR:
11807                 fromX = currentMoveString[0] - AAA;
11808                 fromY = currentMoveString[1] - ONE;
11809                 toX = currentMoveString[2] - AAA;
11810                 toY = currentMoveString[3] - ONE;
11811                 promoChar = currentMoveString[4];
11812                 break;
11813             case WhiteDrop:
11814             case BlackDrop:
11815                 fromX = next == WhiteDrop ?
11816                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11817                   (int) CharToPiece(ToLower(currentMoveString[0]));
11818                 fromY = DROP_RANK;
11819                 toX = currentMoveString[2] - AAA;
11820                 toY = currentMoveString[3] - ONE;
11821                 promoChar = 0;
11822                 break;
11823         }
11824         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11825         plyNr++;
11826         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11827         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11828         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11829         if(appData.findMirror) {
11830             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11831             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11832         }
11833     }
11834 }
11835
11836 /* Load the nth game from open file f */
11837 int
11838 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11839 {
11840     ChessMove cm;
11841     char buf[MSG_SIZ];
11842     int gn = gameNumber;
11843     ListGame *lg = NULL;
11844     int numPGNTags = 0;
11845     int err, pos = -1;
11846     GameMode oldGameMode;
11847     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11848
11849     if (appData.debugMode)
11850         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11851
11852     if (gameMode == Training )
11853         SetTrainingModeOff();
11854
11855     oldGameMode = gameMode;
11856     if (gameMode != BeginningOfGame) {
11857       Reset(FALSE, TRUE);
11858     }
11859
11860     gameFileFP = f;
11861     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11862         fclose(lastLoadGameFP);
11863     }
11864
11865     if (useList) {
11866         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11867
11868         if (lg) {
11869             fseek(f, lg->offset, 0);
11870             GameListHighlight(gameNumber);
11871             pos = lg->position;
11872             gn = 1;
11873         }
11874         else {
11875             DisplayError(_("Game number out of range"), 0);
11876             return FALSE;
11877         }
11878     } else {
11879         GameListDestroy();
11880         if (fseek(f, 0, 0) == -1) {
11881             if (f == lastLoadGameFP ?
11882                 gameNumber == lastLoadGameNumber + 1 :
11883                 gameNumber == 1) {
11884                 gn = 1;
11885             } else {
11886                 DisplayError(_("Can't seek on game file"), 0);
11887                 return FALSE;
11888             }
11889         }
11890     }
11891     lastLoadGameFP = f;
11892     lastLoadGameNumber = gameNumber;
11893     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11894     lastLoadGameUseList = useList;
11895
11896     yynewfile(f);
11897
11898     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11899       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11900                 lg->gameInfo.black);
11901             DisplayTitle(buf);
11902     } else if (*title != NULLCHAR) {
11903         if (gameNumber > 1) {
11904           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11905             DisplayTitle(buf);
11906         } else {
11907             DisplayTitle(title);
11908         }
11909     }
11910
11911     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11912         gameMode = PlayFromGameFile;
11913         ModeHighlight();
11914     }
11915
11916     currentMove = forwardMostMove = backwardMostMove = 0;
11917     CopyBoard(boards[0], initialPosition);
11918     StopClocks();
11919
11920     /*
11921      * Skip the first gn-1 games in the file.
11922      * Also skip over anything that precedes an identifiable
11923      * start of game marker, to avoid being confused by
11924      * garbage at the start of the file.  Currently
11925      * recognized start of game markers are the move number "1",
11926      * the pattern "gnuchess .* game", the pattern
11927      * "^[#;%] [^ ]* game file", and a PGN tag block.
11928      * A game that starts with one of the latter two patterns
11929      * will also have a move number 1, possibly
11930      * following a position diagram.
11931      * 5-4-02: Let's try being more lenient and allowing a game to
11932      * start with an unnumbered move.  Does that break anything?
11933      */
11934     cm = lastLoadGameStart = EndOfFile;
11935     while (gn > 0) {
11936         yyboardindex = forwardMostMove;
11937         cm = (ChessMove) Myylex();
11938         switch (cm) {
11939           case EndOfFile:
11940             if (cmailMsgLoaded) {
11941                 nCmailGames = CMAIL_MAX_GAMES - gn;
11942             } else {
11943                 Reset(TRUE, TRUE);
11944                 DisplayError(_("Game not found in file"), 0);
11945             }
11946             return FALSE;
11947
11948           case GNUChessGame:
11949           case XBoardGame:
11950             gn--;
11951             lastLoadGameStart = cm;
11952             break;
11953
11954           case MoveNumberOne:
11955             switch (lastLoadGameStart) {
11956               case GNUChessGame:
11957               case XBoardGame:
11958               case PGNTag:
11959                 break;
11960               case MoveNumberOne:
11961               case EndOfFile:
11962                 gn--;           /* count this game */
11963                 lastLoadGameStart = cm;
11964                 break;
11965               default:
11966                 /* impossible */
11967                 break;
11968             }
11969             break;
11970
11971           case PGNTag:
11972             switch (lastLoadGameStart) {
11973               case GNUChessGame:
11974               case PGNTag:
11975               case MoveNumberOne:
11976               case EndOfFile:
11977                 gn--;           /* count this game */
11978                 lastLoadGameStart = cm;
11979                 break;
11980               case XBoardGame:
11981                 lastLoadGameStart = cm; /* game counted already */
11982                 break;
11983               default:
11984                 /* impossible */
11985                 break;
11986             }
11987             if (gn > 0) {
11988                 do {
11989                     yyboardindex = forwardMostMove;
11990                     cm = (ChessMove) Myylex();
11991                 } while (cm == PGNTag || cm == Comment);
11992             }
11993             break;
11994
11995           case WhiteWins:
11996           case BlackWins:
11997           case GameIsDrawn:
11998             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11999                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12000                     != CMAIL_OLD_RESULT) {
12001                     nCmailResults ++ ;
12002                     cmailResult[  CMAIL_MAX_GAMES
12003                                 - gn - 1] = CMAIL_OLD_RESULT;
12004                 }
12005             }
12006             break;
12007
12008           case NormalMove:
12009             /* Only a NormalMove can be at the start of a game
12010              * without a position diagram. */
12011             if (lastLoadGameStart == EndOfFile ) {
12012               gn--;
12013               lastLoadGameStart = MoveNumberOne;
12014             }
12015             break;
12016
12017           default:
12018             break;
12019         }
12020     }
12021
12022     if (appData.debugMode)
12023       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12024
12025     if (cm == XBoardGame) {
12026         /* Skip any header junk before position diagram and/or move 1 */
12027         for (;;) {
12028             yyboardindex = forwardMostMove;
12029             cm = (ChessMove) Myylex();
12030
12031             if (cm == EndOfFile ||
12032                 cm == GNUChessGame || cm == XBoardGame) {
12033                 /* Empty game; pretend end-of-file and handle later */
12034                 cm = EndOfFile;
12035                 break;
12036             }
12037
12038             if (cm == MoveNumberOne || cm == PositionDiagram ||
12039                 cm == PGNTag || cm == Comment)
12040               break;
12041         }
12042     } else if (cm == GNUChessGame) {
12043         if (gameInfo.event != NULL) {
12044             free(gameInfo.event);
12045         }
12046         gameInfo.event = StrSave(yy_text);
12047     }
12048
12049     startedFromSetupPosition = FALSE;
12050     while (cm == PGNTag) {
12051         if (appData.debugMode)
12052           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12053         err = ParsePGNTag(yy_text, &gameInfo);
12054         if (!err) numPGNTags++;
12055
12056         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12057         if(gameInfo.variant != oldVariant) {
12058             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12059             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12060             InitPosition(TRUE);
12061             oldVariant = gameInfo.variant;
12062             if (appData.debugMode)
12063               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12064         }
12065
12066
12067         if (gameInfo.fen != NULL) {
12068           Board initial_position;
12069           startedFromSetupPosition = TRUE;
12070           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
12071             Reset(TRUE, TRUE);
12072             DisplayError(_("Bad FEN position in file"), 0);
12073             return FALSE;
12074           }
12075           CopyBoard(boards[0], initial_position);
12076           if (blackPlaysFirst) {
12077             currentMove = forwardMostMove = backwardMostMove = 1;
12078             CopyBoard(boards[1], initial_position);
12079             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12080             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12081             timeRemaining[0][1] = whiteTimeRemaining;
12082             timeRemaining[1][1] = blackTimeRemaining;
12083             if (commentList[0] != NULL) {
12084               commentList[1] = commentList[0];
12085               commentList[0] = NULL;
12086             }
12087           } else {
12088             currentMove = forwardMostMove = backwardMostMove = 0;
12089           }
12090           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12091           {   int i;
12092               initialRulePlies = FENrulePlies;
12093               for( i=0; i< nrCastlingRights; i++ )
12094                   initialRights[i] = initial_position[CASTLING][i];
12095           }
12096           yyboardindex = forwardMostMove;
12097           free(gameInfo.fen);
12098           gameInfo.fen = NULL;
12099         }
12100
12101         yyboardindex = forwardMostMove;
12102         cm = (ChessMove) Myylex();
12103
12104         /* Handle comments interspersed among the tags */
12105         while (cm == Comment) {
12106             char *p;
12107             if (appData.debugMode)
12108               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12109             p = yy_text;
12110             AppendComment(currentMove, p, FALSE);
12111             yyboardindex = forwardMostMove;
12112             cm = (ChessMove) Myylex();
12113         }
12114     }
12115
12116     /* don't rely on existence of Event tag since if game was
12117      * pasted from clipboard the Event tag may not exist
12118      */
12119     if (numPGNTags > 0){
12120         char *tags;
12121         if (gameInfo.variant == VariantNormal) {
12122           VariantClass v = StringToVariant(gameInfo.event);
12123           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12124           if(v < VariantShogi) gameInfo.variant = v;
12125         }
12126         if (!matchMode) {
12127           if( appData.autoDisplayTags ) {
12128             tags = PGNTags(&gameInfo);
12129             TagsPopUp(tags, CmailMsg());
12130             free(tags);
12131           }
12132         }
12133     } else {
12134         /* Make something up, but don't display it now */
12135         SetGameInfo();
12136         TagsPopDown();
12137     }
12138
12139     if (cm == PositionDiagram) {
12140         int i, j;
12141         char *p;
12142         Board initial_position;
12143
12144         if (appData.debugMode)
12145           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12146
12147         if (!startedFromSetupPosition) {
12148             p = yy_text;
12149             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12150               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12151                 switch (*p) {
12152                   case '{':
12153                   case '[':
12154                   case '-':
12155                   case ' ':
12156                   case '\t':
12157                   case '\n':
12158                   case '\r':
12159                     break;
12160                   default:
12161                     initial_position[i][j++] = CharToPiece(*p);
12162                     break;
12163                 }
12164             while (*p == ' ' || *p == '\t' ||
12165                    *p == '\n' || *p == '\r') p++;
12166
12167             if (strncmp(p, "black", strlen("black"))==0)
12168               blackPlaysFirst = TRUE;
12169             else
12170               blackPlaysFirst = FALSE;
12171             startedFromSetupPosition = TRUE;
12172
12173             CopyBoard(boards[0], initial_position);
12174             if (blackPlaysFirst) {
12175                 currentMove = forwardMostMove = backwardMostMove = 1;
12176                 CopyBoard(boards[1], initial_position);
12177                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12178                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12179                 timeRemaining[0][1] = whiteTimeRemaining;
12180                 timeRemaining[1][1] = blackTimeRemaining;
12181                 if (commentList[0] != NULL) {
12182                     commentList[1] = commentList[0];
12183                     commentList[0] = NULL;
12184                 }
12185             } else {
12186                 currentMove = forwardMostMove = backwardMostMove = 0;
12187             }
12188         }
12189         yyboardindex = forwardMostMove;
12190         cm = (ChessMove) Myylex();
12191     }
12192
12193     if (first.pr == NoProc) {
12194         StartChessProgram(&first);
12195     }
12196     InitChessProgram(&first, FALSE);
12197     SendToProgram("force\n", &first);
12198     if (startedFromSetupPosition) {
12199         SendBoard(&first, forwardMostMove);
12200     if (appData.debugMode) {
12201         fprintf(debugFP, "Load Game\n");
12202     }
12203         DisplayBothClocks();
12204     }
12205
12206     /* [HGM] server: flag to write setup moves in broadcast file as one */
12207     loadFlag = appData.suppressLoadMoves;
12208
12209     while (cm == Comment) {
12210         char *p;
12211         if (appData.debugMode)
12212           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12213         p = yy_text;
12214         AppendComment(currentMove, p, FALSE);
12215         yyboardindex = forwardMostMove;
12216         cm = (ChessMove) Myylex();
12217     }
12218
12219     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12220         cm == WhiteWins || cm == BlackWins ||
12221         cm == GameIsDrawn || cm == GameUnfinished) {
12222         DisplayMessage("", _("No moves in game"));
12223         if (cmailMsgLoaded) {
12224             if (appData.debugMode)
12225               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12226             ClearHighlights();
12227             flipView = FALSE;
12228         }
12229         DrawPosition(FALSE, boards[currentMove]);
12230         DisplayBothClocks();
12231         gameMode = EditGame;
12232         ModeHighlight();
12233         gameFileFP = NULL;
12234         cmailOldMove = 0;
12235         return TRUE;
12236     }
12237
12238     // [HGM] PV info: routine tests if comment empty
12239     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12240         DisplayComment(currentMove - 1, commentList[currentMove]);
12241     }
12242     if (!matchMode && appData.timeDelay != 0)
12243       DrawPosition(FALSE, boards[currentMove]);
12244
12245     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12246       programStats.ok_to_send = 1;
12247     }
12248
12249     /* if the first token after the PGN tags is a move
12250      * and not move number 1, retrieve it from the parser
12251      */
12252     if (cm != MoveNumberOne)
12253         LoadGameOneMove(cm);
12254
12255     /* load the remaining moves from the file */
12256     while (LoadGameOneMove(EndOfFile)) {
12257       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12258       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12259     }
12260
12261     /* rewind to the start of the game */
12262     currentMove = backwardMostMove;
12263
12264     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12265
12266     if (oldGameMode == AnalyzeFile ||
12267         oldGameMode == AnalyzeMode) {
12268       AnalyzeFileEvent();
12269     }
12270
12271     if (!matchMode && pos > 0) {
12272         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12273     } else
12274     if (matchMode || appData.timeDelay == 0) {
12275       ToEndEvent();
12276     } else if (appData.timeDelay > 0) {
12277       AutoPlayGameLoop();
12278     }
12279
12280     if (appData.debugMode)
12281         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12282
12283     loadFlag = 0; /* [HGM] true game starts */
12284     return TRUE;
12285 }
12286
12287 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12288 int
12289 ReloadPosition (int offset)
12290 {
12291     int positionNumber = lastLoadPositionNumber + offset;
12292     if (lastLoadPositionFP == NULL) {
12293         DisplayError(_("No position has been loaded yet"), 0);
12294         return FALSE;
12295     }
12296     if (positionNumber <= 0) {
12297         DisplayError(_("Can't back up any further"), 0);
12298         return FALSE;
12299     }
12300     return LoadPosition(lastLoadPositionFP, positionNumber,
12301                         lastLoadPositionTitle);
12302 }
12303
12304 /* Load the nth position from the given file */
12305 int
12306 LoadPositionFromFile (char *filename, int n, char *title)
12307 {
12308     FILE *f;
12309     char buf[MSG_SIZ];
12310
12311     if (strcmp(filename, "-") == 0) {
12312         return LoadPosition(stdin, n, "stdin");
12313     } else {
12314         f = fopen(filename, "rb");
12315         if (f == NULL) {
12316             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12317             DisplayError(buf, errno);
12318             return FALSE;
12319         } else {
12320             return LoadPosition(f, n, title);
12321         }
12322     }
12323 }
12324
12325 /* Load the nth position from the given open file, and close it */
12326 int
12327 LoadPosition (FILE *f, int positionNumber, char *title)
12328 {
12329     char *p, line[MSG_SIZ];
12330     Board initial_position;
12331     int i, j, fenMode, pn;
12332
12333     if (gameMode == Training )
12334         SetTrainingModeOff();
12335
12336     if (gameMode != BeginningOfGame) {
12337         Reset(FALSE, TRUE);
12338     }
12339     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12340         fclose(lastLoadPositionFP);
12341     }
12342     if (positionNumber == 0) positionNumber = 1;
12343     lastLoadPositionFP = f;
12344     lastLoadPositionNumber = positionNumber;
12345     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12346     if (first.pr == NoProc && !appData.noChessProgram) {
12347       StartChessProgram(&first);
12348       InitChessProgram(&first, FALSE);
12349     }
12350     pn = positionNumber;
12351     if (positionNumber < 0) {
12352         /* Negative position number means to seek to that byte offset */
12353         if (fseek(f, -positionNumber, 0) == -1) {
12354             DisplayError(_("Can't seek on position file"), 0);
12355             return FALSE;
12356         };
12357         pn = 1;
12358     } else {
12359         if (fseek(f, 0, 0) == -1) {
12360             if (f == lastLoadPositionFP ?
12361                 positionNumber == lastLoadPositionNumber + 1 :
12362                 positionNumber == 1) {
12363                 pn = 1;
12364             } else {
12365                 DisplayError(_("Can't seek on position file"), 0);
12366                 return FALSE;
12367             }
12368         }
12369     }
12370     /* See if this file is FEN or old-style xboard */
12371     if (fgets(line, MSG_SIZ, f) == NULL) {
12372         DisplayError(_("Position not found in file"), 0);
12373         return FALSE;
12374     }
12375     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12376     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12377
12378     if (pn >= 2) {
12379         if (fenMode || line[0] == '#') pn--;
12380         while (pn > 0) {
12381             /* skip positions before number pn */
12382             if (fgets(line, MSG_SIZ, f) == NULL) {
12383                 Reset(TRUE, TRUE);
12384                 DisplayError(_("Position not found in file"), 0);
12385                 return FALSE;
12386             }
12387             if (fenMode || line[0] == '#') pn--;
12388         }
12389     }
12390
12391     if (fenMode) {
12392         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12393             DisplayError(_("Bad FEN position in file"), 0);
12394             return FALSE;
12395         }
12396     } else {
12397         (void) fgets(line, MSG_SIZ, f);
12398         (void) fgets(line, MSG_SIZ, f);
12399
12400         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12401             (void) fgets(line, MSG_SIZ, f);
12402             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12403                 if (*p == ' ')
12404                   continue;
12405                 initial_position[i][j++] = CharToPiece(*p);
12406             }
12407         }
12408
12409         blackPlaysFirst = FALSE;
12410         if (!feof(f)) {
12411             (void) fgets(line, MSG_SIZ, f);
12412             if (strncmp(line, "black", strlen("black"))==0)
12413               blackPlaysFirst = TRUE;
12414         }
12415     }
12416     startedFromSetupPosition = TRUE;
12417
12418     CopyBoard(boards[0], initial_position);
12419     if (blackPlaysFirst) {
12420         currentMove = forwardMostMove = backwardMostMove = 1;
12421         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12422         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12423         CopyBoard(boards[1], initial_position);
12424         DisplayMessage("", _("Black to play"));
12425     } else {
12426         currentMove = forwardMostMove = backwardMostMove = 0;
12427         DisplayMessage("", _("White to play"));
12428     }
12429     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12430     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12431         SendToProgram("force\n", &first);
12432         SendBoard(&first, forwardMostMove);
12433     }
12434     if (appData.debugMode) {
12435 int i, j;
12436   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12437   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12438         fprintf(debugFP, "Load Position\n");
12439     }
12440
12441     if (positionNumber > 1) {
12442       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12443         DisplayTitle(line);
12444     } else {
12445         DisplayTitle(title);
12446     }
12447     gameMode = EditGame;
12448     ModeHighlight();
12449     ResetClocks();
12450     timeRemaining[0][1] = whiteTimeRemaining;
12451     timeRemaining[1][1] = blackTimeRemaining;
12452     DrawPosition(FALSE, boards[currentMove]);
12453
12454     return TRUE;
12455 }
12456
12457
12458 void
12459 CopyPlayerNameIntoFileName (char **dest, char *src)
12460 {
12461     while (*src != NULLCHAR && *src != ',') {
12462         if (*src == ' ') {
12463             *(*dest)++ = '_';
12464             src++;
12465         } else {
12466             *(*dest)++ = *src++;
12467         }
12468     }
12469 }
12470
12471 char *
12472 DefaultFileName (char *ext)
12473 {
12474     static char def[MSG_SIZ];
12475     char *p;
12476
12477     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12478         p = def;
12479         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12480         *p++ = '-';
12481         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12482         *p++ = '.';
12483         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12484     } else {
12485         def[0] = NULLCHAR;
12486     }
12487     return def;
12488 }
12489
12490 /* Save the current game to the given file */
12491 int
12492 SaveGameToFile (char *filename, int append)
12493 {
12494     FILE *f;
12495     char buf[MSG_SIZ];
12496     int result, i, t,tot=0;
12497
12498     if (strcmp(filename, "-") == 0) {
12499         return SaveGame(stdout, 0, NULL);
12500     } else {
12501         for(i=0; i<10; i++) { // upto 10 tries
12502              f = fopen(filename, append ? "a" : "w");
12503              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12504              if(f || errno != 13) break;
12505              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12506              tot += t;
12507         }
12508         if (f == NULL) {
12509             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12510             DisplayError(buf, errno);
12511             return FALSE;
12512         } else {
12513             safeStrCpy(buf, lastMsg, MSG_SIZ);
12514             DisplayMessage(_("Waiting for access to save file"), "");
12515             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12516             DisplayMessage(_("Saving game"), "");
12517             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12518             result = SaveGame(f, 0, NULL);
12519             DisplayMessage(buf, "");
12520             return result;
12521         }
12522     }
12523 }
12524
12525 char *
12526 SavePart (char *str)
12527 {
12528     static char buf[MSG_SIZ];
12529     char *p;
12530
12531     p = strchr(str, ' ');
12532     if (p == NULL) return str;
12533     strncpy(buf, str, p - str);
12534     buf[p - str] = NULLCHAR;
12535     return buf;
12536 }
12537
12538 #define PGN_MAX_LINE 75
12539
12540 #define PGN_SIDE_WHITE  0
12541 #define PGN_SIDE_BLACK  1
12542
12543 static int
12544 FindFirstMoveOutOfBook (int side)
12545 {
12546     int result = -1;
12547
12548     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12549         int index = backwardMostMove;
12550         int has_book_hit = 0;
12551
12552         if( (index % 2) != side ) {
12553             index++;
12554         }
12555
12556         while( index < forwardMostMove ) {
12557             /* Check to see if engine is in book */
12558             int depth = pvInfoList[index].depth;
12559             int score = pvInfoList[index].score;
12560             int in_book = 0;
12561
12562             if( depth <= 2 ) {
12563                 in_book = 1;
12564             }
12565             else if( score == 0 && depth == 63 ) {
12566                 in_book = 1; /* Zappa */
12567             }
12568             else if( score == 2 && depth == 99 ) {
12569                 in_book = 1; /* Abrok */
12570             }
12571
12572             has_book_hit += in_book;
12573
12574             if( ! in_book ) {
12575                 result = index;
12576
12577                 break;
12578             }
12579
12580             index += 2;
12581         }
12582     }
12583
12584     return result;
12585 }
12586
12587 void
12588 GetOutOfBookInfo (char * buf)
12589 {
12590     int oob[2];
12591     int i;
12592     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12593
12594     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12595     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12596
12597     *buf = '\0';
12598
12599     if( oob[0] >= 0 || oob[1] >= 0 ) {
12600         for( i=0; i<2; i++ ) {
12601             int idx = oob[i];
12602
12603             if( idx >= 0 ) {
12604                 if( i > 0 && oob[0] >= 0 ) {
12605                     strcat( buf, "   " );
12606                 }
12607
12608                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12609                 sprintf( buf+strlen(buf), "%s%.2f",
12610                     pvInfoList[idx].score >= 0 ? "+" : "",
12611                     pvInfoList[idx].score / 100.0 );
12612             }
12613         }
12614     }
12615 }
12616
12617 /* Save game in PGN style and close the file */
12618 int
12619 SaveGamePGN (FILE *f)
12620 {
12621     int i, offset, linelen, newblock;
12622 //    char *movetext;
12623     char numtext[32];
12624     int movelen, numlen, blank;
12625     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12626
12627     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12628
12629     PrintPGNTags(f, &gameInfo);
12630
12631     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12632
12633     if (backwardMostMove > 0 || startedFromSetupPosition) {
12634         char *fen = PositionToFEN(backwardMostMove, NULL);
12635         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12636         fprintf(f, "\n{--------------\n");
12637         PrintPosition(f, backwardMostMove);
12638         fprintf(f, "--------------}\n");
12639         free(fen);
12640     }
12641     else {
12642         /* [AS] Out of book annotation */
12643         if( appData.saveOutOfBookInfo ) {
12644             char buf[64];
12645
12646             GetOutOfBookInfo( buf );
12647
12648             if( buf[0] != '\0' ) {
12649                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12650             }
12651         }
12652
12653         fprintf(f, "\n");
12654     }
12655
12656     i = backwardMostMove;
12657     linelen = 0;
12658     newblock = TRUE;
12659
12660     while (i < forwardMostMove) {
12661         /* Print comments preceding this move */
12662         if (commentList[i] != NULL) {
12663             if (linelen > 0) fprintf(f, "\n");
12664             fprintf(f, "%s", commentList[i]);
12665             linelen = 0;
12666             newblock = TRUE;
12667         }
12668
12669         /* Format move number */
12670         if ((i % 2) == 0)
12671           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12672         else
12673           if (newblock)
12674             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12675           else
12676             numtext[0] = NULLCHAR;
12677
12678         numlen = strlen(numtext);
12679         newblock = FALSE;
12680
12681         /* Print move number */
12682         blank = linelen > 0 && numlen > 0;
12683         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12684             fprintf(f, "\n");
12685             linelen = 0;
12686             blank = 0;
12687         }
12688         if (blank) {
12689             fprintf(f, " ");
12690             linelen++;
12691         }
12692         fprintf(f, "%s", numtext);
12693         linelen += numlen;
12694
12695         /* Get move */
12696         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12697         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12698
12699         /* Print move */
12700         blank = linelen > 0 && movelen > 0;
12701         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12702             fprintf(f, "\n");
12703             linelen = 0;
12704             blank = 0;
12705         }
12706         if (blank) {
12707             fprintf(f, " ");
12708             linelen++;
12709         }
12710         fprintf(f, "%s", move_buffer);
12711         linelen += movelen;
12712
12713         /* [AS] Add PV info if present */
12714         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12715             /* [HGM] add time */
12716             char buf[MSG_SIZ]; int seconds;
12717
12718             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12719
12720             if( seconds <= 0)
12721               buf[0] = 0;
12722             else
12723               if( seconds < 30 )
12724                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12725               else
12726                 {
12727                   seconds = (seconds + 4)/10; // round to full seconds
12728                   if( seconds < 60 )
12729                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12730                   else
12731                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12732                 }
12733
12734             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12735                       pvInfoList[i].score >= 0 ? "+" : "",
12736                       pvInfoList[i].score / 100.0,
12737                       pvInfoList[i].depth,
12738                       buf );
12739
12740             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12741
12742             /* Print score/depth */
12743             blank = linelen > 0 && movelen > 0;
12744             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12745                 fprintf(f, "\n");
12746                 linelen = 0;
12747                 blank = 0;
12748             }
12749             if (blank) {
12750                 fprintf(f, " ");
12751                 linelen++;
12752             }
12753             fprintf(f, "%s", move_buffer);
12754             linelen += movelen;
12755         }
12756
12757         i++;
12758     }
12759
12760     /* Start a new line */
12761     if (linelen > 0) fprintf(f, "\n");
12762
12763     /* Print comments after last move */
12764     if (commentList[i] != NULL) {
12765         fprintf(f, "%s\n", commentList[i]);
12766     }
12767
12768     /* Print result */
12769     if (gameInfo.resultDetails != NULL &&
12770         gameInfo.resultDetails[0] != NULLCHAR) {
12771         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12772                 PGNResult(gameInfo.result));
12773     } else {
12774         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12775     }
12776
12777     fclose(f);
12778     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12779     return TRUE;
12780 }
12781
12782 /* Save game in old style and close the file */
12783 int
12784 SaveGameOldStyle (FILE *f)
12785 {
12786     int i, offset;
12787     time_t tm;
12788
12789     tm = time((time_t *) NULL);
12790
12791     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12792     PrintOpponents(f);
12793
12794     if (backwardMostMove > 0 || startedFromSetupPosition) {
12795         fprintf(f, "\n[--------------\n");
12796         PrintPosition(f, backwardMostMove);
12797         fprintf(f, "--------------]\n");
12798     } else {
12799         fprintf(f, "\n");
12800     }
12801
12802     i = backwardMostMove;
12803     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12804
12805     while (i < forwardMostMove) {
12806         if (commentList[i] != NULL) {
12807             fprintf(f, "[%s]\n", commentList[i]);
12808         }
12809
12810         if ((i % 2) == 1) {
12811             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12812             i++;
12813         } else {
12814             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12815             i++;
12816             if (commentList[i] != NULL) {
12817                 fprintf(f, "\n");
12818                 continue;
12819             }
12820             if (i >= forwardMostMove) {
12821                 fprintf(f, "\n");
12822                 break;
12823             }
12824             fprintf(f, "%s\n", parseList[i]);
12825             i++;
12826         }
12827     }
12828
12829     if (commentList[i] != NULL) {
12830         fprintf(f, "[%s]\n", commentList[i]);
12831     }
12832
12833     /* This isn't really the old style, but it's close enough */
12834     if (gameInfo.resultDetails != NULL &&
12835         gameInfo.resultDetails[0] != NULLCHAR) {
12836         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12837                 gameInfo.resultDetails);
12838     } else {
12839         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12840     }
12841
12842     fclose(f);
12843     return TRUE;
12844 }
12845
12846 /* Save the current game to open file f and close the file */
12847 int
12848 SaveGame (FILE *f, int dummy, char *dummy2)
12849 {
12850     if (gameMode == EditPosition) EditPositionDone(TRUE);
12851     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12852     if (appData.oldSaveStyle)
12853       return SaveGameOldStyle(f);
12854     else
12855       return SaveGamePGN(f);
12856 }
12857
12858 /* Save the current position to the given file */
12859 int
12860 SavePositionToFile (char *filename)
12861 {
12862     FILE *f;
12863     char buf[MSG_SIZ];
12864
12865     if (strcmp(filename, "-") == 0) {
12866         return SavePosition(stdout, 0, NULL);
12867     } else {
12868         f = fopen(filename, "a");
12869         if (f == NULL) {
12870             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12871             DisplayError(buf, errno);
12872             return FALSE;
12873         } else {
12874             safeStrCpy(buf, lastMsg, MSG_SIZ);
12875             DisplayMessage(_("Waiting for access to save file"), "");
12876             flock(fileno(f), LOCK_EX); // [HGM] lock
12877             DisplayMessage(_("Saving position"), "");
12878             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12879             SavePosition(f, 0, NULL);
12880             DisplayMessage(buf, "");
12881             return TRUE;
12882         }
12883     }
12884 }
12885
12886 /* Save the current position to the given open file and close the file */
12887 int
12888 SavePosition (FILE *f, int dummy, char *dummy2)
12889 {
12890     time_t tm;
12891     char *fen;
12892
12893     if (gameMode == EditPosition) EditPositionDone(TRUE);
12894     if (appData.oldSaveStyle) {
12895         tm = time((time_t *) NULL);
12896
12897         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12898         PrintOpponents(f);
12899         fprintf(f, "[--------------\n");
12900         PrintPosition(f, currentMove);
12901         fprintf(f, "--------------]\n");
12902     } else {
12903         fen = PositionToFEN(currentMove, NULL);
12904         fprintf(f, "%s\n", fen);
12905         free(fen);
12906     }
12907     fclose(f);
12908     return TRUE;
12909 }
12910
12911 void
12912 ReloadCmailMsgEvent (int unregister)
12913 {
12914 #if !WIN32
12915     static char *inFilename = NULL;
12916     static char *outFilename;
12917     int i;
12918     struct stat inbuf, outbuf;
12919     int status;
12920
12921     /* Any registered moves are unregistered if unregister is set, */
12922     /* i.e. invoked by the signal handler */
12923     if (unregister) {
12924         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12925             cmailMoveRegistered[i] = FALSE;
12926             if (cmailCommentList[i] != NULL) {
12927                 free(cmailCommentList[i]);
12928                 cmailCommentList[i] = NULL;
12929             }
12930         }
12931         nCmailMovesRegistered = 0;
12932     }
12933
12934     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12935         cmailResult[i] = CMAIL_NOT_RESULT;
12936     }
12937     nCmailResults = 0;
12938
12939     if (inFilename == NULL) {
12940         /* Because the filenames are static they only get malloced once  */
12941         /* and they never get freed                                      */
12942         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12943         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12944
12945         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12946         sprintf(outFilename, "%s.out", appData.cmailGameName);
12947     }
12948
12949     status = stat(outFilename, &outbuf);
12950     if (status < 0) {
12951         cmailMailedMove = FALSE;
12952     } else {
12953         status = stat(inFilename, &inbuf);
12954         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12955     }
12956
12957     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12958        counts the games, notes how each one terminated, etc.
12959
12960        It would be nice to remove this kludge and instead gather all
12961        the information while building the game list.  (And to keep it
12962        in the game list nodes instead of having a bunch of fixed-size
12963        parallel arrays.)  Note this will require getting each game's
12964        termination from the PGN tags, as the game list builder does
12965        not process the game moves.  --mann
12966        */
12967     cmailMsgLoaded = TRUE;
12968     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12969
12970     /* Load first game in the file or popup game menu */
12971     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12972
12973 #endif /* !WIN32 */
12974     return;
12975 }
12976
12977 int
12978 RegisterMove ()
12979 {
12980     FILE *f;
12981     char string[MSG_SIZ];
12982
12983     if (   cmailMailedMove
12984         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12985         return TRUE;            /* Allow free viewing  */
12986     }
12987
12988     /* Unregister move to ensure that we don't leave RegisterMove        */
12989     /* with the move registered when the conditions for registering no   */
12990     /* longer hold                                                       */
12991     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12992         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12993         nCmailMovesRegistered --;
12994
12995         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12996           {
12997               free(cmailCommentList[lastLoadGameNumber - 1]);
12998               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12999           }
13000     }
13001
13002     if (cmailOldMove == -1) {
13003         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13004         return FALSE;
13005     }
13006
13007     if (currentMove > cmailOldMove + 1) {
13008         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13009         return FALSE;
13010     }
13011
13012     if (currentMove < cmailOldMove) {
13013         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13014         return FALSE;
13015     }
13016
13017     if (forwardMostMove > currentMove) {
13018         /* Silently truncate extra moves */
13019         TruncateGame();
13020     }
13021
13022     if (   (currentMove == cmailOldMove + 1)
13023         || (   (currentMove == cmailOldMove)
13024             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13025                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13026         if (gameInfo.result != GameUnfinished) {
13027             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13028         }
13029
13030         if (commentList[currentMove] != NULL) {
13031             cmailCommentList[lastLoadGameNumber - 1]
13032               = StrSave(commentList[currentMove]);
13033         }
13034         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13035
13036         if (appData.debugMode)
13037           fprintf(debugFP, "Saving %s for game %d\n",
13038                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13039
13040         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13041
13042         f = fopen(string, "w");
13043         if (appData.oldSaveStyle) {
13044             SaveGameOldStyle(f); /* also closes the file */
13045
13046             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13047             f = fopen(string, "w");
13048             SavePosition(f, 0, NULL); /* also closes the file */
13049         } else {
13050             fprintf(f, "{--------------\n");
13051             PrintPosition(f, currentMove);
13052             fprintf(f, "--------------}\n\n");
13053
13054             SaveGame(f, 0, NULL); /* also closes the file*/
13055         }
13056
13057         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13058         nCmailMovesRegistered ++;
13059     } else if (nCmailGames == 1) {
13060         DisplayError(_("You have not made a move yet"), 0);
13061         return FALSE;
13062     }
13063
13064     return TRUE;
13065 }
13066
13067 void
13068 MailMoveEvent ()
13069 {
13070 #if !WIN32
13071     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13072     FILE *commandOutput;
13073     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13074     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13075     int nBuffers;
13076     int i;
13077     int archived;
13078     char *arcDir;
13079
13080     if (! cmailMsgLoaded) {
13081         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13082         return;
13083     }
13084
13085     if (nCmailGames == nCmailResults) {
13086         DisplayError(_("No unfinished games"), 0);
13087         return;
13088     }
13089
13090 #if CMAIL_PROHIBIT_REMAIL
13091     if (cmailMailedMove) {
13092       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);
13093         DisplayError(msg, 0);
13094         return;
13095     }
13096 #endif
13097
13098     if (! (cmailMailedMove || RegisterMove())) return;
13099
13100     if (   cmailMailedMove
13101         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13102       snprintf(string, MSG_SIZ, partCommandString,
13103                appData.debugMode ? " -v" : "", appData.cmailGameName);
13104         commandOutput = popen(string, "r");
13105
13106         if (commandOutput == NULL) {
13107             DisplayError(_("Failed to invoke cmail"), 0);
13108         } else {
13109             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13110                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13111             }
13112             if (nBuffers > 1) {
13113                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13114                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13115                 nBytes = MSG_SIZ - 1;
13116             } else {
13117                 (void) memcpy(msg, buffer, nBytes);
13118             }
13119             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13120
13121             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13122                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13123
13124                 archived = TRUE;
13125                 for (i = 0; i < nCmailGames; i ++) {
13126                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13127                         archived = FALSE;
13128                     }
13129                 }
13130                 if (   archived
13131                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13132                         != NULL)) {
13133                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13134                            arcDir,
13135                            appData.cmailGameName,
13136                            gameInfo.date);
13137                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13138                     cmailMsgLoaded = FALSE;
13139                 }
13140             }
13141
13142             DisplayInformation(msg);
13143             pclose(commandOutput);
13144         }
13145     } else {
13146         if ((*cmailMsg) != '\0') {
13147             DisplayInformation(cmailMsg);
13148         }
13149     }
13150
13151     return;
13152 #endif /* !WIN32 */
13153 }
13154
13155 char *
13156 CmailMsg ()
13157 {
13158 #if WIN32
13159     return NULL;
13160 #else
13161     int  prependComma = 0;
13162     char number[5];
13163     char string[MSG_SIZ];       /* Space for game-list */
13164     int  i;
13165
13166     if (!cmailMsgLoaded) return "";
13167
13168     if (cmailMailedMove) {
13169       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13170     } else {
13171         /* Create a list of games left */
13172       snprintf(string, MSG_SIZ, "[");
13173         for (i = 0; i < nCmailGames; i ++) {
13174             if (! (   cmailMoveRegistered[i]
13175                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13176                 if (prependComma) {
13177                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13178                 } else {
13179                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13180                     prependComma = 1;
13181                 }
13182
13183                 strcat(string, number);
13184             }
13185         }
13186         strcat(string, "]");
13187
13188         if (nCmailMovesRegistered + nCmailResults == 0) {
13189             switch (nCmailGames) {
13190               case 1:
13191                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13192                 break;
13193
13194               case 2:
13195                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13196                 break;
13197
13198               default:
13199                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13200                          nCmailGames);
13201                 break;
13202             }
13203         } else {
13204             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13205               case 1:
13206                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13207                          string);
13208                 break;
13209
13210               case 0:
13211                 if (nCmailResults == nCmailGames) {
13212                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13213                 } else {
13214                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13215                 }
13216                 break;
13217
13218               default:
13219                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13220                          string);
13221             }
13222         }
13223     }
13224     return cmailMsg;
13225 #endif /* WIN32 */
13226 }
13227
13228 void
13229 ResetGameEvent ()
13230 {
13231     if (gameMode == Training)
13232       SetTrainingModeOff();
13233
13234     Reset(TRUE, TRUE);
13235     cmailMsgLoaded = FALSE;
13236     if (appData.icsActive) {
13237       SendToICS(ics_prefix);
13238       SendToICS("refresh\n");
13239     }
13240 }
13241
13242 void
13243 ExitEvent (int status)
13244 {
13245     exiting++;
13246     if (exiting > 2) {
13247       /* Give up on clean exit */
13248       exit(status);
13249     }
13250     if (exiting > 1) {
13251       /* Keep trying for clean exit */
13252       return;
13253     }
13254
13255     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13256
13257     if (telnetISR != NULL) {
13258       RemoveInputSource(telnetISR);
13259     }
13260     if (icsPR != NoProc) {
13261       DestroyChildProcess(icsPR, TRUE);
13262     }
13263
13264     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13265     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13266
13267     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13268     /* make sure this other one finishes before killing it!                  */
13269     if(endingGame) { int count = 0;
13270         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13271         while(endingGame && count++ < 10) DoSleep(1);
13272         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13273     }
13274
13275     /* Kill off chess programs */
13276     if (first.pr != NoProc) {
13277         ExitAnalyzeMode();
13278
13279         DoSleep( appData.delayBeforeQuit );
13280         SendToProgram("quit\n", &first);
13281         DoSleep( appData.delayAfterQuit );
13282         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13283     }
13284     if (second.pr != NoProc) {
13285         DoSleep( appData.delayBeforeQuit );
13286         SendToProgram("quit\n", &second);
13287         DoSleep( appData.delayAfterQuit );
13288         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13289     }
13290     if (first.isr != NULL) {
13291         RemoveInputSource(first.isr);
13292     }
13293     if (second.isr != NULL) {
13294         RemoveInputSource(second.isr);
13295     }
13296
13297     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13298     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13299
13300     ShutDownFrontEnd();
13301     exit(status);
13302 }
13303
13304 void
13305 PauseEvent ()
13306 {
13307     if (appData.debugMode)
13308         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13309     if (pausing) {
13310         pausing = FALSE;
13311         ModeHighlight();
13312         if (gameMode == MachinePlaysWhite ||
13313             gameMode == MachinePlaysBlack) {
13314             StartClocks();
13315         } else {
13316             DisplayBothClocks();
13317         }
13318         if (gameMode == PlayFromGameFile) {
13319             if (appData.timeDelay >= 0)
13320                 AutoPlayGameLoop();
13321         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13322             Reset(FALSE, TRUE);
13323             SendToICS(ics_prefix);
13324             SendToICS("refresh\n");
13325         } else if (currentMove < forwardMostMove) {
13326             ForwardInner(forwardMostMove);
13327         }
13328         pauseExamInvalid = FALSE;
13329     } else {
13330         switch (gameMode) {
13331           default:
13332             return;
13333           case IcsExamining:
13334             pauseExamForwardMostMove = forwardMostMove;
13335             pauseExamInvalid = FALSE;
13336             /* fall through */
13337           case IcsObserving:
13338           case IcsPlayingWhite:
13339           case IcsPlayingBlack:
13340             pausing = TRUE;
13341             ModeHighlight();
13342             return;
13343           case PlayFromGameFile:
13344             (void) StopLoadGameTimer();
13345             pausing = TRUE;
13346             ModeHighlight();
13347             break;
13348           case BeginningOfGame:
13349             if (appData.icsActive) return;
13350             /* else fall through */
13351           case MachinePlaysWhite:
13352           case MachinePlaysBlack:
13353           case TwoMachinesPlay:
13354             if (forwardMostMove == 0)
13355               return;           /* don't pause if no one has moved */
13356             if ((gameMode == MachinePlaysWhite &&
13357                  !WhiteOnMove(forwardMostMove)) ||
13358                 (gameMode == MachinePlaysBlack &&
13359                  WhiteOnMove(forwardMostMove))) {
13360                 StopClocks();
13361             }
13362             pausing = TRUE;
13363             ModeHighlight();
13364             break;
13365         }
13366     }
13367 }
13368
13369 void
13370 EditCommentEvent ()
13371 {
13372     char title[MSG_SIZ];
13373
13374     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13375       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13376     } else {
13377       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13378                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13379                parseList[currentMove - 1]);
13380     }
13381
13382     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13383 }
13384
13385
13386 void
13387 EditTagsEvent ()
13388 {
13389     char *tags = PGNTags(&gameInfo);
13390     bookUp = FALSE;
13391     EditTagsPopUp(tags, NULL);
13392     free(tags);
13393 }
13394
13395 void
13396 ToggleSecond ()
13397 {
13398   if(second.analyzing) {
13399     SendToProgram("exit\n", &second);
13400     second.analyzing = FALSE;
13401   } else {
13402     if (second.pr == NoProc) StartChessProgram(&second);
13403     InitChessProgram(&second, FALSE);
13404     FeedMovesToProgram(&second, currentMove);
13405
13406     SendToProgram("analyze\n", &second);
13407     second.analyzing = TRUE;
13408   }
13409 }
13410
13411 void
13412 AnalyzeModeEvent ()
13413 {
13414     if (gameMode == AnalyzeMode) { ToggleSecond(); return; }
13415     if (appData.noChessProgram || gameMode == AnalyzeMode)
13416       return;
13417
13418     if (gameMode != AnalyzeFile) {
13419         if (!appData.icsEngineAnalyze) {
13420                EditGameEvent();
13421                if (gameMode != EditGame) return;
13422         }
13423         ResurrectChessProgram();
13424         SendToProgram("analyze\n", &first);
13425         first.analyzing = TRUE;
13426         /*first.maybeThinking = TRUE;*/
13427         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13428         EngineOutputPopUp();
13429     }
13430     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13431     pausing = FALSE;
13432     ModeHighlight();
13433     SetGameInfo();
13434
13435     StartAnalysisClock();
13436     GetTimeMark(&lastNodeCountTime);
13437     lastNodeCount = 0;
13438 }
13439
13440 void
13441 AnalyzeFileEvent ()
13442 {
13443     if (appData.noChessProgram || gameMode == AnalyzeFile)
13444       return;
13445
13446     if (gameMode != AnalyzeMode) {
13447         EditGameEvent();
13448         if (gameMode != EditGame) return;
13449         ResurrectChessProgram();
13450         SendToProgram("analyze\n", &first);
13451         first.analyzing = TRUE;
13452         /*first.maybeThinking = TRUE;*/
13453         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13454         EngineOutputPopUp();
13455     }
13456     gameMode = AnalyzeFile;
13457     pausing = FALSE;
13458     ModeHighlight();
13459     SetGameInfo();
13460
13461     StartAnalysisClock();
13462     GetTimeMark(&lastNodeCountTime);
13463     lastNodeCount = 0;
13464     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
13465 }
13466
13467 void
13468 MachineWhiteEvent ()
13469 {
13470     char buf[MSG_SIZ];
13471     char *bookHit = NULL;
13472
13473     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13474       return;
13475
13476
13477     if (gameMode == PlayFromGameFile ||
13478         gameMode == TwoMachinesPlay  ||
13479         gameMode == Training         ||
13480         gameMode == AnalyzeMode      ||
13481         gameMode == EndOfGame)
13482         EditGameEvent();
13483
13484     if (gameMode == EditPosition)
13485         EditPositionDone(TRUE);
13486
13487     if (!WhiteOnMove(currentMove)) {
13488         DisplayError(_("It is not White's turn"), 0);
13489         return;
13490     }
13491
13492     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13493       ExitAnalyzeMode();
13494
13495     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13496         gameMode == AnalyzeFile)
13497         TruncateGame();
13498
13499     ResurrectChessProgram();    /* in case it isn't running */
13500     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13501         gameMode = MachinePlaysWhite;
13502         ResetClocks();
13503     } else
13504     gameMode = MachinePlaysWhite;
13505     pausing = FALSE;
13506     ModeHighlight();
13507     SetGameInfo();
13508     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13509     DisplayTitle(buf);
13510     if (first.sendName) {
13511       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13512       SendToProgram(buf, &first);
13513     }
13514     if (first.sendTime) {
13515       if (first.useColors) {
13516         SendToProgram("black\n", &first); /*gnu kludge*/
13517       }
13518       SendTimeRemaining(&first, TRUE);
13519     }
13520     if (first.useColors) {
13521       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13522     }
13523     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13524     SetMachineThinkingEnables();
13525     first.maybeThinking = TRUE;
13526     StartClocks();
13527     firstMove = FALSE;
13528
13529     if (appData.autoFlipView && !flipView) {
13530       flipView = !flipView;
13531       DrawPosition(FALSE, NULL);
13532       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13533     }
13534
13535     if(bookHit) { // [HGM] book: simulate book reply
13536         static char bookMove[MSG_SIZ]; // a bit generous?
13537
13538         programStats.nodes = programStats.depth = programStats.time =
13539         programStats.score = programStats.got_only_move = 0;
13540         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13541
13542         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13543         strcat(bookMove, bookHit);
13544         HandleMachineMove(bookMove, &first);
13545     }
13546 }
13547
13548 void
13549 MachineBlackEvent ()
13550 {
13551   char buf[MSG_SIZ];
13552   char *bookHit = NULL;
13553
13554     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13555         return;
13556
13557
13558     if (gameMode == PlayFromGameFile ||
13559         gameMode == TwoMachinesPlay  ||
13560         gameMode == Training         ||
13561         gameMode == AnalyzeMode      ||
13562         gameMode == EndOfGame)
13563         EditGameEvent();
13564
13565     if (gameMode == EditPosition)
13566         EditPositionDone(TRUE);
13567
13568     if (WhiteOnMove(currentMove)) {
13569         DisplayError(_("It is not Black's turn"), 0);
13570         return;
13571     }
13572
13573     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13574       ExitAnalyzeMode();
13575
13576     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13577         gameMode == AnalyzeFile)
13578         TruncateGame();
13579
13580     ResurrectChessProgram();    /* in case it isn't running */
13581     gameMode = MachinePlaysBlack;
13582     pausing = FALSE;
13583     ModeHighlight();
13584     SetGameInfo();
13585     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13586     DisplayTitle(buf);
13587     if (first.sendName) {
13588       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13589       SendToProgram(buf, &first);
13590     }
13591     if (first.sendTime) {
13592       if (first.useColors) {
13593         SendToProgram("white\n", &first); /*gnu kludge*/
13594       }
13595       SendTimeRemaining(&first, FALSE);
13596     }
13597     if (first.useColors) {
13598       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13599     }
13600     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13601     SetMachineThinkingEnables();
13602     first.maybeThinking = TRUE;
13603     StartClocks();
13604
13605     if (appData.autoFlipView && flipView) {
13606       flipView = !flipView;
13607       DrawPosition(FALSE, NULL);
13608       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13609     }
13610     if(bookHit) { // [HGM] book: simulate book reply
13611         static char bookMove[MSG_SIZ]; // a bit generous?
13612
13613         programStats.nodes = programStats.depth = programStats.time =
13614         programStats.score = programStats.got_only_move = 0;
13615         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13616
13617         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13618         strcat(bookMove, bookHit);
13619         HandleMachineMove(bookMove, &first);
13620     }
13621 }
13622
13623
13624 void
13625 DisplayTwoMachinesTitle ()
13626 {
13627     char buf[MSG_SIZ];
13628     if (appData.matchGames > 0) {
13629         if(appData.tourneyFile[0]) {
13630           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13631                    gameInfo.white, _("vs."), gameInfo.black,
13632                    nextGame+1, appData.matchGames+1,
13633                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13634         } else 
13635         if (first.twoMachinesColor[0] == 'w') {
13636           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13637                    gameInfo.white, _("vs."),  gameInfo.black,
13638                    first.matchWins, second.matchWins,
13639                    matchGame - 1 - (first.matchWins + second.matchWins));
13640         } else {
13641           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13642                    gameInfo.white, _("vs."), gameInfo.black,
13643                    second.matchWins, first.matchWins,
13644                    matchGame - 1 - (first.matchWins + second.matchWins));
13645         }
13646     } else {
13647       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13648     }
13649     DisplayTitle(buf);
13650 }
13651
13652 void
13653 SettingsMenuIfReady ()
13654 {
13655   if (second.lastPing != second.lastPong) {
13656     DisplayMessage("", _("Waiting for second chess program"));
13657     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13658     return;
13659   }
13660   ThawUI();
13661   DisplayMessage("", "");
13662   SettingsPopUp(&second);
13663 }
13664
13665 int
13666 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13667 {
13668     char buf[MSG_SIZ];
13669     if (cps->pr == NoProc) {
13670         StartChessProgram(cps);
13671         if (cps->protocolVersion == 1) {
13672           retry();
13673         } else {
13674           /* kludge: allow timeout for initial "feature" command */
13675           FreezeUI();
13676           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13677           DisplayMessage("", buf);
13678           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13679         }
13680         return 1;
13681     }
13682     return 0;
13683 }
13684
13685 void
13686 TwoMachinesEvent P((void))
13687 {
13688     int i;
13689     char buf[MSG_SIZ];
13690     ChessProgramState *onmove;
13691     char *bookHit = NULL;
13692     static int stalling = 0;
13693     TimeMark now;
13694     long wait;
13695
13696     if (appData.noChessProgram) return;
13697
13698     switch (gameMode) {
13699       case TwoMachinesPlay:
13700         return;
13701       case MachinePlaysWhite:
13702       case MachinePlaysBlack:
13703         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13704             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13705             return;
13706         }
13707         /* fall through */
13708       case BeginningOfGame:
13709       case PlayFromGameFile:
13710       case EndOfGame:
13711         EditGameEvent();
13712         if (gameMode != EditGame) return;
13713         break;
13714       case EditPosition:
13715         EditPositionDone(TRUE);
13716         break;
13717       case AnalyzeMode:
13718       case AnalyzeFile:
13719         ExitAnalyzeMode();
13720         break;
13721       case EditGame:
13722       default:
13723         break;
13724     }
13725
13726 //    forwardMostMove = currentMove;
13727     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13728
13729     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13730
13731     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13732     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13733       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13734       return;
13735     }
13736
13737     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13738         DisplayError("second engine does not play this", 0);
13739         return;
13740     }
13741
13742     if(!stalling) {
13743       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13744       SendToProgram("force\n", &second);
13745       stalling = 1;
13746       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13747       return;
13748     }
13749     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13750     if(appData.matchPause>10000 || appData.matchPause<10)
13751                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13752     wait = SubtractTimeMarks(&now, &pauseStart);
13753     if(wait < appData.matchPause) {
13754         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13755         return;
13756     }
13757     // we are now committed to starting the game
13758     stalling = 0;
13759     DisplayMessage("", "");
13760     if (startedFromSetupPosition) {
13761         SendBoard(&second, backwardMostMove);
13762     if (appData.debugMode) {
13763         fprintf(debugFP, "Two Machines\n");
13764     }
13765     }
13766     for (i = backwardMostMove; i < forwardMostMove; i++) {
13767         SendMoveToProgram(i, &second);
13768     }
13769
13770     gameMode = TwoMachinesPlay;
13771     pausing = FALSE;
13772     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13773     SetGameInfo();
13774     DisplayTwoMachinesTitle();
13775     firstMove = TRUE;
13776     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13777         onmove = &first;
13778     } else {
13779         onmove = &second;
13780     }
13781     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13782     SendToProgram(first.computerString, &first);
13783     if (first.sendName) {
13784       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13785       SendToProgram(buf, &first);
13786     }
13787     SendToProgram(second.computerString, &second);
13788     if (second.sendName) {
13789       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13790       SendToProgram(buf, &second);
13791     }
13792
13793     ResetClocks();
13794     if (!first.sendTime || !second.sendTime) {
13795         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13796         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13797     }
13798     if (onmove->sendTime) {
13799       if (onmove->useColors) {
13800         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13801       }
13802       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13803     }
13804     if (onmove->useColors) {
13805       SendToProgram(onmove->twoMachinesColor, onmove);
13806     }
13807     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13808 //    SendToProgram("go\n", onmove);
13809     onmove->maybeThinking = TRUE;
13810     SetMachineThinkingEnables();
13811
13812     StartClocks();
13813
13814     if(bookHit) { // [HGM] book: simulate book reply
13815         static char bookMove[MSG_SIZ]; // a bit generous?
13816
13817         programStats.nodes = programStats.depth = programStats.time =
13818         programStats.score = programStats.got_only_move = 0;
13819         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13820
13821         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13822         strcat(bookMove, bookHit);
13823         savedMessage = bookMove; // args for deferred call
13824         savedState = onmove;
13825         ScheduleDelayedEvent(DeferredBookMove, 1);
13826     }
13827 }
13828
13829 void
13830 TrainingEvent ()
13831 {
13832     if (gameMode == Training) {
13833       SetTrainingModeOff();
13834       gameMode = PlayFromGameFile;
13835       DisplayMessage("", _("Training mode off"));
13836     } else {
13837       gameMode = Training;
13838       animateTraining = appData.animate;
13839
13840       /* make sure we are not already at the end of the game */
13841       if (currentMove < forwardMostMove) {
13842         SetTrainingModeOn();
13843         DisplayMessage("", _("Training mode on"));
13844       } else {
13845         gameMode = PlayFromGameFile;
13846         DisplayError(_("Already at end of game"), 0);
13847       }
13848     }
13849     ModeHighlight();
13850 }
13851
13852 void
13853 IcsClientEvent ()
13854 {
13855     if (!appData.icsActive) return;
13856     switch (gameMode) {
13857       case IcsPlayingWhite:
13858       case IcsPlayingBlack:
13859       case IcsObserving:
13860       case IcsIdle:
13861       case BeginningOfGame:
13862       case IcsExamining:
13863         return;
13864
13865       case EditGame:
13866         break;
13867
13868       case EditPosition:
13869         EditPositionDone(TRUE);
13870         break;
13871
13872       case AnalyzeMode:
13873       case AnalyzeFile:
13874         ExitAnalyzeMode();
13875         break;
13876
13877       default:
13878         EditGameEvent();
13879         break;
13880     }
13881
13882     gameMode = IcsIdle;
13883     ModeHighlight();
13884     return;
13885 }
13886
13887 void
13888 EditGameEvent ()
13889 {
13890     int i;
13891
13892     switch (gameMode) {
13893       case Training:
13894         SetTrainingModeOff();
13895         break;
13896       case MachinePlaysWhite:
13897       case MachinePlaysBlack:
13898       case BeginningOfGame:
13899         SendToProgram("force\n", &first);
13900         SetUserThinkingEnables();
13901         break;
13902       case PlayFromGameFile:
13903         (void) StopLoadGameTimer();
13904         if (gameFileFP != NULL) {
13905             gameFileFP = NULL;
13906         }
13907         break;
13908       case EditPosition:
13909         EditPositionDone(TRUE);
13910         break;
13911       case AnalyzeMode:
13912       case AnalyzeFile:
13913         ExitAnalyzeMode();
13914         SendToProgram("force\n", &first);
13915         break;
13916       case TwoMachinesPlay:
13917         GameEnds(EndOfFile, NULL, GE_PLAYER);
13918         ResurrectChessProgram();
13919         SetUserThinkingEnables();
13920         break;
13921       case EndOfGame:
13922         ResurrectChessProgram();
13923         break;
13924       case IcsPlayingBlack:
13925       case IcsPlayingWhite:
13926         DisplayError(_("Warning: You are still playing a game"), 0);
13927         break;
13928       case IcsObserving:
13929         DisplayError(_("Warning: You are still observing a game"), 0);
13930         break;
13931       case IcsExamining:
13932         DisplayError(_("Warning: You are still examining a game"), 0);
13933         break;
13934       case IcsIdle:
13935         break;
13936       case EditGame:
13937       default:
13938         return;
13939     }
13940
13941     pausing = FALSE;
13942     StopClocks();
13943     first.offeredDraw = second.offeredDraw = 0;
13944
13945     if (gameMode == PlayFromGameFile) {
13946         whiteTimeRemaining = timeRemaining[0][currentMove];
13947         blackTimeRemaining = timeRemaining[1][currentMove];
13948         DisplayTitle("");
13949     }
13950
13951     if (gameMode == MachinePlaysWhite ||
13952         gameMode == MachinePlaysBlack ||
13953         gameMode == TwoMachinesPlay ||
13954         gameMode == EndOfGame) {
13955         i = forwardMostMove;
13956         while (i > currentMove) {
13957             SendToProgram("undo\n", &first);
13958             i--;
13959         }
13960         if(!adjustedClock) {
13961         whiteTimeRemaining = timeRemaining[0][currentMove];
13962         blackTimeRemaining = timeRemaining[1][currentMove];
13963         DisplayBothClocks();
13964         }
13965         if (whiteFlag || blackFlag) {
13966             whiteFlag = blackFlag = 0;
13967         }
13968         DisplayTitle("");
13969     }
13970
13971     gameMode = EditGame;
13972     ModeHighlight();
13973     SetGameInfo();
13974 }
13975
13976
13977 void
13978 EditPositionEvent ()
13979 {
13980     if (gameMode == EditPosition) {
13981         EditGameEvent();
13982         return;
13983     }
13984
13985     EditGameEvent();
13986     if (gameMode != EditGame) return;
13987
13988     gameMode = EditPosition;
13989     ModeHighlight();
13990     SetGameInfo();
13991     if (currentMove > 0)
13992       CopyBoard(boards[0], boards[currentMove]);
13993
13994     blackPlaysFirst = !WhiteOnMove(currentMove);
13995     ResetClocks();
13996     currentMove = forwardMostMove = backwardMostMove = 0;
13997     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13998     DisplayMove(-1);
13999     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14000 }
14001
14002 void
14003 ExitAnalyzeMode ()
14004 {
14005     /* [DM] icsEngineAnalyze - possible call from other functions */
14006     if (appData.icsEngineAnalyze) {
14007         appData.icsEngineAnalyze = FALSE;
14008
14009         DisplayMessage("",_("Close ICS engine analyze..."));
14010     }
14011     if (first.analysisSupport && first.analyzing) {
14012       SendToBoth("exit\n");
14013       first.analyzing = second.analyzing = FALSE;
14014     }
14015     thinkOutput[0] = NULLCHAR;
14016 }
14017
14018 void
14019 EditPositionDone (Boolean fakeRights)
14020 {
14021     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14022
14023     startedFromSetupPosition = TRUE;
14024     InitChessProgram(&first, FALSE);
14025     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14026       boards[0][EP_STATUS] = EP_NONE;
14027       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14028       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14029         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14030         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14031       } else boards[0][CASTLING][2] = NoRights;
14032       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14033         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14034         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14035       } else boards[0][CASTLING][5] = NoRights;
14036       if(gameInfo.variant == VariantSChess) {
14037         int i;
14038         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14039           boards[0][VIRGIN][i] = 0;
14040           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14041           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14042         }
14043       }
14044     }
14045     SendToProgram("force\n", &first);
14046     if (blackPlaysFirst) {
14047         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14048         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14049         currentMove = forwardMostMove = backwardMostMove = 1;
14050         CopyBoard(boards[1], boards[0]);
14051     } else {
14052         currentMove = forwardMostMove = backwardMostMove = 0;
14053     }
14054     SendBoard(&first, forwardMostMove);
14055     if (appData.debugMode) {
14056         fprintf(debugFP, "EditPosDone\n");
14057     }
14058     DisplayTitle("");
14059     DisplayMessage("", "");
14060     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14061     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14062     gameMode = EditGame;
14063     ModeHighlight();
14064     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14065     ClearHighlights(); /* [AS] */
14066 }
14067
14068 /* Pause for `ms' milliseconds */
14069 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14070 void
14071 TimeDelay (long ms)
14072 {
14073     TimeMark m1, m2;
14074
14075     GetTimeMark(&m1);
14076     do {
14077         GetTimeMark(&m2);
14078     } while (SubtractTimeMarks(&m2, &m1) < ms);
14079 }
14080
14081 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14082 void
14083 SendMultiLineToICS (char *buf)
14084 {
14085     char temp[MSG_SIZ+1], *p;
14086     int len;
14087
14088     len = strlen(buf);
14089     if (len > MSG_SIZ)
14090       len = MSG_SIZ;
14091
14092     strncpy(temp, buf, len);
14093     temp[len] = 0;
14094
14095     p = temp;
14096     while (*p) {
14097         if (*p == '\n' || *p == '\r')
14098           *p = ' ';
14099         ++p;
14100     }
14101
14102     strcat(temp, "\n");
14103     SendToICS(temp);
14104     SendToPlayer(temp, strlen(temp));
14105 }
14106
14107 void
14108 SetWhiteToPlayEvent ()
14109 {
14110     if (gameMode == EditPosition) {
14111         blackPlaysFirst = FALSE;
14112         DisplayBothClocks();    /* works because currentMove is 0 */
14113     } else if (gameMode == IcsExamining) {
14114         SendToICS(ics_prefix);
14115         SendToICS("tomove white\n");
14116     }
14117 }
14118
14119 void
14120 SetBlackToPlayEvent ()
14121 {
14122     if (gameMode == EditPosition) {
14123         blackPlaysFirst = TRUE;
14124         currentMove = 1;        /* kludge */
14125         DisplayBothClocks();
14126         currentMove = 0;
14127     } else if (gameMode == IcsExamining) {
14128         SendToICS(ics_prefix);
14129         SendToICS("tomove black\n");
14130     }
14131 }
14132
14133 void
14134 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14135 {
14136     char buf[MSG_SIZ];
14137     ChessSquare piece = boards[0][y][x];
14138
14139     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14140
14141     switch (selection) {
14142       case ClearBoard:
14143         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14144             SendToICS(ics_prefix);
14145             SendToICS("bsetup clear\n");
14146         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14147             SendToICS(ics_prefix);
14148             SendToICS("clearboard\n");
14149         } else {
14150             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14151                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14152                 for (y = 0; y < BOARD_HEIGHT; y++) {
14153                     if (gameMode == IcsExamining) {
14154                         if (boards[currentMove][y][x] != EmptySquare) {
14155                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14156                                     AAA + x, ONE + y);
14157                             SendToICS(buf);
14158                         }
14159                     } else {
14160                         boards[0][y][x] = p;
14161                     }
14162                 }
14163             }
14164         }
14165         if (gameMode == EditPosition) {
14166             DrawPosition(FALSE, boards[0]);
14167         }
14168         break;
14169
14170       case WhitePlay:
14171         SetWhiteToPlayEvent();
14172         break;
14173
14174       case BlackPlay:
14175         SetBlackToPlayEvent();
14176         break;
14177
14178       case EmptySquare:
14179         if (gameMode == IcsExamining) {
14180             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14181             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14182             SendToICS(buf);
14183         } else {
14184             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14185                 if(x == BOARD_LEFT-2) {
14186                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14187                     boards[0][y][1] = 0;
14188                 } else
14189                 if(x == BOARD_RGHT+1) {
14190                     if(y >= gameInfo.holdingsSize) break;
14191                     boards[0][y][BOARD_WIDTH-2] = 0;
14192                 } else break;
14193             }
14194             boards[0][y][x] = EmptySquare;
14195             DrawPosition(FALSE, boards[0]);
14196         }
14197         break;
14198
14199       case PromotePiece:
14200         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14201            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14202             selection = (ChessSquare) (PROMOTED piece);
14203         } else if(piece == EmptySquare) selection = WhiteSilver;
14204         else selection = (ChessSquare)((int)piece - 1);
14205         goto defaultlabel;
14206
14207       case DemotePiece:
14208         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14209            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14210             selection = (ChessSquare) (DEMOTED piece);
14211         } else if(piece == EmptySquare) selection = BlackSilver;
14212         else selection = (ChessSquare)((int)piece + 1);
14213         goto defaultlabel;
14214
14215       case WhiteQueen:
14216       case BlackQueen:
14217         if(gameInfo.variant == VariantShatranj ||
14218            gameInfo.variant == VariantXiangqi  ||
14219            gameInfo.variant == VariantCourier  ||
14220            gameInfo.variant == VariantMakruk     )
14221             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14222         goto defaultlabel;
14223
14224       case WhiteKing:
14225       case BlackKing:
14226         if(gameInfo.variant == VariantXiangqi)
14227             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14228         if(gameInfo.variant == VariantKnightmate)
14229             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14230       default:
14231         defaultlabel:
14232         if (gameMode == IcsExamining) {
14233             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14234             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14235                      PieceToChar(selection), AAA + x, ONE + y);
14236             SendToICS(buf);
14237         } else {
14238             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14239                 int n;
14240                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14241                     n = PieceToNumber(selection - BlackPawn);
14242                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14243                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14244                     boards[0][BOARD_HEIGHT-1-n][1]++;
14245                 } else
14246                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14247                     n = PieceToNumber(selection);
14248                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14249                     boards[0][n][BOARD_WIDTH-1] = selection;
14250                     boards[0][n][BOARD_WIDTH-2]++;
14251                 }
14252             } else
14253             boards[0][y][x] = selection;
14254             DrawPosition(TRUE, boards[0]);
14255             ClearHighlights();
14256             fromX = fromY = -1;
14257         }
14258         break;
14259     }
14260 }
14261
14262
14263 void
14264 DropMenuEvent (ChessSquare selection, int x, int y)
14265 {
14266     ChessMove moveType;
14267
14268     switch (gameMode) {
14269       case IcsPlayingWhite:
14270       case MachinePlaysBlack:
14271         if (!WhiteOnMove(currentMove)) {
14272             DisplayMoveError(_("It is Black's turn"));
14273             return;
14274         }
14275         moveType = WhiteDrop;
14276         break;
14277       case IcsPlayingBlack:
14278       case MachinePlaysWhite:
14279         if (WhiteOnMove(currentMove)) {
14280             DisplayMoveError(_("It is White's turn"));
14281             return;
14282         }
14283         moveType = BlackDrop;
14284         break;
14285       case EditGame:
14286         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14287         break;
14288       default:
14289         return;
14290     }
14291
14292     if (moveType == BlackDrop && selection < BlackPawn) {
14293       selection = (ChessSquare) ((int) selection
14294                                  + (int) BlackPawn - (int) WhitePawn);
14295     }
14296     if (boards[currentMove][y][x] != EmptySquare) {
14297         DisplayMoveError(_("That square is occupied"));
14298         return;
14299     }
14300
14301     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14302 }
14303
14304 void
14305 AcceptEvent ()
14306 {
14307     /* Accept a pending offer of any kind from opponent */
14308
14309     if (appData.icsActive) {
14310         SendToICS(ics_prefix);
14311         SendToICS("accept\n");
14312     } else if (cmailMsgLoaded) {
14313         if (currentMove == cmailOldMove &&
14314             commentList[cmailOldMove] != NULL &&
14315             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14316                    "Black offers a draw" : "White offers a draw")) {
14317             TruncateGame();
14318             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14319             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14320         } else {
14321             DisplayError(_("There is no pending offer on this move"), 0);
14322             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14323         }
14324     } else {
14325         /* Not used for offers from chess program */
14326     }
14327 }
14328
14329 void
14330 DeclineEvent ()
14331 {
14332     /* Decline a pending offer of any kind from opponent */
14333
14334     if (appData.icsActive) {
14335         SendToICS(ics_prefix);
14336         SendToICS("decline\n");
14337     } else if (cmailMsgLoaded) {
14338         if (currentMove == cmailOldMove &&
14339             commentList[cmailOldMove] != NULL &&
14340             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14341                    "Black offers a draw" : "White offers a draw")) {
14342 #ifdef NOTDEF
14343             AppendComment(cmailOldMove, "Draw declined", TRUE);
14344             DisplayComment(cmailOldMove - 1, "Draw declined");
14345 #endif /*NOTDEF*/
14346         } else {
14347             DisplayError(_("There is no pending offer on this move"), 0);
14348         }
14349     } else {
14350         /* Not used for offers from chess program */
14351     }
14352 }
14353
14354 void
14355 RematchEvent ()
14356 {
14357     /* Issue ICS rematch command */
14358     if (appData.icsActive) {
14359         SendToICS(ics_prefix);
14360         SendToICS("rematch\n");
14361     }
14362 }
14363
14364 void
14365 CallFlagEvent ()
14366 {
14367     /* Call your opponent's flag (claim a win on time) */
14368     if (appData.icsActive) {
14369         SendToICS(ics_prefix);
14370         SendToICS("flag\n");
14371     } else {
14372         switch (gameMode) {
14373           default:
14374             return;
14375           case MachinePlaysWhite:
14376             if (whiteFlag) {
14377                 if (blackFlag)
14378                   GameEnds(GameIsDrawn, "Both players ran out of time",
14379                            GE_PLAYER);
14380                 else
14381                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14382             } else {
14383                 DisplayError(_("Your opponent is not out of time"), 0);
14384             }
14385             break;
14386           case MachinePlaysBlack:
14387             if (blackFlag) {
14388                 if (whiteFlag)
14389                   GameEnds(GameIsDrawn, "Both players ran out of time",
14390                            GE_PLAYER);
14391                 else
14392                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14393             } else {
14394                 DisplayError(_("Your opponent is not out of time"), 0);
14395             }
14396             break;
14397         }
14398     }
14399 }
14400
14401 void
14402 ClockClick (int which)
14403 {       // [HGM] code moved to back-end from winboard.c
14404         if(which) { // black clock
14405           if (gameMode == EditPosition || gameMode == IcsExamining) {
14406             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14407             SetBlackToPlayEvent();
14408           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14409           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14410           } else if (shiftKey) {
14411             AdjustClock(which, -1);
14412           } else if (gameMode == IcsPlayingWhite ||
14413                      gameMode == MachinePlaysBlack) {
14414             CallFlagEvent();
14415           }
14416         } else { // white clock
14417           if (gameMode == EditPosition || gameMode == IcsExamining) {
14418             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14419             SetWhiteToPlayEvent();
14420           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14421           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14422           } else if (shiftKey) {
14423             AdjustClock(which, -1);
14424           } else if (gameMode == IcsPlayingBlack ||
14425                    gameMode == MachinePlaysWhite) {
14426             CallFlagEvent();
14427           }
14428         }
14429 }
14430
14431 void
14432 DrawEvent ()
14433 {
14434     /* Offer draw or accept pending draw offer from opponent */
14435
14436     if (appData.icsActive) {
14437         /* Note: tournament rules require draw offers to be
14438            made after you make your move but before you punch
14439            your clock.  Currently ICS doesn't let you do that;
14440            instead, you immediately punch your clock after making
14441            a move, but you can offer a draw at any time. */
14442
14443         SendToICS(ics_prefix);
14444         SendToICS("draw\n");
14445         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14446     } else if (cmailMsgLoaded) {
14447         if (currentMove == cmailOldMove &&
14448             commentList[cmailOldMove] != NULL &&
14449             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14450                    "Black offers a draw" : "White offers a draw")) {
14451             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14452             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14453         } else if (currentMove == cmailOldMove + 1) {
14454             char *offer = WhiteOnMove(cmailOldMove) ?
14455               "White offers a draw" : "Black offers a draw";
14456             AppendComment(currentMove, offer, TRUE);
14457             DisplayComment(currentMove - 1, offer);
14458             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14459         } else {
14460             DisplayError(_("You must make your move before offering a draw"), 0);
14461             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14462         }
14463     } else if (first.offeredDraw) {
14464         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14465     } else {
14466         if (first.sendDrawOffers) {
14467             SendToProgram("draw\n", &first);
14468             userOfferedDraw = TRUE;
14469         }
14470     }
14471 }
14472
14473 void
14474 AdjournEvent ()
14475 {
14476     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14477
14478     if (appData.icsActive) {
14479         SendToICS(ics_prefix);
14480         SendToICS("adjourn\n");
14481     } else {
14482         /* Currently GNU Chess doesn't offer or accept Adjourns */
14483     }
14484 }
14485
14486
14487 void
14488 AbortEvent ()
14489 {
14490     /* Offer Abort or accept pending Abort offer from opponent */
14491
14492     if (appData.icsActive) {
14493         SendToICS(ics_prefix);
14494         SendToICS("abort\n");
14495     } else {
14496         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14497     }
14498 }
14499
14500 void
14501 ResignEvent ()
14502 {
14503     /* Resign.  You can do this even if it's not your turn. */
14504
14505     if (appData.icsActive) {
14506         SendToICS(ics_prefix);
14507         SendToICS("resign\n");
14508     } else {
14509         switch (gameMode) {
14510           case MachinePlaysWhite:
14511             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14512             break;
14513           case MachinePlaysBlack:
14514             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14515             break;
14516           case EditGame:
14517             if (cmailMsgLoaded) {
14518                 TruncateGame();
14519                 if (WhiteOnMove(cmailOldMove)) {
14520                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14521                 } else {
14522                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14523                 }
14524                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14525             }
14526             break;
14527           default:
14528             break;
14529         }
14530     }
14531 }
14532
14533
14534 void
14535 StopObservingEvent ()
14536 {
14537     /* Stop observing current games */
14538     SendToICS(ics_prefix);
14539     SendToICS("unobserve\n");
14540 }
14541
14542 void
14543 StopExaminingEvent ()
14544 {
14545     /* Stop observing current game */
14546     SendToICS(ics_prefix);
14547     SendToICS("unexamine\n");
14548 }
14549
14550 void
14551 ForwardInner (int target)
14552 {
14553     int limit; int oldSeekGraphUp = seekGraphUp;
14554
14555     if (appData.debugMode)
14556         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14557                 target, currentMove, forwardMostMove);
14558
14559     if (gameMode == EditPosition)
14560       return;
14561
14562     seekGraphUp = FALSE;
14563     MarkTargetSquares(1);
14564
14565     if (gameMode == PlayFromGameFile && !pausing)
14566       PauseEvent();
14567
14568     if (gameMode == IcsExamining && pausing)
14569       limit = pauseExamForwardMostMove;
14570     else
14571       limit = forwardMostMove;
14572
14573     if (target > limit) target = limit;
14574
14575     if (target > 0 && moveList[target - 1][0]) {
14576         int fromX, fromY, toX, toY;
14577         toX = moveList[target - 1][2] - AAA;
14578         toY = moveList[target - 1][3] - ONE;
14579         if (moveList[target - 1][1] == '@') {
14580             if (appData.highlightLastMove) {
14581                 SetHighlights(-1, -1, toX, toY);
14582             }
14583         } else {
14584             fromX = moveList[target - 1][0] - AAA;
14585             fromY = moveList[target - 1][1] - ONE;
14586             if (target == currentMove + 1) {
14587                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14588             }
14589             if (appData.highlightLastMove) {
14590                 SetHighlights(fromX, fromY, toX, toY);
14591             }
14592         }
14593     }
14594     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14595         gameMode == Training || gameMode == PlayFromGameFile ||
14596         gameMode == AnalyzeFile) {
14597         while (currentMove < target) {
14598             if(second.analyzing) SendMoveToProgram(currentMove, &second);
14599             SendMoveToProgram(currentMove++, &first);
14600         }
14601     } else {
14602         currentMove = target;
14603     }
14604
14605     if (gameMode == EditGame || gameMode == EndOfGame) {
14606         whiteTimeRemaining = timeRemaining[0][currentMove];
14607         blackTimeRemaining = timeRemaining[1][currentMove];
14608     }
14609     DisplayBothClocks();
14610     DisplayMove(currentMove - 1);
14611     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14612     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14613     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14614         DisplayComment(currentMove - 1, commentList[currentMove]);
14615     }
14616     ClearMap(); // [HGM] exclude: invalidate map
14617 }
14618
14619
14620 void
14621 ForwardEvent ()
14622 {
14623     if (gameMode == IcsExamining && !pausing) {
14624         SendToICS(ics_prefix);
14625         SendToICS("forward\n");
14626     } else {
14627         ForwardInner(currentMove + 1);
14628     }
14629 }
14630
14631 void
14632 ToEndEvent ()
14633 {
14634     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14635         /* to optimze, we temporarily turn off analysis mode while we feed
14636          * the remaining moves to the engine. Otherwise we get analysis output
14637          * after each move.
14638          */
14639         if (first.analysisSupport) {
14640           SendToProgram("exit\nforce\n", &first);
14641           first.analyzing = FALSE;
14642         }
14643     }
14644
14645     if (gameMode == IcsExamining && !pausing) {
14646         SendToICS(ics_prefix);
14647         SendToICS("forward 999999\n");
14648     } else {
14649         ForwardInner(forwardMostMove);
14650     }
14651
14652     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14653         /* we have fed all the moves, so reactivate analysis mode */
14654         SendToProgram("analyze\n", &first);
14655         first.analyzing = TRUE;
14656         /*first.maybeThinking = TRUE;*/
14657         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14658     }
14659 }
14660
14661 void
14662 BackwardInner (int target)
14663 {
14664     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14665
14666     if (appData.debugMode)
14667         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14668                 target, currentMove, forwardMostMove);
14669
14670     if (gameMode == EditPosition) return;
14671     seekGraphUp = FALSE;
14672     MarkTargetSquares(1);
14673     if (currentMove <= backwardMostMove) {
14674         ClearHighlights();
14675         DrawPosition(full_redraw, boards[currentMove]);
14676         return;
14677     }
14678     if (gameMode == PlayFromGameFile && !pausing)
14679       PauseEvent();
14680
14681     if (moveList[target][0]) {
14682         int fromX, fromY, toX, toY;
14683         toX = moveList[target][2] - AAA;
14684         toY = moveList[target][3] - ONE;
14685         if (moveList[target][1] == '@') {
14686             if (appData.highlightLastMove) {
14687                 SetHighlights(-1, -1, toX, toY);
14688             }
14689         } else {
14690             fromX = moveList[target][0] - AAA;
14691             fromY = moveList[target][1] - ONE;
14692             if (target == currentMove - 1) {
14693                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14694             }
14695             if (appData.highlightLastMove) {
14696                 SetHighlights(fromX, fromY, toX, toY);
14697             }
14698         }
14699     }
14700     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14701         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14702         while (currentMove > target) {
14703             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14704                 // null move cannot be undone. Reload program with move history before it.
14705                 int i;
14706                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14707                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14708                 }
14709                 SendBoard(&first, i); 
14710               if(second.analyzing) SendBoard(&second, i);
14711                 for(currentMove=i; currentMove<target; currentMove++) {
14712                     SendMoveToProgram(currentMove, &first);
14713                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
14714                 }
14715                 break;
14716             }
14717             SendToBoth("undo\n");
14718             currentMove--;
14719         }
14720     } else {
14721         currentMove = target;
14722     }
14723
14724     if (gameMode == EditGame || gameMode == EndOfGame) {
14725         whiteTimeRemaining = timeRemaining[0][currentMove];
14726         blackTimeRemaining = timeRemaining[1][currentMove];
14727     }
14728     DisplayBothClocks();
14729     DisplayMove(currentMove - 1);
14730     DrawPosition(full_redraw, boards[currentMove]);
14731     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14732     // [HGM] PV info: routine tests if comment empty
14733     DisplayComment(currentMove - 1, commentList[currentMove]);
14734     ClearMap(); // [HGM] exclude: invalidate map
14735 }
14736
14737 void
14738 BackwardEvent ()
14739 {
14740     if (gameMode == IcsExamining && !pausing) {
14741         SendToICS(ics_prefix);
14742         SendToICS("backward\n");
14743     } else {
14744         BackwardInner(currentMove - 1);
14745     }
14746 }
14747
14748 void
14749 ToStartEvent ()
14750 {
14751     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14752         /* to optimize, we temporarily turn off analysis mode while we undo
14753          * all the moves. Otherwise we get analysis output after each undo.
14754          */
14755         if (first.analysisSupport) {
14756           SendToProgram("exit\nforce\n", &first);
14757           first.analyzing = FALSE;
14758         }
14759     }
14760
14761     if (gameMode == IcsExamining && !pausing) {
14762         SendToICS(ics_prefix);
14763         SendToICS("backward 999999\n");
14764     } else {
14765         BackwardInner(backwardMostMove);
14766     }
14767
14768     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14769         /* we have fed all the moves, so reactivate analysis mode */
14770         SendToProgram("analyze\n", &first);
14771         first.analyzing = TRUE;
14772         /*first.maybeThinking = TRUE;*/
14773         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14774     }
14775 }
14776
14777 void
14778 ToNrEvent (int to)
14779 {
14780   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14781   if (to >= forwardMostMove) to = forwardMostMove;
14782   if (to <= backwardMostMove) to = backwardMostMove;
14783   if (to < currentMove) {
14784     BackwardInner(to);
14785   } else {
14786     ForwardInner(to);
14787   }
14788 }
14789
14790 void
14791 RevertEvent (Boolean annotate)
14792 {
14793     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14794         return;
14795     }
14796     if (gameMode != IcsExamining) {
14797         DisplayError(_("You are not examining a game"), 0);
14798         return;
14799     }
14800     if (pausing) {
14801         DisplayError(_("You can't revert while pausing"), 0);
14802         return;
14803     }
14804     SendToICS(ics_prefix);
14805     SendToICS("revert\n");
14806 }
14807
14808 void
14809 RetractMoveEvent ()
14810 {
14811     switch (gameMode) {
14812       case MachinePlaysWhite:
14813       case MachinePlaysBlack:
14814         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14815             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14816             return;
14817         }
14818         if (forwardMostMove < 2) return;
14819         currentMove = forwardMostMove = forwardMostMove - 2;
14820         whiteTimeRemaining = timeRemaining[0][currentMove];
14821         blackTimeRemaining = timeRemaining[1][currentMove];
14822         DisplayBothClocks();
14823         DisplayMove(currentMove - 1);
14824         ClearHighlights();/*!! could figure this out*/
14825         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14826         SendToProgram("remove\n", &first);
14827         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14828         break;
14829
14830       case BeginningOfGame:
14831       default:
14832         break;
14833
14834       case IcsPlayingWhite:
14835       case IcsPlayingBlack:
14836         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14837             SendToICS(ics_prefix);
14838             SendToICS("takeback 2\n");
14839         } else {
14840             SendToICS(ics_prefix);
14841             SendToICS("takeback 1\n");
14842         }
14843         break;
14844     }
14845 }
14846
14847 void
14848 MoveNowEvent ()
14849 {
14850     ChessProgramState *cps;
14851
14852     switch (gameMode) {
14853       case MachinePlaysWhite:
14854         if (!WhiteOnMove(forwardMostMove)) {
14855             DisplayError(_("It is your turn"), 0);
14856             return;
14857         }
14858         cps = &first;
14859         break;
14860       case MachinePlaysBlack:
14861         if (WhiteOnMove(forwardMostMove)) {
14862             DisplayError(_("It is your turn"), 0);
14863             return;
14864         }
14865         cps = &first;
14866         break;
14867       case TwoMachinesPlay:
14868         if (WhiteOnMove(forwardMostMove) ==
14869             (first.twoMachinesColor[0] == 'w')) {
14870             cps = &first;
14871         } else {
14872             cps = &second;
14873         }
14874         break;
14875       case BeginningOfGame:
14876       default:
14877         return;
14878     }
14879     SendToProgram("?\n", cps);
14880 }
14881
14882 void
14883 TruncateGameEvent ()
14884 {
14885     EditGameEvent();
14886     if (gameMode != EditGame) return;
14887     TruncateGame();
14888 }
14889
14890 void
14891 TruncateGame ()
14892 {
14893     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14894     if (forwardMostMove > currentMove) {
14895         if (gameInfo.resultDetails != NULL) {
14896             free(gameInfo.resultDetails);
14897             gameInfo.resultDetails = NULL;
14898             gameInfo.result = GameUnfinished;
14899         }
14900         forwardMostMove = currentMove;
14901         HistorySet(parseList, backwardMostMove, forwardMostMove,
14902                    currentMove-1);
14903     }
14904 }
14905
14906 void
14907 HintEvent ()
14908 {
14909     if (appData.noChessProgram) return;
14910     switch (gameMode) {
14911       case MachinePlaysWhite:
14912         if (WhiteOnMove(forwardMostMove)) {
14913             DisplayError(_("Wait until your turn"), 0);
14914             return;
14915         }
14916         break;
14917       case BeginningOfGame:
14918       case MachinePlaysBlack:
14919         if (!WhiteOnMove(forwardMostMove)) {
14920             DisplayError(_("Wait until your turn"), 0);
14921             return;
14922         }
14923         break;
14924       default:
14925         DisplayError(_("No hint available"), 0);
14926         return;
14927     }
14928     SendToProgram("hint\n", &first);
14929     hintRequested = TRUE;
14930 }
14931
14932 void
14933 BookEvent ()
14934 {
14935     if (appData.noChessProgram) return;
14936     switch (gameMode) {
14937       case MachinePlaysWhite:
14938         if (WhiteOnMove(forwardMostMove)) {
14939             DisplayError(_("Wait until your turn"), 0);
14940             return;
14941         }
14942         break;
14943       case BeginningOfGame:
14944       case MachinePlaysBlack:
14945         if (!WhiteOnMove(forwardMostMove)) {
14946             DisplayError(_("Wait until your turn"), 0);
14947             return;
14948         }
14949         break;
14950       case EditPosition:
14951         EditPositionDone(TRUE);
14952         break;
14953       case TwoMachinesPlay:
14954         return;
14955       default:
14956         break;
14957     }
14958     SendToProgram("bk\n", &first);
14959     bookOutput[0] = NULLCHAR;
14960     bookRequested = TRUE;
14961 }
14962
14963 void
14964 AboutGameEvent ()
14965 {
14966     char *tags = PGNTags(&gameInfo);
14967     TagsPopUp(tags, CmailMsg());
14968     free(tags);
14969 }
14970
14971 /* end button procedures */
14972
14973 void
14974 PrintPosition (FILE *fp, int move)
14975 {
14976     int i, j;
14977
14978     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14979         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14980             char c = PieceToChar(boards[move][i][j]);
14981             fputc(c == 'x' ? '.' : c, fp);
14982             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14983         }
14984     }
14985     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14986       fprintf(fp, "white to play\n");
14987     else
14988       fprintf(fp, "black to play\n");
14989 }
14990
14991 void
14992 PrintOpponents (FILE *fp)
14993 {
14994     if (gameInfo.white != NULL) {
14995         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14996     } else {
14997         fprintf(fp, "\n");
14998     }
14999 }
15000
15001 /* Find last component of program's own name, using some heuristics */
15002 void
15003 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15004 {
15005     char *p, *q, c;
15006     int local = (strcmp(host, "localhost") == 0);
15007     while (!local && (p = strchr(prog, ';')) != NULL) {
15008         p++;
15009         while (*p == ' ') p++;
15010         prog = p;
15011     }
15012     if (*prog == '"' || *prog == '\'') {
15013         q = strchr(prog + 1, *prog);
15014     } else {
15015         q = strchr(prog, ' ');
15016     }
15017     if (q == NULL) q = prog + strlen(prog);
15018     p = q;
15019     while (p >= prog && *p != '/' && *p != '\\') p--;
15020     p++;
15021     if(p == prog && *p == '"') p++;
15022     c = *q; *q = 0;
15023     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15024     memcpy(buf, p, q - p);
15025     buf[q - p] = NULLCHAR;
15026     if (!local) {
15027         strcat(buf, "@");
15028         strcat(buf, host);
15029     }
15030 }
15031
15032 char *
15033 TimeControlTagValue ()
15034 {
15035     char buf[MSG_SIZ];
15036     if (!appData.clockMode) {
15037       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15038     } else if (movesPerSession > 0) {
15039       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15040     } else if (timeIncrement == 0) {
15041       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15042     } else {
15043       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15044     }
15045     return StrSave(buf);
15046 }
15047
15048 void
15049 SetGameInfo ()
15050 {
15051     /* This routine is used only for certain modes */
15052     VariantClass v = gameInfo.variant;
15053     ChessMove r = GameUnfinished;
15054     char *p = NULL;
15055
15056     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15057         r = gameInfo.result;
15058         p = gameInfo.resultDetails;
15059         gameInfo.resultDetails = NULL;
15060     }
15061     ClearGameInfo(&gameInfo);
15062     gameInfo.variant = v;
15063
15064     switch (gameMode) {
15065       case MachinePlaysWhite:
15066         gameInfo.event = StrSave( appData.pgnEventHeader );
15067         gameInfo.site = StrSave(HostName());
15068         gameInfo.date = PGNDate();
15069         gameInfo.round = StrSave("-");
15070         gameInfo.white = StrSave(first.tidy);
15071         gameInfo.black = StrSave(UserName());
15072         gameInfo.timeControl = TimeControlTagValue();
15073         break;
15074
15075       case MachinePlaysBlack:
15076         gameInfo.event = StrSave( appData.pgnEventHeader );
15077         gameInfo.site = StrSave(HostName());
15078         gameInfo.date = PGNDate();
15079         gameInfo.round = StrSave("-");
15080         gameInfo.white = StrSave(UserName());
15081         gameInfo.black = StrSave(first.tidy);
15082         gameInfo.timeControl = TimeControlTagValue();
15083         break;
15084
15085       case TwoMachinesPlay:
15086         gameInfo.event = StrSave( appData.pgnEventHeader );
15087         gameInfo.site = StrSave(HostName());
15088         gameInfo.date = PGNDate();
15089         if (roundNr > 0) {
15090             char buf[MSG_SIZ];
15091             snprintf(buf, MSG_SIZ, "%d", roundNr);
15092             gameInfo.round = StrSave(buf);
15093         } else {
15094             gameInfo.round = StrSave("-");
15095         }
15096         if (first.twoMachinesColor[0] == 'w') {
15097             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15098             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15099         } else {
15100             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15101             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15102         }
15103         gameInfo.timeControl = TimeControlTagValue();
15104         break;
15105
15106       case EditGame:
15107         gameInfo.event = StrSave("Edited game");
15108         gameInfo.site = StrSave(HostName());
15109         gameInfo.date = PGNDate();
15110         gameInfo.round = StrSave("-");
15111         gameInfo.white = StrSave("-");
15112         gameInfo.black = StrSave("-");
15113         gameInfo.result = r;
15114         gameInfo.resultDetails = p;
15115         break;
15116
15117       case EditPosition:
15118         gameInfo.event = StrSave("Edited position");
15119         gameInfo.site = StrSave(HostName());
15120         gameInfo.date = PGNDate();
15121         gameInfo.round = StrSave("-");
15122         gameInfo.white = StrSave("-");
15123         gameInfo.black = StrSave("-");
15124         break;
15125
15126       case IcsPlayingWhite:
15127       case IcsPlayingBlack:
15128       case IcsObserving:
15129       case IcsExamining:
15130         break;
15131
15132       case PlayFromGameFile:
15133         gameInfo.event = StrSave("Game from non-PGN file");
15134         gameInfo.site = StrSave(HostName());
15135         gameInfo.date = PGNDate();
15136         gameInfo.round = StrSave("-");
15137         gameInfo.white = StrSave("?");
15138         gameInfo.black = StrSave("?");
15139         break;
15140
15141       default:
15142         break;
15143     }
15144 }
15145
15146 void
15147 ReplaceComment (int index, char *text)
15148 {
15149     int len;
15150     char *p;
15151     float score;
15152
15153     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15154        pvInfoList[index-1].depth == len &&
15155        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15156        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15157     while (*text == '\n') text++;
15158     len = strlen(text);
15159     while (len > 0 && text[len - 1] == '\n') len--;
15160
15161     if (commentList[index] != NULL)
15162       free(commentList[index]);
15163
15164     if (len == 0) {
15165         commentList[index] = NULL;
15166         return;
15167     }
15168   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15169       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15170       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15171     commentList[index] = (char *) malloc(len + 2);
15172     strncpy(commentList[index], text, len);
15173     commentList[index][len] = '\n';
15174     commentList[index][len + 1] = NULLCHAR;
15175   } else {
15176     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15177     char *p;
15178     commentList[index] = (char *) malloc(len + 7);
15179     safeStrCpy(commentList[index], "{\n", 3);
15180     safeStrCpy(commentList[index]+2, text, len+1);
15181     commentList[index][len+2] = NULLCHAR;
15182     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15183     strcat(commentList[index], "\n}\n");
15184   }
15185 }
15186
15187 void
15188 CrushCRs (char *text)
15189 {
15190   char *p = text;
15191   char *q = text;
15192   char ch;
15193
15194   do {
15195     ch = *p++;
15196     if (ch == '\r') continue;
15197     *q++ = ch;
15198   } while (ch != '\0');
15199 }
15200
15201 void
15202 AppendComment (int index, char *text, Boolean addBraces)
15203 /* addBraces  tells if we should add {} */
15204 {
15205     int oldlen, len;
15206     char *old;
15207
15208 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15209     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15210
15211     CrushCRs(text);
15212     while (*text == '\n') text++;
15213     len = strlen(text);
15214     while (len > 0 && text[len - 1] == '\n') len--;
15215     text[len] = NULLCHAR;
15216
15217     if (len == 0) return;
15218
15219     if (commentList[index] != NULL) {
15220       Boolean addClosingBrace = addBraces;
15221         old = commentList[index];
15222         oldlen = strlen(old);
15223         while(commentList[index][oldlen-1] ==  '\n')
15224           commentList[index][--oldlen] = NULLCHAR;
15225         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15226         safeStrCpy(commentList[index], old, oldlen + len + 6);
15227         free(old);
15228         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15229         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15230           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15231           while (*text == '\n') { text++; len--; }
15232           commentList[index][--oldlen] = NULLCHAR;
15233       }
15234         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15235         else          strcat(commentList[index], "\n");
15236         strcat(commentList[index], text);
15237         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15238         else          strcat(commentList[index], "\n");
15239     } else {
15240         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15241         if(addBraces)
15242           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15243         else commentList[index][0] = NULLCHAR;
15244         strcat(commentList[index], text);
15245         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15246         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15247     }
15248 }
15249
15250 static char *
15251 FindStr (char * text, char * sub_text)
15252 {
15253     char * result = strstr( text, sub_text );
15254
15255     if( result != NULL ) {
15256         result += strlen( sub_text );
15257     }
15258
15259     return result;
15260 }
15261
15262 /* [AS] Try to extract PV info from PGN comment */
15263 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15264 char *
15265 GetInfoFromComment (int index, char * text)
15266 {
15267     char * sep = text, *p;
15268
15269     if( text != NULL && index > 0 ) {
15270         int score = 0;
15271         int depth = 0;
15272         int time = -1, sec = 0, deci;
15273         char * s_eval = FindStr( text, "[%eval " );
15274         char * s_emt = FindStr( text, "[%emt " );
15275
15276         if( s_eval != NULL || s_emt != NULL ) {
15277             /* New style */
15278             char delim;
15279
15280             if( s_eval != NULL ) {
15281                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15282                     return text;
15283                 }
15284
15285                 if( delim != ']' ) {
15286                     return text;
15287                 }
15288             }
15289
15290             if( s_emt != NULL ) {
15291             }
15292                 return text;
15293         }
15294         else {
15295             /* We expect something like: [+|-]nnn.nn/dd */
15296             int score_lo = 0;
15297
15298             if(*text != '{') return text; // [HGM] braces: must be normal comment
15299
15300             sep = strchr( text, '/' );
15301             if( sep == NULL || sep < (text+4) ) {
15302                 return text;
15303             }
15304
15305             p = text;
15306             if(p[1] == '(') { // comment starts with PV
15307                p = strchr(p, ')'); // locate end of PV
15308                if(p == NULL || sep < p+5) return text;
15309                // at this point we have something like "{(.*) +0.23/6 ..."
15310                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15311                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15312                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15313             }
15314             time = -1; sec = -1; deci = -1;
15315             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15316                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15317                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15318                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15319                 return text;
15320             }
15321
15322             if( score_lo < 0 || score_lo >= 100 ) {
15323                 return text;
15324             }
15325
15326             if(sec >= 0) time = 600*time + 10*sec; else
15327             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15328
15329             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15330
15331             /* [HGM] PV time: now locate end of PV info */
15332             while( *++sep >= '0' && *sep <= '9'); // strip depth
15333             if(time >= 0)
15334             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15335             if(sec >= 0)
15336             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15337             if(deci >= 0)
15338             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15339             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15340         }
15341
15342         if( depth <= 0 ) {
15343             return text;
15344         }
15345
15346         if( time < 0 ) {
15347             time = -1;
15348         }
15349
15350         pvInfoList[index-1].depth = depth;
15351         pvInfoList[index-1].score = score;
15352         pvInfoList[index-1].time  = 10*time; // centi-sec
15353         if(*sep == '}') *sep = 0; else *--sep = '{';
15354         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15355     }
15356     return sep;
15357 }
15358
15359 void
15360 SendToProgram (char *message, ChessProgramState *cps)
15361 {
15362     int count, outCount, error;
15363     char buf[MSG_SIZ];
15364
15365     if (cps->pr == NoProc) return;
15366     Attention(cps);
15367
15368     if (appData.debugMode) {
15369         TimeMark now;
15370         GetTimeMark(&now);
15371         fprintf(debugFP, "%ld >%-6s: %s",
15372                 SubtractTimeMarks(&now, &programStartTime),
15373                 cps->which, message);
15374         if(serverFP)
15375             fprintf(serverFP, "%ld >%-6s: %s",
15376                 SubtractTimeMarks(&now, &programStartTime),
15377                 cps->which, message), fflush(serverFP);
15378     }
15379
15380     count = strlen(message);
15381     outCount = OutputToProcess(cps->pr, message, count, &error);
15382     if (outCount < count && !exiting
15383                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15384       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15385       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15386         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15387             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15388                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15389                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15390                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15391             } else {
15392                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15393                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15394                 gameInfo.result = res;
15395             }
15396             gameInfo.resultDetails = StrSave(buf);
15397         }
15398         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15399         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15400     }
15401 }
15402
15403 void
15404 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15405 {
15406     char *end_str;
15407     char buf[MSG_SIZ];
15408     ChessProgramState *cps = (ChessProgramState *)closure;
15409
15410     if (isr != cps->isr) return; /* Killed intentionally */
15411     if (count <= 0) {
15412         if (count == 0) {
15413             RemoveInputSource(cps->isr);
15414             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15415                     _(cps->which), cps->program);
15416             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
15417             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15418                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15419                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15420                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15421                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15422                 } else {
15423                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15424                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15425                     gameInfo.result = res;
15426                 }
15427                 gameInfo.resultDetails = StrSave(buf);
15428             }
15429             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15430             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15431         } else {
15432             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15433                     _(cps->which), cps->program);
15434             RemoveInputSource(cps->isr);
15435
15436             /* [AS] Program is misbehaving badly... kill it */
15437             if( count == -2 ) {
15438                 DestroyChildProcess( cps->pr, 9 );
15439                 cps->pr = NoProc;
15440             }
15441
15442             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15443         }
15444         return;
15445     }
15446
15447     if ((end_str = strchr(message, '\r')) != NULL)
15448       *end_str = NULLCHAR;
15449     if ((end_str = strchr(message, '\n')) != NULL)
15450       *end_str = NULLCHAR;
15451
15452     if (appData.debugMode) {
15453         TimeMark now; int print = 1;
15454         char *quote = ""; char c; int i;
15455
15456         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15457                 char start = message[0];
15458                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15459                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15460                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15461                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15462                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15463                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15464                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15465                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15466                    sscanf(message, "hint: %c", &c)!=1 && 
15467                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15468                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15469                     print = (appData.engineComments >= 2);
15470                 }
15471                 message[0] = start; // restore original message
15472         }
15473         if(print) {
15474                 GetTimeMark(&now);
15475                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15476                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15477                         quote,
15478                         message);
15479                 if(serverFP)
15480                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15481                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15482                         quote,
15483                         message), fflush(serverFP);
15484         }
15485     }
15486
15487     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15488     if (appData.icsEngineAnalyze) {
15489         if (strstr(message, "whisper") != NULL ||
15490              strstr(message, "kibitz") != NULL ||
15491             strstr(message, "tellics") != NULL) return;
15492     }
15493
15494     HandleMachineMove(message, cps);
15495 }
15496
15497
15498 void
15499 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15500 {
15501     char buf[MSG_SIZ];
15502     int seconds;
15503
15504     if( timeControl_2 > 0 ) {
15505         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15506             tc = timeControl_2;
15507         }
15508     }
15509     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15510     inc /= cps->timeOdds;
15511     st  /= cps->timeOdds;
15512
15513     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15514
15515     if (st > 0) {
15516       /* Set exact time per move, normally using st command */
15517       if (cps->stKludge) {
15518         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15519         seconds = st % 60;
15520         if (seconds == 0) {
15521           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15522         } else {
15523           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15524         }
15525       } else {
15526         snprintf(buf, MSG_SIZ, "st %d\n", st);
15527       }
15528     } else {
15529       /* Set conventional or incremental time control, using level command */
15530       if (seconds == 0) {
15531         /* Note old gnuchess bug -- minutes:seconds used to not work.
15532            Fixed in later versions, but still avoid :seconds
15533            when seconds is 0. */
15534         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15535       } else {
15536         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15537                  seconds, inc/1000.);
15538       }
15539     }
15540     SendToProgram(buf, cps);
15541
15542     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15543     /* Orthogonally, limit search to given depth */
15544     if (sd > 0) {
15545       if (cps->sdKludge) {
15546         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15547       } else {
15548         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15549       }
15550       SendToProgram(buf, cps);
15551     }
15552
15553     if(cps->nps >= 0) { /* [HGM] nps */
15554         if(cps->supportsNPS == FALSE)
15555           cps->nps = -1; // don't use if engine explicitly says not supported!
15556         else {
15557           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15558           SendToProgram(buf, cps);
15559         }
15560     }
15561 }
15562
15563 ChessProgramState *
15564 WhitePlayer ()
15565 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15566 {
15567     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15568        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15569         return &second;
15570     return &first;
15571 }
15572
15573 void
15574 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15575 {
15576     char message[MSG_SIZ];
15577     long time, otime;
15578
15579     /* Note: this routine must be called when the clocks are stopped
15580        or when they have *just* been set or switched; otherwise
15581        it will be off by the time since the current tick started.
15582     */
15583     if (machineWhite) {
15584         time = whiteTimeRemaining / 10;
15585         otime = blackTimeRemaining / 10;
15586     } else {
15587         time = blackTimeRemaining / 10;
15588         otime = whiteTimeRemaining / 10;
15589     }
15590     /* [HGM] translate opponent's time by time-odds factor */
15591     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15592
15593     if (time <= 0) time = 1;
15594     if (otime <= 0) otime = 1;
15595
15596     snprintf(message, MSG_SIZ, "time %ld\n", time);
15597     SendToProgram(message, cps);
15598
15599     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15600     SendToProgram(message, cps);
15601 }
15602
15603 int
15604 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15605 {
15606   char buf[MSG_SIZ];
15607   int len = strlen(name);
15608   int val;
15609
15610   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15611     (*p) += len + 1;
15612     sscanf(*p, "%d", &val);
15613     *loc = (val != 0);
15614     while (**p && **p != ' ')
15615       (*p)++;
15616     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15617     SendToProgram(buf, cps);
15618     return TRUE;
15619   }
15620   return FALSE;
15621 }
15622
15623 int
15624 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15625 {
15626   char buf[MSG_SIZ];
15627   int len = strlen(name);
15628   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15629     (*p) += len + 1;
15630     sscanf(*p, "%d", loc);
15631     while (**p && **p != ' ') (*p)++;
15632     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15633     SendToProgram(buf, cps);
15634     return TRUE;
15635   }
15636   return FALSE;
15637 }
15638
15639 int
15640 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15641 {
15642   char buf[MSG_SIZ];
15643   int len = strlen(name);
15644   if (strncmp((*p), name, len) == 0
15645       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15646     (*p) += len + 2;
15647     sscanf(*p, "%[^\"]", loc);
15648     while (**p && **p != '\"') (*p)++;
15649     if (**p == '\"') (*p)++;
15650     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15651     SendToProgram(buf, cps);
15652     return TRUE;
15653   }
15654   return FALSE;
15655 }
15656
15657 int
15658 ParseOption (Option *opt, ChessProgramState *cps)
15659 // [HGM] options: process the string that defines an engine option, and determine
15660 // name, type, default value, and allowed value range
15661 {
15662         char *p, *q, buf[MSG_SIZ];
15663         int n, min = (-1)<<31, max = 1<<31, def;
15664
15665         if(p = strstr(opt->name, " -spin ")) {
15666             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15667             if(max < min) max = min; // enforce consistency
15668             if(def < min) def = min;
15669             if(def > max) def = max;
15670             opt->value = def;
15671             opt->min = min;
15672             opt->max = max;
15673             opt->type = Spin;
15674         } else if((p = strstr(opt->name, " -slider "))) {
15675             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15676             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15677             if(max < min) max = min; // enforce consistency
15678             if(def < min) def = min;
15679             if(def > max) def = max;
15680             opt->value = def;
15681             opt->min = min;
15682             opt->max = max;
15683             opt->type = Spin; // Slider;
15684         } else if((p = strstr(opt->name, " -string "))) {
15685             opt->textValue = p+9;
15686             opt->type = TextBox;
15687         } else if((p = strstr(opt->name, " -file "))) {
15688             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15689             opt->textValue = p+7;
15690             opt->type = FileName; // FileName;
15691         } else if((p = strstr(opt->name, " -path "))) {
15692             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15693             opt->textValue = p+7;
15694             opt->type = PathName; // PathName;
15695         } else if(p = strstr(opt->name, " -check ")) {
15696             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15697             opt->value = (def != 0);
15698             opt->type = CheckBox;
15699         } else if(p = strstr(opt->name, " -combo ")) {
15700             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15701             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15702             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15703             opt->value = n = 0;
15704             while(q = StrStr(q, " /// ")) {
15705                 n++; *q = 0;    // count choices, and null-terminate each of them
15706                 q += 5;
15707                 if(*q == '*') { // remember default, which is marked with * prefix
15708                     q++;
15709                     opt->value = n;
15710                 }
15711                 cps->comboList[cps->comboCnt++] = q;
15712             }
15713             cps->comboList[cps->comboCnt++] = NULL;
15714             opt->max = n + 1;
15715             opt->type = ComboBox;
15716         } else if(p = strstr(opt->name, " -button")) {
15717             opt->type = Button;
15718         } else if(p = strstr(opt->name, " -save")) {
15719             opt->type = SaveButton;
15720         } else return FALSE;
15721         *p = 0; // terminate option name
15722         // now look if the command-line options define a setting for this engine option.
15723         if(cps->optionSettings && cps->optionSettings[0])
15724             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15725         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15726           snprintf(buf, MSG_SIZ, "option %s", p);
15727                 if(p = strstr(buf, ",")) *p = 0;
15728                 if(q = strchr(buf, '=')) switch(opt->type) {
15729                     case ComboBox:
15730                         for(n=0; n<opt->max; n++)
15731                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15732                         break;
15733                     case TextBox:
15734                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15735                         break;
15736                     case Spin:
15737                     case CheckBox:
15738                         opt->value = atoi(q+1);
15739                     default:
15740                         break;
15741                 }
15742                 strcat(buf, "\n");
15743                 SendToProgram(buf, cps);
15744         }
15745         return TRUE;
15746 }
15747
15748 void
15749 FeatureDone (ChessProgramState *cps, int val)
15750 {
15751   DelayedEventCallback cb = GetDelayedEvent();
15752   if ((cb == InitBackEnd3 && cps == &first) ||
15753       (cb == SettingsMenuIfReady && cps == &second) ||
15754       (cb == LoadEngine) ||
15755       (cb == TwoMachinesEventIfReady)) {
15756     CancelDelayedEvent();
15757     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15758   }
15759   cps->initDone = val;
15760 }
15761
15762 /* Parse feature command from engine */
15763 void
15764 ParseFeatures (char *args, ChessProgramState *cps)
15765 {
15766   char *p = args;
15767   char *q;
15768   int val;
15769   char buf[MSG_SIZ];
15770
15771   for (;;) {
15772     while (*p == ' ') p++;
15773     if (*p == NULLCHAR) return;
15774
15775     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15776     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15777     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15778     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15779     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15780     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15781     if (BoolFeature(&p, "reuse", &val, cps)) {
15782       /* Engine can disable reuse, but can't enable it if user said no */
15783       if (!val) cps->reuse = FALSE;
15784       continue;
15785     }
15786     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15787     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15788       if (gameMode == TwoMachinesPlay) {
15789         DisplayTwoMachinesTitle();
15790       } else {
15791         DisplayTitle("");
15792       }
15793       continue;
15794     }
15795     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15796     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15797     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15798     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15799     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15800     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15801     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15802     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15803     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15804     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15805     if (IntFeature(&p, "done", &val, cps)) {
15806       FeatureDone(cps, val);
15807       continue;
15808     }
15809     /* Added by Tord: */
15810     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15811     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15812     /* End of additions by Tord */
15813
15814     /* [HGM] added features: */
15815     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15816     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15817     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15818     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15819     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15820     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15821     if (StringFeature(&p, "option", buf, cps)) {
15822         FREE(cps->option[cps->nrOptions].name);
15823         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15824         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15825         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15826           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15827             SendToProgram(buf, cps);
15828             continue;
15829         }
15830         if(cps->nrOptions >= MAX_OPTIONS) {
15831             cps->nrOptions--;
15832             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15833             DisplayError(buf, 0);
15834         }
15835         continue;
15836     }
15837     /* End of additions by HGM */
15838
15839     /* unknown feature: complain and skip */
15840     q = p;
15841     while (*q && *q != '=') q++;
15842     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15843     SendToProgram(buf, cps);
15844     p = q;
15845     if (*p == '=') {
15846       p++;
15847       if (*p == '\"') {
15848         p++;
15849         while (*p && *p != '\"') p++;
15850         if (*p == '\"') p++;
15851       } else {
15852         while (*p && *p != ' ') p++;
15853       }
15854     }
15855   }
15856
15857 }
15858
15859 void
15860 PeriodicUpdatesEvent (int newState)
15861 {
15862     if (newState == appData.periodicUpdates)
15863       return;
15864
15865     appData.periodicUpdates=newState;
15866
15867     /* Display type changes, so update it now */
15868 //    DisplayAnalysis();
15869
15870     /* Get the ball rolling again... */
15871     if (newState) {
15872         AnalysisPeriodicEvent(1);
15873         StartAnalysisClock();
15874     }
15875 }
15876
15877 void
15878 PonderNextMoveEvent (int newState)
15879 {
15880     if (newState == appData.ponderNextMove) return;
15881     if (gameMode == EditPosition) EditPositionDone(TRUE);
15882     if (newState) {
15883         SendToProgram("hard\n", &first);
15884         if (gameMode == TwoMachinesPlay) {
15885             SendToProgram("hard\n", &second);
15886         }
15887     } else {
15888         SendToProgram("easy\n", &first);
15889         thinkOutput[0] = NULLCHAR;
15890         if (gameMode == TwoMachinesPlay) {
15891             SendToProgram("easy\n", &second);
15892         }
15893     }
15894     appData.ponderNextMove = newState;
15895 }
15896
15897 void
15898 NewSettingEvent (int option, int *feature, char *command, int value)
15899 {
15900     char buf[MSG_SIZ];
15901
15902     if (gameMode == EditPosition) EditPositionDone(TRUE);
15903     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15904     if(feature == NULL || *feature) SendToProgram(buf, &first);
15905     if (gameMode == TwoMachinesPlay) {
15906         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15907     }
15908 }
15909
15910 void
15911 ShowThinkingEvent ()
15912 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15913 {
15914     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15915     int newState = appData.showThinking
15916         // [HGM] thinking: other features now need thinking output as well
15917         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15918
15919     if (oldState == newState) return;
15920     oldState = newState;
15921     if (gameMode == EditPosition) EditPositionDone(TRUE);
15922     if (oldState) {
15923         SendToProgram("post\n", &first);
15924         if (gameMode == TwoMachinesPlay) {
15925             SendToProgram("post\n", &second);
15926         }
15927     } else {
15928         SendToProgram("nopost\n", &first);
15929         thinkOutput[0] = NULLCHAR;
15930         if (gameMode == TwoMachinesPlay) {
15931             SendToProgram("nopost\n", &second);
15932         }
15933     }
15934 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15935 }
15936
15937 void
15938 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15939 {
15940   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15941   if (pr == NoProc) return;
15942   AskQuestion(title, question, replyPrefix, pr);
15943 }
15944
15945 void
15946 TypeInEvent (char firstChar)
15947 {
15948     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15949         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15950         gameMode == AnalyzeMode || gameMode == EditGame || 
15951         gameMode == EditPosition || gameMode == IcsExamining ||
15952         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15953         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15954                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15955                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15956         gameMode == Training) PopUpMoveDialog(firstChar);
15957 }
15958
15959 void
15960 TypeInDoneEvent (char *move)
15961 {
15962         Board board;
15963         int n, fromX, fromY, toX, toY;
15964         char promoChar;
15965         ChessMove moveType;
15966
15967         // [HGM] FENedit
15968         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15969                 EditPositionPasteFEN(move);
15970                 return;
15971         }
15972         // [HGM] movenum: allow move number to be typed in any mode
15973         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15974           ToNrEvent(2*n-1);
15975           return;
15976         }
15977         // undocumented kludge: allow command-line option to be typed in!
15978         // (potentially fatal, and does not implement the effect of the option.)
15979         // should only be used for options that are values on which future decisions will be made,
15980         // and definitely not on options that would be used during initialization.
15981         if(strstr(move, "!!! -") == move) {
15982             ParseArgsFromString(move+4);
15983             return;
15984         }
15985
15986       if (gameMode != EditGame && currentMove != forwardMostMove && 
15987         gameMode != Training) {
15988         DisplayMoveError(_("Displayed move is not current"));
15989       } else {
15990         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15991           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15992         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15993         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15994           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15995           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15996         } else {
15997           DisplayMoveError(_("Could not parse move"));
15998         }
15999       }
16000 }
16001
16002 void
16003 DisplayMove (int moveNumber)
16004 {
16005     char message[MSG_SIZ];
16006     char res[MSG_SIZ];
16007     char cpThinkOutput[MSG_SIZ];
16008
16009     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16010
16011     if (moveNumber == forwardMostMove - 1 ||
16012         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16013
16014         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16015
16016         if (strchr(cpThinkOutput, '\n')) {
16017             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16018         }
16019     } else {
16020         *cpThinkOutput = NULLCHAR;
16021     }
16022
16023     /* [AS] Hide thinking from human user */
16024     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16025         *cpThinkOutput = NULLCHAR;
16026         if( thinkOutput[0] != NULLCHAR ) {
16027             int i;
16028
16029             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16030                 cpThinkOutput[i] = '.';
16031             }
16032             cpThinkOutput[i] = NULLCHAR;
16033             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16034         }
16035     }
16036
16037     if (moveNumber == forwardMostMove - 1 &&
16038         gameInfo.resultDetails != NULL) {
16039         if (gameInfo.resultDetails[0] == NULLCHAR) {
16040           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16041         } else {
16042           snprintf(res, MSG_SIZ, " {%s} %s",
16043                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16044         }
16045     } else {
16046         res[0] = NULLCHAR;
16047     }
16048
16049     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16050         DisplayMessage(res, cpThinkOutput);
16051     } else {
16052       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16053                 WhiteOnMove(moveNumber) ? " " : ".. ",
16054                 parseList[moveNumber], res);
16055         DisplayMessage(message, cpThinkOutput);
16056     }
16057 }
16058
16059 void
16060 DisplayComment (int moveNumber, char *text)
16061 {
16062     char title[MSG_SIZ];
16063
16064     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16065       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16066     } else {
16067       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16068               WhiteOnMove(moveNumber) ? " " : ".. ",
16069               parseList[moveNumber]);
16070     }
16071     if (text != NULL && (appData.autoDisplayComment || commentUp))
16072         CommentPopUp(title, text);
16073 }
16074
16075 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16076  * might be busy thinking or pondering.  It can be omitted if your
16077  * gnuchess is configured to stop thinking immediately on any user
16078  * input.  However, that gnuchess feature depends on the FIONREAD
16079  * ioctl, which does not work properly on some flavors of Unix.
16080  */
16081 void
16082 Attention (ChessProgramState *cps)
16083 {
16084 #if ATTENTION
16085     if (!cps->useSigint) return;
16086     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16087     switch (gameMode) {
16088       case MachinePlaysWhite:
16089       case MachinePlaysBlack:
16090       case TwoMachinesPlay:
16091       case IcsPlayingWhite:
16092       case IcsPlayingBlack:
16093       case AnalyzeMode:
16094       case AnalyzeFile:
16095         /* Skip if we know it isn't thinking */
16096         if (!cps->maybeThinking) return;
16097         if (appData.debugMode)
16098           fprintf(debugFP, "Interrupting %s\n", cps->which);
16099         InterruptChildProcess(cps->pr);
16100         cps->maybeThinking = FALSE;
16101         break;
16102       default:
16103         break;
16104     }
16105 #endif /*ATTENTION*/
16106 }
16107
16108 int
16109 CheckFlags ()
16110 {
16111     if (whiteTimeRemaining <= 0) {
16112         if (!whiteFlag) {
16113             whiteFlag = TRUE;
16114             if (appData.icsActive) {
16115                 if (appData.autoCallFlag &&
16116                     gameMode == IcsPlayingBlack && !blackFlag) {
16117                   SendToICS(ics_prefix);
16118                   SendToICS("flag\n");
16119                 }
16120             } else {
16121                 if (blackFlag) {
16122                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16123                 } else {
16124                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16125                     if (appData.autoCallFlag) {
16126                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16127                         return TRUE;
16128                     }
16129                 }
16130             }
16131         }
16132     }
16133     if (blackTimeRemaining <= 0) {
16134         if (!blackFlag) {
16135             blackFlag = TRUE;
16136             if (appData.icsActive) {
16137                 if (appData.autoCallFlag &&
16138                     gameMode == IcsPlayingWhite && !whiteFlag) {
16139                   SendToICS(ics_prefix);
16140                   SendToICS("flag\n");
16141                 }
16142             } else {
16143                 if (whiteFlag) {
16144                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16145                 } else {
16146                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16147                     if (appData.autoCallFlag) {
16148                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16149                         return TRUE;
16150                     }
16151                 }
16152             }
16153         }
16154     }
16155     return FALSE;
16156 }
16157
16158 void
16159 CheckTimeControl ()
16160 {
16161     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16162         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16163
16164     /*
16165      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16166      */
16167     if ( !WhiteOnMove(forwardMostMove) ) {
16168         /* White made time control */
16169         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16170         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16171         /* [HGM] time odds: correct new time quota for time odds! */
16172                                             / WhitePlayer()->timeOdds;
16173         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16174     } else {
16175         lastBlack -= blackTimeRemaining;
16176         /* Black made time control */
16177         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16178                                             / WhitePlayer()->other->timeOdds;
16179         lastWhite = whiteTimeRemaining;
16180     }
16181 }
16182
16183 void
16184 DisplayBothClocks ()
16185 {
16186     int wom = gameMode == EditPosition ?
16187       !blackPlaysFirst : WhiteOnMove(currentMove);
16188     DisplayWhiteClock(whiteTimeRemaining, wom);
16189     DisplayBlackClock(blackTimeRemaining, !wom);
16190 }
16191
16192
16193 /* Timekeeping seems to be a portability nightmare.  I think everyone
16194    has ftime(), but I'm really not sure, so I'm including some ifdefs
16195    to use other calls if you don't.  Clocks will be less accurate if
16196    you have neither ftime nor gettimeofday.
16197 */
16198
16199 /* VS 2008 requires the #include outside of the function */
16200 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16201 #include <sys/timeb.h>
16202 #endif
16203
16204 /* Get the current time as a TimeMark */
16205 void
16206 GetTimeMark (TimeMark *tm)
16207 {
16208 #if HAVE_GETTIMEOFDAY
16209
16210     struct timeval timeVal;
16211     struct timezone timeZone;
16212
16213     gettimeofday(&timeVal, &timeZone);
16214     tm->sec = (long) timeVal.tv_sec;
16215     tm->ms = (int) (timeVal.tv_usec / 1000L);
16216
16217 #else /*!HAVE_GETTIMEOFDAY*/
16218 #if HAVE_FTIME
16219
16220 // include <sys/timeb.h> / moved to just above start of function
16221     struct timeb timeB;
16222
16223     ftime(&timeB);
16224     tm->sec = (long) timeB.time;
16225     tm->ms = (int) timeB.millitm;
16226
16227 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16228     tm->sec = (long) time(NULL);
16229     tm->ms = 0;
16230 #endif
16231 #endif
16232 }
16233
16234 /* Return the difference in milliseconds between two
16235    time marks.  We assume the difference will fit in a long!
16236 */
16237 long
16238 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16239 {
16240     return 1000L*(tm2->sec - tm1->sec) +
16241            (long) (tm2->ms - tm1->ms);
16242 }
16243
16244
16245 /*
16246  * Code to manage the game clocks.
16247  *
16248  * In tournament play, black starts the clock and then white makes a move.
16249  * We give the human user a slight advantage if he is playing white---the
16250  * clocks don't run until he makes his first move, so it takes zero time.
16251  * Also, we don't account for network lag, so we could get out of sync
16252  * with GNU Chess's clock -- but then, referees are always right.
16253  */
16254
16255 static TimeMark tickStartTM;
16256 static long intendedTickLength;
16257
16258 long
16259 NextTickLength (long timeRemaining)
16260 {
16261     long nominalTickLength, nextTickLength;
16262
16263     if (timeRemaining > 0L && timeRemaining <= 10000L)
16264       nominalTickLength = 100L;
16265     else
16266       nominalTickLength = 1000L;
16267     nextTickLength = timeRemaining % nominalTickLength;
16268     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16269
16270     return nextTickLength;
16271 }
16272
16273 /* Adjust clock one minute up or down */
16274 void
16275 AdjustClock (Boolean which, int dir)
16276 {
16277     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16278     if(which) blackTimeRemaining += 60000*dir;
16279     else      whiteTimeRemaining += 60000*dir;
16280     DisplayBothClocks();
16281     adjustedClock = TRUE;
16282 }
16283
16284 /* Stop clocks and reset to a fresh time control */
16285 void
16286 ResetClocks ()
16287 {
16288     (void) StopClockTimer();
16289     if (appData.icsActive) {
16290         whiteTimeRemaining = blackTimeRemaining = 0;
16291     } else if (searchTime) {
16292         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16293         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16294     } else { /* [HGM] correct new time quote for time odds */
16295         whiteTC = blackTC = fullTimeControlString;
16296         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16297         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16298     }
16299     if (whiteFlag || blackFlag) {
16300         DisplayTitle("");
16301         whiteFlag = blackFlag = FALSE;
16302     }
16303     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16304     DisplayBothClocks();
16305     adjustedClock = FALSE;
16306 }
16307
16308 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16309
16310 /* Decrement running clock by amount of time that has passed */
16311 void
16312 DecrementClocks ()
16313 {
16314     long timeRemaining;
16315     long lastTickLength, fudge;
16316     TimeMark now;
16317
16318     if (!appData.clockMode) return;
16319     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16320
16321     GetTimeMark(&now);
16322
16323     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16324
16325     /* Fudge if we woke up a little too soon */
16326     fudge = intendedTickLength - lastTickLength;
16327     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16328
16329     if (WhiteOnMove(forwardMostMove)) {
16330         if(whiteNPS >= 0) lastTickLength = 0;
16331         timeRemaining = whiteTimeRemaining -= lastTickLength;
16332         if(timeRemaining < 0 && !appData.icsActive) {
16333             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16334             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16335                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16336                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16337             }
16338         }
16339         DisplayWhiteClock(whiteTimeRemaining - fudge,
16340                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16341     } else {
16342         if(blackNPS >= 0) lastTickLength = 0;
16343         timeRemaining = blackTimeRemaining -= lastTickLength;
16344         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16345             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16346             if(suddenDeath) {
16347                 blackStartMove = forwardMostMove;
16348                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16349             }
16350         }
16351         DisplayBlackClock(blackTimeRemaining - fudge,
16352                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16353     }
16354     if (CheckFlags()) return;
16355
16356     if(twoBoards) { // count down secondary board's clocks as well
16357         activePartnerTime -= lastTickLength;
16358         partnerUp = 1;
16359         if(activePartner == 'W')
16360             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
16361         else
16362             DisplayBlackClock(activePartnerTime, TRUE);
16363         partnerUp = 0;
16364     }
16365
16366     tickStartTM = now;
16367     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16368     StartClockTimer(intendedTickLength);
16369
16370     /* if the time remaining has fallen below the alarm threshold, sound the
16371      * alarm. if the alarm has sounded and (due to a takeback or time control
16372      * with increment) the time remaining has increased to a level above the
16373      * threshold, reset the alarm so it can sound again.
16374      */
16375
16376     if (appData.icsActive && appData.icsAlarm) {
16377
16378         /* make sure we are dealing with the user's clock */
16379         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16380                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16381            )) return;
16382
16383         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16384             alarmSounded = FALSE;
16385         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16386             PlayAlarmSound();
16387             alarmSounded = TRUE;
16388         }
16389     }
16390 }
16391
16392
16393 /* A player has just moved, so stop the previously running
16394    clock and (if in clock mode) start the other one.
16395    We redisplay both clocks in case we're in ICS mode, because
16396    ICS gives us an update to both clocks after every move.
16397    Note that this routine is called *after* forwardMostMove
16398    is updated, so the last fractional tick must be subtracted
16399    from the color that is *not* on move now.
16400 */
16401 void
16402 SwitchClocks (int newMoveNr)
16403 {
16404     long lastTickLength;
16405     TimeMark now;
16406     int flagged = FALSE;
16407
16408     GetTimeMark(&now);
16409
16410     if (StopClockTimer() && appData.clockMode) {
16411         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16412         if (!WhiteOnMove(forwardMostMove)) {
16413             if(blackNPS >= 0) lastTickLength = 0;
16414             blackTimeRemaining -= lastTickLength;
16415            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16416 //         if(pvInfoList[forwardMostMove].time == -1)
16417                  pvInfoList[forwardMostMove].time =               // use GUI time
16418                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16419         } else {
16420            if(whiteNPS >= 0) lastTickLength = 0;
16421            whiteTimeRemaining -= lastTickLength;
16422            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16423 //         if(pvInfoList[forwardMostMove].time == -1)
16424                  pvInfoList[forwardMostMove].time =
16425                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16426         }
16427         flagged = CheckFlags();
16428     }
16429     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16430     CheckTimeControl();
16431
16432     if (flagged || !appData.clockMode) return;
16433
16434     switch (gameMode) {
16435       case MachinePlaysBlack:
16436       case MachinePlaysWhite:
16437       case BeginningOfGame:
16438         if (pausing) return;
16439         break;
16440
16441       case EditGame:
16442       case PlayFromGameFile:
16443       case IcsExamining:
16444         return;
16445
16446       default:
16447         break;
16448     }
16449
16450     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16451         if(WhiteOnMove(forwardMostMove))
16452              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16453         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16454     }
16455
16456     tickStartTM = now;
16457     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16458       whiteTimeRemaining : blackTimeRemaining);
16459     StartClockTimer(intendedTickLength);
16460 }
16461
16462
16463 /* Stop both clocks */
16464 void
16465 StopClocks ()
16466 {
16467     long lastTickLength;
16468     TimeMark now;
16469
16470     if (!StopClockTimer()) return;
16471     if (!appData.clockMode) return;
16472
16473     GetTimeMark(&now);
16474
16475     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16476     if (WhiteOnMove(forwardMostMove)) {
16477         if(whiteNPS >= 0) lastTickLength = 0;
16478         whiteTimeRemaining -= lastTickLength;
16479         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16480     } else {
16481         if(blackNPS >= 0) lastTickLength = 0;
16482         blackTimeRemaining -= lastTickLength;
16483         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16484     }
16485     CheckFlags();
16486 }
16487
16488 /* Start clock of player on move.  Time may have been reset, so
16489    if clock is already running, stop and restart it. */
16490 void
16491 StartClocks ()
16492 {
16493     (void) StopClockTimer(); /* in case it was running already */
16494     DisplayBothClocks();
16495     if (CheckFlags()) return;
16496
16497     if (!appData.clockMode) return;
16498     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16499
16500     GetTimeMark(&tickStartTM);
16501     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16502       whiteTimeRemaining : blackTimeRemaining);
16503
16504    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16505     whiteNPS = blackNPS = -1;
16506     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16507        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16508         whiteNPS = first.nps;
16509     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16510        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16511         blackNPS = first.nps;
16512     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16513         whiteNPS = second.nps;
16514     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16515         blackNPS = second.nps;
16516     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16517
16518     StartClockTimer(intendedTickLength);
16519 }
16520
16521 char *
16522 TimeString (long ms)
16523 {
16524     long second, minute, hour, day;
16525     char *sign = "";
16526     static char buf[32];
16527
16528     if (ms > 0 && ms <= 9900) {
16529       /* convert milliseconds to tenths, rounding up */
16530       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16531
16532       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16533       return buf;
16534     }
16535
16536     /* convert milliseconds to seconds, rounding up */
16537     /* use floating point to avoid strangeness of integer division
16538        with negative dividends on many machines */
16539     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16540
16541     if (second < 0) {
16542         sign = "-";
16543         second = -second;
16544     }
16545
16546     day = second / (60 * 60 * 24);
16547     second = second % (60 * 60 * 24);
16548     hour = second / (60 * 60);
16549     second = second % (60 * 60);
16550     minute = second / 60;
16551     second = second % 60;
16552
16553     if (day > 0)
16554       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16555               sign, day, hour, minute, second);
16556     else if (hour > 0)
16557       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16558     else
16559       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16560
16561     return buf;
16562 }
16563
16564
16565 /*
16566  * This is necessary because some C libraries aren't ANSI C compliant yet.
16567  */
16568 char *
16569 StrStr (char *string, char *match)
16570 {
16571     int i, length;
16572
16573     length = strlen(match);
16574
16575     for (i = strlen(string) - length; i >= 0; i--, string++)
16576       if (!strncmp(match, string, length))
16577         return string;
16578
16579     return NULL;
16580 }
16581
16582 char *
16583 StrCaseStr (char *string, char *match)
16584 {
16585     int i, j, length;
16586
16587     length = strlen(match);
16588
16589     for (i = strlen(string) - length; i >= 0; i--, string++) {
16590         for (j = 0; j < length; j++) {
16591             if (ToLower(match[j]) != ToLower(string[j]))
16592               break;
16593         }
16594         if (j == length) return string;
16595     }
16596
16597     return NULL;
16598 }
16599
16600 #ifndef _amigados
16601 int
16602 StrCaseCmp (char *s1, char *s2)
16603 {
16604     char c1, c2;
16605
16606     for (;;) {
16607         c1 = ToLower(*s1++);
16608         c2 = ToLower(*s2++);
16609         if (c1 > c2) return 1;
16610         if (c1 < c2) return -1;
16611         if (c1 == NULLCHAR) return 0;
16612     }
16613 }
16614
16615
16616 int
16617 ToLower (int c)
16618 {
16619     return isupper(c) ? tolower(c) : c;
16620 }
16621
16622
16623 int
16624 ToUpper (int c)
16625 {
16626     return islower(c) ? toupper(c) : c;
16627 }
16628 #endif /* !_amigados    */
16629
16630 char *
16631 StrSave (char *s)
16632 {
16633   char *ret;
16634
16635   if ((ret = (char *) malloc(strlen(s) + 1)))
16636     {
16637       safeStrCpy(ret, s, strlen(s)+1);
16638     }
16639   return ret;
16640 }
16641
16642 char *
16643 StrSavePtr (char *s, char **savePtr)
16644 {
16645     if (*savePtr) {
16646         free(*savePtr);
16647     }
16648     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16649       safeStrCpy(*savePtr, s, strlen(s)+1);
16650     }
16651     return(*savePtr);
16652 }
16653
16654 char *
16655 PGNDate ()
16656 {
16657     time_t clock;
16658     struct tm *tm;
16659     char buf[MSG_SIZ];
16660
16661     clock = time((time_t *)NULL);
16662     tm = localtime(&clock);
16663     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16664             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16665     return StrSave(buf);
16666 }
16667
16668
16669 char *
16670 PositionToFEN (int move, char *overrideCastling)
16671 {
16672     int i, j, fromX, fromY, toX, toY;
16673     int whiteToPlay;
16674     char buf[MSG_SIZ];
16675     char *p, *q;
16676     int emptycount;
16677     ChessSquare piece;
16678
16679     whiteToPlay = (gameMode == EditPosition) ?
16680       !blackPlaysFirst : (move % 2 == 0);
16681     p = buf;
16682
16683     /* Piece placement data */
16684     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16685         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16686         emptycount = 0;
16687         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16688             if (boards[move][i][j] == EmptySquare) {
16689                 emptycount++;
16690             } else { ChessSquare piece = boards[move][i][j];
16691                 if (emptycount > 0) {
16692                     if(emptycount<10) /* [HGM] can be >= 10 */
16693                         *p++ = '0' + emptycount;
16694                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16695                     emptycount = 0;
16696                 }
16697                 if(PieceToChar(piece) == '+') {
16698                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16699                     *p++ = '+';
16700                     piece = (ChessSquare)(DEMOTED piece);
16701                 }
16702                 *p++ = PieceToChar(piece);
16703                 if(p[-1] == '~') {
16704                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16705                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16706                     *p++ = '~';
16707                 }
16708             }
16709         }
16710         if (emptycount > 0) {
16711             if(emptycount<10) /* [HGM] can be >= 10 */
16712                 *p++ = '0' + emptycount;
16713             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16714             emptycount = 0;
16715         }
16716         *p++ = '/';
16717     }
16718     *(p - 1) = ' ';
16719
16720     /* [HGM] print Crazyhouse or Shogi holdings */
16721     if( gameInfo.holdingsWidth ) {
16722         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16723         q = p;
16724         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16725             piece = boards[move][i][BOARD_WIDTH-1];
16726             if( piece != EmptySquare )
16727               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16728                   *p++ = PieceToChar(piece);
16729         }
16730         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16731             piece = boards[move][BOARD_HEIGHT-i-1][0];
16732             if( piece != EmptySquare )
16733               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16734                   *p++ = PieceToChar(piece);
16735         }
16736
16737         if( q == p ) *p++ = '-';
16738         *p++ = ']';
16739         *p++ = ' ';
16740     }
16741
16742     /* Active color */
16743     *p++ = whiteToPlay ? 'w' : 'b';
16744     *p++ = ' ';
16745
16746   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16747     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16748   } else {
16749   if(nrCastlingRights) {
16750      q = p;
16751      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16752        /* [HGM] write directly from rights */
16753            if(boards[move][CASTLING][2] != NoRights &&
16754               boards[move][CASTLING][0] != NoRights   )
16755                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16756            if(boards[move][CASTLING][2] != NoRights &&
16757               boards[move][CASTLING][1] != NoRights   )
16758                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16759            if(boards[move][CASTLING][5] != NoRights &&
16760               boards[move][CASTLING][3] != NoRights   )
16761                 *p++ = boards[move][CASTLING][3] + AAA;
16762            if(boards[move][CASTLING][5] != NoRights &&
16763               boards[move][CASTLING][4] != NoRights   )
16764                 *p++ = boards[move][CASTLING][4] + AAA;
16765      } else {
16766
16767         /* [HGM] write true castling rights */
16768         if( nrCastlingRights == 6 ) {
16769             int q, k=0;
16770             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16771                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
16772             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
16773                  boards[move][CASTLING][2] != NoRights  );
16774             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
16775                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
16776                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16777                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
16778                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
16779             }
16780             if(q) *p++ = 'Q';
16781             k = 0;
16782             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16783                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
16784             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
16785                  boards[move][CASTLING][5] != NoRights  );
16786             if(gameInfo.variant == VariantSChess) {
16787                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
16788                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
16789                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
16790                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
16791             }
16792             if(q) *p++ = 'q';
16793         }
16794      }
16795      if (q == p) *p++ = '-'; /* No castling rights */
16796      *p++ = ' ';
16797   }
16798
16799   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16800      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16801     /* En passant target square */
16802     if (move > backwardMostMove) {
16803         fromX = moveList[move - 1][0] - AAA;
16804         fromY = moveList[move - 1][1] - ONE;
16805         toX = moveList[move - 1][2] - AAA;
16806         toY = moveList[move - 1][3] - ONE;
16807         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16808             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16809             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16810             fromX == toX) {
16811             /* 2-square pawn move just happened */
16812             *p++ = toX + AAA;
16813             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16814         } else {
16815             *p++ = '-';
16816         }
16817     } else if(move == backwardMostMove) {
16818         // [HGM] perhaps we should always do it like this, and forget the above?
16819         if((signed char)boards[move][EP_STATUS] >= 0) {
16820             *p++ = boards[move][EP_STATUS] + AAA;
16821             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16822         } else {
16823             *p++ = '-';
16824         }
16825     } else {
16826         *p++ = '-';
16827     }
16828     *p++ = ' ';
16829   }
16830   }
16831
16832     /* [HGM] find reversible plies */
16833     {   int i = 0, j=move;
16834
16835         if (appData.debugMode) { int k;
16836             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16837             for(k=backwardMostMove; k<=forwardMostMove; k++)
16838                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16839
16840         }
16841
16842         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16843         if( j == backwardMostMove ) i += initialRulePlies;
16844         sprintf(p, "%d ", i);
16845         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16846     }
16847     /* Fullmove number */
16848     sprintf(p, "%d", (move / 2) + 1);
16849
16850     return StrSave(buf);
16851 }
16852
16853 Boolean
16854 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16855 {
16856     int i, j;
16857     char *p, c;
16858     int emptycount, virgin[BOARD_FILES];
16859     ChessSquare piece;
16860
16861     p = fen;
16862
16863     /* [HGM] by default clear Crazyhouse holdings, if present */
16864     if(gameInfo.holdingsWidth) {
16865        for(i=0; i<BOARD_HEIGHT; i++) {
16866            board[i][0]             = EmptySquare; /* black holdings */
16867            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16868            board[i][1]             = (ChessSquare) 0; /* black counts */
16869            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16870        }
16871     }
16872
16873     /* Piece placement data */
16874     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16875         j = 0;
16876         for (;;) {
16877             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16878                 if (*p == '/') p++;
16879                 emptycount = gameInfo.boardWidth - j;
16880                 while (emptycount--)
16881                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16882                 break;
16883 #if(BOARD_FILES >= 10)
16884             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16885                 p++; emptycount=10;
16886                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16887                 while (emptycount--)
16888                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16889 #endif
16890             } else if (isdigit(*p)) {
16891                 emptycount = *p++ - '0';
16892                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16893                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16894                 while (emptycount--)
16895                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16896             } else if (*p == '+' || isalpha(*p)) {
16897                 if (j >= gameInfo.boardWidth) return FALSE;
16898                 if(*p=='+') {
16899                     piece = CharToPiece(*++p);
16900                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16901                     piece = (ChessSquare) (PROMOTED piece ); p++;
16902                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16903                 } else piece = CharToPiece(*p++);
16904
16905                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16906                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16907                     piece = (ChessSquare) (PROMOTED piece);
16908                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16909                     p++;
16910                 }
16911                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16912             } else {
16913                 return FALSE;
16914             }
16915         }
16916     }
16917     while (*p == '/' || *p == ' ') p++;
16918
16919     /* [HGM] look for Crazyhouse holdings here */
16920     while(*p==' ') p++;
16921     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16922         if(*p == '[') p++;
16923         if(*p == '-' ) p++; /* empty holdings */ else {
16924             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16925             /* if we would allow FEN reading to set board size, we would   */
16926             /* have to add holdings and shift the board read so far here   */
16927             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16928                 p++;
16929                 if((int) piece >= (int) BlackPawn ) {
16930                     i = (int)piece - (int)BlackPawn;
16931                     i = PieceToNumber((ChessSquare)i);
16932                     if( i >= gameInfo.holdingsSize ) return FALSE;
16933                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16934                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16935                 } else {
16936                     i = (int)piece - (int)WhitePawn;
16937                     i = PieceToNumber((ChessSquare)i);
16938                     if( i >= gameInfo.holdingsSize ) return FALSE;
16939                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16940                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16941                 }
16942             }
16943         }
16944         if(*p == ']') p++;
16945     }
16946
16947     while(*p == ' ') p++;
16948
16949     /* Active color */
16950     c = *p++;
16951     if(appData.colorNickNames) {
16952       if( c == appData.colorNickNames[0] ) c = 'w'; else
16953       if( c == appData.colorNickNames[1] ) c = 'b';
16954     }
16955     switch (c) {
16956       case 'w':
16957         *blackPlaysFirst = FALSE;
16958         break;
16959       case 'b':
16960         *blackPlaysFirst = TRUE;
16961         break;
16962       default:
16963         return FALSE;
16964     }
16965
16966     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16967     /* return the extra info in global variiables             */
16968
16969     /* set defaults in case FEN is incomplete */
16970     board[EP_STATUS] = EP_UNKNOWN;
16971     for(i=0; i<nrCastlingRights; i++ ) {
16972         board[CASTLING][i] =
16973             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16974     }   /* assume possible unless obviously impossible */
16975     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16976     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16977     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16978                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16979     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16980     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16981     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16982                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16983     FENrulePlies = 0;
16984
16985     while(*p==' ') p++;
16986     if(nrCastlingRights) {
16987       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
16988       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
16989           /* castling indicator present, so default becomes no castlings */
16990           for(i=0; i<nrCastlingRights; i++ ) {
16991                  board[CASTLING][i] = NoRights;
16992           }
16993       }
16994       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16995              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
16996              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16997              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16998         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
16999
17000         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17001             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17002             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17003         }
17004         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17005             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17006         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17007                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17008         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17009                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17010         switch(c) {
17011           case'K':
17012               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17013               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17014               board[CASTLING][2] = whiteKingFile;
17015               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17016               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17017               break;
17018           case'Q':
17019               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17020               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17021               board[CASTLING][2] = whiteKingFile;
17022               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17023               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17024               break;
17025           case'k':
17026               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17027               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17028               board[CASTLING][5] = blackKingFile;
17029               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17030               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17031               break;
17032           case'q':
17033               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17034               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17035               board[CASTLING][5] = blackKingFile;
17036               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17037               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17038           case '-':
17039               break;
17040           default: /* FRC castlings */
17041               if(c >= 'a') { /* black rights */
17042                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17043                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17044                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17045                   if(i == BOARD_RGHT) break;
17046                   board[CASTLING][5] = i;
17047                   c -= AAA;
17048                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17049                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17050                   if(c > i)
17051                       board[CASTLING][3] = c;
17052                   else
17053                       board[CASTLING][4] = c;
17054               } else { /* white rights */
17055                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17056                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17057                     if(board[0][i] == WhiteKing) break;
17058                   if(i == BOARD_RGHT) break;
17059                   board[CASTLING][2] = i;
17060                   c -= AAA - 'a' + 'A';
17061                   if(board[0][c] >= WhiteKing) break;
17062                   if(c > i)
17063                       board[CASTLING][0] = c;
17064                   else
17065                       board[CASTLING][1] = c;
17066               }
17067         }
17068       }
17069       for(i=0; i<nrCastlingRights; i++)
17070         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17071       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17072     if (appData.debugMode) {
17073         fprintf(debugFP, "FEN castling rights:");
17074         for(i=0; i<nrCastlingRights; i++)
17075         fprintf(debugFP, " %d", board[CASTLING][i]);
17076         fprintf(debugFP, "\n");
17077     }
17078
17079       while(*p==' ') p++;
17080     }
17081
17082     /* read e.p. field in games that know e.p. capture */
17083     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17084        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
17085       if(*p=='-') {
17086         p++; board[EP_STATUS] = EP_NONE;
17087       } else {
17088          char c = *p++ - AAA;
17089
17090          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17091          if(*p >= '0' && *p <='9') p++;
17092          board[EP_STATUS] = c;
17093       }
17094     }
17095
17096
17097     if(sscanf(p, "%d", &i) == 1) {
17098         FENrulePlies = i; /* 50-move ply counter */
17099         /* (The move number is still ignored)    */
17100     }
17101
17102     return TRUE;
17103 }
17104
17105 void
17106 EditPositionPasteFEN (char *fen)
17107 {
17108   if (fen != NULL) {
17109     Board initial_position;
17110
17111     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
17112       DisplayError(_("Bad FEN position in clipboard"), 0);
17113       return ;
17114     } else {
17115       int savedBlackPlaysFirst = blackPlaysFirst;
17116       EditPositionEvent();
17117       blackPlaysFirst = savedBlackPlaysFirst;
17118       CopyBoard(boards[0], initial_position);
17119       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17120       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17121       DisplayBothClocks();
17122       DrawPosition(FALSE, boards[currentMove]);
17123     }
17124   }
17125 }
17126
17127 static char cseq[12] = "\\   ";
17128
17129 Boolean
17130 set_cont_sequence (char *new_seq)
17131 {
17132     int len;
17133     Boolean ret;
17134
17135     // handle bad attempts to set the sequence
17136         if (!new_seq)
17137                 return 0; // acceptable error - no debug
17138
17139     len = strlen(new_seq);
17140     ret = (len > 0) && (len < sizeof(cseq));
17141     if (ret)
17142       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17143     else if (appData.debugMode)
17144       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17145     return ret;
17146 }
17147
17148 /*
17149     reformat a source message so words don't cross the width boundary.  internal
17150     newlines are not removed.  returns the wrapped size (no null character unless
17151     included in source message).  If dest is NULL, only calculate the size required
17152     for the dest buffer.  lp argument indicats line position upon entry, and it's
17153     passed back upon exit.
17154 */
17155 int
17156 wrap (char *dest, char *src, int count, int width, int *lp)
17157 {
17158     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17159
17160     cseq_len = strlen(cseq);
17161     old_line = line = *lp;
17162     ansi = len = clen = 0;
17163
17164     for (i=0; i < count; i++)
17165     {
17166         if (src[i] == '\033')
17167             ansi = 1;
17168
17169         // if we hit the width, back up
17170         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17171         {
17172             // store i & len in case the word is too long
17173             old_i = i, old_len = len;
17174
17175             // find the end of the last word
17176             while (i && src[i] != ' ' && src[i] != '\n')
17177             {
17178                 i--;
17179                 len--;
17180             }
17181
17182             // word too long?  restore i & len before splitting it
17183             if ((old_i-i+clen) >= width)
17184             {
17185                 i = old_i;
17186                 len = old_len;
17187             }
17188
17189             // extra space?
17190             if (i && src[i-1] == ' ')
17191                 len--;
17192
17193             if (src[i] != ' ' && src[i] != '\n')
17194             {
17195                 i--;
17196                 if (len)
17197                     len--;
17198             }
17199
17200             // now append the newline and continuation sequence
17201             if (dest)
17202                 dest[len] = '\n';
17203             len++;
17204             if (dest)
17205                 strncpy(dest+len, cseq, cseq_len);
17206             len += cseq_len;
17207             line = cseq_len;
17208             clen = cseq_len;
17209             continue;
17210         }
17211
17212         if (dest)
17213             dest[len] = src[i];
17214         len++;
17215         if (!ansi)
17216             line++;
17217         if (src[i] == '\n')
17218             line = 0;
17219         if (src[i] == 'm')
17220             ansi = 0;
17221     }
17222     if (dest && appData.debugMode)
17223     {
17224         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17225             count, width, line, len, *lp);
17226         show_bytes(debugFP, src, count);
17227         fprintf(debugFP, "\ndest: ");
17228         show_bytes(debugFP, dest, len);
17229         fprintf(debugFP, "\n");
17230     }
17231     *lp = dest ? line : old_line;
17232
17233     return len;
17234 }
17235
17236 // [HGM] vari: routines for shelving variations
17237 Boolean modeRestore = FALSE;
17238
17239 void
17240 PushInner (int firstMove, int lastMove)
17241 {
17242         int i, j, nrMoves = lastMove - firstMove;
17243
17244         // push current tail of game on stack
17245         savedResult[storedGames] = gameInfo.result;
17246         savedDetails[storedGames] = gameInfo.resultDetails;
17247         gameInfo.resultDetails = NULL;
17248         savedFirst[storedGames] = firstMove;
17249         savedLast [storedGames] = lastMove;
17250         savedFramePtr[storedGames] = framePtr;
17251         framePtr -= nrMoves; // reserve space for the boards
17252         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17253             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17254             for(j=0; j<MOVE_LEN; j++)
17255                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17256             for(j=0; j<2*MOVE_LEN; j++)
17257                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17258             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17259             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17260             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17261             pvInfoList[firstMove+i-1].depth = 0;
17262             commentList[framePtr+i] = commentList[firstMove+i];
17263             commentList[firstMove+i] = NULL;
17264         }
17265
17266         storedGames++;
17267         forwardMostMove = firstMove; // truncate game so we can start variation
17268 }
17269
17270 void
17271 PushTail (int firstMove, int lastMove)
17272 {
17273         if(appData.icsActive) { // only in local mode
17274                 forwardMostMove = currentMove; // mimic old ICS behavior
17275                 return;
17276         }
17277         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17278
17279         PushInner(firstMove, lastMove);
17280         if(storedGames == 1) GreyRevert(FALSE);
17281         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17282 }
17283
17284 void
17285 PopInner (Boolean annotate)
17286 {
17287         int i, j, nrMoves;
17288         char buf[8000], moveBuf[20];
17289
17290         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17291         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17292         nrMoves = savedLast[storedGames] - currentMove;
17293         if(annotate) {
17294                 int cnt = 10;
17295                 if(!WhiteOnMove(currentMove))
17296                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17297                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17298                 for(i=currentMove; i<forwardMostMove; i++) {
17299                         if(WhiteOnMove(i))
17300                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17301                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17302                         strcat(buf, moveBuf);
17303                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17304                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17305                 }
17306                 strcat(buf, ")");
17307         }
17308         for(i=1; i<=nrMoves; i++) { // copy last variation back
17309             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17310             for(j=0; j<MOVE_LEN; j++)
17311                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17312             for(j=0; j<2*MOVE_LEN; j++)
17313                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17314             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17315             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17316             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17317             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17318             commentList[currentMove+i] = commentList[framePtr+i];
17319             commentList[framePtr+i] = NULL;
17320         }
17321         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17322         framePtr = savedFramePtr[storedGames];
17323         gameInfo.result = savedResult[storedGames];
17324         if(gameInfo.resultDetails != NULL) {
17325             free(gameInfo.resultDetails);
17326       }
17327         gameInfo.resultDetails = savedDetails[storedGames];
17328         forwardMostMove = currentMove + nrMoves;
17329 }
17330
17331 Boolean
17332 PopTail (Boolean annotate)
17333 {
17334         if(appData.icsActive) return FALSE; // only in local mode
17335         if(!storedGames) return FALSE; // sanity
17336         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17337
17338         PopInner(annotate);
17339         if(currentMove < forwardMostMove) ForwardEvent(); else
17340         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17341
17342         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17343         return TRUE;
17344 }
17345
17346 void
17347 CleanupTail ()
17348 {       // remove all shelved variations
17349         int i;
17350         for(i=0; i<storedGames; i++) {
17351             if(savedDetails[i])
17352                 free(savedDetails[i]);
17353             savedDetails[i] = NULL;
17354         }
17355         for(i=framePtr; i<MAX_MOVES; i++) {
17356                 if(commentList[i]) free(commentList[i]);
17357                 commentList[i] = NULL;
17358         }
17359         framePtr = MAX_MOVES-1;
17360         storedGames = 0;
17361 }
17362
17363 void
17364 LoadVariation (int index, char *text)
17365 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17366         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17367         int level = 0, move;
17368
17369         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17370         // first find outermost bracketing variation
17371         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17372             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17373                 if(*p == '{') wait = '}'; else
17374                 if(*p == '[') wait = ']'; else
17375                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17376                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17377             }
17378             if(*p == wait) wait = NULLCHAR; // closing ]} found
17379             p++;
17380         }
17381         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17382         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17383         end[1] = NULLCHAR; // clip off comment beyond variation
17384         ToNrEvent(currentMove-1);
17385         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17386         // kludge: use ParsePV() to append variation to game
17387         move = currentMove;
17388         ParsePV(start, TRUE, TRUE);
17389         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17390         ClearPremoveHighlights();
17391         CommentPopDown();
17392         ToNrEvent(currentMove+1);
17393 }
17394