Fix browsing for save file in WB
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
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 int 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; // [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;
459 Boolean adjustedClock;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [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 < forwardMostMove; 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         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
880
881 void
882 FloatToFront(char **list, char *engineLine)
883 {
884     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
885     int i=0;
886     if(appData.recentEngines <= 0) return;
887     TidyProgramName(engineLine, "localhost", tidy+1);
888     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
889     strncpy(buf+1, *list, MSG_SIZ-50);
890     if(p = strstr(buf, tidy)) { // tidy name appears in list
891         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
892         while(*p++ = *++q); // squeeze out
893     }
894     strcat(tidy, buf+1); // put list behind tidy name
895     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
896     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
897     ASSIGN(*list, tidy+1);
898 }
899
900 void
901 Load (ChessProgramState *cps, int i)
902 {
903     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
904     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
905         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
906         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
907         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
908         appData.firstProtocolVersion = PROTOVER;
909         ParseArgsFromString(buf);
910         SwapEngines(i);
911         ReplaceEngine(cps, i);
912         FloatToFront(&appData.recentEngineList, engineLine);
913         return;
914     }
915     p = engineName;
916     while(q = strchr(p, SLASH)) p = q+1;
917     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
918     if(engineDir[0] != NULLCHAR)
919         appData.directory[i] = engineDir;
920     else if(p != engineName) { // derive directory from engine path, when not given
921         p[-1] = 0;
922         appData.directory[i] = strdup(engineName);
923         p[-1] = SLASH;
924         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
925     } else appData.directory[i] = ".";
926     if(params[0]) {
927         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
928         snprintf(command, MSG_SIZ, "%s %s", p, params);
929         p = command;
930     }
931     appData.chessProgram[i] = strdup(p);
932     appData.isUCI[i] = isUCI;
933     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
934     appData.hasOwnBookUCI[i] = hasBook;
935     if(!nickName[0]) useNick = FALSE;
936     if(useNick) ASSIGN(appData.pgnName[i], nickName);
937     if(addToList) {
938         int len;
939         char quote;
940         q = firstChessProgramNames;
941         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
942         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
943         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
944                         quote, p, quote, appData.directory[i], 
945                         useNick ? " -fn \"" : "",
946                         useNick ? nickName : "",
947                         useNick ? "\"" : "",
948                         v1 ? " -firstProtocolVersion 1" : "",
949                         hasBook ? "" : " -fNoOwnBookUCI",
950                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
951                         storeVariant ? " -variant " : "",
952                         storeVariant ? VariantName(gameInfo.variant) : "");
953         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
954         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
955         if(q)   free(q);
956         FloatToFront(&appData.recentEngineList, buf);
957     }
958     ReplaceEngine(cps, i);
959 }
960
961 void
962 InitTimeControls ()
963 {
964     int matched, min, sec;
965     /*
966      * Parse timeControl resource
967      */
968     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
969                           appData.movesPerSession)) {
970         char buf[MSG_SIZ];
971         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
972         DisplayFatalError(buf, 0, 2);
973     }
974
975     /*
976      * Parse searchTime resource
977      */
978     if (*appData.searchTime != NULLCHAR) {
979         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
980         if (matched == 1) {
981             searchTime = min * 60;
982         } else if (matched == 2) {
983             searchTime = min * 60 + sec;
984         } else {
985             char buf[MSG_SIZ];
986             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
987             DisplayFatalError(buf, 0, 2);
988         }
989     }
990 }
991
992 void
993 InitBackEnd1 ()
994 {
995
996     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
997     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
998
999     GetTimeMark(&programStartTime);
1000     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1001     appData.seedBase = random() + (random()<<15);
1002     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1003
1004     ClearProgramStats();
1005     programStats.ok_to_send = 1;
1006     programStats.seen_stat = 0;
1007
1008     /*
1009      * Initialize game list
1010      */
1011     ListNew(&gameList);
1012
1013
1014     /*
1015      * Internet chess server status
1016      */
1017     if (appData.icsActive) {
1018         appData.matchMode = FALSE;
1019         appData.matchGames = 0;
1020 #if ZIPPY
1021         appData.noChessProgram = !appData.zippyPlay;
1022 #else
1023         appData.zippyPlay = FALSE;
1024         appData.zippyTalk = FALSE;
1025         appData.noChessProgram = TRUE;
1026 #endif
1027         if (*appData.icsHelper != NULLCHAR) {
1028             appData.useTelnet = TRUE;
1029             appData.telnetProgram = appData.icsHelper;
1030         }
1031     } else {
1032         appData.zippyTalk = appData.zippyPlay = FALSE;
1033     }
1034
1035     /* [AS] Initialize pv info list [HGM] and game state */
1036     {
1037         int i, j;
1038
1039         for( i=0; i<=framePtr; i++ ) {
1040             pvInfoList[i].depth = -1;
1041             boards[i][EP_STATUS] = EP_NONE;
1042             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1043         }
1044     }
1045
1046     InitTimeControls();
1047
1048     /* [AS] Adjudication threshold */
1049     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1050
1051     InitEngine(&first, 0);
1052     InitEngine(&second, 1);
1053     CommonEngineInit();
1054
1055     pairing.which = "pairing"; // pairing engine
1056     pairing.pr = NoProc;
1057     pairing.isr = NULL;
1058     pairing.program = appData.pairingEngine;
1059     pairing.host = "localhost";
1060     pairing.dir = ".";
1061
1062     if (appData.icsActive) {
1063         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1064     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1065         appData.clockMode = FALSE;
1066         first.sendTime = second.sendTime = 0;
1067     }
1068
1069 #if ZIPPY
1070     /* Override some settings from environment variables, for backward
1071        compatibility.  Unfortunately it's not feasible to have the env
1072        vars just set defaults, at least in xboard.  Ugh.
1073     */
1074     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1075       ZippyInit();
1076     }
1077 #endif
1078
1079     if (!appData.icsActive) {
1080       char buf[MSG_SIZ];
1081       int len;
1082
1083       /* Check for variants that are supported only in ICS mode,
1084          or not at all.  Some that are accepted here nevertheless
1085          have bugs; see comments below.
1086       */
1087       VariantClass variant = StringToVariant(appData.variant);
1088       switch (variant) {
1089       case VariantBughouse:     /* need four players and two boards */
1090       case VariantKriegspiel:   /* need to hide pieces and move details */
1091         /* case VariantFischeRandom: (Fabien: moved below) */
1092         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1093         if( (len >= MSG_SIZ) && appData.debugMode )
1094           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1095
1096         DisplayFatalError(buf, 0, 2);
1097         return;
1098
1099       case VariantUnknown:
1100       case VariantLoadable:
1101       case Variant29:
1102       case Variant30:
1103       case Variant31:
1104       case Variant32:
1105       case Variant33:
1106       case Variant34:
1107       case Variant35:
1108       case Variant36:
1109       default:
1110         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1111         if( (len >= MSG_SIZ) && appData.debugMode )
1112           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1113
1114         DisplayFatalError(buf, 0, 2);
1115         return;
1116
1117       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1118       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1119       case VariantGothic:     /* [HGM] should work */
1120       case VariantCapablanca: /* [HGM] should work */
1121       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1122       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1123       case VariantKnightmate: /* [HGM] should work */
1124       case VariantCylinder:   /* [HGM] untested */
1125       case VariantFalcon:     /* [HGM] untested */
1126       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1127                                  offboard interposition not understood */
1128       case VariantNormal:     /* definitely works! */
1129       case VariantWildCastle: /* pieces not automatically shuffled */
1130       case VariantNoCastle:   /* pieces not automatically shuffled */
1131       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1132       case VariantLosers:     /* should work except for win condition,
1133                                  and doesn't know captures are mandatory */
1134       case VariantSuicide:    /* should work except for win condition,
1135                                  and doesn't know captures are mandatory */
1136       case VariantGiveaway:   /* should work except for win condition,
1137                                  and doesn't know captures are mandatory */
1138       case VariantTwoKings:   /* should work */
1139       case VariantAtomic:     /* should work except for win condition */
1140       case Variant3Check:     /* should work except for win condition */
1141       case VariantShatranj:   /* should work except for all win conditions */
1142       case VariantMakruk:     /* should work except for draw countdown */
1143       case VariantBerolina:   /* might work if TestLegality is off */
1144       case VariantCapaRandom: /* should work */
1145       case VariantJanus:      /* should work */
1146       case VariantSuper:      /* experimental */
1147       case VariantGreat:      /* experimental, requires legality testing to be off */
1148       case VariantSChess:     /* S-Chess, should work */
1149       case VariantGrand:      /* should work */
1150       case VariantSpartan:    /* should work */
1151         break;
1152       }
1153     }
1154
1155 }
1156
1157 int
1158 NextIntegerFromString (char ** str, long * value)
1159 {
1160     int result = -1;
1161     char * s = *str;
1162
1163     while( *s == ' ' || *s == '\t' ) {
1164         s++;
1165     }
1166
1167     *value = 0;
1168
1169     if( *s >= '0' && *s <= '9' ) {
1170         while( *s >= '0' && *s <= '9' ) {
1171             *value = *value * 10 + (*s - '0');
1172             s++;
1173         }
1174
1175         result = 0;
1176     }
1177
1178     *str = s;
1179
1180     return result;
1181 }
1182
1183 int
1184 NextTimeControlFromString (char ** str, long * value)
1185 {
1186     long temp;
1187     int result = NextIntegerFromString( str, &temp );
1188
1189     if( result == 0 ) {
1190         *value = temp * 60; /* Minutes */
1191         if( **str == ':' ) {
1192             (*str)++;
1193             result = NextIntegerFromString( str, &temp );
1194             *value += temp; /* Seconds */
1195         }
1196     }
1197
1198     return result;
1199 }
1200
1201 int
1202 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1203 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1204     int result = -1, type = 0; long temp, temp2;
1205
1206     if(**str != ':') return -1; // old params remain in force!
1207     (*str)++;
1208     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1209     if( NextIntegerFromString( str, &temp ) ) return -1;
1210     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1211
1212     if(**str != '/') {
1213         /* time only: incremental or sudden-death time control */
1214         if(**str == '+') { /* increment follows; read it */
1215             (*str)++;
1216             if(**str == '!') type = *(*str)++; // Bronstein TC
1217             if(result = NextIntegerFromString( str, &temp2)) return -1;
1218             *inc = temp2 * 1000;
1219             if(**str == '.') { // read fraction of increment
1220                 char *start = ++(*str);
1221                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1222                 temp2 *= 1000;
1223                 while(start++ < *str) temp2 /= 10;
1224                 *inc += temp2;
1225             }
1226         } else *inc = 0;
1227         *moves = 0; *tc = temp * 1000; *incType = type;
1228         return 0;
1229     }
1230
1231     (*str)++; /* classical time control */
1232     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1233
1234     if(result == 0) {
1235         *moves = temp;
1236         *tc    = temp2 * 1000;
1237         *inc   = 0;
1238         *incType = type;
1239     }
1240     return result;
1241 }
1242
1243 int
1244 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1245 {   /* [HGM] get time to add from the multi-session time-control string */
1246     int incType, moves=1; /* kludge to force reading of first session */
1247     long time, increment;
1248     char *s = tcString;
1249
1250     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1251     do {
1252         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1253         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1254         if(movenr == -1) return time;    /* last move before new session     */
1255         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1256         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1257         if(!moves) return increment;     /* current session is incremental   */
1258         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1259     } while(movenr >= -1);               /* try again for next session       */
1260
1261     return 0; // no new time quota on this move
1262 }
1263
1264 int
1265 ParseTimeControl (char *tc, float ti, int mps)
1266 {
1267   long tc1;
1268   long tc2;
1269   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1270   int min, sec=0;
1271
1272   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1273   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1274       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1275   if(ti > 0) {
1276
1277     if(mps)
1278       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1279     else 
1280       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1281   } else {
1282     if(mps)
1283       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1284     else 
1285       snprintf(buf, MSG_SIZ, ":%s", mytc);
1286   }
1287   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1288   
1289   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1290     return FALSE;
1291   }
1292
1293   if( *tc == '/' ) {
1294     /* Parse second time control */
1295     tc++;
1296
1297     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1298       return FALSE;
1299     }
1300
1301     if( tc2 == 0 ) {
1302       return FALSE;
1303     }
1304
1305     timeControl_2 = tc2 * 1000;
1306   }
1307   else {
1308     timeControl_2 = 0;
1309   }
1310
1311   if( tc1 == 0 ) {
1312     return FALSE;
1313   }
1314
1315   timeControl = tc1 * 1000;
1316
1317   if (ti >= 0) {
1318     timeIncrement = ti * 1000;  /* convert to ms */
1319     movesPerSession = 0;
1320   } else {
1321     timeIncrement = 0;
1322     movesPerSession = mps;
1323   }
1324   return TRUE;
1325 }
1326
1327 void
1328 InitBackEnd2 ()
1329 {
1330     if (appData.debugMode) {
1331         fprintf(debugFP, "%s\n", programVersion);
1332     }
1333     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1334
1335     set_cont_sequence(appData.wrapContSeq);
1336     if (appData.matchGames > 0) {
1337         appData.matchMode = TRUE;
1338     } else if (appData.matchMode) {
1339         appData.matchGames = 1;
1340     }
1341     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1342         appData.matchGames = appData.sameColorGames;
1343     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1344         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1345         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1346     }
1347     Reset(TRUE, FALSE);
1348     if (appData.noChessProgram || first.protocolVersion == 1) {
1349       InitBackEnd3();
1350     } else {
1351       /* kludge: allow timeout for initial "feature" commands */
1352       FreezeUI();
1353       DisplayMessage("", _("Starting chess program"));
1354       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1355     }
1356 }
1357
1358 int
1359 CalculateIndex (int index, int gameNr)
1360 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1361     int res;
1362     if(index > 0) return index; // fixed nmber
1363     if(index == 0) return 1;
1364     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1365     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1366     return res;
1367 }
1368
1369 int
1370 LoadGameOrPosition (int gameNr)
1371 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1372     if (*appData.loadGameFile != NULLCHAR) {
1373         if (!LoadGameFromFile(appData.loadGameFile,
1374                 CalculateIndex(appData.loadGameIndex, gameNr),
1375                               appData.loadGameFile, FALSE)) {
1376             DisplayFatalError(_("Bad game file"), 0, 1);
1377             return 0;
1378         }
1379     } else if (*appData.loadPositionFile != NULLCHAR) {
1380         if (!LoadPositionFromFile(appData.loadPositionFile,
1381                 CalculateIndex(appData.loadPositionIndex, gameNr),
1382                                   appData.loadPositionFile)) {
1383             DisplayFatalError(_("Bad position file"), 0, 1);
1384             return 0;
1385         }
1386     }
1387     return 1;
1388 }
1389
1390 void
1391 ReserveGame (int gameNr, char resChar)
1392 {
1393     FILE *tf = fopen(appData.tourneyFile, "r+");
1394     char *p, *q, c, buf[MSG_SIZ];
1395     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1396     safeStrCpy(buf, lastMsg, MSG_SIZ);
1397     DisplayMessage(_("Pick new game"), "");
1398     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1399     ParseArgsFromFile(tf);
1400     p = q = appData.results;
1401     if(appData.debugMode) {
1402       char *r = appData.participants;
1403       fprintf(debugFP, "results = '%s'\n", p);
1404       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1405       fprintf(debugFP, "\n");
1406     }
1407     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1408     nextGame = q - p;
1409     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1410     safeStrCpy(q, p, strlen(p) + 2);
1411     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1412     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1413     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1414         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1415         q[nextGame] = '*';
1416     }
1417     fseek(tf, -(strlen(p)+4), SEEK_END);
1418     c = fgetc(tf);
1419     if(c != '"') // depending on DOS or Unix line endings we can be one off
1420          fseek(tf, -(strlen(p)+2), SEEK_END);
1421     else fseek(tf, -(strlen(p)+3), SEEK_END);
1422     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1423     DisplayMessage(buf, "");
1424     free(p); appData.results = q;
1425     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1426        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1427       int round = appData.defaultMatchGames * appData.tourneyType;
1428       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1429          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1430         UnloadEngine(&first);  // next game belongs to other pairing;
1431         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1432     }
1433     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1434 }
1435
1436 void
1437 MatchEvent (int mode)
1438 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1439         int dummy;
1440         if(matchMode) { // already in match mode: switch it off
1441             abortMatch = TRUE;
1442             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1443             return;
1444         }
1445 //      if(gameMode != BeginningOfGame) {
1446 //          DisplayError(_("You can only start a match from the initial position."), 0);
1447 //          return;
1448 //      }
1449         abortMatch = FALSE;
1450         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1451         /* Set up machine vs. machine match */
1452         nextGame = 0;
1453         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1454         if(appData.tourneyFile[0]) {
1455             ReserveGame(-1, 0);
1456             if(nextGame > appData.matchGames) {
1457                 char buf[MSG_SIZ];
1458                 if(strchr(appData.results, '*') == NULL) {
1459                     FILE *f;
1460                     appData.tourneyCycles++;
1461                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1462                         fclose(f);
1463                         NextTourneyGame(-1, &dummy);
1464                         ReserveGame(-1, 0);
1465                         if(nextGame <= appData.matchGames) {
1466                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1467                             matchMode = mode;
1468                             ScheduleDelayedEvent(NextMatchGame, 10000);
1469                             return;
1470                         }
1471                     }
1472                 }
1473                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1474                 DisplayError(buf, 0);
1475                 appData.tourneyFile[0] = 0;
1476                 return;
1477             }
1478         } else
1479         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1480             DisplayFatalError(_("Can't have a match with no chess programs"),
1481                               0, 2);
1482             return;
1483         }
1484         matchMode = mode;
1485         matchGame = roundNr = 1;
1486         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1487         NextMatchGame();
1488 }
1489
1490 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1491
1492 void
1493 InitBackEnd3 P((void))
1494 {
1495     GameMode initialMode;
1496     char buf[MSG_SIZ];
1497     int err, len;
1498
1499     InitChessProgram(&first, startedFromSetupPosition);
1500
1501     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1502         free(programVersion);
1503         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1504         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1505         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1506     }
1507
1508     if (appData.icsActive) {
1509 #ifdef WIN32
1510         /* [DM] Make a console window if needed [HGM] merged ifs */
1511         ConsoleCreate();
1512 #endif
1513         err = establish();
1514         if (err != 0)
1515           {
1516             if (*appData.icsCommPort != NULLCHAR)
1517               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1518                              appData.icsCommPort);
1519             else
1520               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1521                         appData.icsHost, appData.icsPort);
1522
1523             if( (len >= MSG_SIZ) && appData.debugMode )
1524               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1525
1526             DisplayFatalError(buf, err, 1);
1527             return;
1528         }
1529         SetICSMode();
1530         telnetISR =
1531           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1532         fromUserISR =
1533           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1534         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1535             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1536     } else if (appData.noChessProgram) {
1537         SetNCPMode();
1538     } else {
1539         SetGNUMode();
1540     }
1541
1542     if (*appData.cmailGameName != NULLCHAR) {
1543         SetCmailMode();
1544         OpenLoopback(&cmailPR);
1545         cmailISR =
1546           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1547     }
1548
1549     ThawUI();
1550     DisplayMessage("", "");
1551     if (StrCaseCmp(appData.initialMode, "") == 0) {
1552       initialMode = BeginningOfGame;
1553       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1554         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1555         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1556         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1557         ModeHighlight();
1558       }
1559     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1560       initialMode = TwoMachinesPlay;
1561     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1562       initialMode = AnalyzeFile;
1563     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1564       initialMode = AnalyzeMode;
1565     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1566       initialMode = MachinePlaysWhite;
1567     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1568       initialMode = MachinePlaysBlack;
1569     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1570       initialMode = EditGame;
1571     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1572       initialMode = EditPosition;
1573     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1574       initialMode = Training;
1575     } else {
1576       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1577       if( (len >= MSG_SIZ) && appData.debugMode )
1578         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1579
1580       DisplayFatalError(buf, 0, 2);
1581       return;
1582     }
1583
1584     if (appData.matchMode) {
1585         if(appData.tourneyFile[0]) { // start tourney from command line
1586             FILE *f;
1587             if(f = fopen(appData.tourneyFile, "r")) {
1588                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1589                 fclose(f);
1590                 appData.clockMode = TRUE;
1591                 SetGNUMode();
1592             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1593         }
1594         MatchEvent(TRUE);
1595     } else if (*appData.cmailGameName != NULLCHAR) {
1596         /* Set up cmail mode */
1597         ReloadCmailMsgEvent(TRUE);
1598     } else {
1599         /* Set up other modes */
1600         if (initialMode == AnalyzeFile) {
1601           if (*appData.loadGameFile == NULLCHAR) {
1602             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1603             return;
1604           }
1605         }
1606         if (*appData.loadGameFile != NULLCHAR) {
1607             (void) LoadGameFromFile(appData.loadGameFile,
1608                                     appData.loadGameIndex,
1609                                     appData.loadGameFile, TRUE);
1610         } else if (*appData.loadPositionFile != NULLCHAR) {
1611             (void) LoadPositionFromFile(appData.loadPositionFile,
1612                                         appData.loadPositionIndex,
1613                                         appData.loadPositionFile);
1614             /* [HGM] try to make self-starting even after FEN load */
1615             /* to allow automatic setup of fairy variants with wtm */
1616             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1617                 gameMode = BeginningOfGame;
1618                 setboardSpoiledMachineBlack = 1;
1619             }
1620             /* [HGM] loadPos: make that every new game uses the setup */
1621             /* from file as long as we do not switch variant          */
1622             if(!blackPlaysFirst) {
1623                 startedFromPositionFile = TRUE;
1624                 CopyBoard(filePosition, boards[0]);
1625             }
1626         }
1627         if (initialMode == AnalyzeMode) {
1628           if (appData.noChessProgram) {
1629             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1630             return;
1631           }
1632           if (appData.icsActive) {
1633             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1634             return;
1635           }
1636           AnalyzeModeEvent();
1637         } else if (initialMode == AnalyzeFile) {
1638           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1639           ShowThinkingEvent();
1640           AnalyzeFileEvent();
1641           AnalysisPeriodicEvent(1);
1642         } else if (initialMode == MachinePlaysWhite) {
1643           if (appData.noChessProgram) {
1644             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1645                               0, 2);
1646             return;
1647           }
1648           if (appData.icsActive) {
1649             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1650                               0, 2);
1651             return;
1652           }
1653           MachineWhiteEvent();
1654         } else if (initialMode == MachinePlaysBlack) {
1655           if (appData.noChessProgram) {
1656             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1657                               0, 2);
1658             return;
1659           }
1660           if (appData.icsActive) {
1661             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1662                               0, 2);
1663             return;
1664           }
1665           MachineBlackEvent();
1666         } else if (initialMode == TwoMachinesPlay) {
1667           if (appData.noChessProgram) {
1668             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1669                               0, 2);
1670             return;
1671           }
1672           if (appData.icsActive) {
1673             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1674                               0, 2);
1675             return;
1676           }
1677           TwoMachinesEvent();
1678         } else if (initialMode == EditGame) {
1679           EditGameEvent();
1680         } else if (initialMode == EditPosition) {
1681           EditPositionEvent();
1682         } else if (initialMode == Training) {
1683           if (*appData.loadGameFile == NULLCHAR) {
1684             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1685             return;
1686           }
1687           TrainingEvent();
1688         }
1689     }
1690 }
1691
1692 void
1693 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1694 {
1695     DisplayBook(current+1);
1696
1697     MoveHistorySet( movelist, first, last, current, pvInfoList );
1698
1699     EvalGraphSet( first, last, current, pvInfoList );
1700
1701     MakeEngineOutputTitle();
1702 }
1703
1704 /*
1705  * Establish will establish a contact to a remote host.port.
1706  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1707  *  used to talk to the host.
1708  * Returns 0 if okay, error code if not.
1709  */
1710 int
1711 establish ()
1712 {
1713     char buf[MSG_SIZ];
1714
1715     if (*appData.icsCommPort != NULLCHAR) {
1716         /* Talk to the host through a serial comm port */
1717         return OpenCommPort(appData.icsCommPort, &icsPR);
1718
1719     } else if (*appData.gateway != NULLCHAR) {
1720         if (*appData.remoteShell == NULLCHAR) {
1721             /* Use the rcmd protocol to run telnet program on a gateway host */
1722             snprintf(buf, sizeof(buf), "%s %s %s",
1723                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1724             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1725
1726         } else {
1727             /* Use the rsh program to run telnet program on a gateway host */
1728             if (*appData.remoteUser == NULLCHAR) {
1729                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1730                         appData.gateway, appData.telnetProgram,
1731                         appData.icsHost, appData.icsPort);
1732             } else {
1733                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1734                         appData.remoteShell, appData.gateway,
1735                         appData.remoteUser, appData.telnetProgram,
1736                         appData.icsHost, appData.icsPort);
1737             }
1738             return StartChildProcess(buf, "", &icsPR);
1739
1740         }
1741     } else if (appData.useTelnet) {
1742         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1743
1744     } else {
1745         /* TCP socket interface differs somewhat between
1746            Unix and NT; handle details in the front end.
1747            */
1748         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1749     }
1750 }
1751
1752 void
1753 EscapeExpand (char *p, char *q)
1754 {       // [HGM] initstring: routine to shape up string arguments
1755         while(*p++ = *q++) if(p[-1] == '\\')
1756             switch(*q++) {
1757                 case 'n': p[-1] = '\n'; break;
1758                 case 'r': p[-1] = '\r'; break;
1759                 case 't': p[-1] = '\t'; break;
1760                 case '\\': p[-1] = '\\'; break;
1761                 case 0: *p = 0; return;
1762                 default: p[-1] = q[-1]; break;
1763             }
1764 }
1765
1766 void
1767 show_bytes (FILE *fp, char *buf, int count)
1768 {
1769     while (count--) {
1770         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1771             fprintf(fp, "\\%03o", *buf & 0xff);
1772         } else {
1773             putc(*buf, fp);
1774         }
1775         buf++;
1776     }
1777     fflush(fp);
1778 }
1779
1780 /* Returns an errno value */
1781 int
1782 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1783 {
1784     char buf[8192], *p, *q, *buflim;
1785     int left, newcount, outcount;
1786
1787     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1788         *appData.gateway != NULLCHAR) {
1789         if (appData.debugMode) {
1790             fprintf(debugFP, ">ICS: ");
1791             show_bytes(debugFP, message, count);
1792             fprintf(debugFP, "\n");
1793         }
1794         return OutputToProcess(pr, message, count, outError);
1795     }
1796
1797     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1798     p = message;
1799     q = buf;
1800     left = count;
1801     newcount = 0;
1802     while (left) {
1803         if (q >= buflim) {
1804             if (appData.debugMode) {
1805                 fprintf(debugFP, ">ICS: ");
1806                 show_bytes(debugFP, buf, newcount);
1807                 fprintf(debugFP, "\n");
1808             }
1809             outcount = OutputToProcess(pr, buf, newcount, outError);
1810             if (outcount < newcount) return -1; /* to be sure */
1811             q = buf;
1812             newcount = 0;
1813         }
1814         if (*p == '\n') {
1815             *q++ = '\r';
1816             newcount++;
1817         } else if (((unsigned char) *p) == TN_IAC) {
1818             *q++ = (char) TN_IAC;
1819             newcount ++;
1820         }
1821         *q++ = *p++;
1822         newcount++;
1823         left--;
1824     }
1825     if (appData.debugMode) {
1826         fprintf(debugFP, ">ICS: ");
1827         show_bytes(debugFP, buf, newcount);
1828         fprintf(debugFP, "\n");
1829     }
1830     outcount = OutputToProcess(pr, buf, newcount, outError);
1831     if (outcount < newcount) return -1; /* to be sure */
1832     return count;
1833 }
1834
1835 void
1836 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1837 {
1838     int outError, outCount;
1839     static int gotEof = 0;
1840
1841     /* Pass data read from player on to ICS */
1842     if (count > 0) {
1843         gotEof = 0;
1844         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1845         if (outCount < count) {
1846             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1847         }
1848     } else if (count < 0) {
1849         RemoveInputSource(isr);
1850         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1851     } else if (gotEof++ > 0) {
1852         RemoveInputSource(isr);
1853         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1854     }
1855 }
1856
1857 void
1858 KeepAlive ()
1859 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1860     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1861     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1862     SendToICS("date\n");
1863     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1864 }
1865
1866 /* added routine for printf style output to ics */
1867 void
1868 ics_printf (char *format, ...)
1869 {
1870     char buffer[MSG_SIZ];
1871     va_list args;
1872
1873     va_start(args, format);
1874     vsnprintf(buffer, sizeof(buffer), format, args);
1875     buffer[sizeof(buffer)-1] = '\0';
1876     SendToICS(buffer);
1877     va_end(args);
1878 }
1879
1880 void
1881 SendToICS (char *s)
1882 {
1883     int count, outCount, outError;
1884
1885     if (icsPR == NoProc) return;
1886
1887     count = strlen(s);
1888     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1889     if (outCount < count) {
1890         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1891     }
1892 }
1893
1894 /* This is used for sending logon scripts to the ICS. Sending
1895    without a delay causes problems when using timestamp on ICC
1896    (at least on my machine). */
1897 void
1898 SendToICSDelayed (char *s, long msdelay)
1899 {
1900     int count, outCount, outError;
1901
1902     if (icsPR == NoProc) return;
1903
1904     count = strlen(s);
1905     if (appData.debugMode) {
1906         fprintf(debugFP, ">ICS: ");
1907         show_bytes(debugFP, s, count);
1908         fprintf(debugFP, "\n");
1909     }
1910     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1911                                       msdelay);
1912     if (outCount < count) {
1913         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1914     }
1915 }
1916
1917
1918 /* Remove all highlighting escape sequences in s
1919    Also deletes any suffix starting with '('
1920    */
1921 char *
1922 StripHighlightAndTitle (char *s)
1923 {
1924     static char retbuf[MSG_SIZ];
1925     char *p = retbuf;
1926
1927     while (*s != NULLCHAR) {
1928         while (*s == '\033') {
1929             while (*s != NULLCHAR && !isalpha(*s)) s++;
1930             if (*s != NULLCHAR) s++;
1931         }
1932         while (*s != NULLCHAR && *s != '\033') {
1933             if (*s == '(' || *s == '[') {
1934                 *p = NULLCHAR;
1935                 return retbuf;
1936             }
1937             *p++ = *s++;
1938         }
1939     }
1940     *p = NULLCHAR;
1941     return retbuf;
1942 }
1943
1944 /* Remove all highlighting escape sequences in s */
1945 char *
1946 StripHighlight (char *s)
1947 {
1948     static char retbuf[MSG_SIZ];
1949     char *p = retbuf;
1950
1951     while (*s != NULLCHAR) {
1952         while (*s == '\033') {
1953             while (*s != NULLCHAR && !isalpha(*s)) s++;
1954             if (*s != NULLCHAR) s++;
1955         }
1956         while (*s != NULLCHAR && *s != '\033') {
1957             *p++ = *s++;
1958         }
1959     }
1960     *p = NULLCHAR;
1961     return retbuf;
1962 }
1963
1964 char *variantNames[] = VARIANT_NAMES;
1965 char *
1966 VariantName (VariantClass v)
1967 {
1968     return variantNames[v];
1969 }
1970
1971
1972 /* Identify a variant from the strings the chess servers use or the
1973    PGN Variant tag names we use. */
1974 VariantClass
1975 StringToVariant (char *e)
1976 {
1977     char *p;
1978     int wnum = -1;
1979     VariantClass v = VariantNormal;
1980     int i, found = FALSE;
1981     char buf[MSG_SIZ];
1982     int len;
1983
1984     if (!e) return v;
1985
1986     /* [HGM] skip over optional board-size prefixes */
1987     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1988         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1989         while( *e++ != '_');
1990     }
1991
1992     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1993         v = VariantNormal;
1994         found = TRUE;
1995     } else
1996     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1997       if (StrCaseStr(e, variantNames[i])) {
1998         v = (VariantClass) i;
1999         found = TRUE;
2000         break;
2001       }
2002     }
2003
2004     if (!found) {
2005       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2006           || StrCaseStr(e, "wild/fr")
2007           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2008         v = VariantFischeRandom;
2009       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2010                  (i = 1, p = StrCaseStr(e, "w"))) {
2011         p += i;
2012         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2013         if (isdigit(*p)) {
2014           wnum = atoi(p);
2015         } else {
2016           wnum = -1;
2017         }
2018         switch (wnum) {
2019         case 0: /* FICS only, actually */
2020         case 1:
2021           /* Castling legal even if K starts on d-file */
2022           v = VariantWildCastle;
2023           break;
2024         case 2:
2025         case 3:
2026         case 4:
2027           /* Castling illegal even if K & R happen to start in
2028              normal positions. */
2029           v = VariantNoCastle;
2030           break;
2031         case 5:
2032         case 7:
2033         case 8:
2034         case 10:
2035         case 11:
2036         case 12:
2037         case 13:
2038         case 14:
2039         case 15:
2040         case 18:
2041         case 19:
2042           /* Castling legal iff K & R start in normal positions */
2043           v = VariantNormal;
2044           break;
2045         case 6:
2046         case 20:
2047         case 21:
2048           /* Special wilds for position setup; unclear what to do here */
2049           v = VariantLoadable;
2050           break;
2051         case 9:
2052           /* Bizarre ICC game */
2053           v = VariantTwoKings;
2054           break;
2055         case 16:
2056           v = VariantKriegspiel;
2057           break;
2058         case 17:
2059           v = VariantLosers;
2060           break;
2061         case 22:
2062           v = VariantFischeRandom;
2063           break;
2064         case 23:
2065           v = VariantCrazyhouse;
2066           break;
2067         case 24:
2068           v = VariantBughouse;
2069           break;
2070         case 25:
2071           v = Variant3Check;
2072           break;
2073         case 26:
2074           /* Not quite the same as FICS suicide! */
2075           v = VariantGiveaway;
2076           break;
2077         case 27:
2078           v = VariantAtomic;
2079           break;
2080         case 28:
2081           v = VariantShatranj;
2082           break;
2083
2084         /* Temporary names for future ICC types.  The name *will* change in
2085            the next xboard/WinBoard release after ICC defines it. */
2086         case 29:
2087           v = Variant29;
2088           break;
2089         case 30:
2090           v = Variant30;
2091           break;
2092         case 31:
2093           v = Variant31;
2094           break;
2095         case 32:
2096           v = Variant32;
2097           break;
2098         case 33:
2099           v = Variant33;
2100           break;
2101         case 34:
2102           v = Variant34;
2103           break;
2104         case 35:
2105           v = Variant35;
2106           break;
2107         case 36:
2108           v = Variant36;
2109           break;
2110         case 37:
2111           v = VariantShogi;
2112           break;
2113         case 38:
2114           v = VariantXiangqi;
2115           break;
2116         case 39:
2117           v = VariantCourier;
2118           break;
2119         case 40:
2120           v = VariantGothic;
2121           break;
2122         case 41:
2123           v = VariantCapablanca;
2124           break;
2125         case 42:
2126           v = VariantKnightmate;
2127           break;
2128         case 43:
2129           v = VariantFairy;
2130           break;
2131         case 44:
2132           v = VariantCylinder;
2133           break;
2134         case 45:
2135           v = VariantFalcon;
2136           break;
2137         case 46:
2138           v = VariantCapaRandom;
2139           break;
2140         case 47:
2141           v = VariantBerolina;
2142           break;
2143         case 48:
2144           v = VariantJanus;
2145           break;
2146         case 49:
2147           v = VariantSuper;
2148           break;
2149         case 50:
2150           v = VariantGreat;
2151           break;
2152         case -1:
2153           /* Found "wild" or "w" in the string but no number;
2154              must assume it's normal chess. */
2155           v = VariantNormal;
2156           break;
2157         default:
2158           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2159           if( (len >= MSG_SIZ) && appData.debugMode )
2160             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2161
2162           DisplayError(buf, 0);
2163           v = VariantUnknown;
2164           break;
2165         }
2166       }
2167     }
2168     if (appData.debugMode) {
2169       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2170               e, wnum, VariantName(v));
2171     }
2172     return v;
2173 }
2174
2175 static int leftover_start = 0, leftover_len = 0;
2176 char star_match[STAR_MATCH_N][MSG_SIZ];
2177
2178 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2179    advance *index beyond it, and set leftover_start to the new value of
2180    *index; else return FALSE.  If pattern contains the character '*', it
2181    matches any sequence of characters not containing '\r', '\n', or the
2182    character following the '*' (if any), and the matched sequence(s) are
2183    copied into star_match.
2184    */
2185 int
2186 looking_at ( char *buf, int *index, char *pattern)
2187 {
2188     char *bufp = &buf[*index], *patternp = pattern;
2189     int star_count = 0;
2190     char *matchp = star_match[0];
2191
2192     for (;;) {
2193         if (*patternp == NULLCHAR) {
2194             *index = leftover_start = bufp - buf;
2195             *matchp = NULLCHAR;
2196             return TRUE;
2197         }
2198         if (*bufp == NULLCHAR) return FALSE;
2199         if (*patternp == '*') {
2200             if (*bufp == *(patternp + 1)) {
2201                 *matchp = NULLCHAR;
2202                 matchp = star_match[++star_count];
2203                 patternp += 2;
2204                 bufp++;
2205                 continue;
2206             } else if (*bufp == '\n' || *bufp == '\r') {
2207                 patternp++;
2208                 if (*patternp == NULLCHAR)
2209                   continue;
2210                 else
2211                   return FALSE;
2212             } else {
2213                 *matchp++ = *bufp++;
2214                 continue;
2215             }
2216         }
2217         if (*patternp != *bufp) return FALSE;
2218         patternp++;
2219         bufp++;
2220     }
2221 }
2222
2223 void
2224 SendToPlayer (char *data, int length)
2225 {
2226     int error, outCount;
2227     outCount = OutputToProcess(NoProc, data, length, &error);
2228     if (outCount < length) {
2229         DisplayFatalError(_("Error writing to display"), error, 1);
2230     }
2231 }
2232
2233 void
2234 PackHolding (char packed[], char *holding)
2235 {
2236     char *p = holding;
2237     char *q = packed;
2238     int runlength = 0;
2239     int curr = 9999;
2240     do {
2241         if (*p == curr) {
2242             runlength++;
2243         } else {
2244             switch (runlength) {
2245               case 0:
2246                 break;
2247               case 1:
2248                 *q++ = curr;
2249                 break;
2250               case 2:
2251                 *q++ = curr;
2252                 *q++ = curr;
2253                 break;
2254               default:
2255                 sprintf(q, "%d", runlength);
2256                 while (*q) q++;
2257                 *q++ = curr;
2258                 break;
2259             }
2260             runlength = 1;
2261             curr = *p;
2262         }
2263     } while (*p++);
2264     *q = NULLCHAR;
2265 }
2266
2267 /* Telnet protocol requests from the front end */
2268 void
2269 TelnetRequest (unsigned char ddww, unsigned char option)
2270 {
2271     unsigned char msg[3];
2272     int outCount, outError;
2273
2274     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2275
2276     if (appData.debugMode) {
2277         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2278         switch (ddww) {
2279           case TN_DO:
2280             ddwwStr = "DO";
2281             break;
2282           case TN_DONT:
2283             ddwwStr = "DONT";
2284             break;
2285           case TN_WILL:
2286             ddwwStr = "WILL";
2287             break;
2288           case TN_WONT:
2289             ddwwStr = "WONT";
2290             break;
2291           default:
2292             ddwwStr = buf1;
2293             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2294             break;
2295         }
2296         switch (option) {
2297           case TN_ECHO:
2298             optionStr = "ECHO";
2299             break;
2300           default:
2301             optionStr = buf2;
2302             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2303             break;
2304         }
2305         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2306     }
2307     msg[0] = TN_IAC;
2308     msg[1] = ddww;
2309     msg[2] = option;
2310     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2311     if (outCount < 3) {
2312         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2313     }
2314 }
2315
2316 void
2317 DoEcho ()
2318 {
2319     if (!appData.icsActive) return;
2320     TelnetRequest(TN_DO, TN_ECHO);
2321 }
2322
2323 void
2324 DontEcho ()
2325 {
2326     if (!appData.icsActive) return;
2327     TelnetRequest(TN_DONT, TN_ECHO);
2328 }
2329
2330 void
2331 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2332 {
2333     /* put the holdings sent to us by the server on the board holdings area */
2334     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2335     char p;
2336     ChessSquare piece;
2337
2338     if(gameInfo.holdingsWidth < 2)  return;
2339     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2340         return; // prevent overwriting by pre-board holdings
2341
2342     if( (int)lowestPiece >= BlackPawn ) {
2343         holdingsColumn = 0;
2344         countsColumn = 1;
2345         holdingsStartRow = BOARD_HEIGHT-1;
2346         direction = -1;
2347     } else {
2348         holdingsColumn = BOARD_WIDTH-1;
2349         countsColumn = BOARD_WIDTH-2;
2350         holdingsStartRow = 0;
2351         direction = 1;
2352     }
2353
2354     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2355         board[i][holdingsColumn] = EmptySquare;
2356         board[i][countsColumn]   = (ChessSquare) 0;
2357     }
2358     while( (p=*holdings++) != NULLCHAR ) {
2359         piece = CharToPiece( ToUpper(p) );
2360         if(piece == EmptySquare) continue;
2361         /*j = (int) piece - (int) WhitePawn;*/
2362         j = PieceToNumber(piece);
2363         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2364         if(j < 0) continue;               /* should not happen */
2365         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2366         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2367         board[holdingsStartRow+j*direction][countsColumn]++;
2368     }
2369 }
2370
2371
2372 void
2373 VariantSwitch (Board board, VariantClass newVariant)
2374 {
2375    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2376    static Board oldBoard;
2377
2378    startedFromPositionFile = FALSE;
2379    if(gameInfo.variant == newVariant) return;
2380
2381    /* [HGM] This routine is called each time an assignment is made to
2382     * gameInfo.variant during a game, to make sure the board sizes
2383     * are set to match the new variant. If that means adding or deleting
2384     * holdings, we shift the playing board accordingly
2385     * This kludge is needed because in ICS observe mode, we get boards
2386     * of an ongoing game without knowing the variant, and learn about the
2387     * latter only later. This can be because of the move list we requested,
2388     * in which case the game history is refilled from the beginning anyway,
2389     * but also when receiving holdings of a crazyhouse game. In the latter
2390     * case we want to add those holdings to the already received position.
2391     */
2392
2393
2394    if (appData.debugMode) {
2395      fprintf(debugFP, "Switch board from %s to %s\n",
2396              VariantName(gameInfo.variant), VariantName(newVariant));
2397      setbuf(debugFP, NULL);
2398    }
2399    shuffleOpenings = 0;       /* [HGM] shuffle */
2400    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2401    switch(newVariant)
2402      {
2403      case VariantShogi:
2404        newWidth = 9;  newHeight = 9;
2405        gameInfo.holdingsSize = 7;
2406      case VariantBughouse:
2407      case VariantCrazyhouse:
2408        newHoldingsWidth = 2; break;
2409      case VariantGreat:
2410        newWidth = 10;
2411      case VariantSuper:
2412        newHoldingsWidth = 2;
2413        gameInfo.holdingsSize = 8;
2414        break;
2415      case VariantGothic:
2416      case VariantCapablanca:
2417      case VariantCapaRandom:
2418        newWidth = 10;
2419      default:
2420        newHoldingsWidth = gameInfo.holdingsSize = 0;
2421      };
2422
2423    if(newWidth  != gameInfo.boardWidth  ||
2424       newHeight != gameInfo.boardHeight ||
2425       newHoldingsWidth != gameInfo.holdingsWidth ) {
2426
2427      /* shift position to new playing area, if needed */
2428      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2429        for(i=0; i<BOARD_HEIGHT; i++)
2430          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2431            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2432              board[i][j];
2433        for(i=0; i<newHeight; i++) {
2434          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2435          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2436        }
2437      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2438        for(i=0; i<BOARD_HEIGHT; i++)
2439          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2440            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2441              board[i][j];
2442      }
2443      gameInfo.boardWidth  = newWidth;
2444      gameInfo.boardHeight = newHeight;
2445      gameInfo.holdingsWidth = newHoldingsWidth;
2446      gameInfo.variant = newVariant;
2447      InitDrawingSizes(-2, 0);
2448    } else gameInfo.variant = newVariant;
2449    CopyBoard(oldBoard, board);   // remember correctly formatted board
2450      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2451    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2452 }
2453
2454 static int loggedOn = FALSE;
2455
2456 /*-- Game start info cache: --*/
2457 int gs_gamenum;
2458 char gs_kind[MSG_SIZ];
2459 static char player1Name[128] = "";
2460 static char player2Name[128] = "";
2461 static char cont_seq[] = "\n\\   ";
2462 static int player1Rating = -1;
2463 static int player2Rating = -1;
2464 /*----------------------------*/
2465
2466 ColorClass curColor = ColorNormal;
2467 int suppressKibitz = 0;
2468
2469 // [HGM] seekgraph
2470 Boolean soughtPending = FALSE;
2471 Boolean seekGraphUp;
2472 #define MAX_SEEK_ADS 200
2473 #define SQUARE 0x80
2474 char *seekAdList[MAX_SEEK_ADS];
2475 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2476 float tcList[MAX_SEEK_ADS];
2477 char colorList[MAX_SEEK_ADS];
2478 int nrOfSeekAds = 0;
2479 int minRating = 1010, maxRating = 2800;
2480 int hMargin = 10, vMargin = 20, h, w;
2481 extern int squareSize, lineGap;
2482
2483 void
2484 PlotSeekAd (int i)
2485 {
2486         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2487         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2488         if(r < minRating+100 && r >=0 ) r = minRating+100;
2489         if(r > maxRating) r = maxRating;
2490         if(tc < 1.) tc = 1.;
2491         if(tc > 95.) tc = 95.;
2492         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2493         y = ((double)r - minRating)/(maxRating - minRating)
2494             * (h-vMargin-squareSize/8-1) + vMargin;
2495         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2496         if(strstr(seekAdList[i], " u ")) color = 1;
2497         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2498            !strstr(seekAdList[i], "bullet") &&
2499            !strstr(seekAdList[i], "blitz") &&
2500            !strstr(seekAdList[i], "standard") ) color = 2;
2501         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2502         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2503 }
2504
2505 void
2506 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2507 {
2508         char buf[MSG_SIZ], *ext = "";
2509         VariantClass v = StringToVariant(type);
2510         if(strstr(type, "wild")) {
2511             ext = type + 4; // append wild number
2512             if(v == VariantFischeRandom) type = "chess960"; else
2513             if(v == VariantLoadable) type = "setup"; else
2514             type = VariantName(v);
2515         }
2516         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2517         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2518             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2519             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2520             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2521             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2522             seekNrList[nrOfSeekAds] = nr;
2523             zList[nrOfSeekAds] = 0;
2524             seekAdList[nrOfSeekAds++] = StrSave(buf);
2525             if(plot) PlotSeekAd(nrOfSeekAds-1);
2526         }
2527 }
2528
2529 void
2530 EraseSeekDot (int i)
2531 {
2532     int x = xList[i], y = yList[i], d=squareSize/4, k;
2533     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2534     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2535     // now replot every dot that overlapped
2536     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2537         int xx = xList[k], yy = yList[k];
2538         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2539             DrawSeekDot(xx, yy, colorList[k]);
2540     }
2541 }
2542
2543 void
2544 RemoveSeekAd (int nr)
2545 {
2546         int i;
2547         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2548             EraseSeekDot(i);
2549             if(seekAdList[i]) free(seekAdList[i]);
2550             seekAdList[i] = seekAdList[--nrOfSeekAds];
2551             seekNrList[i] = seekNrList[nrOfSeekAds];
2552             ratingList[i] = ratingList[nrOfSeekAds];
2553             colorList[i]  = colorList[nrOfSeekAds];
2554             tcList[i] = tcList[nrOfSeekAds];
2555             xList[i]  = xList[nrOfSeekAds];
2556             yList[i]  = yList[nrOfSeekAds];
2557             zList[i]  = zList[nrOfSeekAds];
2558             seekAdList[nrOfSeekAds] = NULL;
2559             break;
2560         }
2561 }
2562
2563 Boolean
2564 MatchSoughtLine (char *line)
2565 {
2566     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2567     int nr, base, inc, u=0; char dummy;
2568
2569     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2570        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2571        (u=1) &&
2572        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2573         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2574         // match: compact and save the line
2575         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2576         return TRUE;
2577     }
2578     return FALSE;
2579 }
2580
2581 int
2582 DrawSeekGraph ()
2583 {
2584     int i;
2585     if(!seekGraphUp) return FALSE;
2586     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2587     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2588
2589     DrawSeekBackground(0, 0, w, h);
2590     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2591     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2592     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2593         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2594         yy = h-1-yy;
2595         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2596         if(i%500 == 0) {
2597             char buf[MSG_SIZ];
2598             snprintf(buf, MSG_SIZ, "%d", i);
2599             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2600         }
2601     }
2602     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2603     for(i=1; i<100; i+=(i<10?1:5)) {
2604         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2605         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2606         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2607             char buf[MSG_SIZ];
2608             snprintf(buf, MSG_SIZ, "%d", i);
2609             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2610         }
2611     }
2612     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2613     return TRUE;
2614 }
2615
2616 int
2617 SeekGraphClick (ClickType click, int x, int y, int moving)
2618 {
2619     static int lastDown = 0, displayed = 0, lastSecond;
2620     if(y < 0) return FALSE;
2621     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2622         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2623         if(!seekGraphUp) return FALSE;
2624         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2625         DrawPosition(TRUE, NULL);
2626         return TRUE;
2627     }
2628     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2629         if(click == Release || moving) return FALSE;
2630         nrOfSeekAds = 0;
2631         soughtPending = TRUE;
2632         SendToICS(ics_prefix);
2633         SendToICS("sought\n"); // should this be "sought all"?
2634     } else { // issue challenge based on clicked ad
2635         int dist = 10000; int i, closest = 0, second = 0;
2636         for(i=0; i<nrOfSeekAds; i++) {
2637             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2638             if(d < dist) { dist = d; closest = i; }
2639             second += (d - zList[i] < 120); // count in-range ads
2640             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2641         }
2642         if(dist < 120) {
2643             char buf[MSG_SIZ];
2644             second = (second > 1);
2645             if(displayed != closest || second != lastSecond) {
2646                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2647                 lastSecond = second; displayed = closest;
2648             }
2649             if(click == Press) {
2650                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2651                 lastDown = closest;
2652                 return TRUE;
2653             } // on press 'hit', only show info
2654             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2655             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2656             SendToICS(ics_prefix);
2657             SendToICS(buf);
2658             return TRUE; // let incoming board of started game pop down the graph
2659         } else if(click == Release) { // release 'miss' is ignored
2660             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2661             if(moving == 2) { // right up-click
2662                 nrOfSeekAds = 0; // refresh graph
2663                 soughtPending = TRUE;
2664                 SendToICS(ics_prefix);
2665                 SendToICS("sought\n"); // should this be "sought all"?
2666             }
2667             return TRUE;
2668         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2669         // press miss or release hit 'pop down' seek graph
2670         seekGraphUp = FALSE;
2671         DrawPosition(TRUE, NULL);
2672     }
2673     return TRUE;
2674 }
2675
2676 void
2677 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2678 {
2679 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2680 #define STARTED_NONE 0
2681 #define STARTED_MOVES 1
2682 #define STARTED_BOARD 2
2683 #define STARTED_OBSERVE 3
2684 #define STARTED_HOLDINGS 4
2685 #define STARTED_CHATTER 5
2686 #define STARTED_COMMENT 6
2687 #define STARTED_MOVES_NOHIDE 7
2688
2689     static int started = STARTED_NONE;
2690     static char parse[20000];
2691     static int parse_pos = 0;
2692     static char buf[BUF_SIZE + 1];
2693     static int firstTime = TRUE, intfSet = FALSE;
2694     static ColorClass prevColor = ColorNormal;
2695     static int savingComment = FALSE;
2696     static int cmatch = 0; // continuation sequence match
2697     char *bp;
2698     char str[MSG_SIZ];
2699     int i, oldi;
2700     int buf_len;
2701     int next_out;
2702     int tkind;
2703     int backup;    /* [DM] For zippy color lines */
2704     char *p;
2705     char talker[MSG_SIZ]; // [HGM] chat
2706     int channel;
2707
2708     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2709
2710     if (appData.debugMode) {
2711       if (!error) {
2712         fprintf(debugFP, "<ICS: ");
2713         show_bytes(debugFP, data, count);
2714         fprintf(debugFP, "\n");
2715       }
2716     }
2717
2718     if (appData.debugMode) { int f = forwardMostMove;
2719         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2720                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2721                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2722     }
2723     if (count > 0) {
2724         /* If last read ended with a partial line that we couldn't parse,
2725            prepend it to the new read and try again. */
2726         if (leftover_len > 0) {
2727             for (i=0; i<leftover_len; i++)
2728               buf[i] = buf[leftover_start + i];
2729         }
2730
2731     /* copy new characters into the buffer */
2732     bp = buf + leftover_len;
2733     buf_len=leftover_len;
2734     for (i=0; i<count; i++)
2735     {
2736         // ignore these
2737         if (data[i] == '\r')
2738             continue;
2739
2740         // join lines split by ICS?
2741         if (!appData.noJoin)
2742         {
2743             /*
2744                 Joining just consists of finding matches against the
2745                 continuation sequence, and discarding that sequence
2746                 if found instead of copying it.  So, until a match
2747                 fails, there's nothing to do since it might be the
2748                 complete sequence, and thus, something we don't want
2749                 copied.
2750             */
2751             if (data[i] == cont_seq[cmatch])
2752             {
2753                 cmatch++;
2754                 if (cmatch == strlen(cont_seq))
2755                 {
2756                     cmatch = 0; // complete match.  just reset the counter
2757
2758                     /*
2759                         it's possible for the ICS to not include the space
2760                         at the end of the last word, making our [correct]
2761                         join operation fuse two separate words.  the server
2762                         does this when the space occurs at the width setting.
2763                     */
2764                     if (!buf_len || buf[buf_len-1] != ' ')
2765                     {
2766                         *bp++ = ' ';
2767                         buf_len++;
2768                     }
2769                 }
2770                 continue;
2771             }
2772             else if (cmatch)
2773             {
2774                 /*
2775                     match failed, so we have to copy what matched before
2776                     falling through and copying this character.  In reality,
2777                     this will only ever be just the newline character, but
2778                     it doesn't hurt to be precise.
2779                 */
2780                 strncpy(bp, cont_seq, cmatch);
2781                 bp += cmatch;
2782                 buf_len += cmatch;
2783                 cmatch = 0;
2784             }
2785         }
2786
2787         // copy this char
2788         *bp++ = data[i];
2789         buf_len++;
2790     }
2791
2792         buf[buf_len] = NULLCHAR;
2793 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2794         next_out = 0;
2795         leftover_start = 0;
2796
2797         i = 0;
2798         while (i < buf_len) {
2799             /* Deal with part of the TELNET option negotiation
2800                protocol.  We refuse to do anything beyond the
2801                defaults, except that we allow the WILL ECHO option,
2802                which ICS uses to turn off password echoing when we are
2803                directly connected to it.  We reject this option
2804                if localLineEditing mode is on (always on in xboard)
2805                and we are talking to port 23, which might be a real
2806                telnet server that will try to keep WILL ECHO on permanently.
2807              */
2808             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2809                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2810                 unsigned char option;
2811                 oldi = i;
2812                 switch ((unsigned char) buf[++i]) {
2813                   case TN_WILL:
2814                     if (appData.debugMode)
2815                       fprintf(debugFP, "\n<WILL ");
2816                     switch (option = (unsigned char) buf[++i]) {
2817                       case TN_ECHO:
2818                         if (appData.debugMode)
2819                           fprintf(debugFP, "ECHO ");
2820                         /* Reply only if this is a change, according
2821                            to the protocol rules. */
2822                         if (remoteEchoOption) break;
2823                         if (appData.localLineEditing &&
2824                             atoi(appData.icsPort) == TN_PORT) {
2825                             TelnetRequest(TN_DONT, TN_ECHO);
2826                         } else {
2827                             EchoOff();
2828                             TelnetRequest(TN_DO, TN_ECHO);
2829                             remoteEchoOption = TRUE;
2830                         }
2831                         break;
2832                       default:
2833                         if (appData.debugMode)
2834                           fprintf(debugFP, "%d ", option);
2835                         /* Whatever this is, we don't want it. */
2836                         TelnetRequest(TN_DONT, option);
2837                         break;
2838                     }
2839                     break;
2840                   case TN_WONT:
2841                     if (appData.debugMode)
2842                       fprintf(debugFP, "\n<WONT ");
2843                     switch (option = (unsigned char) buf[++i]) {
2844                       case TN_ECHO:
2845                         if (appData.debugMode)
2846                           fprintf(debugFP, "ECHO ");
2847                         /* Reply only if this is a change, according
2848                            to the protocol rules. */
2849                         if (!remoteEchoOption) break;
2850                         EchoOn();
2851                         TelnetRequest(TN_DONT, TN_ECHO);
2852                         remoteEchoOption = FALSE;
2853                         break;
2854                       default:
2855                         if (appData.debugMode)
2856                           fprintf(debugFP, "%d ", (unsigned char) option);
2857                         /* Whatever this is, it must already be turned
2858                            off, because we never agree to turn on
2859                            anything non-default, so according to the
2860                            protocol rules, we don't reply. */
2861                         break;
2862                     }
2863                     break;
2864                   case TN_DO:
2865                     if (appData.debugMode)
2866                       fprintf(debugFP, "\n<DO ");
2867                     switch (option = (unsigned char) buf[++i]) {
2868                       default:
2869                         /* Whatever this is, we refuse to do it. */
2870                         if (appData.debugMode)
2871                           fprintf(debugFP, "%d ", option);
2872                         TelnetRequest(TN_WONT, option);
2873                         break;
2874                     }
2875                     break;
2876                   case TN_DONT:
2877                     if (appData.debugMode)
2878                       fprintf(debugFP, "\n<DONT ");
2879                     switch (option = (unsigned char) buf[++i]) {
2880                       default:
2881                         if (appData.debugMode)
2882                           fprintf(debugFP, "%d ", option);
2883                         /* Whatever this is, we are already not doing
2884                            it, because we never agree to do anything
2885                            non-default, so according to the protocol
2886                            rules, we don't reply. */
2887                         break;
2888                     }
2889                     break;
2890                   case TN_IAC:
2891                     if (appData.debugMode)
2892                       fprintf(debugFP, "\n<IAC ");
2893                     /* Doubled IAC; pass it through */
2894                     i--;
2895                     break;
2896                   default:
2897                     if (appData.debugMode)
2898                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2899                     /* Drop all other telnet commands on the floor */
2900                     break;
2901                 }
2902                 if (oldi > next_out)
2903                   SendToPlayer(&buf[next_out], oldi - next_out);
2904                 if (++i > next_out)
2905                   next_out = i;
2906                 continue;
2907             }
2908
2909             /* OK, this at least will *usually* work */
2910             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2911                 loggedOn = TRUE;
2912             }
2913
2914             if (loggedOn && !intfSet) {
2915                 if (ics_type == ICS_ICC) {
2916                   snprintf(str, MSG_SIZ,
2917                           "/set-quietly interface %s\n/set-quietly style 12\n",
2918                           programVersion);
2919                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2920                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2921                 } else if (ics_type == ICS_CHESSNET) {
2922                   snprintf(str, MSG_SIZ, "/style 12\n");
2923                 } else {
2924                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2925                   strcat(str, programVersion);
2926                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2927                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2928                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2929 #ifdef WIN32
2930                   strcat(str, "$iset nohighlight 1\n");
2931 #endif
2932                   strcat(str, "$iset lock 1\n$style 12\n");
2933                 }
2934                 SendToICS(str);
2935                 NotifyFrontendLogin();
2936                 intfSet = TRUE;
2937             }
2938
2939             if (started == STARTED_COMMENT) {
2940                 /* Accumulate characters in comment */
2941                 parse[parse_pos++] = buf[i];
2942                 if (buf[i] == '\n') {
2943                     parse[parse_pos] = NULLCHAR;
2944                     if(chattingPartner>=0) {
2945                         char mess[MSG_SIZ];
2946                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2947                         OutputChatMessage(chattingPartner, mess);
2948                         chattingPartner = -1;
2949                         next_out = i+1; // [HGM] suppress printing in ICS window
2950                     } else
2951                     if(!suppressKibitz) // [HGM] kibitz
2952                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2953                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2954                         int nrDigit = 0, nrAlph = 0, j;
2955                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2956                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2957                         parse[parse_pos] = NULLCHAR;
2958                         // try to be smart: if it does not look like search info, it should go to
2959                         // ICS interaction window after all, not to engine-output window.
2960                         for(j=0; j<parse_pos; j++) { // count letters and digits
2961                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2962                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2963                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2964                         }
2965                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2966                             int depth=0; float score;
2967                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2968                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2969                                 pvInfoList[forwardMostMove-1].depth = depth;
2970                                 pvInfoList[forwardMostMove-1].score = 100*score;
2971                             }
2972                             OutputKibitz(suppressKibitz, parse);
2973                         } else {
2974                             char tmp[MSG_SIZ];
2975                             if(gameMode == IcsObserving) // restore original ICS messages
2976                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2977                             else
2978                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2979                             SendToPlayer(tmp, strlen(tmp));
2980                         }
2981                         next_out = i+1; // [HGM] suppress printing in ICS window
2982                     }
2983                     started = STARTED_NONE;
2984                 } else {
2985                     /* Don't match patterns against characters in comment */
2986                     i++;
2987                     continue;
2988                 }
2989             }
2990             if (started == STARTED_CHATTER) {
2991                 if (buf[i] != '\n') {
2992                     /* Don't match patterns against characters in chatter */
2993                     i++;
2994                     continue;
2995                 }
2996                 started = STARTED_NONE;
2997                 if(suppressKibitz) next_out = i+1;
2998             }
2999
3000             /* Kludge to deal with rcmd protocol */
3001             if (firstTime && looking_at(buf, &i, "\001*")) {
3002                 DisplayFatalError(&buf[1], 0, 1);
3003                 continue;
3004             } else {
3005                 firstTime = FALSE;
3006             }
3007
3008             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3009                 ics_type = ICS_ICC;
3010                 ics_prefix = "/";
3011                 if (appData.debugMode)
3012                   fprintf(debugFP, "ics_type %d\n", ics_type);
3013                 continue;
3014             }
3015             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3016                 ics_type = ICS_FICS;
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, "chess.net")) {
3023                 ics_type = ICS_CHESSNET;
3024                 ics_prefix = "/";
3025                 if (appData.debugMode)
3026                   fprintf(debugFP, "ics_type %d\n", ics_type);
3027                 continue;
3028             }
3029
3030             if (!loggedOn &&
3031                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3032                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3033                  looking_at(buf, &i, "will be \"*\""))) {
3034               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3035               continue;
3036             }
3037
3038             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3039               char buf[MSG_SIZ];
3040               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3041               DisplayIcsInteractionTitle(buf);
3042               have_set_title = TRUE;
3043             }
3044
3045             /* skip finger notes */
3046             if (started == STARTED_NONE &&
3047                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3048                  (buf[i] == '1' && buf[i+1] == '0')) &&
3049                 buf[i+2] == ':' && buf[i+3] == ' ') {
3050               started = STARTED_CHATTER;
3051               i += 3;
3052               continue;
3053             }
3054
3055             oldi = i;
3056             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3057             if(appData.seekGraph) {
3058                 if(soughtPending && MatchSoughtLine(buf+i)) {
3059                     i = strstr(buf+i, "rated") - buf;
3060                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3061                     next_out = leftover_start = i;
3062                     started = STARTED_CHATTER;
3063                     suppressKibitz = TRUE;
3064                     continue;
3065                 }
3066                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3067                         && looking_at(buf, &i, "* ads displayed")) {
3068                     soughtPending = FALSE;
3069                     seekGraphUp = TRUE;
3070                     DrawSeekGraph();
3071                     continue;
3072                 }
3073                 if(appData.autoRefresh) {
3074                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3075                         int s = (ics_type == ICS_ICC); // ICC format differs
3076                         if(seekGraphUp)
3077                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3078                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3079                         looking_at(buf, &i, "*% "); // eat prompt
3080                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3081                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3082                         next_out = i; // suppress
3083                         continue;
3084                     }
3085                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3086                         char *p = star_match[0];
3087                         while(*p) {
3088                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3089                             while(*p && *p++ != ' '); // next
3090                         }
3091                         looking_at(buf, &i, "*% "); // eat prompt
3092                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3093                         next_out = i;
3094                         continue;
3095                     }
3096                 }
3097             }
3098
3099             /* skip formula vars */
3100             if (started == STARTED_NONE &&
3101                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3102               started = STARTED_CHATTER;
3103               i += 3;
3104               continue;
3105             }
3106
3107             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3108             if (appData.autoKibitz && started == STARTED_NONE &&
3109                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3110                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3111                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3112                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3113                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3114                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3115                         suppressKibitz = TRUE;
3116                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3117                         next_out = i;
3118                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3119                                 && (gameMode == IcsPlayingWhite)) ||
3120                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3121                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3122                             started = STARTED_CHATTER; // own kibitz we simply discard
3123                         else {
3124                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3125                             parse_pos = 0; parse[0] = NULLCHAR;
3126                             savingComment = TRUE;
3127                             suppressKibitz = gameMode != IcsObserving ? 2 :
3128                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3129                         }
3130                         continue;
3131                 } else
3132                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3133                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3134                          && atoi(star_match[0])) {
3135                     // suppress the acknowledgements of our own autoKibitz
3136                     char *p;
3137                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3138                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3139                     SendToPlayer(star_match[0], strlen(star_match[0]));
3140                     if(looking_at(buf, &i, "*% ")) // eat prompt
3141                         suppressKibitz = FALSE;
3142                     next_out = i;
3143                     continue;
3144                 }
3145             } // [HGM] kibitz: end of patch
3146
3147             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3148
3149             // [HGM] chat: intercept tells by users for which we have an open chat window
3150             channel = -1;
3151             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3152                                            looking_at(buf, &i, "* whispers:") ||
3153                                            looking_at(buf, &i, "* kibitzes:") ||
3154                                            looking_at(buf, &i, "* shouts:") ||
3155                                            looking_at(buf, &i, "* c-shouts:") ||
3156                                            looking_at(buf, &i, "--> * ") ||
3157                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3158                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3159                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3160                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3161                 int p;
3162                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3163                 chattingPartner = -1;
3164
3165                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3166                 for(p=0; p<MAX_CHAT; p++) {
3167                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3168                     talker[0] = '['; strcat(talker, "] ");
3169                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3170                     chattingPartner = p; break;
3171                     }
3172                 } else
3173                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3174                 for(p=0; p<MAX_CHAT; p++) {
3175                     if(!strcmp("kibitzes", chatPartner[p])) {
3176                         talker[0] = '['; strcat(talker, "] ");
3177                         chattingPartner = p; break;
3178                     }
3179                 } else
3180                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3181                 for(p=0; p<MAX_CHAT; p++) {
3182                     if(!strcmp("whispers", chatPartner[p])) {
3183                         talker[0] = '['; strcat(talker, "] ");
3184                         chattingPartner = p; break;
3185                     }
3186                 } else
3187                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3188                   if(buf[i-8] == '-' && buf[i-3] == 't')
3189                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3190                     if(!strcmp("c-shouts", chatPartner[p])) {
3191                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3192                         chattingPartner = p; break;
3193                     }
3194                   }
3195                   if(chattingPartner < 0)
3196                   for(p=0; p<MAX_CHAT; p++) {
3197                     if(!strcmp("shouts", chatPartner[p])) {
3198                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3199                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3200                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3201                         chattingPartner = p; break;
3202                     }
3203                   }
3204                 }
3205                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3206                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3207                     talker[0] = 0; Colorize(ColorTell, FALSE);
3208                     chattingPartner = p; break;
3209                 }
3210                 if(chattingPartner<0) i = oldi; else {
3211                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3212                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3213                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3214                     started = STARTED_COMMENT;
3215                     parse_pos = 0; parse[0] = NULLCHAR;
3216                     savingComment = 3 + chattingPartner; // counts as TRUE
3217                     suppressKibitz = TRUE;
3218                     continue;
3219                 }
3220             } // [HGM] chat: end of patch
3221
3222           backup = i;
3223             if (appData.zippyTalk || appData.zippyPlay) {
3224                 /* [DM] Backup address for color zippy lines */
3225 #if ZIPPY
3226                if (loggedOn == TRUE)
3227                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3228                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3229 #endif
3230             } // [DM] 'else { ' deleted
3231                 if (
3232                     /* Regular tells and says */
3233                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3234                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3235                     looking_at(buf, &i, "* says: ") ||
3236                     /* Don't color "message" or "messages" output */
3237                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3238                     looking_at(buf, &i, "*. * at *:*: ") ||
3239                     looking_at(buf, &i, "--* (*:*): ") ||
3240                     /* Message notifications (same color as tells) */
3241                     looking_at(buf, &i, "* has left a message ") ||
3242                     looking_at(buf, &i, "* just sent you a message:\n") ||
3243                     /* Whispers and kibitzes */
3244                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3245                     looking_at(buf, &i, "* kibitzes: ") ||
3246                     /* Channel tells */
3247                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3248
3249                   if (tkind == 1 && strchr(star_match[0], ':')) {
3250                       /* Avoid "tells you:" spoofs in channels */
3251                      tkind = 3;
3252                   }
3253                   if (star_match[0][0] == NULLCHAR ||
3254                       strchr(star_match[0], ' ') ||
3255                       (tkind == 3 && strchr(star_match[1], ' '))) {
3256                     /* Reject bogus matches */
3257                     i = oldi;
3258                   } else {
3259                     if (appData.colorize) {
3260                       if (oldi > next_out) {
3261                         SendToPlayer(&buf[next_out], oldi - next_out);
3262                         next_out = oldi;
3263                       }
3264                       switch (tkind) {
3265                       case 1:
3266                         Colorize(ColorTell, FALSE);
3267                         curColor = ColorTell;
3268                         break;
3269                       case 2:
3270                         Colorize(ColorKibitz, FALSE);
3271                         curColor = ColorKibitz;
3272                         break;
3273                       case 3:
3274                         p = strrchr(star_match[1], '(');
3275                         if (p == NULL) {
3276                           p = star_match[1];
3277                         } else {
3278                           p++;
3279                         }
3280                         if (atoi(p) == 1) {
3281                           Colorize(ColorChannel1, FALSE);
3282                           curColor = ColorChannel1;
3283                         } else {
3284                           Colorize(ColorChannel, FALSE);
3285                           curColor = ColorChannel;
3286                         }
3287                         break;
3288                       case 5:
3289                         curColor = ColorNormal;
3290                         break;
3291                       }
3292                     }
3293                     if (started == STARTED_NONE && appData.autoComment &&
3294                         (gameMode == IcsObserving ||
3295                          gameMode == IcsPlayingWhite ||
3296                          gameMode == IcsPlayingBlack)) {
3297                       parse_pos = i - oldi;
3298                       memcpy(parse, &buf[oldi], parse_pos);
3299                       parse[parse_pos] = NULLCHAR;
3300                       started = STARTED_COMMENT;
3301                       savingComment = TRUE;
3302                     } else {
3303                       started = STARTED_CHATTER;
3304                       savingComment = FALSE;
3305                     }
3306                     loggedOn = TRUE;
3307                     continue;
3308                   }
3309                 }
3310
3311                 if (looking_at(buf, &i, "* s-shouts: ") ||
3312                     looking_at(buf, &i, "* c-shouts: ")) {
3313                     if (appData.colorize) {
3314                         if (oldi > next_out) {
3315                             SendToPlayer(&buf[next_out], oldi - next_out);
3316                             next_out = oldi;
3317                         }
3318                         Colorize(ColorSShout, FALSE);
3319                         curColor = ColorSShout;
3320                     }
3321                     loggedOn = TRUE;
3322                     started = STARTED_CHATTER;
3323                     continue;
3324                 }
3325
3326                 if (looking_at(buf, &i, "--->")) {
3327                     loggedOn = TRUE;
3328                     continue;
3329                 }
3330
3331                 if (looking_at(buf, &i, "* shouts: ") ||
3332                     looking_at(buf, &i, "--> ")) {
3333                     if (appData.colorize) {
3334                         if (oldi > next_out) {
3335                             SendToPlayer(&buf[next_out], oldi - next_out);
3336                             next_out = oldi;
3337                         }
3338                         Colorize(ColorShout, FALSE);
3339                         curColor = ColorShout;
3340                     }
3341                     loggedOn = TRUE;
3342                     started = STARTED_CHATTER;
3343                     continue;
3344                 }
3345
3346                 if (looking_at( buf, &i, "Challenge:")) {
3347                     if (appData.colorize) {
3348                         if (oldi > next_out) {
3349                             SendToPlayer(&buf[next_out], oldi - next_out);
3350                             next_out = oldi;
3351                         }
3352                         Colorize(ColorChallenge, FALSE);
3353                         curColor = ColorChallenge;
3354                     }
3355                     loggedOn = TRUE;
3356                     continue;
3357                 }
3358
3359                 if (looking_at(buf, &i, "* offers you") ||
3360                     looking_at(buf, &i, "* offers to be") ||
3361                     looking_at(buf, &i, "* would like to") ||
3362                     looking_at(buf, &i, "* requests to") ||
3363                     looking_at(buf, &i, "Your opponent offers") ||
3364                     looking_at(buf, &i, "Your opponent requests")) {
3365
3366                     if (appData.colorize) {
3367                         if (oldi > next_out) {
3368                             SendToPlayer(&buf[next_out], oldi - next_out);
3369                             next_out = oldi;
3370                         }
3371                         Colorize(ColorRequest, FALSE);
3372                         curColor = ColorRequest;
3373                     }
3374                     continue;
3375                 }
3376
3377                 if (looking_at(buf, &i, "* (*) seeking")) {
3378                     if (appData.colorize) {
3379                         if (oldi > next_out) {
3380                             SendToPlayer(&buf[next_out], oldi - next_out);
3381                             next_out = oldi;
3382                         }
3383                         Colorize(ColorSeek, FALSE);
3384                         curColor = ColorSeek;
3385                     }
3386                     continue;
3387             }
3388
3389           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3390
3391             if (looking_at(buf, &i, "\\   ")) {
3392                 if (prevColor != ColorNormal) {
3393                     if (oldi > next_out) {
3394                         SendToPlayer(&buf[next_out], oldi - next_out);
3395                         next_out = oldi;
3396                     }
3397                     Colorize(prevColor, TRUE);
3398                     curColor = prevColor;
3399                 }
3400                 if (savingComment) {
3401                     parse_pos = i - oldi;
3402                     memcpy(parse, &buf[oldi], parse_pos);
3403                     parse[parse_pos] = NULLCHAR;
3404                     started = STARTED_COMMENT;
3405                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3406                         chattingPartner = savingComment - 3; // kludge to remember the box
3407                 } else {
3408                     started = STARTED_CHATTER;
3409                 }
3410                 continue;
3411             }
3412
3413             if (looking_at(buf, &i, "Black Strength :") ||
3414                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3415                 looking_at(buf, &i, "<10>") ||
3416                 looking_at(buf, &i, "#@#")) {
3417                 /* Wrong board style */
3418                 loggedOn = TRUE;
3419                 SendToICS(ics_prefix);
3420                 SendToICS("set style 12\n");
3421                 SendToICS(ics_prefix);
3422                 SendToICS("refresh\n");
3423                 continue;
3424             }
3425
3426             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3427                 ICSInitScript();
3428                 have_sent_ICS_logon = 1;
3429                 continue;
3430             }
3431
3432             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3433                 (looking_at(buf, &i, "\n<12> ") ||
3434                  looking_at(buf, &i, "<12> "))) {
3435                 loggedOn = TRUE;
3436                 if (oldi > next_out) {
3437                     SendToPlayer(&buf[next_out], oldi - next_out);
3438                 }
3439                 next_out = i;
3440                 started = STARTED_BOARD;
3441                 parse_pos = 0;
3442                 continue;
3443             }
3444
3445             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3446                 looking_at(buf, &i, "<b1> ")) {
3447                 if (oldi > next_out) {
3448                     SendToPlayer(&buf[next_out], oldi - next_out);
3449                 }
3450                 next_out = i;
3451                 started = STARTED_HOLDINGS;
3452                 parse_pos = 0;
3453                 continue;
3454             }
3455
3456             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3457                 loggedOn = TRUE;
3458                 /* Header for a move list -- first line */
3459
3460                 switch (ics_getting_history) {
3461                   case H_FALSE:
3462                     switch (gameMode) {
3463                       case IcsIdle:
3464                       case BeginningOfGame:
3465                         /* User typed "moves" or "oldmoves" while we
3466                            were idle.  Pretend we asked for these
3467                            moves and soak them up so user can step
3468                            through them and/or save them.
3469                            */
3470                         Reset(FALSE, TRUE);
3471                         gameMode = IcsObserving;
3472                         ModeHighlight();
3473                         ics_gamenum = -1;
3474                         ics_getting_history = H_GOT_UNREQ_HEADER;
3475                         break;
3476                       case EditGame: /*?*/
3477                       case EditPosition: /*?*/
3478                         /* Should above feature work in these modes too? */
3479                         /* For now it doesn't */
3480                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3481                         break;
3482                       default:
3483                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3484                         break;
3485                     }
3486                     break;
3487                   case H_REQUESTED:
3488                     /* Is this the right one? */
3489                     if (gameInfo.white && gameInfo.black &&
3490                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3491                         strcmp(gameInfo.black, star_match[2]) == 0) {
3492                         /* All is well */
3493                         ics_getting_history = H_GOT_REQ_HEADER;
3494                     }
3495                     break;
3496                   case H_GOT_REQ_HEADER:
3497                   case H_GOT_UNREQ_HEADER:
3498                   case H_GOT_UNWANTED_HEADER:
3499                   case H_GETTING_MOVES:
3500                     /* Should not happen */
3501                     DisplayError(_("Error gathering move list: two headers"), 0);
3502                     ics_getting_history = H_FALSE;
3503                     break;
3504                 }
3505
3506                 /* Save player ratings into gameInfo if needed */
3507                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3508                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3509                     (gameInfo.whiteRating == -1 ||
3510                      gameInfo.blackRating == -1)) {
3511
3512                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3513                     gameInfo.blackRating = string_to_rating(star_match[3]);
3514                     if (appData.debugMode)
3515                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3516                               gameInfo.whiteRating, gameInfo.blackRating);
3517                 }
3518                 continue;
3519             }
3520
3521             if (looking_at(buf, &i,
3522               "* * match, initial time: * minute*, increment: * second")) {
3523                 /* Header for a move list -- second line */
3524                 /* Initial board will follow if this is a wild game */
3525                 if (gameInfo.event != NULL) free(gameInfo.event);
3526                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3527                 gameInfo.event = StrSave(str);
3528                 /* [HGM] we switched variant. Translate boards if needed. */
3529                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3530                 continue;
3531             }
3532
3533             if (looking_at(buf, &i, "Move  ")) {
3534                 /* Beginning of a move list */
3535                 switch (ics_getting_history) {
3536                   case H_FALSE:
3537                     /* Normally should not happen */
3538                     /* Maybe user hit reset while we were parsing */
3539                     break;
3540                   case H_REQUESTED:
3541                     /* Happens if we are ignoring a move list that is not
3542                      * the one we just requested.  Common if the user
3543                      * tries to observe two games without turning off
3544                      * getMoveList */
3545                     break;
3546                   case H_GETTING_MOVES:
3547                     /* Should not happen */
3548                     DisplayError(_("Error gathering move list: nested"), 0);
3549                     ics_getting_history = H_FALSE;
3550                     break;
3551                   case H_GOT_REQ_HEADER:
3552                     ics_getting_history = H_GETTING_MOVES;
3553                     started = STARTED_MOVES;
3554                     parse_pos = 0;
3555                     if (oldi > next_out) {
3556                         SendToPlayer(&buf[next_out], oldi - next_out);
3557                     }
3558                     break;
3559                   case H_GOT_UNREQ_HEADER:
3560                     ics_getting_history = H_GETTING_MOVES;
3561                     started = STARTED_MOVES_NOHIDE;
3562                     parse_pos = 0;
3563                     break;
3564                   case H_GOT_UNWANTED_HEADER:
3565                     ics_getting_history = H_FALSE;
3566                     break;
3567                 }
3568                 continue;
3569             }
3570
3571             if (looking_at(buf, &i, "% ") ||
3572                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3573                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3574                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3575                     soughtPending = FALSE;
3576                     seekGraphUp = TRUE;
3577                     DrawSeekGraph();
3578                 }
3579                 if(suppressKibitz) next_out = i;
3580                 savingComment = FALSE;
3581                 suppressKibitz = 0;
3582                 switch (started) {
3583                   case STARTED_MOVES:
3584                   case STARTED_MOVES_NOHIDE:
3585                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3586                     parse[parse_pos + i - oldi] = NULLCHAR;
3587                     ParseGameHistory(parse);
3588 #if ZIPPY
3589                     if (appData.zippyPlay && first.initDone) {
3590                         FeedMovesToProgram(&first, forwardMostMove);
3591                         if (gameMode == IcsPlayingWhite) {
3592                             if (WhiteOnMove(forwardMostMove)) {
3593                                 if (first.sendTime) {
3594                                   if (first.useColors) {
3595                                     SendToProgram("black\n", &first);
3596                                   }
3597                                   SendTimeRemaining(&first, TRUE);
3598                                 }
3599                                 if (first.useColors) {
3600                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3601                                 }
3602                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3603                                 first.maybeThinking = TRUE;
3604                             } else {
3605                                 if (first.usePlayother) {
3606                                   if (first.sendTime) {
3607                                     SendTimeRemaining(&first, TRUE);
3608                                   }
3609                                   SendToProgram("playother\n", &first);
3610                                   firstMove = FALSE;
3611                                 } else {
3612                                   firstMove = TRUE;
3613                                 }
3614                             }
3615                         } else if (gameMode == IcsPlayingBlack) {
3616                             if (!WhiteOnMove(forwardMostMove)) {
3617                                 if (first.sendTime) {
3618                                   if (first.useColors) {
3619                                     SendToProgram("white\n", &first);
3620                                   }
3621                                   SendTimeRemaining(&first, FALSE);
3622                                 }
3623                                 if (first.useColors) {
3624                                   SendToProgram("black\n", &first);
3625                                 }
3626                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3627                                 first.maybeThinking = TRUE;
3628                             } else {
3629                                 if (first.usePlayother) {
3630                                   if (first.sendTime) {
3631                                     SendTimeRemaining(&first, FALSE);
3632                                   }
3633                                   SendToProgram("playother\n", &first);
3634                                   firstMove = FALSE;
3635                                 } else {
3636                                   firstMove = TRUE;
3637                                 }
3638                             }
3639                         }
3640                     }
3641 #endif
3642                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3643                         /* Moves came from oldmoves or moves command
3644                            while we weren't doing anything else.
3645                            */
3646                         currentMove = forwardMostMove;
3647                         ClearHighlights();/*!!could figure this out*/
3648                         flipView = appData.flipView;
3649                         DrawPosition(TRUE, boards[currentMove]);
3650                         DisplayBothClocks();
3651                         snprintf(str, MSG_SIZ, "%s %s %s",
3652                                 gameInfo.white, _("vs."),  gameInfo.black);
3653                         DisplayTitle(str);
3654                         gameMode = IcsIdle;
3655                     } else {
3656                         /* Moves were history of an active game */
3657                         if (gameInfo.resultDetails != NULL) {
3658                             free(gameInfo.resultDetails);
3659                             gameInfo.resultDetails = NULL;
3660                         }
3661                     }
3662                     HistorySet(parseList, backwardMostMove,
3663                                forwardMostMove, currentMove-1);
3664                     DisplayMove(currentMove - 1);
3665                     if (started == STARTED_MOVES) next_out = i;
3666                     started = STARTED_NONE;
3667                     ics_getting_history = H_FALSE;
3668                     break;
3669
3670                   case STARTED_OBSERVE:
3671                     started = STARTED_NONE;
3672                     SendToICS(ics_prefix);
3673                     SendToICS("refresh\n");
3674                     break;
3675
3676                   default:
3677                     break;
3678                 }
3679                 if(bookHit) { // [HGM] book: simulate book reply
3680                     static char bookMove[MSG_SIZ]; // a bit generous?
3681
3682                     programStats.nodes = programStats.depth = programStats.time =
3683                     programStats.score = programStats.got_only_move = 0;
3684                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3685
3686                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3687                     strcat(bookMove, bookHit);
3688                     HandleMachineMove(bookMove, &first);
3689                 }
3690                 continue;
3691             }
3692
3693             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3694                  started == STARTED_HOLDINGS ||
3695                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3696                 /* Accumulate characters in move list or board */
3697                 parse[parse_pos++] = buf[i];
3698             }
3699
3700             /* Start of game messages.  Mostly we detect start of game
3701                when the first board image arrives.  On some versions
3702                of the ICS, though, we need to do a "refresh" after starting
3703                to observe in order to get the current board right away. */
3704             if (looking_at(buf, &i, "Adding game * to observation list")) {
3705                 started = STARTED_OBSERVE;
3706                 continue;
3707             }
3708
3709             /* Handle auto-observe */
3710             if (appData.autoObserve &&
3711                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3712                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3713                 char *player;
3714                 /* Choose the player that was highlighted, if any. */
3715                 if (star_match[0][0] == '\033' ||
3716                     star_match[1][0] != '\033') {
3717                     player = star_match[0];
3718                 } else {
3719                     player = star_match[2];
3720                 }
3721                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3722                         ics_prefix, StripHighlightAndTitle(player));
3723                 SendToICS(str);
3724
3725                 /* Save ratings from notify string */
3726                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3727                 player1Rating = string_to_rating(star_match[1]);
3728                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3729                 player2Rating = string_to_rating(star_match[3]);
3730
3731                 if (appData.debugMode)
3732                   fprintf(debugFP,
3733                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3734                           player1Name, player1Rating,
3735                           player2Name, player2Rating);
3736
3737                 continue;
3738             }
3739
3740             /* Deal with automatic examine mode after a game,
3741                and with IcsObserving -> IcsExamining transition */
3742             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3743                 looking_at(buf, &i, "has made you an examiner of game *")) {
3744
3745                 int gamenum = atoi(star_match[0]);
3746                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3747                     gamenum == ics_gamenum) {
3748                     /* We were already playing or observing this game;
3749                        no need to refetch history */
3750                     gameMode = IcsExamining;
3751                     if (pausing) {
3752                         pauseExamForwardMostMove = forwardMostMove;
3753                     } else if (currentMove < forwardMostMove) {
3754                         ForwardInner(forwardMostMove);
3755                     }
3756                 } else {
3757                     /* I don't think this case really can happen */
3758                     SendToICS(ics_prefix);
3759                     SendToICS("refresh\n");
3760                 }
3761                 continue;
3762             }
3763
3764             /* Error messages */
3765 //          if (ics_user_moved) {
3766             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3767                 if (looking_at(buf, &i, "Illegal move") ||
3768                     looking_at(buf, &i, "Not a legal move") ||
3769                     looking_at(buf, &i, "Your king is in check") ||
3770                     looking_at(buf, &i, "It isn't your turn") ||
3771                     looking_at(buf, &i, "It is not your move")) {
3772                     /* Illegal move */
3773                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3774                         currentMove = forwardMostMove-1;
3775                         DisplayMove(currentMove - 1); /* before DMError */
3776                         DrawPosition(FALSE, boards[currentMove]);
3777                         SwitchClocks(forwardMostMove-1); // [HGM] race
3778                         DisplayBothClocks();
3779                     }
3780                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3781                     ics_user_moved = 0;
3782                     continue;
3783                 }
3784             }
3785
3786             if (looking_at(buf, &i, "still have time") ||
3787                 looking_at(buf, &i, "not out of time") ||
3788                 looking_at(buf, &i, "either player is out of time") ||
3789                 looking_at(buf, &i, "has timeseal; checking")) {
3790                 /* We must have called his flag a little too soon */
3791                 whiteFlag = blackFlag = FALSE;
3792                 continue;
3793             }
3794
3795             if (looking_at(buf, &i, "added * seconds to") ||
3796                 looking_at(buf, &i, "seconds were added to")) {
3797                 /* Update the clocks */
3798                 SendToICS(ics_prefix);
3799                 SendToICS("refresh\n");
3800                 continue;
3801             }
3802
3803             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3804                 ics_clock_paused = TRUE;
3805                 StopClocks();
3806                 continue;
3807             }
3808
3809             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3810                 ics_clock_paused = FALSE;
3811                 StartClocks();
3812                 continue;
3813             }
3814
3815             /* Grab player ratings from the Creating: message.
3816                Note we have to check for the special case when
3817                the ICS inserts things like [white] or [black]. */
3818             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3819                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3820                 /* star_matches:
3821                    0    player 1 name (not necessarily white)
3822                    1    player 1 rating
3823                    2    empty, white, or black (IGNORED)
3824                    3    player 2 name (not necessarily black)
3825                    4    player 2 rating
3826
3827                    The names/ratings are sorted out when the game
3828                    actually starts (below).
3829                 */
3830                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3831                 player1Rating = string_to_rating(star_match[1]);
3832                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3833                 player2Rating = string_to_rating(star_match[4]);
3834
3835                 if (appData.debugMode)
3836                   fprintf(debugFP,
3837                           "Ratings from 'Creating:' %s %d, %s %d\n",
3838                           player1Name, player1Rating,
3839                           player2Name, player2Rating);
3840
3841                 continue;
3842             }
3843
3844             /* Improved generic start/end-of-game messages */
3845             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3846                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3847                 /* If tkind == 0: */
3848                 /* star_match[0] is the game number */
3849                 /*           [1] is the white player's name */
3850                 /*           [2] is the black player's name */
3851                 /* For end-of-game: */
3852                 /*           [3] is the reason for the game end */
3853                 /*           [4] is a PGN end game-token, preceded by " " */
3854                 /* For start-of-game: */
3855                 /*           [3] begins with "Creating" or "Continuing" */
3856                 /*           [4] is " *" or empty (don't care). */
3857                 int gamenum = atoi(star_match[0]);
3858                 char *whitename, *blackname, *why, *endtoken;
3859                 ChessMove endtype = EndOfFile;
3860
3861                 if (tkind == 0) {
3862                   whitename = star_match[1];
3863                   blackname = star_match[2];
3864                   why = star_match[3];
3865                   endtoken = star_match[4];
3866                 } else {
3867                   whitename = star_match[1];
3868                   blackname = star_match[3];
3869                   why = star_match[5];
3870                   endtoken = star_match[6];
3871                 }
3872
3873                 /* Game start messages */
3874                 if (strncmp(why, "Creating ", 9) == 0 ||
3875                     strncmp(why, "Continuing ", 11) == 0) {
3876                     gs_gamenum = gamenum;
3877                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3878                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3879                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3880 #if ZIPPY
3881                     if (appData.zippyPlay) {
3882                         ZippyGameStart(whitename, blackname);
3883                     }
3884 #endif /*ZIPPY*/
3885                     partnerBoardValid = FALSE; // [HGM] bughouse
3886                     continue;
3887                 }
3888
3889                 /* Game end messages */
3890                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3891                     ics_gamenum != gamenum) {
3892                     continue;
3893                 }
3894                 while (endtoken[0] == ' ') endtoken++;
3895                 switch (endtoken[0]) {
3896                   case '*':
3897                   default:
3898                     endtype = GameUnfinished;
3899                     break;
3900                   case '0':
3901                     endtype = BlackWins;
3902                     break;
3903                   case '1':
3904                     if (endtoken[1] == '/')
3905                       endtype = GameIsDrawn;
3906                     else
3907                       endtype = WhiteWins;
3908                     break;
3909                 }
3910                 GameEnds(endtype, why, GE_ICS);
3911 #if ZIPPY
3912                 if (appData.zippyPlay && first.initDone) {
3913                     ZippyGameEnd(endtype, why);
3914                     if (first.pr == NoProc) {
3915                       /* Start the next process early so that we'll
3916                          be ready for the next challenge */
3917                       StartChessProgram(&first);
3918                     }
3919                     /* Send "new" early, in case this command takes
3920                        a long time to finish, so that we'll be ready
3921                        for the next challenge. */
3922                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3923                     Reset(TRUE, TRUE);
3924                 }
3925 #endif /*ZIPPY*/
3926                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3927                 continue;
3928             }
3929
3930             if (looking_at(buf, &i, "Removing game * from observation") ||
3931                 looking_at(buf, &i, "no longer observing game *") ||
3932                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3933                 if (gameMode == IcsObserving &&
3934                     atoi(star_match[0]) == ics_gamenum)
3935                   {
3936                       /* icsEngineAnalyze */
3937                       if (appData.icsEngineAnalyze) {
3938                             ExitAnalyzeMode();
3939                             ModeHighlight();
3940                       }
3941                       StopClocks();
3942                       gameMode = IcsIdle;
3943                       ics_gamenum = -1;
3944                       ics_user_moved = FALSE;
3945                   }
3946                 continue;
3947             }
3948
3949             if (looking_at(buf, &i, "no longer examining game *")) {
3950                 if (gameMode == IcsExamining &&
3951                     atoi(star_match[0]) == ics_gamenum)
3952                   {
3953                       gameMode = IcsIdle;
3954                       ics_gamenum = -1;
3955                       ics_user_moved = FALSE;
3956                   }
3957                 continue;
3958             }
3959
3960             /* Advance leftover_start past any newlines we find,
3961                so only partial lines can get reparsed */
3962             if (looking_at(buf, &i, "\n")) {
3963                 prevColor = curColor;
3964                 if (curColor != ColorNormal) {
3965                     if (oldi > next_out) {
3966                         SendToPlayer(&buf[next_out], oldi - next_out);
3967                         next_out = oldi;
3968                     }
3969                     Colorize(ColorNormal, FALSE);
3970                     curColor = ColorNormal;
3971                 }
3972                 if (started == STARTED_BOARD) {
3973                     started = STARTED_NONE;
3974                     parse[parse_pos] = NULLCHAR;
3975                     ParseBoard12(parse);
3976                     ics_user_moved = 0;
3977
3978                     /* Send premove here */
3979                     if (appData.premove) {
3980                       char str[MSG_SIZ];
3981                       if (currentMove == 0 &&
3982                           gameMode == IcsPlayingWhite &&
3983                           appData.premoveWhite) {
3984                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3985                         if (appData.debugMode)
3986                           fprintf(debugFP, "Sending premove:\n");
3987                         SendToICS(str);
3988                       } else if (currentMove == 1 &&
3989                                  gameMode == IcsPlayingBlack &&
3990                                  appData.premoveBlack) {
3991                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3992                         if (appData.debugMode)
3993                           fprintf(debugFP, "Sending premove:\n");
3994                         SendToICS(str);
3995                       } else if (gotPremove) {
3996                         gotPremove = 0;
3997                         ClearPremoveHighlights();
3998                         if (appData.debugMode)
3999                           fprintf(debugFP, "Sending premove:\n");
4000                           UserMoveEvent(premoveFromX, premoveFromY,
4001                                         premoveToX, premoveToY,
4002                                         premovePromoChar);
4003                       }
4004                     }
4005
4006                     /* Usually suppress following prompt */
4007                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4008                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4009                         if (looking_at(buf, &i, "*% ")) {
4010                             savingComment = FALSE;
4011                             suppressKibitz = 0;
4012                         }
4013                     }
4014                     next_out = i;
4015                 } else if (started == STARTED_HOLDINGS) {
4016                     int gamenum;
4017                     char new_piece[MSG_SIZ];
4018                     started = STARTED_NONE;
4019                     parse[parse_pos] = NULLCHAR;
4020                     if (appData.debugMode)
4021                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4022                                                         parse, currentMove);
4023                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4024                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4025                         if (gameInfo.variant == VariantNormal) {
4026                           /* [HGM] We seem to switch variant during a game!
4027                            * Presumably no holdings were displayed, so we have
4028                            * to move the position two files to the right to
4029                            * create room for them!
4030                            */
4031                           VariantClass newVariant;
4032                           switch(gameInfo.boardWidth) { // base guess on board width
4033                                 case 9:  newVariant = VariantShogi; break;
4034                                 case 10: newVariant = VariantGreat; break;
4035                                 default: newVariant = VariantCrazyhouse; break;
4036                           }
4037                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4038                           /* Get a move list just to see the header, which
4039                              will tell us whether this is really bug or zh */
4040                           if (ics_getting_history == H_FALSE) {
4041                             ics_getting_history = H_REQUESTED;
4042                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4043                             SendToICS(str);
4044                           }
4045                         }
4046                         new_piece[0] = NULLCHAR;
4047                         sscanf(parse, "game %d white [%s black [%s <- %s",
4048                                &gamenum, white_holding, black_holding,
4049                                new_piece);
4050                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4051                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4052                         /* [HGM] copy holdings to board holdings area */
4053                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4054                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4055                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4056 #if ZIPPY
4057                         if (appData.zippyPlay && first.initDone) {
4058                             ZippyHoldings(white_holding, black_holding,
4059                                           new_piece);
4060                         }
4061 #endif /*ZIPPY*/
4062                         if (tinyLayout || smallLayout) {
4063                             char wh[16], bh[16];
4064                             PackHolding(wh, white_holding);
4065                             PackHolding(bh, black_holding);
4066                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4067                                     gameInfo.white, gameInfo.black);
4068                         } else {
4069                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4070                                     gameInfo.white, white_holding, _("vs."),
4071                                     gameInfo.black, black_holding);
4072                         }
4073                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4074                         DrawPosition(FALSE, boards[currentMove]);
4075                         DisplayTitle(str);
4076                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4077                         sscanf(parse, "game %d white [%s black [%s <- %s",
4078                                &gamenum, white_holding, black_holding,
4079                                new_piece);
4080                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4081                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4082                         /* [HGM] copy holdings to partner-board holdings area */
4083                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4084                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4085                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4086                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4087                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4088                       }
4089                     }
4090                     /* Suppress following prompt */
4091                     if (looking_at(buf, &i, "*% ")) {
4092                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4093                         savingComment = FALSE;
4094                         suppressKibitz = 0;
4095                     }
4096                     next_out = i;
4097                 }
4098                 continue;
4099             }
4100
4101             i++;                /* skip unparsed character and loop back */
4102         }
4103
4104         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4105 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4106 //          SendToPlayer(&buf[next_out], i - next_out);
4107             started != STARTED_HOLDINGS && leftover_start > next_out) {
4108             SendToPlayer(&buf[next_out], leftover_start - next_out);
4109             next_out = i;
4110         }
4111
4112         leftover_len = buf_len - leftover_start;
4113         /* if buffer ends with something we couldn't parse,
4114            reparse it after appending the next read */
4115
4116     } else if (count == 0) {
4117         RemoveInputSource(isr);
4118         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4119     } else {
4120         DisplayFatalError(_("Error reading from ICS"), error, 1);
4121     }
4122 }
4123
4124
4125 /* Board style 12 looks like this:
4126
4127    <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
4128
4129  * The "<12> " is stripped before it gets to this routine.  The two
4130  * trailing 0's (flip state and clock ticking) are later addition, and
4131  * some chess servers may not have them, or may have only the first.
4132  * Additional trailing fields may be added in the future.
4133  */
4134
4135 #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"
4136
4137 #define RELATION_OBSERVING_PLAYED    0
4138 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4139 #define RELATION_PLAYING_MYMOVE      1
4140 #define RELATION_PLAYING_NOTMYMOVE  -1
4141 #define RELATION_EXAMINING           2
4142 #define RELATION_ISOLATED_BOARD     -3
4143 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4144
4145 void
4146 ParseBoard12 (char *string)
4147 {
4148     GameMode newGameMode;
4149     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4150     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4151     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4152     char to_play, board_chars[200];
4153     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4154     char black[32], white[32];
4155     Board board;
4156     int prevMove = currentMove;
4157     int ticking = 2;
4158     ChessMove moveType;
4159     int fromX, fromY, toX, toY;
4160     char promoChar;
4161     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4162     char *bookHit = NULL; // [HGM] book
4163     Boolean weird = FALSE, reqFlag = FALSE;
4164
4165     fromX = fromY = toX = toY = -1;
4166
4167     newGame = FALSE;
4168
4169     if (appData.debugMode)
4170       fprintf(debugFP, _("Parsing board: %s\n"), string);
4171
4172     move_str[0] = NULLCHAR;
4173     elapsed_time[0] = NULLCHAR;
4174     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4175         int  i = 0, j;
4176         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4177             if(string[i] == ' ') { ranks++; files = 0; }
4178             else files++;
4179             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4180             i++;
4181         }
4182         for(j = 0; j <i; j++) board_chars[j] = string[j];
4183         board_chars[i] = '\0';
4184         string += i + 1;
4185     }
4186     n = sscanf(string, PATTERN, &to_play, &double_push,
4187                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4188                &gamenum, white, black, &relation, &basetime, &increment,
4189                &white_stren, &black_stren, &white_time, &black_time,
4190                &moveNum, str, elapsed_time, move_str, &ics_flip,
4191                &ticking);
4192
4193     if (n < 21) {
4194         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4195         DisplayError(str, 0);
4196         return;
4197     }
4198
4199     /* Convert the move number to internal form */
4200     moveNum = (moveNum - 1) * 2;
4201     if (to_play == 'B') moveNum++;
4202     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4203       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4204                         0, 1);
4205       return;
4206     }
4207
4208     switch (relation) {
4209       case RELATION_OBSERVING_PLAYED:
4210       case RELATION_OBSERVING_STATIC:
4211         if (gamenum == -1) {
4212             /* Old ICC buglet */
4213             relation = RELATION_OBSERVING_STATIC;
4214         }
4215         newGameMode = IcsObserving;
4216         break;
4217       case RELATION_PLAYING_MYMOVE:
4218       case RELATION_PLAYING_NOTMYMOVE:
4219         newGameMode =
4220           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4221             IcsPlayingWhite : IcsPlayingBlack;
4222         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4223         break;
4224       case RELATION_EXAMINING:
4225         newGameMode = IcsExamining;
4226         break;
4227       case RELATION_ISOLATED_BOARD:
4228       default:
4229         /* Just display this board.  If user was doing something else,
4230            we will forget about it until the next board comes. */
4231         newGameMode = IcsIdle;
4232         break;
4233       case RELATION_STARTING_POSITION:
4234         newGameMode = gameMode;
4235         break;
4236     }
4237
4238     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4239          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4240       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4241       char *toSqr;
4242       for (k = 0; k < ranks; k++) {
4243         for (j = 0; j < files; j++)
4244           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4245         if(gameInfo.holdingsWidth > 1) {
4246              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4247              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4248         }
4249       }
4250       CopyBoard(partnerBoard, board);
4251       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4252         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4253         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4254       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4255       if(toSqr = strchr(str, '-')) {
4256         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4257         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4258       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4259       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4260       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4261       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4262       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4263       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4264                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4265       DisplayMessage(partnerStatus, "");
4266         partnerBoardValid = TRUE;
4267       return;
4268     }
4269
4270     /* Modify behavior for initial board display on move listing
4271        of wild games.
4272        */
4273     switch (ics_getting_history) {
4274       case H_FALSE:
4275       case H_REQUESTED:
4276         break;
4277       case H_GOT_REQ_HEADER:
4278       case H_GOT_UNREQ_HEADER:
4279         /* This is the initial position of the current game */
4280         gamenum = ics_gamenum;
4281         moveNum = 0;            /* old ICS bug workaround */
4282         if (to_play == 'B') {
4283           startedFromSetupPosition = TRUE;
4284           blackPlaysFirst = TRUE;
4285           moveNum = 1;
4286           if (forwardMostMove == 0) forwardMostMove = 1;
4287           if (backwardMostMove == 0) backwardMostMove = 1;
4288           if (currentMove == 0) currentMove = 1;
4289         }
4290         newGameMode = gameMode;
4291         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4292         break;
4293       case H_GOT_UNWANTED_HEADER:
4294         /* This is an initial board that we don't want */
4295         return;
4296       case H_GETTING_MOVES:
4297         /* Should not happen */
4298         DisplayError(_("Error gathering move list: extra board"), 0);
4299         ics_getting_history = H_FALSE;
4300         return;
4301     }
4302
4303    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4304                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4305      /* [HGM] We seem to have switched variant unexpectedly
4306       * Try to guess new variant from board size
4307       */
4308           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4309           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4310           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4311           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4312           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4313           if(!weird) newVariant = VariantNormal;
4314           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4315           /* Get a move list just to see the header, which
4316              will tell us whether this is really bug or zh */
4317           if (ics_getting_history == H_FALSE) {
4318             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4319             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4320             SendToICS(str);
4321           }
4322     }
4323
4324     /* Take action if this is the first board of a new game, or of a
4325        different game than is currently being displayed.  */
4326     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4327         relation == RELATION_ISOLATED_BOARD) {
4328
4329         /* Forget the old game and get the history (if any) of the new one */
4330         if (gameMode != BeginningOfGame) {
4331           Reset(TRUE, TRUE);
4332         }
4333         newGame = TRUE;
4334         if (appData.autoRaiseBoard) BoardToTop();
4335         prevMove = -3;
4336         if (gamenum == -1) {
4337             newGameMode = IcsIdle;
4338         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4339                    appData.getMoveList && !reqFlag) {
4340             /* Need to get game history */
4341             ics_getting_history = H_REQUESTED;
4342             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4343             SendToICS(str);
4344         }
4345
4346         /* Initially flip the board to have black on the bottom if playing
4347            black or if the ICS flip flag is set, but let the user change
4348            it with the Flip View button. */
4349         flipView = appData.autoFlipView ?
4350           (newGameMode == IcsPlayingBlack) || ics_flip :
4351           appData.flipView;
4352
4353         /* Done with values from previous mode; copy in new ones */
4354         gameMode = newGameMode;
4355         ModeHighlight();
4356         ics_gamenum = gamenum;
4357         if (gamenum == gs_gamenum) {
4358             int klen = strlen(gs_kind);
4359             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4360             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4361             gameInfo.event = StrSave(str);
4362         } else {
4363             gameInfo.event = StrSave("ICS game");
4364         }
4365         gameInfo.site = StrSave(appData.icsHost);
4366         gameInfo.date = PGNDate();
4367         gameInfo.round = StrSave("-");
4368         gameInfo.white = StrSave(white);
4369         gameInfo.black = StrSave(black);
4370         timeControl = basetime * 60 * 1000;
4371         timeControl_2 = 0;
4372         timeIncrement = increment * 1000;
4373         movesPerSession = 0;
4374         gameInfo.timeControl = TimeControlTagValue();
4375         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4376   if (appData.debugMode) {
4377     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4378     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4379     setbuf(debugFP, NULL);
4380   }
4381
4382         gameInfo.outOfBook = NULL;
4383
4384         /* Do we have the ratings? */
4385         if (strcmp(player1Name, white) == 0 &&
4386             strcmp(player2Name, black) == 0) {
4387             if (appData.debugMode)
4388               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4389                       player1Rating, player2Rating);
4390             gameInfo.whiteRating = player1Rating;
4391             gameInfo.blackRating = player2Rating;
4392         } else if (strcmp(player2Name, white) == 0 &&
4393                    strcmp(player1Name, black) == 0) {
4394             if (appData.debugMode)
4395               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4396                       player2Rating, player1Rating);
4397             gameInfo.whiteRating = player2Rating;
4398             gameInfo.blackRating = player1Rating;
4399         }
4400         player1Name[0] = player2Name[0] = NULLCHAR;
4401
4402         /* Silence shouts if requested */
4403         if (appData.quietPlay &&
4404             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4405             SendToICS(ics_prefix);
4406             SendToICS("set shout 0\n");
4407         }
4408     }
4409
4410     /* Deal with midgame name changes */
4411     if (!newGame) {
4412         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4413             if (gameInfo.white) free(gameInfo.white);
4414             gameInfo.white = StrSave(white);
4415         }
4416         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4417             if (gameInfo.black) free(gameInfo.black);
4418             gameInfo.black = StrSave(black);
4419         }
4420     }
4421
4422     /* Throw away game result if anything actually changes in examine mode */
4423     if (gameMode == IcsExamining && !newGame) {
4424         gameInfo.result = GameUnfinished;
4425         if (gameInfo.resultDetails != NULL) {
4426             free(gameInfo.resultDetails);
4427             gameInfo.resultDetails = NULL;
4428         }
4429     }
4430
4431     /* In pausing && IcsExamining mode, we ignore boards coming
4432        in if they are in a different variation than we are. */
4433     if (pauseExamInvalid) return;
4434     if (pausing && gameMode == IcsExamining) {
4435         if (moveNum <= pauseExamForwardMostMove) {
4436             pauseExamInvalid = TRUE;
4437             forwardMostMove = pauseExamForwardMostMove;
4438             return;
4439         }
4440     }
4441
4442   if (appData.debugMode) {
4443     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4444   }
4445     /* Parse the board */
4446     for (k = 0; k < ranks; k++) {
4447       for (j = 0; j < files; j++)
4448         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4449       if(gameInfo.holdingsWidth > 1) {
4450            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4451            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4452       }
4453     }
4454     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4455       board[5][BOARD_RGHT+1] = WhiteAngel;
4456       board[6][BOARD_RGHT+1] = WhiteMarshall;
4457       board[1][0] = BlackMarshall;
4458       board[2][0] = BlackAngel;
4459       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4460     }
4461     CopyBoard(boards[moveNum], board);
4462     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4463     if (moveNum == 0) {
4464         startedFromSetupPosition =
4465           !CompareBoards(board, initialPosition);
4466         if(startedFromSetupPosition)
4467             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4468     }
4469
4470     /* [HGM] Set castling rights. Take the outermost Rooks,
4471        to make it also work for FRC opening positions. Note that board12
4472        is really defective for later FRC positions, as it has no way to
4473        indicate which Rook can castle if they are on the same side of King.
4474        For the initial position we grant rights to the outermost Rooks,
4475        and remember thos rights, and we then copy them on positions
4476        later in an FRC game. This means WB might not recognize castlings with
4477        Rooks that have moved back to their original position as illegal,
4478        but in ICS mode that is not its job anyway.
4479     */
4480     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4481     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4482
4483         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4484             if(board[0][i] == WhiteRook) j = i;
4485         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4486         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4487             if(board[0][i] == WhiteRook) j = i;
4488         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4489         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4490             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4491         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4492         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4493             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4494         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4495
4496         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4497         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4498         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4499             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4500         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4501             if(board[BOARD_HEIGHT-1][k] == bKing)
4502                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4503         if(gameInfo.variant == VariantTwoKings) {
4504             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4505             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4506             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4507         }
4508     } else { int r;
4509         r = boards[moveNum][CASTLING][0] = initialRights[0];
4510         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4511         r = boards[moveNum][CASTLING][1] = initialRights[1];
4512         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4513         r = boards[moveNum][CASTLING][3] = initialRights[3];
4514         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4515         r = boards[moveNum][CASTLING][4] = initialRights[4];
4516         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4517         /* wildcastle kludge: always assume King has rights */
4518         r = boards[moveNum][CASTLING][2] = initialRights[2];
4519         r = boards[moveNum][CASTLING][5] = initialRights[5];
4520     }
4521     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4522     boards[moveNum][EP_STATUS] = EP_NONE;
4523     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4524     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4525     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4526
4527
4528     if (ics_getting_history == H_GOT_REQ_HEADER ||
4529         ics_getting_history == H_GOT_UNREQ_HEADER) {
4530         /* This was an initial position from a move list, not
4531            the current position */
4532         return;
4533     }
4534
4535     /* Update currentMove and known move number limits */
4536     newMove = newGame || moveNum > forwardMostMove;
4537
4538     if (newGame) {
4539         forwardMostMove = backwardMostMove = currentMove = moveNum;
4540         if (gameMode == IcsExamining && moveNum == 0) {
4541           /* Workaround for ICS limitation: we are not told the wild
4542              type when starting to examine a game.  But if we ask for
4543              the move list, the move list header will tell us */
4544             ics_getting_history = H_REQUESTED;
4545             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4546             SendToICS(str);
4547         }
4548     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4549                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4550 #if ZIPPY
4551         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4552         /* [HGM] applied this also to an engine that is silently watching        */
4553         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4554             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4555             gameInfo.variant == currentlyInitializedVariant) {
4556           takeback = forwardMostMove - moveNum;
4557           for (i = 0; i < takeback; i++) {
4558             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4559             SendToProgram("undo\n", &first);
4560           }
4561         }
4562 #endif
4563
4564         forwardMostMove = moveNum;
4565         if (!pausing || currentMove > forwardMostMove)
4566           currentMove = forwardMostMove;
4567     } else {
4568         /* New part of history that is not contiguous with old part */
4569         if (pausing && gameMode == IcsExamining) {
4570             pauseExamInvalid = TRUE;
4571             forwardMostMove = pauseExamForwardMostMove;
4572             return;
4573         }
4574         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4575 #if ZIPPY
4576             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4577                 // [HGM] when we will receive the move list we now request, it will be
4578                 // fed to the engine from the first move on. So if the engine is not
4579                 // in the initial position now, bring it there.
4580                 InitChessProgram(&first, 0);
4581             }
4582 #endif
4583             ics_getting_history = H_REQUESTED;
4584             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4585             SendToICS(str);
4586         }
4587         forwardMostMove = backwardMostMove = currentMove = moveNum;
4588     }
4589
4590     /* Update the clocks */
4591     if (strchr(elapsed_time, '.')) {
4592       /* Time is in ms */
4593       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4594       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4595     } else {
4596       /* Time is in seconds */
4597       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4598       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4599     }
4600
4601
4602 #if ZIPPY
4603     if (appData.zippyPlay && newGame &&
4604         gameMode != IcsObserving && gameMode != IcsIdle &&
4605         gameMode != IcsExamining)
4606       ZippyFirstBoard(moveNum, basetime, increment);
4607 #endif
4608
4609     /* Put the move on the move list, first converting
4610        to canonical algebraic form. */
4611     if (moveNum > 0) {
4612   if (appData.debugMode) {
4613     if (appData.debugMode) { int f = forwardMostMove;
4614         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4615                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4616                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4617     }
4618     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4619     fprintf(debugFP, "moveNum = %d\n", moveNum);
4620     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4621     setbuf(debugFP, NULL);
4622   }
4623         if (moveNum <= backwardMostMove) {
4624             /* We don't know what the board looked like before
4625                this move.  Punt. */
4626           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4627             strcat(parseList[moveNum - 1], " ");
4628             strcat(parseList[moveNum - 1], elapsed_time);
4629             moveList[moveNum - 1][0] = NULLCHAR;
4630         } else if (strcmp(move_str, "none") == 0) {
4631             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4632             /* Again, we don't know what the board looked like;
4633                this is really the start of the game. */
4634             parseList[moveNum - 1][0] = NULLCHAR;
4635             moveList[moveNum - 1][0] = NULLCHAR;
4636             backwardMostMove = moveNum;
4637             startedFromSetupPosition = TRUE;
4638             fromX = fromY = toX = toY = -1;
4639         } else {
4640           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4641           //                 So we parse the long-algebraic move string in stead of the SAN move
4642           int valid; char buf[MSG_SIZ], *prom;
4643
4644           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4645                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4646           // str looks something like "Q/a1-a2"; kill the slash
4647           if(str[1] == '/')
4648             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4649           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4650           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4651                 strcat(buf, prom); // long move lacks promo specification!
4652           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4653                 if(appData.debugMode)
4654                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4655                 safeStrCpy(move_str, buf, MSG_SIZ);
4656           }
4657           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4658                                 &fromX, &fromY, &toX, &toY, &promoChar)
4659                || ParseOneMove(buf, moveNum - 1, &moveType,
4660                                 &fromX, &fromY, &toX, &toY, &promoChar);
4661           // end of long SAN patch
4662           if (valid) {
4663             (void) CoordsToAlgebraic(boards[moveNum - 1],
4664                                      PosFlags(moveNum - 1),
4665                                      fromY, fromX, toY, toX, promoChar,
4666                                      parseList[moveNum-1]);
4667             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4668               case MT_NONE:
4669               case MT_STALEMATE:
4670               default:
4671                 break;
4672               case MT_CHECK:
4673                 if(gameInfo.variant != VariantShogi)
4674                     strcat(parseList[moveNum - 1], "+");
4675                 break;
4676               case MT_CHECKMATE:
4677               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4678                 strcat(parseList[moveNum - 1], "#");
4679                 break;
4680             }
4681             strcat(parseList[moveNum - 1], " ");
4682             strcat(parseList[moveNum - 1], elapsed_time);
4683             /* currentMoveString is set as a side-effect of ParseOneMove */
4684             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4685             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4686             strcat(moveList[moveNum - 1], "\n");
4687
4688             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4689                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4690               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4691                 ChessSquare old, new = boards[moveNum][k][j];
4692                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4693                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4694                   if(old == new) continue;
4695                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4696                   else if(new == WhiteWazir || new == BlackWazir) {
4697                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4698                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4699                       else boards[moveNum][k][j] = old; // preserve type of Gold
4700                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4701                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4702               }
4703           } else {
4704             /* Move from ICS was illegal!?  Punt. */
4705             if (appData.debugMode) {
4706               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4707               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4708             }
4709             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4710             strcat(parseList[moveNum - 1], " ");
4711             strcat(parseList[moveNum - 1], elapsed_time);
4712             moveList[moveNum - 1][0] = NULLCHAR;
4713             fromX = fromY = toX = toY = -1;
4714           }
4715         }
4716   if (appData.debugMode) {
4717     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4718     setbuf(debugFP, NULL);
4719   }
4720
4721 #if ZIPPY
4722         /* Send move to chess program (BEFORE animating it). */
4723         if (appData.zippyPlay && !newGame && newMove &&
4724            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4725
4726             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4727                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4728                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4729                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4730                             move_str);
4731                     DisplayError(str, 0);
4732                 } else {
4733                     if (first.sendTime) {
4734                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4735                     }
4736                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4737                     if (firstMove && !bookHit) {
4738                         firstMove = FALSE;
4739                         if (first.useColors) {
4740                           SendToProgram(gameMode == IcsPlayingWhite ?
4741                                         "white\ngo\n" :
4742                                         "black\ngo\n", &first);
4743                         } else {
4744                           SendToProgram("go\n", &first);
4745                         }
4746                         first.maybeThinking = TRUE;
4747                     }
4748                 }
4749             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4750               if (moveList[moveNum - 1][0] == NULLCHAR) {
4751                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4752                 DisplayError(str, 0);
4753               } else {
4754                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4755                 SendMoveToProgram(moveNum - 1, &first);
4756               }
4757             }
4758         }
4759 #endif
4760     }
4761
4762     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4763         /* If move comes from a remote source, animate it.  If it
4764            isn't remote, it will have already been animated. */
4765         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4766             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4767         }
4768         if (!pausing && appData.highlightLastMove) {
4769             SetHighlights(fromX, fromY, toX, toY);
4770         }
4771     }
4772
4773     /* Start the clocks */
4774     whiteFlag = blackFlag = FALSE;
4775     appData.clockMode = !(basetime == 0 && increment == 0);
4776     if (ticking == 0) {
4777       ics_clock_paused = TRUE;
4778       StopClocks();
4779     } else if (ticking == 1) {
4780       ics_clock_paused = FALSE;
4781     }
4782     if (gameMode == IcsIdle ||
4783         relation == RELATION_OBSERVING_STATIC ||
4784         relation == RELATION_EXAMINING ||
4785         ics_clock_paused)
4786       DisplayBothClocks();
4787     else
4788       StartClocks();
4789
4790     /* Display opponents and material strengths */
4791     if (gameInfo.variant != VariantBughouse &&
4792         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4793         if (tinyLayout || smallLayout) {
4794             if(gameInfo.variant == VariantNormal)
4795               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4796                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4797                     basetime, increment);
4798             else
4799               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4800                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4801                     basetime, increment, (int) gameInfo.variant);
4802         } else {
4803             if(gameInfo.variant == VariantNormal)
4804               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4805                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4806                     basetime, increment);
4807             else
4808               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4809                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4810                     basetime, increment, VariantName(gameInfo.variant));
4811         }
4812         DisplayTitle(str);
4813   if (appData.debugMode) {
4814     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4815   }
4816     }
4817
4818
4819     /* Display the board */
4820     if (!pausing && !appData.noGUI) {
4821
4822       if (appData.premove)
4823           if (!gotPremove ||
4824              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4825              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4826               ClearPremoveHighlights();
4827
4828       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4829         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4830       DrawPosition(j, boards[currentMove]);
4831
4832       DisplayMove(moveNum - 1);
4833       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4834             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4835               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4836         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4837       }
4838     }
4839
4840     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4841 #if ZIPPY
4842     if(bookHit) { // [HGM] book: simulate book reply
4843         static char bookMove[MSG_SIZ]; // a bit generous?
4844
4845         programStats.nodes = programStats.depth = programStats.time =
4846         programStats.score = programStats.got_only_move = 0;
4847         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4848
4849         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4850         strcat(bookMove, bookHit);
4851         HandleMachineMove(bookMove, &first);
4852     }
4853 #endif
4854 }
4855
4856 void
4857 GetMoveListEvent ()
4858 {
4859     char buf[MSG_SIZ];
4860     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4861         ics_getting_history = H_REQUESTED;
4862         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4863         SendToICS(buf);
4864     }
4865 }
4866
4867 void
4868 AnalysisPeriodicEvent (int force)
4869 {
4870     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4871          && !force) || !appData.periodicUpdates)
4872       return;
4873
4874     /* Send . command to Crafty to collect stats */
4875     SendToProgram(".\n", &first);
4876
4877     /* Don't send another until we get a response (this makes
4878        us stop sending to old Crafty's which don't understand
4879        the "." command (sending illegal cmds resets node count & time,
4880        which looks bad)) */
4881     programStats.ok_to_send = 0;
4882 }
4883
4884 void
4885 ics_update_width (int new_width)
4886 {
4887         ics_printf("set width %d\n", new_width);
4888 }
4889
4890 void
4891 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4892 {
4893     char buf[MSG_SIZ];
4894
4895     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4896         // null move in variant where engine does not understand it (for analysis purposes)
4897         SendBoard(cps, moveNum + 1); // send position after move in stead.
4898         return;
4899     }
4900     if (cps->useUsermove) {
4901       SendToProgram("usermove ", cps);
4902     }
4903     if (cps->useSAN) {
4904       char *space;
4905       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4906         int len = space - parseList[moveNum];
4907         memcpy(buf, parseList[moveNum], len);
4908         buf[len++] = '\n';
4909         buf[len] = NULLCHAR;
4910       } else {
4911         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4912       }
4913       SendToProgram(buf, cps);
4914     } else {
4915       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4916         AlphaRank(moveList[moveNum], 4);
4917         SendToProgram(moveList[moveNum], cps);
4918         AlphaRank(moveList[moveNum], 4); // and back
4919       } else
4920       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4921        * the engine. It would be nice to have a better way to identify castle
4922        * moves here. */
4923       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4924                                                                          && cps->useOOCastle) {
4925         int fromX = moveList[moveNum][0] - AAA;
4926         int fromY = moveList[moveNum][1] - ONE;
4927         int toX = moveList[moveNum][2] - AAA;
4928         int toY = moveList[moveNum][3] - ONE;
4929         if((boards[moveNum][fromY][fromX] == WhiteKing
4930             && boards[moveNum][toY][toX] == WhiteRook)
4931            || (boards[moveNum][fromY][fromX] == BlackKing
4932                && boards[moveNum][toY][toX] == BlackRook)) {
4933           if(toX > fromX) SendToProgram("O-O\n", cps);
4934           else SendToProgram("O-O-O\n", cps);
4935         }
4936         else SendToProgram(moveList[moveNum], cps);
4937       } else
4938       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4939         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4940           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4941           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4942                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4943         } else
4944           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4945                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4946         SendToProgram(buf, cps);
4947       }
4948       else SendToProgram(moveList[moveNum], cps);
4949       /* End of additions by Tord */
4950     }
4951
4952     /* [HGM] setting up the opening has brought engine in force mode! */
4953     /*       Send 'go' if we are in a mode where machine should play. */
4954     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4955         (gameMode == TwoMachinesPlay   ||
4956 #if ZIPPY
4957          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4958 #endif
4959          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4960         SendToProgram("go\n", cps);
4961   if (appData.debugMode) {
4962     fprintf(debugFP, "(extra)\n");
4963   }
4964     }
4965     setboardSpoiledMachineBlack = 0;
4966 }
4967
4968 void
4969 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4970 {
4971     char user_move[MSG_SIZ];
4972     char suffix[4];
4973
4974     if(gameInfo.variant == VariantSChess && promoChar) {
4975         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4976         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4977     } else suffix[0] = NULLCHAR;
4978
4979     switch (moveType) {
4980       default:
4981         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4982                 (int)moveType, fromX, fromY, toX, toY);
4983         DisplayError(user_move + strlen("say "), 0);
4984         break;
4985       case WhiteKingSideCastle:
4986       case BlackKingSideCastle:
4987       case WhiteQueenSideCastleWild:
4988       case BlackQueenSideCastleWild:
4989       /* PUSH Fabien */
4990       case WhiteHSideCastleFR:
4991       case BlackHSideCastleFR:
4992       /* POP Fabien */
4993         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4994         break;
4995       case WhiteQueenSideCastle:
4996       case BlackQueenSideCastle:
4997       case WhiteKingSideCastleWild:
4998       case BlackKingSideCastleWild:
4999       /* PUSH Fabien */
5000       case WhiteASideCastleFR:
5001       case BlackASideCastleFR:
5002       /* POP Fabien */
5003         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5004         break;
5005       case WhiteNonPromotion:
5006       case BlackNonPromotion:
5007         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5008         break;
5009       case WhitePromotion:
5010       case BlackPromotion:
5011         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5012           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5013                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5014                 PieceToChar(WhiteFerz));
5015         else if(gameInfo.variant == VariantGreat)
5016           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5017                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5018                 PieceToChar(WhiteMan));
5019         else
5020           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5021                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5022                 promoChar);
5023         break;
5024       case WhiteDrop:
5025       case BlackDrop:
5026       drop:
5027         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5028                  ToUpper(PieceToChar((ChessSquare) fromX)),
5029                  AAA + toX, ONE + toY);
5030         break;
5031       case IllegalMove:  /* could be a variant we don't quite understand */
5032         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5033       case NormalMove:
5034       case WhiteCapturesEnPassant:
5035       case BlackCapturesEnPassant:
5036         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5037                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5038         break;
5039     }
5040     SendToICS(user_move);
5041     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5042         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5043 }
5044
5045 void
5046 UploadGameEvent ()
5047 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5048     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5049     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5050     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5051       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5052       return;
5053     }
5054     if(gameMode != IcsExamining) { // is this ever not the case?
5055         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5056
5057         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5058           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5059         } else { // on FICS we must first go to general examine mode
5060           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5061         }
5062         if(gameInfo.variant != VariantNormal) {
5063             // try figure out wild number, as xboard names are not always valid on ICS
5064             for(i=1; i<=36; i++) {
5065               snprintf(buf, MSG_SIZ, "wild/%d", i);
5066                 if(StringToVariant(buf) == gameInfo.variant) break;
5067             }
5068             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5069             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5070             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5071         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5072         SendToICS(ics_prefix);
5073         SendToICS(buf);
5074         if(startedFromSetupPosition || backwardMostMove != 0) {
5075           fen = PositionToFEN(backwardMostMove, NULL);
5076           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5077             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5078             SendToICS(buf);
5079           } else { // FICS: everything has to set by separate bsetup commands
5080             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5081             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5082             SendToICS(buf);
5083             if(!WhiteOnMove(backwardMostMove)) {
5084                 SendToICS("bsetup tomove black\n");
5085             }
5086             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5087             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5088             SendToICS(buf);
5089             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5090             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5091             SendToICS(buf);
5092             i = boards[backwardMostMove][EP_STATUS];
5093             if(i >= 0) { // set e.p.
5094               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5095                 SendToICS(buf);
5096             }
5097             bsetup++;
5098           }
5099         }
5100       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5101             SendToICS("bsetup done\n"); // switch to normal examining.
5102     }
5103     for(i = backwardMostMove; i<last; i++) {
5104         char buf[20];
5105         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5106         SendToICS(buf);
5107     }
5108     SendToICS(ics_prefix);
5109     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5110 }
5111
5112 void
5113 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5114 {
5115     if (rf == DROP_RANK) {
5116       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5117       sprintf(move, "%c@%c%c\n",
5118                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5119     } else {
5120         if (promoChar == 'x' || promoChar == NULLCHAR) {
5121           sprintf(move, "%c%c%c%c\n",
5122                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5123         } else {
5124             sprintf(move, "%c%c%c%c%c\n",
5125                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5126         }
5127     }
5128 }
5129
5130 void
5131 ProcessICSInitScript (FILE *f)
5132 {
5133     char buf[MSG_SIZ];
5134
5135     while (fgets(buf, MSG_SIZ, f)) {
5136         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5137     }
5138
5139     fclose(f);
5140 }
5141
5142
5143 static int lastX, lastY, selectFlag, dragging;
5144
5145 void
5146 Sweep (int step)
5147 {
5148     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5149     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5150     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5151     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5152     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5153     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5154     do {
5155         promoSweep -= step;
5156         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5157         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5158         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5159         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5160         if(!step) step = -1;
5161     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5162             appData.testLegality && (promoSweep == king ||
5163             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5164     ChangeDragPiece(promoSweep);
5165 }
5166
5167 int
5168 PromoScroll (int x, int y)
5169 {
5170   int step = 0;
5171
5172   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5173   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5174   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5175   if(!step) return FALSE;
5176   lastX = x; lastY = y;
5177   if((promoSweep < BlackPawn) == flipView) step = -step;
5178   if(step > 0) selectFlag = 1;
5179   if(!selectFlag) Sweep(step);
5180   return FALSE;
5181 }
5182
5183 void
5184 NextPiece (int step)
5185 {
5186     ChessSquare piece = boards[currentMove][toY][toX];
5187     do {
5188         pieceSweep -= step;
5189         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5190         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5191         if(!step) step = -1;
5192     } while(PieceToChar(pieceSweep) == '.');
5193     boards[currentMove][toY][toX] = pieceSweep;
5194     DrawPosition(FALSE, boards[currentMove]);
5195     boards[currentMove][toY][toX] = piece;
5196 }
5197 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5198 void
5199 AlphaRank (char *move, int n)
5200 {
5201 //    char *p = move, c; int x, y;
5202
5203     if (appData.debugMode) {
5204         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5205     }
5206
5207     if(move[1]=='*' &&
5208        move[2]>='0' && move[2]<='9' &&
5209        move[3]>='a' && move[3]<='x'    ) {
5210         move[1] = '@';
5211         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5212         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5213     } else
5214     if(move[0]>='0' && move[0]<='9' &&
5215        move[1]>='a' && move[1]<='x' &&
5216        move[2]>='0' && move[2]<='9' &&
5217        move[3]>='a' && move[3]<='x'    ) {
5218         /* input move, Shogi -> normal */
5219         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5220         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5221         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5222         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5223     } else
5224     if(move[1]=='@' &&
5225        move[3]>='0' && move[3]<='9' &&
5226        move[2]>='a' && move[2]<='x'    ) {
5227         move[1] = '*';
5228         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5229         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5230     } else
5231     if(
5232        move[0]>='a' && move[0]<='x' &&
5233        move[3]>='0' && move[3]<='9' &&
5234        move[2]>='a' && move[2]<='x'    ) {
5235          /* output move, normal -> Shogi */
5236         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5237         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5238         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5239         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5240         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5241     }
5242     if (appData.debugMode) {
5243         fprintf(debugFP, "   out = '%s'\n", move);
5244     }
5245 }
5246
5247 char yy_textstr[8000];
5248
5249 /* Parser for moves from gnuchess, ICS, or user typein box */
5250 Boolean
5251 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5252 {
5253     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5254
5255     switch (*moveType) {
5256       case WhitePromotion:
5257       case BlackPromotion:
5258       case WhiteNonPromotion:
5259       case BlackNonPromotion:
5260       case NormalMove:
5261       case WhiteCapturesEnPassant:
5262       case BlackCapturesEnPassant:
5263       case WhiteKingSideCastle:
5264       case WhiteQueenSideCastle:
5265       case BlackKingSideCastle:
5266       case BlackQueenSideCastle:
5267       case WhiteKingSideCastleWild:
5268       case WhiteQueenSideCastleWild:
5269       case BlackKingSideCastleWild:
5270       case BlackQueenSideCastleWild:
5271       /* Code added by Tord: */
5272       case WhiteHSideCastleFR:
5273       case WhiteASideCastleFR:
5274       case BlackHSideCastleFR:
5275       case BlackASideCastleFR:
5276       /* End of code added by Tord */
5277       case IllegalMove:         /* bug or odd chess variant */
5278         *fromX = currentMoveString[0] - AAA;
5279         *fromY = currentMoveString[1] - ONE;
5280         *toX = currentMoveString[2] - AAA;
5281         *toY = currentMoveString[3] - ONE;
5282         *promoChar = currentMoveString[4];
5283         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5284             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5285     if (appData.debugMode) {
5286         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5287     }
5288             *fromX = *fromY = *toX = *toY = 0;
5289             return FALSE;
5290         }
5291         if (appData.testLegality) {
5292           return (*moveType != IllegalMove);
5293         } else {
5294           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5295                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5296         }
5297
5298       case WhiteDrop:
5299       case BlackDrop:
5300         *fromX = *moveType == WhiteDrop ?
5301           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5302           (int) CharToPiece(ToLower(currentMoveString[0]));
5303         *fromY = DROP_RANK;
5304         *toX = currentMoveString[2] - AAA;
5305         *toY = currentMoveString[3] - ONE;
5306         *promoChar = NULLCHAR;
5307         return TRUE;
5308
5309       case AmbiguousMove:
5310       case ImpossibleMove:
5311       case EndOfFile:
5312       case ElapsedTime:
5313       case Comment:
5314       case PGNTag:
5315       case NAG:
5316       case WhiteWins:
5317       case BlackWins:
5318       case GameIsDrawn:
5319       default:
5320     if (appData.debugMode) {
5321         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5322     }
5323         /* bug? */
5324         *fromX = *fromY = *toX = *toY = 0;
5325         *promoChar = NULLCHAR;
5326         return FALSE;
5327     }
5328 }
5329
5330 Boolean pushed = FALSE;
5331 char *lastParseAttempt;
5332
5333 void
5334 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5335 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5336   int fromX, fromY, toX, toY; char promoChar;
5337   ChessMove moveType;
5338   Boolean valid;
5339   int nr = 0;
5340
5341   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5342     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5343     pushed = TRUE;
5344   }
5345   endPV = forwardMostMove;
5346   do {
5347     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5348     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5349     lastParseAttempt = pv;
5350     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5351     if(!valid && nr == 0 &&
5352        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5353         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5354         // Hande case where played move is different from leading PV move
5355         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5356         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5357         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5358         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5359           endPV += 2; // if position different, keep this
5360           moveList[endPV-1][0] = fromX + AAA;
5361           moveList[endPV-1][1] = fromY + ONE;
5362           moveList[endPV-1][2] = toX + AAA;
5363           moveList[endPV-1][3] = toY + ONE;
5364           parseList[endPV-1][0] = NULLCHAR;
5365           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5366         }
5367       }
5368     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5369     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5370     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5371     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5372         valid++; // allow comments in PV
5373         continue;
5374     }
5375     nr++;
5376     if(endPV+1 > framePtr) break; // no space, truncate
5377     if(!valid) break;
5378     endPV++;
5379     CopyBoard(boards[endPV], boards[endPV-1]);
5380     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5381     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5382     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5383     CoordsToAlgebraic(boards[endPV - 1],
5384                              PosFlags(endPV - 1),
5385                              fromY, fromX, toY, toX, promoChar,
5386                              parseList[endPV - 1]);
5387   } while(valid);
5388   if(atEnd == 2) return; // used hidden, for PV conversion
5389   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5390   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5391   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5392                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5393   DrawPosition(TRUE, boards[currentMove]);
5394 }
5395
5396 int
5397 MultiPV (ChessProgramState *cps)
5398 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5399         int i;
5400         for(i=0; i<cps->nrOptions; i++)
5401             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5402                 return i;
5403         return -1;
5404 }
5405
5406 Boolean
5407 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5408 {
5409         int startPV, multi, lineStart, origIndex = index;
5410         char *p, buf2[MSG_SIZ];
5411
5412         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5413         lastX = x; lastY = y;
5414         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5415         lineStart = startPV = index;
5416         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5417         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5418         index = startPV;
5419         do{ while(buf[index] && buf[index] != '\n') index++;
5420         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5421         buf[index] = 0;
5422         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5423                 int n = first.option[multi].value;
5424                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5425                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5426                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5427                 first.option[multi].value = n;
5428                 *start = *end = 0;
5429                 return FALSE;
5430         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5431                 ExcludeClick(origIndex - lineStart);
5432                 return FALSE;
5433         }
5434         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5435         *start = startPV; *end = index-1;
5436         return TRUE;
5437 }
5438
5439 char *
5440 PvToSAN (char *pv)
5441 {
5442         static char buf[10*MSG_SIZ];
5443         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5444         *buf = NULLCHAR;
5445         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5446         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5447         for(i = forwardMostMove; i<endPV; i++){
5448             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5449             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5450             k += strlen(buf+k);
5451         }
5452         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5453         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5454         endPV = savedEnd;
5455         return buf;
5456 }
5457
5458 Boolean
5459 LoadPV (int x, int y)
5460 { // called on right mouse click to load PV
5461   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5462   lastX = x; lastY = y;
5463   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5464   return TRUE;
5465 }
5466
5467 void
5468 UnLoadPV ()
5469 {
5470   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5471   if(endPV < 0) return;
5472   if(appData.autoCopyPV) CopyFENToClipboard();
5473   endPV = -1;
5474   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5475         Boolean saveAnimate = appData.animate;
5476         if(pushed) {
5477             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5478                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5479             } else storedGames--; // abandon shelved tail of original game
5480         }
5481         pushed = FALSE;
5482         forwardMostMove = currentMove;
5483         currentMove = oldFMM;
5484         appData.animate = FALSE;
5485         ToNrEvent(forwardMostMove);
5486         appData.animate = saveAnimate;
5487   }
5488   currentMove = forwardMostMove;
5489   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5490   ClearPremoveHighlights();
5491   DrawPosition(TRUE, boards[currentMove]);
5492 }
5493
5494 void
5495 MovePV (int x, int y, int h)
5496 { // step through PV based on mouse coordinates (called on mouse move)
5497   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5498
5499   // we must somehow check if right button is still down (might be released off board!)
5500   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5501   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5502   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5503   if(!step) return;
5504   lastX = x; lastY = y;
5505
5506   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5507   if(endPV < 0) return;
5508   if(y < margin) step = 1; else
5509   if(y > h - margin) step = -1;
5510   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5511   currentMove += step;
5512   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5513   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5514                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5515   DrawPosition(FALSE, boards[currentMove]);
5516 }
5517
5518
5519 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5520 // All positions will have equal probability, but the current method will not provide a unique
5521 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5522 #define DARK 1
5523 #define LITE 2
5524 #define ANY 3
5525
5526 int squaresLeft[4];
5527 int piecesLeft[(int)BlackPawn];
5528 int seed, nrOfShuffles;
5529
5530 void
5531 GetPositionNumber ()
5532 {       // sets global variable seed
5533         int i;
5534
5535         seed = appData.defaultFrcPosition;
5536         if(seed < 0) { // randomize based on time for negative FRC position numbers
5537                 for(i=0; i<50; i++) seed += random();
5538                 seed = random() ^ random() >> 8 ^ random() << 8;
5539                 if(seed<0) seed = -seed;
5540         }
5541 }
5542
5543 int
5544 put (Board board, int pieceType, int rank, int n, int shade)
5545 // put the piece on the (n-1)-th empty squares of the given shade
5546 {
5547         int i;
5548
5549         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5550                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5551                         board[rank][i] = (ChessSquare) pieceType;
5552                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5553                         squaresLeft[ANY]--;
5554                         piecesLeft[pieceType]--;
5555                         return i;
5556                 }
5557         }
5558         return -1;
5559 }
5560
5561
5562 void
5563 AddOnePiece (Board board, int pieceType, int rank, int shade)
5564 // calculate where the next piece goes, (any empty square), and put it there
5565 {
5566         int i;
5567
5568         i = seed % squaresLeft[shade];
5569         nrOfShuffles *= squaresLeft[shade];
5570         seed /= squaresLeft[shade];
5571         put(board, pieceType, rank, i, shade);
5572 }
5573
5574 void
5575 AddTwoPieces (Board board, int pieceType, int rank)
5576 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5577 {
5578         int i, n=squaresLeft[ANY], j=n-1, k;
5579
5580         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5581         i = seed % k;  // pick one
5582         nrOfShuffles *= k;
5583         seed /= k;
5584         while(i >= j) i -= j--;
5585         j = n - 1 - j; i += j;
5586         put(board, pieceType, rank, j, ANY);
5587         put(board, pieceType, rank, i, ANY);
5588 }
5589
5590 void
5591 SetUpShuffle (Board board, int number)
5592 {
5593         int i, p, first=1;
5594
5595         GetPositionNumber(); nrOfShuffles = 1;
5596
5597         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5598         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5599         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5600
5601         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5602
5603         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5604             p = (int) board[0][i];
5605             if(p < (int) BlackPawn) piecesLeft[p] ++;
5606             board[0][i] = EmptySquare;
5607         }
5608
5609         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5610             // shuffles restricted to allow normal castling put KRR first
5611             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5612                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5613             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5614                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5615             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5616                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5617             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5618                 put(board, WhiteRook, 0, 0, ANY);
5619             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5620         }
5621
5622         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5623             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5624             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5625                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5626                 while(piecesLeft[p] >= 2) {
5627                     AddOnePiece(board, p, 0, LITE);
5628                     AddOnePiece(board, p, 0, DARK);
5629                 }
5630                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5631             }
5632
5633         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5634             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5635             // but we leave King and Rooks for last, to possibly obey FRC restriction
5636             if(p == (int)WhiteRook) continue;
5637             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5638             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5639         }
5640
5641         // now everything is placed, except perhaps King (Unicorn) and Rooks
5642
5643         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5644             // Last King gets castling rights
5645             while(piecesLeft[(int)WhiteUnicorn]) {
5646                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5647                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5648             }
5649
5650             while(piecesLeft[(int)WhiteKing]) {
5651                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5652                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5653             }
5654
5655
5656         } else {
5657             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5658             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5659         }
5660
5661         // Only Rooks can be left; simply place them all
5662         while(piecesLeft[(int)WhiteRook]) {
5663                 i = put(board, WhiteRook, 0, 0, ANY);
5664                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5665                         if(first) {
5666                                 first=0;
5667                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5668                         }
5669                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5670                 }
5671         }
5672         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5673             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5674         }
5675
5676         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5677 }
5678
5679 int
5680 SetCharTable (char *table, const char * map)
5681 /* [HGM] moved here from winboard.c because of its general usefulness */
5682 /*       Basically a safe strcpy that uses the last character as King */
5683 {
5684     int result = FALSE; int NrPieces;
5685
5686     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5687                     && NrPieces >= 12 && !(NrPieces&1)) {
5688         int i; /* [HGM] Accept even length from 12 to 34 */
5689
5690         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5691         for( i=0; i<NrPieces/2-1; i++ ) {
5692             table[i] = map[i];
5693             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5694         }
5695         table[(int) WhiteKing]  = map[NrPieces/2-1];
5696         table[(int) BlackKing]  = map[NrPieces-1];
5697
5698         result = TRUE;
5699     }
5700
5701     return result;
5702 }
5703
5704 void
5705 Prelude (Board board)
5706 {       // [HGM] superchess: random selection of exo-pieces
5707         int i, j, k; ChessSquare p;
5708         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5709
5710         GetPositionNumber(); // use FRC position number
5711
5712         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5713             SetCharTable(pieceToChar, appData.pieceToCharTable);
5714             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5715                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5716         }
5717
5718         j = seed%4;                 seed /= 4;
5719         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5720         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5721         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5722         j = seed%3 + (seed%3 >= j); seed /= 3;
5723         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5724         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5725         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5726         j = seed%3;                 seed /= 3;
5727         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5728         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5729         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5730         j = seed%2 + (seed%2 >= j); seed /= 2;
5731         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5732         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5733         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5734         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5735         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5736         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5737         put(board, exoPieces[0],    0, 0, ANY);
5738         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5739 }
5740
5741 void
5742 InitPosition (int redraw)
5743 {
5744     ChessSquare (* pieces)[BOARD_FILES];
5745     int i, j, pawnRow, overrule,
5746     oldx = gameInfo.boardWidth,
5747     oldy = gameInfo.boardHeight,
5748     oldh = gameInfo.holdingsWidth;
5749     static int oldv;
5750
5751     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5752
5753     /* [AS] Initialize pv info list [HGM] and game status */
5754     {
5755         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5756             pvInfoList[i].depth = 0;
5757             boards[i][EP_STATUS] = EP_NONE;
5758             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5759         }
5760
5761         initialRulePlies = 0; /* 50-move counter start */
5762
5763         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5764         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5765     }
5766
5767
5768     /* [HGM] logic here is completely changed. In stead of full positions */
5769     /* the initialized data only consist of the two backranks. The switch */
5770     /* selects which one we will use, which is than copied to the Board   */
5771     /* initialPosition, which for the rest is initialized by Pawns and    */
5772     /* empty squares. This initial position is then copied to boards[0],  */
5773     /* possibly after shuffling, so that it remains available.            */
5774
5775     gameInfo.holdingsWidth = 0; /* default board sizes */
5776     gameInfo.boardWidth    = 8;
5777     gameInfo.boardHeight   = 8;
5778     gameInfo.holdingsSize  = 0;
5779     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5780     for(i=0; i<BOARD_FILES-2; i++)
5781       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5782     initialPosition[EP_STATUS] = EP_NONE;
5783     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5784     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5785          SetCharTable(pieceNickName, appData.pieceNickNames);
5786     else SetCharTable(pieceNickName, "............");
5787     pieces = FIDEArray;
5788
5789     switch (gameInfo.variant) {
5790     case VariantFischeRandom:
5791       shuffleOpenings = TRUE;
5792     default:
5793       break;
5794     case VariantShatranj:
5795       pieces = ShatranjArray;
5796       nrCastlingRights = 0;
5797       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5798       break;
5799     case VariantMakruk:
5800       pieces = makrukArray;
5801       nrCastlingRights = 0;
5802       startedFromSetupPosition = TRUE;
5803       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5804       break;
5805     case VariantTwoKings:
5806       pieces = twoKingsArray;
5807       break;
5808     case VariantGrand:
5809       pieces = GrandArray;
5810       nrCastlingRights = 0;
5811       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5812       gameInfo.boardWidth = 10;
5813       gameInfo.boardHeight = 10;
5814       gameInfo.holdingsSize = 7;
5815       break;
5816     case VariantCapaRandom:
5817       shuffleOpenings = TRUE;
5818     case VariantCapablanca:
5819       pieces = CapablancaArray;
5820       gameInfo.boardWidth = 10;
5821       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5822       break;
5823     case VariantGothic:
5824       pieces = GothicArray;
5825       gameInfo.boardWidth = 10;
5826       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5827       break;
5828     case VariantSChess:
5829       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5830       gameInfo.holdingsSize = 7;
5831       break;
5832     case VariantJanus:
5833       pieces = JanusArray;
5834       gameInfo.boardWidth = 10;
5835       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5836       nrCastlingRights = 6;
5837         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5838         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5839         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5840         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5841         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5842         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5843       break;
5844     case VariantFalcon:
5845       pieces = FalconArray;
5846       gameInfo.boardWidth = 10;
5847       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5848       break;
5849     case VariantXiangqi:
5850       pieces = XiangqiArray;
5851       gameInfo.boardWidth  = 9;
5852       gameInfo.boardHeight = 10;
5853       nrCastlingRights = 0;
5854       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5855       break;
5856     case VariantShogi:
5857       pieces = ShogiArray;
5858       gameInfo.boardWidth  = 9;
5859       gameInfo.boardHeight = 9;
5860       gameInfo.holdingsSize = 7;
5861       nrCastlingRights = 0;
5862       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5863       break;
5864     case VariantCourier:
5865       pieces = CourierArray;
5866       gameInfo.boardWidth  = 12;
5867       nrCastlingRights = 0;
5868       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5869       break;
5870     case VariantKnightmate:
5871       pieces = KnightmateArray;
5872       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5873       break;
5874     case VariantSpartan:
5875       pieces = SpartanArray;
5876       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5877       break;
5878     case VariantFairy:
5879       pieces = fairyArray;
5880       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5881       break;
5882     case VariantGreat:
5883       pieces = GreatArray;
5884       gameInfo.boardWidth = 10;
5885       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5886       gameInfo.holdingsSize = 8;
5887       break;
5888     case VariantSuper:
5889       pieces = FIDEArray;
5890       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5891       gameInfo.holdingsSize = 8;
5892       startedFromSetupPosition = TRUE;
5893       break;
5894     case VariantCrazyhouse:
5895     case VariantBughouse:
5896       pieces = FIDEArray;
5897       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5898       gameInfo.holdingsSize = 5;
5899       break;
5900     case VariantWildCastle:
5901       pieces = FIDEArray;
5902       /* !!?shuffle with kings guaranteed to be on d or e file */
5903       shuffleOpenings = 1;
5904       break;
5905     case VariantNoCastle:
5906       pieces = FIDEArray;
5907       nrCastlingRights = 0;
5908       /* !!?unconstrained back-rank shuffle */
5909       shuffleOpenings = 1;
5910       break;
5911     }
5912
5913     overrule = 0;
5914     if(appData.NrFiles >= 0) {
5915         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5916         gameInfo.boardWidth = appData.NrFiles;
5917     }
5918     if(appData.NrRanks >= 0) {
5919         gameInfo.boardHeight = appData.NrRanks;
5920     }
5921     if(appData.holdingsSize >= 0) {
5922         i = appData.holdingsSize;
5923         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5924         gameInfo.holdingsSize = i;
5925     }
5926     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5927     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5928         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5929
5930     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5931     if(pawnRow < 1) pawnRow = 1;
5932     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5933
5934     /* User pieceToChar list overrules defaults */
5935     if(appData.pieceToCharTable != NULL)
5936         SetCharTable(pieceToChar, appData.pieceToCharTable);
5937
5938     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5939
5940         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5941             s = (ChessSquare) 0; /* account holding counts in guard band */
5942         for( i=0; i<BOARD_HEIGHT; i++ )
5943             initialPosition[i][j] = s;
5944
5945         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5946         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5947         initialPosition[pawnRow][j] = WhitePawn;
5948         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5949         if(gameInfo.variant == VariantXiangqi) {
5950             if(j&1) {
5951                 initialPosition[pawnRow][j] =
5952                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5953                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5954                    initialPosition[2][j] = WhiteCannon;
5955                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5956                 }
5957             }
5958         }
5959         if(gameInfo.variant == VariantGrand) {
5960             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5961                initialPosition[0][j] = WhiteRook;
5962                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5963             }
5964         }
5965         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5966     }
5967     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5968
5969             j=BOARD_LEFT+1;
5970             initialPosition[1][j] = WhiteBishop;
5971             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5972             j=BOARD_RGHT-2;
5973             initialPosition[1][j] = WhiteRook;
5974             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5975     }
5976
5977     if( nrCastlingRights == -1) {
5978         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5979         /*       This sets default castling rights from none to normal corners   */
5980         /* Variants with other castling rights must set them themselves above    */
5981         nrCastlingRights = 6;
5982
5983         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5984         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5985         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5986         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5987         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5988         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5989      }
5990
5991      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5992      if(gameInfo.variant == VariantGreat) { // promotion commoners
5993         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5994         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5995         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5996         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5997      }
5998      if( gameInfo.variant == VariantSChess ) {
5999       initialPosition[1][0] = BlackMarshall;
6000       initialPosition[2][0] = BlackAngel;
6001       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6002       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6003       initialPosition[1][1] = initialPosition[2][1] = 
6004       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6005      }
6006   if (appData.debugMode) {
6007     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6008   }
6009     if(shuffleOpenings) {
6010         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6011         startedFromSetupPosition = TRUE;
6012     }
6013     if(startedFromPositionFile) {
6014       /* [HGM] loadPos: use PositionFile for every new game */
6015       CopyBoard(initialPosition, filePosition);
6016       for(i=0; i<nrCastlingRights; i++)
6017           initialRights[i] = filePosition[CASTLING][i];
6018       startedFromSetupPosition = TRUE;
6019     }
6020
6021     CopyBoard(boards[0], initialPosition);
6022
6023     if(oldx != gameInfo.boardWidth ||
6024        oldy != gameInfo.boardHeight ||
6025        oldv != gameInfo.variant ||
6026        oldh != gameInfo.holdingsWidth
6027                                          )
6028             InitDrawingSizes(-2 ,0);
6029
6030     oldv = gameInfo.variant;
6031     if (redraw)
6032       DrawPosition(TRUE, boards[currentMove]);
6033 }
6034
6035 void
6036 SendBoard (ChessProgramState *cps, int moveNum)
6037 {
6038     char message[MSG_SIZ];
6039
6040     if (cps->useSetboard) {
6041       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6042       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6043       SendToProgram(message, cps);
6044       free(fen);
6045
6046     } else {
6047       ChessSquare *bp;
6048       int i, j, left=0, right=BOARD_WIDTH;
6049       /* Kludge to set black to move, avoiding the troublesome and now
6050        * deprecated "black" command.
6051        */
6052       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6053         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6054
6055       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6056
6057       SendToProgram("edit\n", cps);
6058       SendToProgram("#\n", cps);
6059       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6060         bp = &boards[moveNum][i][left];
6061         for (j = left; j < right; j++, bp++) {
6062           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6063           if ((int) *bp < (int) BlackPawn) {
6064             if(j == BOARD_RGHT+1)
6065                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6066             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6067             if(message[0] == '+' || message[0] == '~') {
6068               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6069                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6070                         AAA + j, ONE + i);
6071             }
6072             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6073                 message[1] = BOARD_RGHT   - 1 - j + '1';
6074                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6075             }
6076             SendToProgram(message, cps);
6077           }
6078         }
6079       }
6080
6081       SendToProgram("c\n", cps);
6082       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6083         bp = &boards[moveNum][i][left];
6084         for (j = left; j < right; j++, bp++) {
6085           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6086           if (((int) *bp != (int) EmptySquare)
6087               && ((int) *bp >= (int) BlackPawn)) {
6088             if(j == BOARD_LEFT-2)
6089                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6090             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6091                     AAA + j, ONE + i);
6092             if(message[0] == '+' || message[0] == '~') {
6093               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6094                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6095                         AAA + j, ONE + i);
6096             }
6097             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6098                 message[1] = BOARD_RGHT   - 1 - j + '1';
6099                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6100             }
6101             SendToProgram(message, cps);
6102           }
6103         }
6104       }
6105
6106       SendToProgram(".\n", cps);
6107     }
6108     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6109 }
6110
6111 char exclusionHeader[MSG_SIZ];
6112 int exCnt, excludePtr;
6113 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6114 static Exclusion excluTab[200];
6115 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6116
6117 static void
6118 WriteMap (int s)
6119 {
6120     int j;
6121     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6122     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6123 }
6124
6125 static void
6126 ClearMap ()
6127 {
6128     int j;
6129     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6130     excludePtr = 24; exCnt = 0;
6131     WriteMap(0);
6132 }
6133
6134 static void
6135 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6136 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6137     char buf[2*MOVE_LEN], *p;
6138     Exclusion *e = excluTab;
6139     int i;
6140     for(i=0; i<exCnt; i++)
6141         if(e[i].ff == fromX && e[i].fr == fromY &&
6142            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6143     if(i == exCnt) { // was not in exclude list; add it
6144         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6145         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6146             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6147             return; // abort
6148         }
6149         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6150         excludePtr++; e[i].mark = excludePtr++;
6151         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6152         exCnt++;
6153     }
6154     exclusionHeader[e[i].mark] = state;
6155 }
6156
6157 static int
6158 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6159 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6160     char *p, buf[MSG_SIZ];
6161     int j, k;
6162     ChessMove moveType;
6163     if(promoChar == -1) { // kludge to indicate best move
6164         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6165             return 1; // if unparsable, abort
6166     }
6167     // update exclusion map (resolving toggle by consulting existing state)
6168     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6169     j = k%8; k >>= 3;
6170     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6171     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6172          excludeMap[k] |=   1<<j;
6173     else excludeMap[k] &= ~(1<<j);
6174     // update header
6175     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6176     // inform engine
6177     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6178     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6179     SendToProgram(buf, &first);
6180     return (state == '+');
6181 }
6182
6183 static void
6184 ExcludeClick (int index)
6185 {
6186     int i, j;
6187     char buf[MSG_SIZ];
6188     Exclusion *e = excluTab;
6189     if(index < 25) { // none, best or tail clicked
6190         if(index < 13) { // none: include all
6191             WriteMap(0); // clear map
6192             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6193             SendToProgram("include all\n", &first); // and inform engine
6194         } else if(index > 18) { // tail
6195             if(exclusionHeader[19] == '-') { // tail was excluded
6196                 SendToProgram("include all\n", &first);
6197                 WriteMap(0); // clear map completely
6198                 // now re-exclude selected moves
6199                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6200                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6201             } else { // tail was included or in mixed state
6202                 SendToProgram("exclude all\n", &first);
6203                 WriteMap(0xFF); // fill map completely
6204                 // now re-include selected moves
6205                 j = 0; // count them
6206                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6207                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6208                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6209             }
6210         } else { // best
6211             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6212         }
6213     } else {
6214         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6215             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6216             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6217             break;
6218         }
6219     }
6220 }
6221
6222 ChessSquare
6223 DefaultPromoChoice (int white)
6224 {
6225     ChessSquare result;
6226     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6227         result = WhiteFerz; // no choice
6228     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6229         result= WhiteKing; // in Suicide Q is the last thing we want
6230     else if(gameInfo.variant == VariantSpartan)
6231         result = white ? WhiteQueen : WhiteAngel;
6232     else result = WhiteQueen;
6233     if(!white) result = WHITE_TO_BLACK result;
6234     return result;
6235 }
6236
6237 static int autoQueen; // [HGM] oneclick
6238
6239 int
6240 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6241 {
6242     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6243     /* [HGM] add Shogi promotions */
6244     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6245     ChessSquare piece;
6246     ChessMove moveType;
6247     Boolean premove;
6248
6249     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6250     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6251
6252     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6253       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6254         return FALSE;
6255
6256     piece = boards[currentMove][fromY][fromX];
6257     if(gameInfo.variant == VariantShogi) {
6258         promotionZoneSize = BOARD_HEIGHT/3;
6259         highestPromotingPiece = (int)WhiteFerz;
6260     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6261         promotionZoneSize = 3;
6262     }
6263
6264     // Treat Lance as Pawn when it is not representing Amazon
6265     if(gameInfo.variant != VariantSuper) {
6266         if(piece == WhiteLance) piece = WhitePawn; else
6267         if(piece == BlackLance) piece = BlackPawn;
6268     }
6269
6270     // next weed out all moves that do not touch the promotion zone at all
6271     if((int)piece >= BlackPawn) {
6272         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6273              return FALSE;
6274         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6275     } else {
6276         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6277            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6278     }
6279
6280     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6281
6282     // weed out mandatory Shogi promotions
6283     if(gameInfo.variant == VariantShogi) {
6284         if(piece >= BlackPawn) {
6285             if(toY == 0 && piece == BlackPawn ||
6286                toY == 0 && piece == BlackQueen ||
6287                toY <= 1 && piece == BlackKnight) {
6288                 *promoChoice = '+';
6289                 return FALSE;
6290             }
6291         } else {
6292             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6293                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6294                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6295                 *promoChoice = '+';
6296                 return FALSE;
6297             }
6298         }
6299     }
6300
6301     // weed out obviously illegal Pawn moves
6302     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6303         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6304         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6305         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6306         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6307         // note we are not allowed to test for valid (non-)capture, due to premove
6308     }
6309
6310     // we either have a choice what to promote to, or (in Shogi) whether to promote
6311     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6312         *promoChoice = PieceToChar(BlackFerz);  // no choice
6313         return FALSE;
6314     }
6315     // no sense asking what we must promote to if it is going to explode...
6316     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6317         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6318         return FALSE;
6319     }
6320     // give caller the default choice even if we will not make it
6321     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6322     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6323     if(        sweepSelect && gameInfo.variant != VariantGreat
6324                            && gameInfo.variant != VariantGrand
6325                            && gameInfo.variant != VariantSuper) return FALSE;
6326     if(autoQueen) return FALSE; // predetermined
6327
6328     // suppress promotion popup on illegal moves that are not premoves
6329     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6330               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6331     if(appData.testLegality && !premove) {
6332         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6333                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6334         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6335             return FALSE;
6336     }
6337
6338     return TRUE;
6339 }
6340
6341 int
6342 InPalace (int row, int column)
6343 {   /* [HGM] for Xiangqi */
6344     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6345          column < (BOARD_WIDTH + 4)/2 &&
6346          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6347     return FALSE;
6348 }
6349
6350 int
6351 PieceForSquare (int x, int y)
6352 {
6353   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6354      return -1;
6355   else
6356      return boards[currentMove][y][x];
6357 }
6358
6359 int
6360 OKToStartUserMove (int x, int y)
6361 {
6362     ChessSquare from_piece;
6363     int white_piece;
6364
6365     if (matchMode) return FALSE;
6366     if (gameMode == EditPosition) return TRUE;
6367
6368     if (x >= 0 && y >= 0)
6369       from_piece = boards[currentMove][y][x];
6370     else
6371       from_piece = EmptySquare;
6372
6373     if (from_piece == EmptySquare) return FALSE;
6374
6375     white_piece = (int)from_piece >= (int)WhitePawn &&
6376       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6377
6378     switch (gameMode) {
6379       case AnalyzeFile:
6380       case TwoMachinesPlay:
6381       case EndOfGame:
6382         return FALSE;
6383
6384       case IcsObserving:
6385       case IcsIdle:
6386         return FALSE;
6387
6388       case MachinePlaysWhite:
6389       case IcsPlayingBlack:
6390         if (appData.zippyPlay) return FALSE;
6391         if (white_piece) {
6392             DisplayMoveError(_("You are playing Black"));
6393             return FALSE;
6394         }
6395         break;
6396
6397       case MachinePlaysBlack:
6398       case IcsPlayingWhite:
6399         if (appData.zippyPlay) return FALSE;
6400         if (!white_piece) {
6401             DisplayMoveError(_("You are playing White"));
6402             return FALSE;
6403         }
6404         break;
6405
6406       case PlayFromGameFile:
6407             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6408       case EditGame:
6409         if (!white_piece && WhiteOnMove(currentMove)) {
6410             DisplayMoveError(_("It is White's turn"));
6411             return FALSE;
6412         }
6413         if (white_piece && !WhiteOnMove(currentMove)) {
6414             DisplayMoveError(_("It is Black's turn"));
6415             return FALSE;
6416         }
6417         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6418             /* Editing correspondence game history */
6419             /* Could disallow this or prompt for confirmation */
6420             cmailOldMove = -1;
6421         }
6422         break;
6423
6424       case BeginningOfGame:
6425         if (appData.icsActive) return FALSE;
6426         if (!appData.noChessProgram) {
6427             if (!white_piece) {
6428                 DisplayMoveError(_("You are playing White"));
6429                 return FALSE;
6430             }
6431         }
6432         break;
6433
6434       case Training:
6435         if (!white_piece && WhiteOnMove(currentMove)) {
6436             DisplayMoveError(_("It is White's turn"));
6437             return FALSE;
6438         }
6439         if (white_piece && !WhiteOnMove(currentMove)) {
6440             DisplayMoveError(_("It is Black's turn"));
6441             return FALSE;
6442         }
6443         break;
6444
6445       default:
6446       case IcsExamining:
6447         break;
6448     }
6449     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6450         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6451         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6452         && gameMode != AnalyzeFile && gameMode != Training) {
6453         DisplayMoveError(_("Displayed position is not current"));
6454         return FALSE;
6455     }
6456     return TRUE;
6457 }
6458
6459 Boolean
6460 OnlyMove (int *x, int *y, Boolean captures) 
6461 {
6462     DisambiguateClosure cl;
6463     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6464     switch(gameMode) {
6465       case MachinePlaysBlack:
6466       case IcsPlayingWhite:
6467       case BeginningOfGame:
6468         if(!WhiteOnMove(currentMove)) return FALSE;
6469         break;
6470       case MachinePlaysWhite:
6471       case IcsPlayingBlack:
6472         if(WhiteOnMove(currentMove)) return FALSE;
6473         break;
6474       case EditGame:
6475         break;
6476       default:
6477         return FALSE;
6478     }
6479     cl.pieceIn = EmptySquare;
6480     cl.rfIn = *y;
6481     cl.ffIn = *x;
6482     cl.rtIn = -1;
6483     cl.ftIn = -1;
6484     cl.promoCharIn = NULLCHAR;
6485     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6486     if( cl.kind == NormalMove ||
6487         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6488         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6489         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6490       fromX = cl.ff;
6491       fromY = cl.rf;
6492       *x = cl.ft;
6493       *y = cl.rt;
6494       return TRUE;
6495     }
6496     if(cl.kind != ImpossibleMove) return FALSE;
6497     cl.pieceIn = EmptySquare;
6498     cl.rfIn = -1;
6499     cl.ffIn = -1;
6500     cl.rtIn = *y;
6501     cl.ftIn = *x;
6502     cl.promoCharIn = NULLCHAR;
6503     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6504     if( cl.kind == NormalMove ||
6505         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6506         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6507         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6508       fromX = cl.ff;
6509       fromY = cl.rf;
6510       *x = cl.ft;
6511       *y = cl.rt;
6512       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6513       return TRUE;
6514     }
6515     return FALSE;
6516 }
6517
6518 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6519 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6520 int lastLoadGameUseList = FALSE;
6521 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6522 ChessMove lastLoadGameStart = EndOfFile;
6523 int doubleClick;
6524
6525 void
6526 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6527 {
6528     ChessMove moveType;
6529     ChessSquare pdown, pup;
6530     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6531
6532
6533     /* Check if the user is playing in turn.  This is complicated because we
6534        let the user "pick up" a piece before it is his turn.  So the piece he
6535        tried to pick up may have been captured by the time he puts it down!
6536        Therefore we use the color the user is supposed to be playing in this
6537        test, not the color of the piece that is currently on the starting
6538        square---except in EditGame mode, where the user is playing both
6539        sides; fortunately there the capture race can't happen.  (It can
6540        now happen in IcsExamining mode, but that's just too bad.  The user
6541        will get a somewhat confusing message in that case.)
6542        */
6543
6544     switch (gameMode) {
6545       case AnalyzeFile:
6546       case TwoMachinesPlay:
6547       case EndOfGame:
6548       case IcsObserving:
6549       case IcsIdle:
6550         /* We switched into a game mode where moves are not accepted,
6551            perhaps while the mouse button was down. */
6552         return;
6553
6554       case MachinePlaysWhite:
6555         /* User is moving for Black */
6556         if (WhiteOnMove(currentMove)) {
6557             DisplayMoveError(_("It is White's turn"));
6558             return;
6559         }
6560         break;
6561
6562       case MachinePlaysBlack:
6563         /* User is moving for White */
6564         if (!WhiteOnMove(currentMove)) {
6565             DisplayMoveError(_("It is Black's turn"));
6566             return;
6567         }
6568         break;
6569
6570       case PlayFromGameFile:
6571             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6572       case EditGame:
6573       case IcsExamining:
6574       case BeginningOfGame:
6575       case AnalyzeMode:
6576       case Training:
6577         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6578         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6579             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6580             /* User is moving for Black */
6581             if (WhiteOnMove(currentMove)) {
6582                 DisplayMoveError(_("It is White's turn"));
6583                 return;
6584             }
6585         } else {
6586             /* User is moving for White */
6587             if (!WhiteOnMove(currentMove)) {
6588                 DisplayMoveError(_("It is Black's turn"));
6589                 return;
6590             }
6591         }
6592         break;
6593
6594       case IcsPlayingBlack:
6595         /* User is moving for Black */
6596         if (WhiteOnMove(currentMove)) {
6597             if (!appData.premove) {
6598                 DisplayMoveError(_("It is White's turn"));
6599             } else if (toX >= 0 && toY >= 0) {
6600                 premoveToX = toX;
6601                 premoveToY = toY;
6602                 premoveFromX = fromX;
6603                 premoveFromY = fromY;
6604                 premovePromoChar = promoChar;
6605                 gotPremove = 1;
6606                 if (appData.debugMode)
6607                     fprintf(debugFP, "Got premove: fromX %d,"
6608                             "fromY %d, toX %d, toY %d\n",
6609                             fromX, fromY, toX, toY);
6610             }
6611             return;
6612         }
6613         break;
6614
6615       case IcsPlayingWhite:
6616         /* User is moving for White */
6617         if (!WhiteOnMove(currentMove)) {
6618             if (!appData.premove) {
6619                 DisplayMoveError(_("It is Black's turn"));
6620             } else if (toX >= 0 && toY >= 0) {
6621                 premoveToX = toX;
6622                 premoveToY = toY;
6623                 premoveFromX = fromX;
6624                 premoveFromY = fromY;
6625                 premovePromoChar = promoChar;
6626                 gotPremove = 1;
6627                 if (appData.debugMode)
6628                     fprintf(debugFP, "Got premove: fromX %d,"
6629                             "fromY %d, toX %d, toY %d\n",
6630                             fromX, fromY, toX, toY);
6631             }
6632             return;
6633         }
6634         break;
6635
6636       default:
6637         break;
6638
6639       case EditPosition:
6640         /* EditPosition, empty square, or different color piece;
6641            click-click move is possible */
6642         if (toX == -2 || toY == -2) {
6643             boards[0][fromY][fromX] = EmptySquare;
6644             DrawPosition(FALSE, boards[currentMove]);
6645             return;
6646         } else if (toX >= 0 && toY >= 0) {
6647             boards[0][toY][toX] = boards[0][fromY][fromX];
6648             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6649                 if(boards[0][fromY][0] != EmptySquare) {
6650                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6651                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6652                 }
6653             } else
6654             if(fromX == BOARD_RGHT+1) {
6655                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6656                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6657                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6658                 }
6659             } else
6660             boards[0][fromY][fromX] = EmptySquare;
6661             DrawPosition(FALSE, boards[currentMove]);
6662             return;
6663         }
6664         return;
6665     }
6666
6667     if(toX < 0 || toY < 0) return;
6668     pdown = boards[currentMove][fromY][fromX];
6669     pup = boards[currentMove][toY][toX];
6670
6671     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6672     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6673          if( pup != EmptySquare ) return;
6674          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6675            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6676                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6677            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6678            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6679            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6680            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6681          fromY = DROP_RANK;
6682     }
6683
6684     /* [HGM] always test for legality, to get promotion info */
6685     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6686                                          fromY, fromX, toY, toX, promoChar);
6687
6688     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6689
6690     /* [HGM] but possibly ignore an IllegalMove result */
6691     if (appData.testLegality) {
6692         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6693             DisplayMoveError(_("Illegal move"));
6694             return;
6695         }
6696     }
6697
6698     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6699         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6700              ClearPremoveHighlights(); // was included
6701         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6702         return;
6703     }
6704
6705     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6706 }
6707
6708 /* Common tail of UserMoveEvent and DropMenuEvent */
6709 int
6710 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6711 {
6712     char *bookHit = 0;
6713
6714     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6715         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6716         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6717         if(WhiteOnMove(currentMove)) {
6718             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6719         } else {
6720             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6721         }
6722     }
6723
6724     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6725        move type in caller when we know the move is a legal promotion */
6726     if(moveType == NormalMove && promoChar)
6727         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6728
6729     /* [HGM] <popupFix> The following if has been moved here from
6730        UserMoveEvent(). Because it seemed to belong here (why not allow
6731        piece drops in training games?), and because it can only be
6732        performed after it is known to what we promote. */
6733     if (gameMode == Training) {
6734       /* compare the move played on the board to the next move in the
6735        * game. If they match, display the move and the opponent's response.
6736        * If they don't match, display an error message.
6737        */
6738       int saveAnimate;
6739       Board testBoard;
6740       CopyBoard(testBoard, boards[currentMove]);
6741       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6742
6743       if (CompareBoards(testBoard, boards[currentMove+1])) {
6744         ForwardInner(currentMove+1);
6745
6746         /* Autoplay the opponent's response.
6747          * if appData.animate was TRUE when Training mode was entered,
6748          * the response will be animated.
6749          */
6750         saveAnimate = appData.animate;
6751         appData.animate = animateTraining;
6752         ForwardInner(currentMove+1);
6753         appData.animate = saveAnimate;
6754
6755         /* check for the end of the game */
6756         if (currentMove >= forwardMostMove) {
6757           gameMode = PlayFromGameFile;
6758           ModeHighlight();
6759           SetTrainingModeOff();
6760           DisplayInformation(_("End of game"));
6761         }
6762       } else {
6763         DisplayError(_("Incorrect move"), 0);
6764       }
6765       return 1;
6766     }
6767
6768   /* Ok, now we know that the move is good, so we can kill
6769      the previous line in Analysis Mode */
6770   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6771                                 && currentMove < forwardMostMove) {
6772     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6773     else forwardMostMove = currentMove;
6774   }
6775
6776   ClearMap();
6777
6778   /* If we need the chess program but it's dead, restart it */
6779   ResurrectChessProgram();
6780
6781   /* A user move restarts a paused game*/
6782   if (pausing)
6783     PauseEvent();
6784
6785   thinkOutput[0] = NULLCHAR;
6786
6787   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6788
6789   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6790     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6791     return 1;
6792   }
6793
6794   if (gameMode == BeginningOfGame) {
6795     if (appData.noChessProgram) {
6796       gameMode = EditGame;
6797       SetGameInfo();
6798     } else {
6799       char buf[MSG_SIZ];
6800       gameMode = MachinePlaysBlack;
6801       StartClocks();
6802       SetGameInfo();
6803       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6804       DisplayTitle(buf);
6805       if (first.sendName) {
6806         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6807         SendToProgram(buf, &first);
6808       }
6809       StartClocks();
6810     }
6811     ModeHighlight();
6812   }
6813
6814   /* Relay move to ICS or chess engine */
6815   if (appData.icsActive) {
6816     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6817         gameMode == IcsExamining) {
6818       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6819         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6820         SendToICS("draw ");
6821         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6822       }
6823       // also send plain move, in case ICS does not understand atomic claims
6824       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6825       ics_user_moved = 1;
6826     }
6827   } else {
6828     if (first.sendTime && (gameMode == BeginningOfGame ||
6829                            gameMode == MachinePlaysWhite ||
6830                            gameMode == MachinePlaysBlack)) {
6831       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6832     }
6833     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6834          // [HGM] book: if program might be playing, let it use book
6835         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6836         first.maybeThinking = TRUE;
6837     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6838         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6839         SendBoard(&first, currentMove+1);
6840     } else SendMoveToProgram(forwardMostMove-1, &first);
6841     if (currentMove == cmailOldMove + 1) {
6842       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6843     }
6844   }
6845
6846   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6847
6848   switch (gameMode) {
6849   case EditGame:
6850     if(appData.testLegality)
6851     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6852     case MT_NONE:
6853     case MT_CHECK:
6854       break;
6855     case MT_CHECKMATE:
6856     case MT_STAINMATE:
6857       if (WhiteOnMove(currentMove)) {
6858         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6859       } else {
6860         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6861       }
6862       break;
6863     case MT_STALEMATE:
6864       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6865       break;
6866     }
6867     break;
6868
6869   case MachinePlaysBlack:
6870   case MachinePlaysWhite:
6871     /* disable certain menu options while machine is thinking */
6872     SetMachineThinkingEnables();
6873     break;
6874
6875   default:
6876     break;
6877   }
6878
6879   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6880   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6881
6882   if(bookHit) { // [HGM] book: simulate book reply
6883         static char bookMove[MSG_SIZ]; // a bit generous?
6884
6885         programStats.nodes = programStats.depth = programStats.time =
6886         programStats.score = programStats.got_only_move = 0;
6887         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6888
6889         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6890         strcat(bookMove, bookHit);
6891         HandleMachineMove(bookMove, &first);
6892   }
6893   return 1;
6894 }
6895
6896 void
6897 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6898 {
6899     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6900     Markers *m = (Markers *) closure;
6901     if(rf == fromY && ff == fromX)
6902         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6903                          || kind == WhiteCapturesEnPassant
6904                          || kind == BlackCapturesEnPassant);
6905     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6906 }
6907
6908 void
6909 MarkTargetSquares (int clear)
6910 {
6911   int x, y;
6912   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6913      !appData.testLegality || gameMode == EditPosition) return;
6914   if(clear) {
6915     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6916   } else {
6917     int capt = 0;
6918     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6919     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6920       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6921       if(capt)
6922       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6923     }
6924   }
6925   DrawPosition(TRUE, NULL);
6926 }
6927
6928 int
6929 Explode (Board board, int fromX, int fromY, int toX, int toY)
6930 {
6931     if(gameInfo.variant == VariantAtomic &&
6932        (board[toY][toX] != EmptySquare ||                     // capture?
6933         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6934                          board[fromY][fromX] == BlackPawn   )
6935       )) {
6936         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6937         return TRUE;
6938     }
6939     return FALSE;
6940 }
6941
6942 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6943
6944 int
6945 CanPromote (ChessSquare piece, int y)
6946 {
6947         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6948         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6949         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6950            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6951            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6952                                                   gameInfo.variant == VariantMakruk) return FALSE;
6953         return (piece == BlackPawn && y == 1 ||
6954                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6955                 piece == BlackLance && y == 1 ||
6956                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6957 }
6958
6959 void
6960 LeftClick (ClickType clickType, int xPix, int yPix)
6961 {
6962     int x, y;
6963     Boolean saveAnimate;
6964     static int second = 0, promotionChoice = 0, clearFlag = 0;
6965     char promoChoice = NULLCHAR;
6966     ChessSquare piece;
6967     static TimeMark lastClickTime, prevClickTime;
6968
6969     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6970
6971     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6972
6973     if (clickType == Press) ErrorPopDown();
6974
6975     x = EventToSquare(xPix, BOARD_WIDTH);
6976     y = EventToSquare(yPix, BOARD_HEIGHT);
6977     if (!flipView && y >= 0) {
6978         y = BOARD_HEIGHT - 1 - y;
6979     }
6980     if (flipView && x >= 0) {
6981         x = BOARD_WIDTH - 1 - x;
6982     }
6983
6984     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6985         defaultPromoChoice = promoSweep;
6986         promoSweep = EmptySquare;   // terminate sweep
6987         promoDefaultAltered = TRUE;
6988         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6989     }
6990
6991     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6992         if(clickType == Release) return; // ignore upclick of click-click destination
6993         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6994         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6995         if(gameInfo.holdingsWidth &&
6996                 (WhiteOnMove(currentMove)
6997                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6998                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6999             // click in right holdings, for determining promotion piece
7000             ChessSquare p = boards[currentMove][y][x];
7001             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7002             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7003             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7004                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7005                 fromX = fromY = -1;
7006                 return;
7007             }
7008         }
7009         DrawPosition(FALSE, boards[currentMove]);
7010         return;
7011     }
7012
7013     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7014     if(clickType == Press
7015             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7016               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7017               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7018         return;
7019
7020     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7021         // could be static click on premove from-square: abort premove
7022         gotPremove = 0;
7023         ClearPremoveHighlights();
7024     }
7025
7026     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7027         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7028
7029     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7030         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7031                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7032         defaultPromoChoice = DefaultPromoChoice(side);
7033     }
7034
7035     autoQueen = appData.alwaysPromoteToQueen;
7036
7037     if (fromX == -1) {
7038       int originalY = y;
7039       gatingPiece = EmptySquare;
7040       if (clickType != Press) {
7041         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7042             DragPieceEnd(xPix, yPix); dragging = 0;
7043             DrawPosition(FALSE, NULL);
7044         }
7045         return;
7046       }
7047       doubleClick = FALSE;
7048       fromX = x; fromY = y; toX = toY = -1;
7049       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7050          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7051          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7052             /* First square */
7053             if (OKToStartUserMove(fromX, fromY)) {
7054                 second = 0;
7055                 MarkTargetSquares(0);
7056                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7057                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7058                     promoSweep = defaultPromoChoice;
7059                     selectFlag = 0; lastX = xPix; lastY = yPix;
7060                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7061                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7062                 }
7063                 if (appData.highlightDragging) {
7064                     SetHighlights(fromX, fromY, -1, -1);
7065                 }
7066             } else fromX = fromY = -1;
7067             return;
7068         }
7069     }
7070
7071     /* fromX != -1 */
7072     if (clickType == Press && gameMode != EditPosition) {
7073         ChessSquare fromP;
7074         ChessSquare toP;
7075         int frc;
7076
7077         // ignore off-board to clicks
7078         if(y < 0 || x < 0) return;
7079
7080         /* Check if clicking again on the same color piece */
7081         fromP = boards[currentMove][fromY][fromX];
7082         toP = boards[currentMove][y][x];
7083         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7084         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7085              WhitePawn <= toP && toP <= WhiteKing &&
7086              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7087              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7088             (BlackPawn <= fromP && fromP <= BlackKing &&
7089              BlackPawn <= toP && toP <= BlackKing &&
7090              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7091              !(fromP == BlackKing && toP == BlackRook && frc))) {
7092             /* Clicked again on same color piece -- changed his mind */
7093             second = (x == fromX && y == fromY);
7094             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7095                 second = FALSE; // first double-click rather than scond click
7096                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7097             }
7098             promoDefaultAltered = FALSE;
7099             MarkTargetSquares(1);
7100            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7101             if (appData.highlightDragging) {
7102                 SetHighlights(x, y, -1, -1);
7103             } else {
7104                 ClearHighlights();
7105             }
7106             if (OKToStartUserMove(x, y)) {
7107                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7108                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7109                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7110                  gatingPiece = boards[currentMove][fromY][fromX];
7111                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7112                 fromX = x;
7113                 fromY = y; dragging = 1;
7114                 MarkTargetSquares(0);
7115                 DragPieceBegin(xPix, yPix, FALSE);
7116                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7117                     promoSweep = defaultPromoChoice;
7118                     selectFlag = 0; lastX = xPix; lastY = yPix;
7119                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7120                 }
7121             }
7122            }
7123            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7124            second = FALSE; 
7125         }
7126         // ignore clicks on holdings
7127         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7128     }
7129
7130     if (clickType == Release && x == fromX && y == fromY) {
7131         DragPieceEnd(xPix, yPix); dragging = 0;
7132         if(clearFlag) {
7133             // a deferred attempt to click-click move an empty square on top of a piece
7134             boards[currentMove][y][x] = EmptySquare;
7135             ClearHighlights();
7136             DrawPosition(FALSE, boards[currentMove]);
7137             fromX = fromY = -1; clearFlag = 0;
7138             return;
7139         }
7140         if (appData.animateDragging) {
7141             /* Undo animation damage if any */
7142             DrawPosition(FALSE, NULL);
7143         }
7144         if (second) {
7145             /* Second up/down in same square; just abort move */
7146             second = 0;
7147             fromX = fromY = -1;
7148             gatingPiece = EmptySquare;
7149             ClearHighlights();
7150             gotPremove = 0;
7151             ClearPremoveHighlights();
7152         } else {
7153             /* First upclick in same square; start click-click mode */
7154             SetHighlights(x, y, -1, -1);
7155         }
7156         return;
7157     }
7158
7159     clearFlag = 0;
7160
7161     /* we now have a different from- and (possibly off-board) to-square */
7162     /* Completed move */
7163     toX = x;
7164     toY = y;
7165     saveAnimate = appData.animate;
7166     MarkTargetSquares(1);
7167     if (clickType == Press) {
7168         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7169             // must be Edit Position mode with empty-square selected
7170             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7171             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7172             return;
7173         }
7174         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7175             ChessSquare piece = boards[currentMove][fromY][fromX];
7176             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7177             promoSweep = defaultPromoChoice;
7178             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7179             selectFlag = 0; lastX = xPix; lastY = yPix;
7180             Sweep(0); // Pawn that is going to promote: preview promotion piece
7181             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7182             DrawPosition(FALSE, boards[currentMove]);
7183             return;
7184         }
7185         /* Finish clickclick move */
7186         if (appData.animate || appData.highlightLastMove) {
7187             SetHighlights(fromX, fromY, toX, toY);
7188         } else {
7189             ClearHighlights();
7190         }
7191     } else {
7192         /* Finish drag move */
7193         if (appData.highlightLastMove) {
7194             SetHighlights(fromX, fromY, toX, toY);
7195         } else {
7196             ClearHighlights();
7197         }
7198         DragPieceEnd(xPix, yPix); dragging = 0;
7199         /* Don't animate move and drag both */
7200         appData.animate = FALSE;
7201     }
7202
7203     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7204     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7205         ChessSquare piece = boards[currentMove][fromY][fromX];
7206         if(gameMode == EditPosition && piece != EmptySquare &&
7207            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7208             int n;
7209
7210             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7211                 n = PieceToNumber(piece - (int)BlackPawn);
7212                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7213                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7214                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7215             } else
7216             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7217                 n = PieceToNumber(piece);
7218                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7219                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7220                 boards[currentMove][n][BOARD_WIDTH-2]++;
7221             }
7222             boards[currentMove][fromY][fromX] = EmptySquare;
7223         }
7224         ClearHighlights();
7225         fromX = fromY = -1;
7226         DrawPosition(TRUE, boards[currentMove]);
7227         return;
7228     }
7229
7230     // off-board moves should not be highlighted
7231     if(x < 0 || y < 0) ClearHighlights();
7232
7233     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7234
7235     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7236         SetHighlights(fromX, fromY, toX, toY);
7237         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7238             // [HGM] super: promotion to captured piece selected from holdings
7239             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7240             promotionChoice = TRUE;
7241             // kludge follows to temporarily execute move on display, without promoting yet
7242             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7243             boards[currentMove][toY][toX] = p;
7244             DrawPosition(FALSE, boards[currentMove]);
7245             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7246             boards[currentMove][toY][toX] = q;
7247             DisplayMessage("Click in holdings to choose piece", "");
7248             return;
7249         }
7250         PromotionPopUp();
7251     } else {
7252         int oldMove = currentMove;
7253         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7254         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7255         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7256         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7257            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7258             DrawPosition(TRUE, boards[currentMove]);
7259         fromX = fromY = -1;
7260     }
7261     appData.animate = saveAnimate;
7262     if (appData.animate || appData.animateDragging) {
7263         /* Undo animation damage if needed */
7264         DrawPosition(FALSE, NULL);
7265     }
7266 }
7267
7268 int
7269 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7270 {   // front-end-free part taken out of PieceMenuPopup
7271     int whichMenu; int xSqr, ySqr;
7272
7273     if(seekGraphUp) { // [HGM] seekgraph
7274         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7275         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7276         return -2;
7277     }
7278
7279     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7280          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7281         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7282         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7283         if(action == Press)   {
7284             originalFlip = flipView;
7285             flipView = !flipView; // temporarily flip board to see game from partners perspective
7286             DrawPosition(TRUE, partnerBoard);
7287             DisplayMessage(partnerStatus, "");
7288             partnerUp = TRUE;
7289         } else if(action == Release) {
7290             flipView = originalFlip;
7291             DrawPosition(TRUE, boards[currentMove]);
7292             partnerUp = FALSE;
7293         }
7294         return -2;
7295     }
7296
7297     xSqr = EventToSquare(x, BOARD_WIDTH);
7298     ySqr = EventToSquare(y, BOARD_HEIGHT);
7299     if (action == Release) {
7300         if(pieceSweep != EmptySquare) {
7301             EditPositionMenuEvent(pieceSweep, toX, toY);
7302             pieceSweep = EmptySquare;
7303         } else UnLoadPV(); // [HGM] pv
7304     }
7305     if (action != Press) return -2; // return code to be ignored
7306     switch (gameMode) {
7307       case IcsExamining:
7308         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7309       case EditPosition:
7310         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7311         if (xSqr < 0 || ySqr < 0) return -1;
7312         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7313         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7314         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7315         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7316         NextPiece(0);
7317         return 2; // grab
7318       case IcsObserving:
7319         if(!appData.icsEngineAnalyze) return -1;
7320       case IcsPlayingWhite:
7321       case IcsPlayingBlack:
7322         if(!appData.zippyPlay) goto noZip;
7323       case AnalyzeMode:
7324       case AnalyzeFile:
7325       case MachinePlaysWhite:
7326       case MachinePlaysBlack:
7327       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7328         if (!appData.dropMenu) {
7329           LoadPV(x, y);
7330           return 2; // flag front-end to grab mouse events
7331         }
7332         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7333            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7334       case EditGame:
7335       noZip:
7336         if (xSqr < 0 || ySqr < 0) return -1;
7337         if (!appData.dropMenu || appData.testLegality &&
7338             gameInfo.variant != VariantBughouse &&
7339             gameInfo.variant != VariantCrazyhouse) return -1;
7340         whichMenu = 1; // drop menu
7341         break;
7342       default:
7343         return -1;
7344     }
7345
7346     if (((*fromX = xSqr) < 0) ||
7347         ((*fromY = ySqr) < 0)) {
7348         *fromX = *fromY = -1;
7349         return -1;
7350     }
7351     if (flipView)
7352       *fromX = BOARD_WIDTH - 1 - *fromX;
7353     else
7354       *fromY = BOARD_HEIGHT - 1 - *fromY;
7355
7356     return whichMenu;
7357 }
7358
7359 void
7360 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7361 {
7362 //    char * hint = lastHint;
7363     FrontEndProgramStats stats;
7364
7365     stats.which = cps == &first ? 0 : 1;
7366     stats.depth = cpstats->depth;
7367     stats.nodes = cpstats->nodes;
7368     stats.score = cpstats->score;
7369     stats.time = cpstats->time;
7370     stats.pv = cpstats->movelist;
7371     stats.hint = lastHint;
7372     stats.an_move_index = 0;
7373     stats.an_move_count = 0;
7374
7375     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7376         stats.hint = cpstats->move_name;
7377         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7378         stats.an_move_count = cpstats->nr_moves;
7379     }
7380
7381     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
7382
7383     SetProgramStats( &stats );
7384 }
7385
7386 void
7387 ClearEngineOutputPane (int which)
7388 {
7389     static FrontEndProgramStats dummyStats;
7390     dummyStats.which = which;
7391     dummyStats.pv = "#";
7392     SetProgramStats( &dummyStats );
7393 }
7394
7395 #define MAXPLAYERS 500
7396
7397 char *
7398 TourneyStandings (int display)
7399 {
7400     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7401     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7402     char result, *p, *names[MAXPLAYERS];
7403
7404     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7405         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7406     names[0] = p = strdup(appData.participants);
7407     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7408
7409     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7410
7411     while(result = appData.results[nr]) {
7412         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7413         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7414         wScore = bScore = 0;
7415         switch(result) {
7416           case '+': wScore = 2; break;
7417           case '-': bScore = 2; break;
7418           case '=': wScore = bScore = 1; break;
7419           case ' ':
7420           case '*': return strdup("busy"); // tourney not finished
7421         }
7422         score[w] += wScore;
7423         score[b] += bScore;
7424         games[w]++;
7425         games[b]++;
7426         nr++;
7427     }
7428     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7429     for(w=0; w<nPlayers; w++) {
7430         bScore = -1;
7431         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7432         ranking[w] = b; points[w] = bScore; score[b] = -2;
7433     }
7434     p = malloc(nPlayers*34+1);
7435     for(w=0; w<nPlayers && w<display; w++)
7436         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7437     free(names[0]);
7438     return p;
7439 }
7440
7441 void
7442 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7443 {       // count all piece types
7444         int p, f, r;
7445         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7446         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7447         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7448                 p = board[r][f];
7449                 pCnt[p]++;
7450                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7451                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7452                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7453                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7454                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7455                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7456         }
7457 }
7458
7459 int
7460 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7461 {
7462         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7463         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7464
7465         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7466         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7467         if(myPawns == 2 && nMine == 3) // KPP
7468             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7469         if(myPawns == 1 && nMine == 2) // KP
7470             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7471         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7472             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7473         if(myPawns) return FALSE;
7474         if(pCnt[WhiteRook+side])
7475             return pCnt[BlackRook-side] ||
7476                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7477                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7478                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7479         if(pCnt[WhiteCannon+side]) {
7480             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7481             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7482         }
7483         if(pCnt[WhiteKnight+side])
7484             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7485         return FALSE;
7486 }
7487
7488 int
7489 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7490 {
7491         VariantClass v = gameInfo.variant;
7492
7493         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7494         if(v == VariantShatranj) return TRUE; // always winnable through baring
7495         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7496         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7497
7498         if(v == VariantXiangqi) {
7499                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7500
7501                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7502                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7503                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7504                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7505                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7506                 if(stale) // we have at least one last-rank P plus perhaps C
7507                     return majors // KPKX
7508                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7509                 else // KCA*E*
7510                     return pCnt[WhiteFerz+side] // KCAK
7511                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7512                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7513                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7514
7515         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7516                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7517
7518                 if(nMine == 1) return FALSE; // bare King
7519                 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
7520                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7521                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7522                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7523                 if(pCnt[WhiteKnight+side])
7524                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7525                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7526                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7527                 if(nBishops)
7528                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7529                 if(pCnt[WhiteAlfil+side])
7530                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7531                 if(pCnt[WhiteWazir+side])
7532                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7533         }
7534
7535         return TRUE;
7536 }
7537
7538 int
7539 CompareWithRights (Board b1, Board b2)
7540 {
7541     int rights = 0;
7542     if(!CompareBoards(b1, b2)) return FALSE;
7543     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7544     /* compare castling rights */
7545     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7546            rights++; /* King lost rights, while rook still had them */
7547     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7548         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7549            rights++; /* but at least one rook lost them */
7550     }
7551     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7552            rights++;
7553     if( b1[CASTLING][5] != NoRights ) {
7554         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7555            rights++;
7556     }
7557     return rights == 0;
7558 }
7559
7560 int
7561 Adjudicate (ChessProgramState *cps)
7562 {       // [HGM] some adjudications useful with buggy engines
7563         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7564         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7565         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7566         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7567         int k, count = 0; static int bare = 1;
7568         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7569         Boolean canAdjudicate = !appData.icsActive;
7570
7571         // most tests only when we understand the game, i.e. legality-checking on
7572             if( appData.testLegality )
7573             {   /* [HGM] Some more adjudications for obstinate engines */
7574                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7575                 static int moveCount = 6;
7576                 ChessMove result;
7577                 char *reason = NULL;
7578
7579                 /* Count what is on board. */
7580                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7581
7582                 /* Some material-based adjudications that have to be made before stalemate test */
7583                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7584                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7585                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7586                      if(canAdjudicate && appData.checkMates) {
7587                          if(engineOpponent)
7588                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7589                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7590                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7591                          return 1;
7592                      }
7593                 }
7594
7595                 /* Bare King in Shatranj (loses) or Losers (wins) */
7596                 if( nrW == 1 || nrB == 1) {
7597                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7598                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7599                      if(canAdjudicate && appData.checkMates) {
7600                          if(engineOpponent)
7601                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7602                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7603                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7604                          return 1;
7605                      }
7606                   } else
7607                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7608                   {    /* bare King */
7609                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7610                         if(canAdjudicate && appData.checkMates) {
7611                             /* but only adjudicate if adjudication enabled */
7612                             if(engineOpponent)
7613                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7614                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7615                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7616                             return 1;
7617                         }
7618                   }
7619                 } else bare = 1;
7620
7621
7622             // don't wait for engine to announce game end if we can judge ourselves
7623             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7624               case MT_CHECK:
7625                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7626                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7627                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7628                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7629                             checkCnt++;
7630                         if(checkCnt >= 2) {
7631                             reason = "Xboard adjudication: 3rd check";
7632                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7633                             break;
7634                         }
7635                     }
7636                 }
7637               case MT_NONE:
7638               default:
7639                 break;
7640               case MT_STALEMATE:
7641               case MT_STAINMATE:
7642                 reason = "Xboard adjudication: Stalemate";
7643                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7644                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7645                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7646                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7647                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7648                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7649                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7650                                                                         EP_CHECKMATE : EP_WINS);
7651                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7652                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7653                 }
7654                 break;
7655               case MT_CHECKMATE:
7656                 reason = "Xboard adjudication: Checkmate";
7657                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7658                 break;
7659             }
7660
7661                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7662                     case EP_STALEMATE:
7663                         result = GameIsDrawn; break;
7664                     case EP_CHECKMATE:
7665                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7666                     case EP_WINS:
7667                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7668                     default:
7669                         result = EndOfFile;
7670                 }
7671                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7672                     if(engineOpponent)
7673                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7674                     GameEnds( result, reason, GE_XBOARD );
7675                     return 1;
7676                 }
7677
7678                 /* Next absolutely insufficient mating material. */
7679                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7680                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7681                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7682
7683                      /* always flag draws, for judging claims */
7684                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7685
7686                      if(canAdjudicate && appData.materialDraws) {
7687                          /* but only adjudicate them if adjudication enabled */
7688                          if(engineOpponent) {
7689                            SendToProgram("force\n", engineOpponent); // suppress reply
7690                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7691                          }
7692                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7693                          return 1;
7694                      }
7695                 }
7696
7697                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7698                 if(gameInfo.variant == VariantXiangqi ?
7699                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7700                  : nrW + nrB == 4 &&
7701                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7702                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7703                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7704                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7705                    ) ) {
7706                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7707                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7708                           if(engineOpponent) {
7709                             SendToProgram("force\n", engineOpponent); // suppress reply
7710                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7711                           }
7712                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7713                           return 1;
7714                      }
7715                 } else moveCount = 6;
7716             }
7717
7718         // Repetition draws and 50-move rule can be applied independently of legality testing
7719
7720                 /* Check for rep-draws */
7721                 count = 0;
7722                 for(k = forwardMostMove-2;
7723                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7724                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7725                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7726                     k-=2)
7727                 {   int rights=0;
7728                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7729                         /* compare castling rights */
7730                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7731                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7732                                 rights++; /* King lost rights, while rook still had them */
7733                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7734                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7735                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7736                                    rights++; /* but at least one rook lost them */
7737                         }
7738                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7739                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7740                                 rights++;
7741                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7742                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7743                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7744                                    rights++;
7745                         }
7746                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7747                             && appData.drawRepeats > 1) {
7748                              /* adjudicate after user-specified nr of repeats */
7749                              int result = GameIsDrawn;
7750                              char *details = "XBoard adjudication: repetition draw";
7751                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7752                                 // [HGM] xiangqi: check for forbidden perpetuals
7753                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7754                                 for(m=forwardMostMove; m>k; m-=2) {
7755                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7756                                         ourPerpetual = 0; // the current mover did not always check
7757                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7758                                         hisPerpetual = 0; // the opponent did not always check
7759                                 }
7760                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7761                                                                         ourPerpetual, hisPerpetual);
7762                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7763                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7764                                     details = "Xboard adjudication: perpetual checking";
7765                                 } else
7766                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7767                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7768                                 } else
7769                                 // Now check for perpetual chases
7770                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7771                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7772                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7773                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7774                                         static char resdet[MSG_SIZ];
7775                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7776                                         details = resdet;
7777                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7778                                     } else
7779                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7780                                         break; // Abort repetition-checking loop.
7781                                 }
7782                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7783                              }
7784                              if(engineOpponent) {
7785                                SendToProgram("force\n", engineOpponent); // suppress reply
7786                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7787                              }
7788                              GameEnds( result, details, GE_XBOARD );
7789                              return 1;
7790                         }
7791                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7792                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7793                     }
7794                 }
7795
7796                 /* Now we test for 50-move draws. Determine ply count */
7797                 count = forwardMostMove;
7798                 /* look for last irreversble move */
7799                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7800                     count--;
7801                 /* if we hit starting position, add initial plies */
7802                 if( count == backwardMostMove )
7803                     count -= initialRulePlies;
7804                 count = forwardMostMove - count;
7805                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7806                         // adjust reversible move counter for checks in Xiangqi
7807                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7808                         if(i < backwardMostMove) i = backwardMostMove;
7809                         while(i <= forwardMostMove) {
7810                                 lastCheck = inCheck; // check evasion does not count
7811                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7812                                 if(inCheck || lastCheck) count--; // check does not count
7813                                 i++;
7814                         }
7815                 }
7816                 if( count >= 100)
7817                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7818                          /* this is used to judge if draw claims are legal */
7819                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7820                          if(engineOpponent) {
7821                            SendToProgram("force\n", engineOpponent); // suppress reply
7822                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7823                          }
7824                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7825                          return 1;
7826                 }
7827
7828                 /* if draw offer is pending, treat it as a draw claim
7829                  * when draw condition present, to allow engines a way to
7830                  * claim draws before making their move to avoid a race
7831                  * condition occurring after their move
7832                  */
7833                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7834                          char *p = NULL;
7835                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7836                              p = "Draw claim: 50-move rule";
7837                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7838                              p = "Draw claim: 3-fold repetition";
7839                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7840                              p = "Draw claim: insufficient mating material";
7841                          if( p != NULL && canAdjudicate) {
7842                              if(engineOpponent) {
7843                                SendToProgram("force\n", engineOpponent); // suppress reply
7844                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7845                              }
7846                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7847                              return 1;
7848                          }
7849                 }
7850
7851                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7852                     if(engineOpponent) {
7853                       SendToProgram("force\n", engineOpponent); // suppress reply
7854                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7855                     }
7856                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7857                     return 1;
7858                 }
7859         return 0;
7860 }
7861
7862 char *
7863 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7864 {   // [HGM] book: this routine intercepts moves to simulate book replies
7865     char *bookHit = NULL;
7866
7867     //first determine if the incoming move brings opponent into his book
7868     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7869         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7870     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7871     if(bookHit != NULL && !cps->bookSuspend) {
7872         // make sure opponent is not going to reply after receiving move to book position
7873         SendToProgram("force\n", cps);
7874         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7875     }
7876     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7877     // now arrange restart after book miss
7878     if(bookHit) {
7879         // after a book hit we never send 'go', and the code after the call to this routine
7880         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7881         char buf[MSG_SIZ], *move = bookHit;
7882         if(cps->useSAN) {
7883             int fromX, fromY, toX, toY;
7884             char promoChar;
7885             ChessMove moveType;
7886             move = buf + 30;
7887             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7888                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7889                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7890                                     PosFlags(forwardMostMove),
7891                                     fromY, fromX, toY, toX, promoChar, move);
7892             } else {
7893                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7894                 bookHit = NULL;
7895             }
7896         }
7897         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7898         SendToProgram(buf, cps);
7899         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7900     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7901         SendToProgram("go\n", cps);
7902         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7903     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7904         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7905             SendToProgram("go\n", cps);
7906         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7907     }
7908     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7909 }
7910
7911 char *savedMessage;
7912 ChessProgramState *savedState;
7913 void
7914 DeferredBookMove (void)
7915 {
7916         if(savedState->lastPing != savedState->lastPong)
7917                     ScheduleDelayedEvent(DeferredBookMove, 10);
7918         else
7919         HandleMachineMove(savedMessage, savedState);
7920 }
7921
7922 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7923
7924 void
7925 HandleMachineMove (char *message, ChessProgramState *cps)
7926 {
7927     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7928     char realname[MSG_SIZ];
7929     int fromX, fromY, toX, toY;
7930     ChessMove moveType;
7931     char promoChar;
7932     char *p, *pv=buf1;
7933     int machineWhite;
7934     char *bookHit;
7935
7936     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7937         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7938         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7939             DisplayError(_("Invalid pairing from pairing engine"), 0);
7940             return;
7941         }
7942         pairingReceived = 1;
7943         NextMatchGame();
7944         return; // Skim the pairing messages here.
7945     }
7946
7947     cps->userError = 0;
7948
7949 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7950     /*
7951      * Kludge to ignore BEL characters
7952      */
7953     while (*message == '\007') message++;
7954
7955     /*
7956      * [HGM] engine debug message: ignore lines starting with '#' character
7957      */
7958     if(cps->debug && *message == '#') return;
7959
7960     /*
7961      * Look for book output
7962      */
7963     if (cps == &first && bookRequested) {
7964         if (message[0] == '\t' || message[0] == ' ') {
7965             /* Part of the book output is here; append it */
7966             strcat(bookOutput, message);
7967             strcat(bookOutput, "  \n");
7968             return;
7969         } else if (bookOutput[0] != NULLCHAR) {
7970             /* All of book output has arrived; display it */
7971             char *p = bookOutput;
7972             while (*p != NULLCHAR) {
7973                 if (*p == '\t') *p = ' ';
7974                 p++;
7975             }
7976             DisplayInformation(bookOutput);
7977             bookRequested = FALSE;
7978             /* Fall through to parse the current output */
7979         }
7980     }
7981
7982     /*
7983      * Look for machine move.
7984      */
7985     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7986         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7987     {
7988         /* This method is only useful on engines that support ping */
7989         if (cps->lastPing != cps->lastPong) {
7990           if (gameMode == BeginningOfGame) {
7991             /* Extra move from before last new; ignore */
7992             if (appData.debugMode) {
7993                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7994             }
7995           } else {
7996             if (appData.debugMode) {
7997                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7998                         cps->which, gameMode);
7999             }
8000
8001             SendToProgram("undo\n", cps);
8002           }
8003           return;
8004         }
8005
8006         switch (gameMode) {
8007           case BeginningOfGame:
8008             /* Extra move from before last reset; ignore */
8009             if (appData.debugMode) {
8010                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8011             }
8012             return;
8013
8014           case EndOfGame:
8015           case IcsIdle:
8016           default:
8017             /* Extra move after we tried to stop.  The mode test is
8018                not a reliable way of detecting this problem, but it's
8019                the best we can do on engines that don't support ping.
8020             */
8021             if (appData.debugMode) {
8022                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8023                         cps->which, gameMode);
8024             }
8025             SendToProgram("undo\n", cps);
8026             return;
8027
8028           case MachinePlaysWhite:
8029           case IcsPlayingWhite:
8030             machineWhite = TRUE;
8031             break;
8032
8033           case MachinePlaysBlack:
8034           case IcsPlayingBlack:
8035             machineWhite = FALSE;
8036             break;
8037
8038           case TwoMachinesPlay:
8039             machineWhite = (cps->twoMachinesColor[0] == 'w');
8040             break;
8041         }
8042         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8043             if (appData.debugMode) {
8044                 fprintf(debugFP,
8045                         "Ignoring move out of turn by %s, gameMode %d"
8046                         ", forwardMost %d\n",
8047                         cps->which, gameMode, forwardMostMove);
8048             }
8049             return;
8050         }
8051
8052         if(cps->alphaRank) AlphaRank(machineMove, 4);
8053         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8054                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8055             /* Machine move could not be parsed; ignore it. */
8056           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8057                     machineMove, _(cps->which));
8058             DisplayError(buf1, 0);
8059             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8060                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8061             if (gameMode == TwoMachinesPlay) {
8062               GameEnds(machineWhite ? BlackWins : WhiteWins,
8063                        buf1, GE_XBOARD);
8064             }
8065             return;
8066         }
8067
8068         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8069         /* So we have to redo legality test with true e.p. status here,  */
8070         /* to make sure an illegal e.p. capture does not slip through,   */
8071         /* to cause a forfeit on a justified illegal-move complaint      */
8072         /* of the opponent.                                              */
8073         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8074            ChessMove moveType;
8075            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8076                              fromY, fromX, toY, toX, promoChar);
8077             if(moveType == IllegalMove) {
8078               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8079                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8080                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8081                            buf1, GE_XBOARD);
8082                 return;
8083            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8084            /* [HGM] Kludge to handle engines that send FRC-style castling
8085               when they shouldn't (like TSCP-Gothic) */
8086            switch(moveType) {
8087              case WhiteASideCastleFR:
8088              case BlackASideCastleFR:
8089                toX+=2;
8090                currentMoveString[2]++;
8091                break;
8092              case WhiteHSideCastleFR:
8093              case BlackHSideCastleFR:
8094                toX--;
8095                currentMoveString[2]--;
8096                break;
8097              default: ; // nothing to do, but suppresses warning of pedantic compilers
8098            }
8099         }
8100         hintRequested = FALSE;
8101         lastHint[0] = NULLCHAR;
8102         bookRequested = FALSE;
8103         /* Program may be pondering now */
8104         cps->maybeThinking = TRUE;
8105         if (cps->sendTime == 2) cps->sendTime = 1;
8106         if (cps->offeredDraw) cps->offeredDraw--;
8107
8108         /* [AS] Save move info*/
8109         pvInfoList[ forwardMostMove ].score = programStats.score;
8110         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8111         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8112
8113         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8114
8115         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8116         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8117             int count = 0;
8118
8119             while( count < adjudicateLossPlies ) {
8120                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8121
8122                 if( count & 1 ) {
8123                     score = -score; /* Flip score for winning side */
8124                 }
8125
8126                 if( score > adjudicateLossThreshold ) {
8127                     break;
8128                 }
8129
8130                 count++;
8131             }
8132
8133             if( count >= adjudicateLossPlies ) {
8134                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8135
8136                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8137                     "Xboard adjudication",
8138                     GE_XBOARD );
8139
8140                 return;
8141             }
8142         }
8143
8144         if(Adjudicate(cps)) {
8145             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8146             return; // [HGM] adjudicate: for all automatic game ends
8147         }
8148
8149 #if ZIPPY
8150         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8151             first.initDone) {
8152           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8153                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8154                 SendToICS("draw ");
8155                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8156           }
8157           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8158           ics_user_moved = 1;
8159           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8160                 char buf[3*MSG_SIZ];
8161
8162                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8163                         programStats.score / 100.,
8164                         programStats.depth,
8165                         programStats.time / 100.,
8166                         (unsigned int)programStats.nodes,
8167                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8168                         programStats.movelist);
8169                 SendToICS(buf);
8170 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8171           }
8172         }
8173 #endif
8174
8175         /* [AS] Clear stats for next move */
8176         ClearProgramStats();
8177         thinkOutput[0] = NULLCHAR;
8178         hiddenThinkOutputState = 0;
8179
8180         bookHit = NULL;
8181         if (gameMode == TwoMachinesPlay) {
8182             /* [HGM] relaying draw offers moved to after reception of move */
8183             /* and interpreting offer as claim if it brings draw condition */
8184             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8185                 SendToProgram("draw\n", cps->other);
8186             }
8187             if (cps->other->sendTime) {
8188                 SendTimeRemaining(cps->other,
8189                                   cps->other->twoMachinesColor[0] == 'w');
8190             }
8191             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8192             if (firstMove && !bookHit) {
8193                 firstMove = FALSE;
8194                 if (cps->other->useColors) {
8195                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8196                 }
8197                 SendToProgram("go\n", cps->other);
8198             }
8199             cps->other->maybeThinking = TRUE;
8200         }
8201
8202         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8203
8204         if (!pausing && appData.ringBellAfterMoves) {
8205             RingBell();
8206         }
8207
8208         /*
8209          * Reenable menu items that were disabled while
8210          * machine was thinking
8211          */
8212         if (gameMode != TwoMachinesPlay)
8213             SetUserThinkingEnables();
8214
8215         // [HGM] book: after book hit opponent has received move and is now in force mode
8216         // force the book reply into it, and then fake that it outputted this move by jumping
8217         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8218         if(bookHit) {
8219                 static char bookMove[MSG_SIZ]; // a bit generous?
8220
8221                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8222                 strcat(bookMove, bookHit);
8223                 message = bookMove;
8224                 cps = cps->other;
8225                 programStats.nodes = programStats.depth = programStats.time =
8226                 programStats.score = programStats.got_only_move = 0;
8227                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8228
8229                 if(cps->lastPing != cps->lastPong) {
8230                     savedMessage = message; // args for deferred call
8231                     savedState = cps;
8232                     ScheduleDelayedEvent(DeferredBookMove, 10);
8233                     return;
8234                 }
8235                 goto FakeBookMove;
8236         }
8237
8238         return;
8239     }
8240
8241     /* Set special modes for chess engines.  Later something general
8242      *  could be added here; for now there is just one kludge feature,
8243      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8244      *  when "xboard" is given as an interactive command.
8245      */
8246     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8247         cps->useSigint = FALSE;
8248         cps->useSigterm = FALSE;
8249     }
8250     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8251       ParseFeatures(message+8, cps);
8252       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8253     }
8254
8255     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8256                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8257       int dummy, s=6; char buf[MSG_SIZ];
8258       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8259       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8260       if(startedFromSetupPosition) return;
8261       ParseFEN(boards[0], &dummy, message+s);
8262       DrawPosition(TRUE, boards[0]);
8263       startedFromSetupPosition = TRUE;
8264       return;
8265     }
8266     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8267      * want this, I was asked to put it in, and obliged.
8268      */
8269     if (!strncmp(message, "setboard ", 9)) {
8270         Board initial_position;
8271
8272         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8273
8274         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8275             DisplayError(_("Bad FEN received from engine"), 0);
8276             return ;
8277         } else {
8278            Reset(TRUE, FALSE);
8279            CopyBoard(boards[0], initial_position);
8280            initialRulePlies = FENrulePlies;
8281            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8282            else gameMode = MachinePlaysBlack;
8283            DrawPosition(FALSE, boards[currentMove]);
8284         }
8285         return;
8286     }
8287
8288     /*
8289      * Look for communication commands
8290      */
8291     if (!strncmp(message, "telluser ", 9)) {
8292         if(message[9] == '\\' && message[10] == '\\')
8293             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8294         PlayTellSound();
8295         DisplayNote(message + 9);
8296         return;
8297     }
8298     if (!strncmp(message, "tellusererror ", 14)) {
8299         cps->userError = 1;
8300         if(message[14] == '\\' && message[15] == '\\')
8301             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8302         PlayTellSound();
8303         DisplayError(message + 14, 0);
8304         return;
8305     }
8306     if (!strncmp(message, "tellopponent ", 13)) {
8307       if (appData.icsActive) {
8308         if (loggedOn) {
8309           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8310           SendToICS(buf1);
8311         }
8312       } else {
8313         DisplayNote(message + 13);
8314       }
8315       return;
8316     }
8317     if (!strncmp(message, "tellothers ", 11)) {
8318       if (appData.icsActive) {
8319         if (loggedOn) {
8320           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8321           SendToICS(buf1);
8322         }
8323       }
8324       return;
8325     }
8326     if (!strncmp(message, "tellall ", 8)) {
8327       if (appData.icsActive) {
8328         if (loggedOn) {
8329           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8330           SendToICS(buf1);
8331         }
8332       } else {
8333         DisplayNote(message + 8);
8334       }
8335       return;
8336     }
8337     if (strncmp(message, "warning", 7) == 0) {
8338         /* Undocumented feature, use tellusererror in new code */
8339         DisplayError(message, 0);
8340         return;
8341     }
8342     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8343         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8344         strcat(realname, " query");
8345         AskQuestion(realname, buf2, buf1, cps->pr);
8346         return;
8347     }
8348     /* Commands from the engine directly to ICS.  We don't allow these to be
8349      *  sent until we are logged on. Crafty kibitzes have been known to
8350      *  interfere with the login process.
8351      */
8352     if (loggedOn) {
8353         if (!strncmp(message, "tellics ", 8)) {
8354             SendToICS(message + 8);
8355             SendToICS("\n");
8356             return;
8357         }
8358         if (!strncmp(message, "tellicsnoalias ", 15)) {
8359             SendToICS(ics_prefix);
8360             SendToICS(message + 15);
8361             SendToICS("\n");
8362             return;
8363         }
8364         /* The following are for backward compatibility only */
8365         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8366             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8367             SendToICS(ics_prefix);
8368             SendToICS(message);
8369             SendToICS("\n");
8370             return;
8371         }
8372     }
8373     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8374         return;
8375     }
8376     /*
8377      * If the move is illegal, cancel it and redraw the board.
8378      * Also deal with other error cases.  Matching is rather loose
8379      * here to accommodate engines written before the spec.
8380      */
8381     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8382         strncmp(message, "Error", 5) == 0) {
8383         if (StrStr(message, "name") ||
8384             StrStr(message, "rating") || StrStr(message, "?") ||
8385             StrStr(message, "result") || StrStr(message, "board") ||
8386             StrStr(message, "bk") || StrStr(message, "computer") ||
8387             StrStr(message, "variant") || StrStr(message, "hint") ||
8388             StrStr(message, "random") || StrStr(message, "depth") ||
8389             StrStr(message, "accepted")) {
8390             return;
8391         }
8392         if (StrStr(message, "protover")) {
8393           /* Program is responding to input, so it's apparently done
8394              initializing, and this error message indicates it is
8395              protocol version 1.  So we don't need to wait any longer
8396              for it to initialize and send feature commands. */
8397           FeatureDone(cps, 1);
8398           cps->protocolVersion = 1;
8399           return;
8400         }
8401         cps->maybeThinking = FALSE;
8402
8403         if (StrStr(message, "draw")) {
8404             /* Program doesn't have "draw" command */
8405             cps->sendDrawOffers = 0;
8406             return;
8407         }
8408         if (cps->sendTime != 1 &&
8409             (StrStr(message, "time") || StrStr(message, "otim"))) {
8410           /* Program apparently doesn't have "time" or "otim" command */
8411           cps->sendTime = 0;
8412           return;
8413         }
8414         if (StrStr(message, "analyze")) {
8415             cps->analysisSupport = FALSE;
8416             cps->analyzing = FALSE;
8417 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8418             EditGameEvent(); // [HGM] try to preserve loaded game
8419             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8420             DisplayError(buf2, 0);
8421             return;
8422         }
8423         if (StrStr(message, "(no matching move)st")) {
8424           /* Special kludge for GNU Chess 4 only */
8425           cps->stKludge = TRUE;
8426           SendTimeControl(cps, movesPerSession, timeControl,
8427                           timeIncrement, appData.searchDepth,
8428                           searchTime);
8429           return;
8430         }
8431         if (StrStr(message, "(no matching move)sd")) {
8432           /* Special kludge for GNU Chess 4 only */
8433           cps->sdKludge = TRUE;
8434           SendTimeControl(cps, movesPerSession, timeControl,
8435                           timeIncrement, appData.searchDepth,
8436                           searchTime);
8437           return;
8438         }
8439         if (!StrStr(message, "llegal")) {
8440             return;
8441         }
8442         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8443             gameMode == IcsIdle) return;
8444         if (forwardMostMove <= backwardMostMove) return;
8445         if (pausing) PauseEvent();
8446       if(appData.forceIllegal) {
8447             // [HGM] illegal: machine refused move; force position after move into it
8448           SendToProgram("force\n", cps);
8449           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8450                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8451                 // when black is to move, while there might be nothing on a2 or black
8452                 // might already have the move. So send the board as if white has the move.
8453                 // But first we must change the stm of the engine, as it refused the last move
8454                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8455                 if(WhiteOnMove(forwardMostMove)) {
8456                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8457                     SendBoard(cps, forwardMostMove); // kludgeless board
8458                 } else {
8459                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8460                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8461                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8462                 }
8463           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8464             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8465                  gameMode == TwoMachinesPlay)
8466               SendToProgram("go\n", cps);
8467             return;
8468       } else
8469         if (gameMode == PlayFromGameFile) {
8470             /* Stop reading this game file */
8471             gameMode = EditGame;
8472             ModeHighlight();
8473         }
8474         /* [HGM] illegal-move claim should forfeit game when Xboard */
8475         /* only passes fully legal moves                            */
8476         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8477             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8478                                 "False illegal-move claim", GE_XBOARD );
8479             return; // do not take back move we tested as valid
8480         }
8481         currentMove = forwardMostMove-1;
8482         DisplayMove(currentMove-1); /* before DisplayMoveError */
8483         SwitchClocks(forwardMostMove-1); // [HGM] race
8484         DisplayBothClocks();
8485         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8486                 parseList[currentMove], _(cps->which));
8487         DisplayMoveError(buf1);
8488         DrawPosition(FALSE, boards[currentMove]);
8489
8490         SetUserThinkingEnables();
8491         return;
8492     }
8493     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8494         /* Program has a broken "time" command that
8495            outputs a string not ending in newline.
8496            Don't use it. */
8497         cps->sendTime = 0;
8498     }
8499
8500     /*
8501      * If chess program startup fails, exit with an error message.
8502      * Attempts to recover here are futile. [HGM] Well, we try anyway
8503      */
8504     if ((StrStr(message, "unknown host") != NULL)
8505         || (StrStr(message, "No remote directory") != NULL)
8506         || (StrStr(message, "not found") != NULL)
8507         || (StrStr(message, "No such file") != NULL)
8508         || (StrStr(message, "can't alloc") != NULL)
8509         || (StrStr(message, "Permission denied") != NULL)) {
8510
8511         cps->maybeThinking = FALSE;
8512         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8513                 _(cps->which), cps->program, cps->host, message);
8514         RemoveInputSource(cps->isr);
8515         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8516             cps->isr = NULL;
8517             DestroyChildProcess(cps->pr, 9 ); // just to be sure
8518             cps->pr = NoProc; 
8519             if(cps == &first) {
8520                 appData.noChessProgram = TRUE;
8521                 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8522                 gameMode = BeginningOfGame; ModeHighlight();
8523                 SetNCPMode();
8524             }
8525             if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8526             DisplayMessage("", ""); // erase waiting message
8527             DisplayError(buf1, 0);
8528         }
8529         return;
8530     }
8531
8532     /*
8533      * Look for hint output
8534      */
8535     if (sscanf(message, "Hint: %s", buf1) == 1) {
8536         if (cps == &first && hintRequested) {
8537             hintRequested = FALSE;
8538             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8539                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8540                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8541                                     PosFlags(forwardMostMove),
8542                                     fromY, fromX, toY, toX, promoChar, buf1);
8543                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8544                 DisplayInformation(buf2);
8545             } else {
8546                 /* Hint move could not be parsed!? */
8547               snprintf(buf2, sizeof(buf2),
8548                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8549                         buf1, _(cps->which));
8550                 DisplayError(buf2, 0);
8551             }
8552         } else {
8553           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8554         }
8555         return;
8556     }
8557
8558     /*
8559      * Ignore other messages if game is not in progress
8560      */
8561     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8562         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8563
8564     /*
8565      * look for win, lose, draw, or draw offer
8566      */
8567     if (strncmp(message, "1-0", 3) == 0) {
8568         char *p, *q, *r = "";
8569         p = strchr(message, '{');
8570         if (p) {
8571             q = strchr(p, '}');
8572             if (q) {
8573                 *q = NULLCHAR;
8574                 r = p + 1;
8575             }
8576         }
8577         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8578         return;
8579     } else if (strncmp(message, "0-1", 3) == 0) {
8580         char *p, *q, *r = "";
8581         p = strchr(message, '{');
8582         if (p) {
8583             q = strchr(p, '}');
8584             if (q) {
8585                 *q = NULLCHAR;
8586                 r = p + 1;
8587             }
8588         }
8589         /* Kludge for Arasan 4.1 bug */
8590         if (strcmp(r, "Black resigns") == 0) {
8591             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8592             return;
8593         }
8594         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8595         return;
8596     } else if (strncmp(message, "1/2", 3) == 0) {
8597         char *p, *q, *r = "";
8598         p = strchr(message, '{');
8599         if (p) {
8600             q = strchr(p, '}');
8601             if (q) {
8602                 *q = NULLCHAR;
8603                 r = p + 1;
8604             }
8605         }
8606
8607         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8608         return;
8609
8610     } else if (strncmp(message, "White resign", 12) == 0) {
8611         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8612         return;
8613     } else if (strncmp(message, "Black resign", 12) == 0) {
8614         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8615         return;
8616     } else if (strncmp(message, "White matches", 13) == 0 ||
8617                strncmp(message, "Black matches", 13) == 0   ) {
8618         /* [HGM] ignore GNUShogi noises */
8619         return;
8620     } else if (strncmp(message, "White", 5) == 0 &&
8621                message[5] != '(' &&
8622                StrStr(message, "Black") == NULL) {
8623         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8624         return;
8625     } else if (strncmp(message, "Black", 5) == 0 &&
8626                message[5] != '(') {
8627         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8628         return;
8629     } else if (strcmp(message, "resign") == 0 ||
8630                strcmp(message, "computer resigns") == 0) {
8631         switch (gameMode) {
8632           case MachinePlaysBlack:
8633           case IcsPlayingBlack:
8634             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8635             break;
8636           case MachinePlaysWhite:
8637           case IcsPlayingWhite:
8638             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8639             break;
8640           case TwoMachinesPlay:
8641             if (cps->twoMachinesColor[0] == 'w')
8642               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8643             else
8644               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8645             break;
8646           default:
8647             /* can't happen */
8648             break;
8649         }
8650         return;
8651     } else if (strncmp(message, "opponent mates", 14) == 0) {
8652         switch (gameMode) {
8653           case MachinePlaysBlack:
8654           case IcsPlayingBlack:
8655             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8656             break;
8657           case MachinePlaysWhite:
8658           case IcsPlayingWhite:
8659             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8660             break;
8661           case TwoMachinesPlay:
8662             if (cps->twoMachinesColor[0] == 'w')
8663               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8664             else
8665               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8666             break;
8667           default:
8668             /* can't happen */
8669             break;
8670         }
8671         return;
8672     } else if (strncmp(message, "computer mates", 14) == 0) {
8673         switch (gameMode) {
8674           case MachinePlaysBlack:
8675           case IcsPlayingBlack:
8676             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8677             break;
8678           case MachinePlaysWhite:
8679           case IcsPlayingWhite:
8680             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8681             break;
8682           case TwoMachinesPlay:
8683             if (cps->twoMachinesColor[0] == 'w')
8684               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8685             else
8686               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8687             break;
8688           default:
8689             /* can't happen */
8690             break;
8691         }
8692         return;
8693     } else if (strncmp(message, "checkmate", 9) == 0) {
8694         if (WhiteOnMove(forwardMostMove)) {
8695             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8696         } else {
8697             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8698         }
8699         return;
8700     } else if (strstr(message, "Draw") != NULL ||
8701                strstr(message, "game is a draw") != NULL) {
8702         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8703         return;
8704     } else if (strstr(message, "offer") != NULL &&
8705                strstr(message, "draw") != NULL) {
8706 #if ZIPPY
8707         if (appData.zippyPlay && first.initDone) {
8708             /* Relay offer to ICS */
8709             SendToICS(ics_prefix);
8710             SendToICS("draw\n");
8711         }
8712 #endif
8713         cps->offeredDraw = 2; /* valid until this engine moves twice */
8714         if (gameMode == TwoMachinesPlay) {
8715             if (cps->other->offeredDraw) {
8716                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8717             /* [HGM] in two-machine mode we delay relaying draw offer      */
8718             /* until after we also have move, to see if it is really claim */
8719             }
8720         } else if (gameMode == MachinePlaysWhite ||
8721                    gameMode == MachinePlaysBlack) {
8722           if (userOfferedDraw) {
8723             DisplayInformation(_("Machine accepts your draw offer"));
8724             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8725           } else {
8726             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8727           }
8728         }
8729     }
8730
8731
8732     /*
8733      * Look for thinking output
8734      */
8735     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8736           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8737                                 ) {
8738         int plylev, mvleft, mvtot, curscore, time;
8739         char mvname[MOVE_LEN];
8740         u64 nodes; // [DM]
8741         char plyext;
8742         int ignore = FALSE;
8743         int prefixHint = FALSE;
8744         mvname[0] = NULLCHAR;
8745
8746         switch (gameMode) {
8747           case MachinePlaysBlack:
8748           case IcsPlayingBlack:
8749             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8750             break;
8751           case MachinePlaysWhite:
8752           case IcsPlayingWhite:
8753             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8754             break;
8755           case AnalyzeMode:
8756           case AnalyzeFile:
8757             break;
8758           case IcsObserving: /* [DM] icsEngineAnalyze */
8759             if (!appData.icsEngineAnalyze) ignore = TRUE;
8760             break;
8761           case TwoMachinesPlay:
8762             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8763                 ignore = TRUE;
8764             }
8765             break;
8766           default:
8767             ignore = TRUE;
8768             break;
8769         }
8770
8771         if (!ignore) {
8772             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8773             buf1[0] = NULLCHAR;
8774             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8775                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8776
8777                 if (plyext != ' ' && plyext != '\t') {
8778                     time *= 100;
8779                 }
8780
8781                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8782                 if( cps->scoreIsAbsolute &&
8783                     ( gameMode == MachinePlaysBlack ||
8784                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8785                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8786                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8787                      !WhiteOnMove(currentMove)
8788                     ) )
8789                 {
8790                     curscore = -curscore;
8791                 }
8792
8793                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8794
8795                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8796                         char buf[MSG_SIZ];
8797                         FILE *f;
8798                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8799                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8800                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8801                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8802                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8803                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8804                                 fclose(f);
8805                         } else DisplayError(_("failed writing PV"), 0);
8806                 }
8807
8808                 tempStats.depth = plylev;
8809                 tempStats.nodes = nodes;
8810                 tempStats.time = time;
8811                 tempStats.score = curscore;
8812                 tempStats.got_only_move = 0;
8813
8814                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8815                         int ticklen;
8816
8817                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8818                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8819                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8820                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8821                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8822                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8823                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8824                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8825                 }
8826
8827                 /* Buffer overflow protection */
8828                 if (pv[0] != NULLCHAR) {
8829                     if (strlen(pv) >= sizeof(tempStats.movelist)
8830                         && appData.debugMode) {
8831                         fprintf(debugFP,
8832                                 "PV is too long; using the first %u bytes.\n",
8833                                 (unsigned) sizeof(tempStats.movelist) - 1);
8834                     }
8835
8836                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8837                 } else {
8838                     sprintf(tempStats.movelist, " no PV\n");
8839                 }
8840
8841                 if (tempStats.seen_stat) {
8842                     tempStats.ok_to_send = 1;
8843                 }
8844
8845                 if (strchr(tempStats.movelist, '(') != NULL) {
8846                     tempStats.line_is_book = 1;
8847                     tempStats.nr_moves = 0;
8848                     tempStats.moves_left = 0;
8849                 } else {
8850                     tempStats.line_is_book = 0;
8851                 }
8852
8853                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8854                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8855
8856                 SendProgramStatsToFrontend( cps, &tempStats );
8857
8858                 /*
8859                     [AS] Protect the thinkOutput buffer from overflow... this
8860                     is only useful if buf1 hasn't overflowed first!
8861                 */
8862                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8863                          plylev,
8864                          (gameMode == TwoMachinesPlay ?
8865                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8866                          ((double) curscore) / 100.0,
8867                          prefixHint ? lastHint : "",
8868                          prefixHint ? " " : "" );
8869
8870                 if( buf1[0] != NULLCHAR ) {
8871                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8872
8873                     if( strlen(pv) > max_len ) {
8874                         if( appData.debugMode) {
8875                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8876                         }
8877                         pv[max_len+1] = '\0';
8878                     }
8879
8880                     strcat( thinkOutput, pv);
8881                 }
8882
8883                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8884                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8885                     DisplayMove(currentMove - 1);
8886                 }
8887                 return;
8888
8889             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8890                 /* crafty (9.25+) says "(only move) <move>"
8891                  * if there is only 1 legal move
8892                  */
8893                 sscanf(p, "(only move) %s", buf1);
8894                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8895                 sprintf(programStats.movelist, "%s (only move)", buf1);
8896                 programStats.depth = 1;
8897                 programStats.nr_moves = 1;
8898                 programStats.moves_left = 1;
8899                 programStats.nodes = 1;
8900                 programStats.time = 1;
8901                 programStats.got_only_move = 1;
8902
8903                 /* Not really, but we also use this member to
8904                    mean "line isn't going to change" (Crafty
8905                    isn't searching, so stats won't change) */
8906                 programStats.line_is_book = 1;
8907
8908                 SendProgramStatsToFrontend( cps, &programStats );
8909
8910                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8911                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8912                     DisplayMove(currentMove - 1);
8913                 }
8914                 return;
8915             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8916                               &time, &nodes, &plylev, &mvleft,
8917                               &mvtot, mvname) >= 5) {
8918                 /* The stat01: line is from Crafty (9.29+) in response
8919                    to the "." command */
8920                 programStats.seen_stat = 1;
8921                 cps->maybeThinking = TRUE;
8922
8923                 if (programStats.got_only_move || !appData.periodicUpdates)
8924                   return;
8925
8926                 programStats.depth = plylev;
8927                 programStats.time = time;
8928                 programStats.nodes = nodes;
8929                 programStats.moves_left = mvleft;
8930                 programStats.nr_moves = mvtot;
8931                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8932                 programStats.ok_to_send = 1;
8933                 programStats.movelist[0] = '\0';
8934
8935                 SendProgramStatsToFrontend( cps, &programStats );
8936
8937                 return;
8938
8939             } else if (strncmp(message,"++",2) == 0) {
8940                 /* Crafty 9.29+ outputs this */
8941                 programStats.got_fail = 2;
8942                 return;
8943
8944             } else if (strncmp(message,"--",2) == 0) {
8945                 /* Crafty 9.29+ outputs this */
8946                 programStats.got_fail = 1;
8947                 return;
8948
8949             } else if (thinkOutput[0] != NULLCHAR &&
8950                        strncmp(message, "    ", 4) == 0) {
8951                 unsigned message_len;
8952
8953                 p = message;
8954                 while (*p && *p == ' ') p++;
8955
8956                 message_len = strlen( p );
8957
8958                 /* [AS] Avoid buffer overflow */
8959                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8960                     strcat(thinkOutput, " ");
8961                     strcat(thinkOutput, p);
8962                 }
8963
8964                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8965                     strcat(programStats.movelist, " ");
8966                     strcat(programStats.movelist, p);
8967                 }
8968
8969                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8970                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8971                     DisplayMove(currentMove - 1);
8972                 }
8973                 return;
8974             }
8975         }
8976         else {
8977             buf1[0] = NULLCHAR;
8978
8979             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8980                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8981             {
8982                 ChessProgramStats cpstats;
8983
8984                 if (plyext != ' ' && plyext != '\t') {
8985                     time *= 100;
8986                 }
8987
8988                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8989                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8990                     curscore = -curscore;
8991                 }
8992
8993                 cpstats.depth = plylev;
8994                 cpstats.nodes = nodes;
8995                 cpstats.time = time;
8996                 cpstats.score = curscore;
8997                 cpstats.got_only_move = 0;
8998                 cpstats.movelist[0] = '\0';
8999
9000                 if (buf1[0] != NULLCHAR) {
9001                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9002                 }
9003
9004                 cpstats.ok_to_send = 0;
9005                 cpstats.line_is_book = 0;
9006                 cpstats.nr_moves = 0;
9007                 cpstats.moves_left = 0;
9008
9009                 SendProgramStatsToFrontend( cps, &cpstats );
9010             }
9011         }
9012     }
9013 }
9014
9015
9016 /* Parse a game score from the character string "game", and
9017    record it as the history of the current game.  The game
9018    score is NOT assumed to start from the standard position.
9019    The display is not updated in any way.
9020    */
9021 void
9022 ParseGameHistory (char *game)
9023 {
9024     ChessMove moveType;
9025     int fromX, fromY, toX, toY, boardIndex;
9026     char promoChar;
9027     char *p, *q;
9028     char buf[MSG_SIZ];
9029
9030     if (appData.debugMode)
9031       fprintf(debugFP, "Parsing game history: %s\n", game);
9032
9033     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9034     gameInfo.site = StrSave(appData.icsHost);
9035     gameInfo.date = PGNDate();
9036     gameInfo.round = StrSave("-");
9037
9038     /* Parse out names of players */
9039     while (*game == ' ') game++;
9040     p = buf;
9041     while (*game != ' ') *p++ = *game++;
9042     *p = NULLCHAR;
9043     gameInfo.white = StrSave(buf);
9044     while (*game == ' ') game++;
9045     p = buf;
9046     while (*game != ' ' && *game != '\n') *p++ = *game++;
9047     *p = NULLCHAR;
9048     gameInfo.black = StrSave(buf);
9049
9050     /* Parse moves */
9051     boardIndex = blackPlaysFirst ? 1 : 0;
9052     yynewstr(game);
9053     for (;;) {
9054         yyboardindex = boardIndex;
9055         moveType = (ChessMove) Myylex();
9056         switch (moveType) {
9057           case IllegalMove:             /* maybe suicide chess, etc. */
9058   if (appData.debugMode) {
9059     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9060     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9061     setbuf(debugFP, NULL);
9062   }
9063           case WhitePromotion:
9064           case BlackPromotion:
9065           case WhiteNonPromotion:
9066           case BlackNonPromotion:
9067           case NormalMove:
9068           case WhiteCapturesEnPassant:
9069           case BlackCapturesEnPassant:
9070           case WhiteKingSideCastle:
9071           case WhiteQueenSideCastle:
9072           case BlackKingSideCastle:
9073           case BlackQueenSideCastle:
9074           case WhiteKingSideCastleWild:
9075           case WhiteQueenSideCastleWild:
9076           case BlackKingSideCastleWild:
9077           case BlackQueenSideCastleWild:
9078           /* PUSH Fabien */
9079           case WhiteHSideCastleFR:
9080           case WhiteASideCastleFR:
9081           case BlackHSideCastleFR:
9082           case BlackASideCastleFR:
9083           /* POP Fabien */
9084             fromX = currentMoveString[0] - AAA;
9085             fromY = currentMoveString[1] - ONE;
9086             toX = currentMoveString[2] - AAA;
9087             toY = currentMoveString[3] - ONE;
9088             promoChar = currentMoveString[4];
9089             break;
9090           case WhiteDrop:
9091           case BlackDrop:
9092             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9093             fromX = moveType == WhiteDrop ?
9094               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9095             (int) CharToPiece(ToLower(currentMoveString[0]));
9096             fromY = DROP_RANK;
9097             toX = currentMoveString[2] - AAA;
9098             toY = currentMoveString[3] - ONE;
9099             promoChar = NULLCHAR;
9100             break;
9101           case AmbiguousMove:
9102             /* bug? */
9103             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9104   if (appData.debugMode) {
9105     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9106     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9107     setbuf(debugFP, NULL);
9108   }
9109             DisplayError(buf, 0);
9110             return;
9111           case ImpossibleMove:
9112             /* bug? */
9113             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9114   if (appData.debugMode) {
9115     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9116     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9117     setbuf(debugFP, NULL);
9118   }
9119             DisplayError(buf, 0);
9120             return;
9121           case EndOfFile:
9122             if (boardIndex < backwardMostMove) {
9123                 /* Oops, gap.  How did that happen? */
9124                 DisplayError(_("Gap in move list"), 0);
9125                 return;
9126             }
9127             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9128             if (boardIndex > forwardMostMove) {
9129                 forwardMostMove = boardIndex;
9130             }
9131             return;
9132           case ElapsedTime:
9133             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9134                 strcat(parseList[boardIndex-1], " ");
9135                 strcat(parseList[boardIndex-1], yy_text);
9136             }
9137             continue;
9138           case Comment:
9139           case PGNTag:
9140           case NAG:
9141           default:
9142             /* ignore */
9143             continue;
9144           case WhiteWins:
9145           case BlackWins:
9146           case GameIsDrawn:
9147           case GameUnfinished:
9148             if (gameMode == IcsExamining) {
9149                 if (boardIndex < backwardMostMove) {
9150                     /* Oops, gap.  How did that happen? */
9151                     return;
9152                 }
9153                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9154                 return;
9155             }
9156             gameInfo.result = moveType;
9157             p = strchr(yy_text, '{');
9158             if (p == NULL) p = strchr(yy_text, '(');
9159             if (p == NULL) {
9160                 p = yy_text;
9161                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9162             } else {
9163                 q = strchr(p, *p == '{' ? '}' : ')');
9164                 if (q != NULL) *q = NULLCHAR;
9165                 p++;
9166             }
9167             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9168             gameInfo.resultDetails = StrSave(p);
9169             continue;
9170         }
9171         if (boardIndex >= forwardMostMove &&
9172             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9173             backwardMostMove = blackPlaysFirst ? 1 : 0;
9174             return;
9175         }
9176         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9177                                  fromY, fromX, toY, toX, promoChar,
9178                                  parseList[boardIndex]);
9179         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9180         /* currentMoveString is set as a side-effect of yylex */
9181         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9182         strcat(moveList[boardIndex], "\n");
9183         boardIndex++;
9184         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9185         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9186           case MT_NONE:
9187           case MT_STALEMATE:
9188           default:
9189             break;
9190           case MT_CHECK:
9191             if(gameInfo.variant != VariantShogi)
9192                 strcat(parseList[boardIndex - 1], "+");
9193             break;
9194           case MT_CHECKMATE:
9195           case MT_STAINMATE:
9196             strcat(parseList[boardIndex - 1], "#");
9197             break;
9198         }
9199     }
9200 }
9201
9202
9203 /* Apply a move to the given board  */
9204 void
9205 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9206 {
9207   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9208   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9209
9210     /* [HGM] compute & store e.p. status and castling rights for new position */
9211     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9212
9213       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9214       oldEP = (signed char)board[EP_STATUS];
9215       board[EP_STATUS] = EP_NONE;
9216
9217   if (fromY == DROP_RANK) {
9218         /* must be first */
9219         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9220             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9221             return;
9222         }
9223         piece = board[toY][toX] = (ChessSquare) fromX;
9224   } else {
9225       int i;
9226
9227       if( board[toY][toX] != EmptySquare )
9228            board[EP_STATUS] = EP_CAPTURE;
9229
9230       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9231            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9232                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9233       } else
9234       if( board[fromY][fromX] == WhitePawn ) {
9235            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9236                board[EP_STATUS] = EP_PAWN_MOVE;
9237            if( toY-fromY==2) {
9238                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9239                         gameInfo.variant != VariantBerolina || toX < fromX)
9240                       board[EP_STATUS] = toX | berolina;
9241                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9242                         gameInfo.variant != VariantBerolina || toX > fromX)
9243                       board[EP_STATUS] = toX;
9244            }
9245       } else
9246       if( board[fromY][fromX] == BlackPawn ) {
9247            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9248                board[EP_STATUS] = EP_PAWN_MOVE;
9249            if( toY-fromY== -2) {
9250                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9251                         gameInfo.variant != VariantBerolina || toX < fromX)
9252                       board[EP_STATUS] = toX | berolina;
9253                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9254                         gameInfo.variant != VariantBerolina || toX > fromX)
9255                       board[EP_STATUS] = toX;
9256            }
9257        }
9258
9259        for(i=0; i<nrCastlingRights; i++) {
9260            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9261               board[CASTLING][i] == toX   && castlingRank[i] == toY
9262              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9263        }
9264
9265      if (fromX == toX && fromY == toY) return;
9266
9267      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9268      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9269      if(gameInfo.variant == VariantKnightmate)
9270          king += (int) WhiteUnicorn - (int) WhiteKing;
9271
9272     /* Code added by Tord: */
9273     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9274     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9275         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9276       board[fromY][fromX] = EmptySquare;
9277       board[toY][toX] = EmptySquare;
9278       if((toX > fromX) != (piece == WhiteRook)) {
9279         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9280       } else {
9281         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9282       }
9283     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9284                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9285       board[fromY][fromX] = EmptySquare;
9286       board[toY][toX] = EmptySquare;
9287       if((toX > fromX) != (piece == BlackRook)) {
9288         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9289       } else {
9290         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9291       }
9292     /* End of code added by Tord */
9293
9294     } else if (board[fromY][fromX] == king
9295         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9296         && toY == fromY && toX > fromX+1) {
9297         board[fromY][fromX] = EmptySquare;
9298         board[toY][toX] = king;
9299         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9300         board[fromY][BOARD_RGHT-1] = EmptySquare;
9301     } else if (board[fromY][fromX] == king
9302         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9303                && toY == fromY && toX < fromX-1) {
9304         board[fromY][fromX] = EmptySquare;
9305         board[toY][toX] = king;
9306         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9307         board[fromY][BOARD_LEFT] = EmptySquare;
9308     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9309                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9310                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9311                ) {
9312         /* white pawn promotion */
9313         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9314         if(gameInfo.variant==VariantBughouse ||
9315            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9316             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9317         board[fromY][fromX] = EmptySquare;
9318     } else if ((fromY >= BOARD_HEIGHT>>1)
9319                && (toX != fromX)
9320                && gameInfo.variant != VariantXiangqi
9321                && gameInfo.variant != VariantBerolina
9322                && (board[fromY][fromX] == WhitePawn)
9323                && (board[toY][toX] == EmptySquare)) {
9324         board[fromY][fromX] = EmptySquare;
9325         board[toY][toX] = WhitePawn;
9326         captured = board[toY - 1][toX];
9327         board[toY - 1][toX] = EmptySquare;
9328     } else if ((fromY == BOARD_HEIGHT-4)
9329                && (toX == fromX)
9330                && gameInfo.variant == VariantBerolina
9331                && (board[fromY][fromX] == WhitePawn)
9332                && (board[toY][toX] == EmptySquare)) {
9333         board[fromY][fromX] = EmptySquare;
9334         board[toY][toX] = WhitePawn;
9335         if(oldEP & EP_BEROLIN_A) {
9336                 captured = board[fromY][fromX-1];
9337                 board[fromY][fromX-1] = EmptySquare;
9338         }else{  captured = board[fromY][fromX+1];
9339                 board[fromY][fromX+1] = EmptySquare;
9340         }
9341     } else if (board[fromY][fromX] == king
9342         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9343                && toY == fromY && toX > fromX+1) {
9344         board[fromY][fromX] = EmptySquare;
9345         board[toY][toX] = king;
9346         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9347         board[fromY][BOARD_RGHT-1] = EmptySquare;
9348     } else if (board[fromY][fromX] == king
9349         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9350                && toY == fromY && toX < fromX-1) {
9351         board[fromY][fromX] = EmptySquare;
9352         board[toY][toX] = king;
9353         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9354         board[fromY][BOARD_LEFT] = EmptySquare;
9355     } else if (fromY == 7 && fromX == 3
9356                && board[fromY][fromX] == BlackKing
9357                && toY == 7 && toX == 5) {
9358         board[fromY][fromX] = EmptySquare;
9359         board[toY][toX] = BlackKing;
9360         board[fromY][7] = EmptySquare;
9361         board[toY][4] = BlackRook;
9362     } else if (fromY == 7 && fromX == 3
9363                && board[fromY][fromX] == BlackKing
9364                && toY == 7 && toX == 1) {
9365         board[fromY][fromX] = EmptySquare;
9366         board[toY][toX] = BlackKing;
9367         board[fromY][0] = EmptySquare;
9368         board[toY][2] = BlackRook;
9369     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9370                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9371                && toY < promoRank && promoChar
9372                ) {
9373         /* black pawn promotion */
9374         board[toY][toX] = CharToPiece(ToLower(promoChar));
9375         if(gameInfo.variant==VariantBughouse ||
9376            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9377             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9378         board[fromY][fromX] = EmptySquare;
9379     } else if ((fromY < BOARD_HEIGHT>>1)
9380                && (toX != fromX)
9381                && gameInfo.variant != VariantXiangqi
9382                && gameInfo.variant != VariantBerolina
9383                && (board[fromY][fromX] == BlackPawn)
9384                && (board[toY][toX] == EmptySquare)) {
9385         board[fromY][fromX] = EmptySquare;
9386         board[toY][toX] = BlackPawn;
9387         captured = board[toY + 1][toX];
9388         board[toY + 1][toX] = EmptySquare;
9389     } else if ((fromY == 3)
9390                && (toX == fromX)
9391                && gameInfo.variant == VariantBerolina
9392                && (board[fromY][fromX] == BlackPawn)
9393                && (board[toY][toX] == EmptySquare)) {
9394         board[fromY][fromX] = EmptySquare;
9395         board[toY][toX] = BlackPawn;
9396         if(oldEP & EP_BEROLIN_A) {
9397                 captured = board[fromY][fromX-1];
9398                 board[fromY][fromX-1] = EmptySquare;
9399         }else{  captured = board[fromY][fromX+1];
9400                 board[fromY][fromX+1] = EmptySquare;
9401         }
9402     } else {
9403         board[toY][toX] = board[fromY][fromX];
9404         board[fromY][fromX] = EmptySquare;
9405     }
9406   }
9407
9408     if (gameInfo.holdingsWidth != 0) {
9409
9410       /* !!A lot more code needs to be written to support holdings  */
9411       /* [HGM] OK, so I have written it. Holdings are stored in the */
9412       /* penultimate board files, so they are automaticlly stored   */
9413       /* in the game history.                                       */
9414       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9415                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9416         /* Delete from holdings, by decreasing count */
9417         /* and erasing image if necessary            */
9418         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9419         if(p < (int) BlackPawn) { /* white drop */
9420              p -= (int)WhitePawn;
9421                  p = PieceToNumber((ChessSquare)p);
9422              if(p >= gameInfo.holdingsSize) p = 0;
9423              if(--board[p][BOARD_WIDTH-2] <= 0)
9424                   board[p][BOARD_WIDTH-1] = EmptySquare;
9425              if((int)board[p][BOARD_WIDTH-2] < 0)
9426                         board[p][BOARD_WIDTH-2] = 0;
9427         } else {                  /* black drop */
9428              p -= (int)BlackPawn;
9429                  p = PieceToNumber((ChessSquare)p);
9430              if(p >= gameInfo.holdingsSize) p = 0;
9431              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9432                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9433              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9434                         board[BOARD_HEIGHT-1-p][1] = 0;
9435         }
9436       }
9437       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9438           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9439         /* [HGM] holdings: Add to holdings, if holdings exist */
9440         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9441                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9442                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9443         }
9444         p = (int) captured;
9445         if (p >= (int) BlackPawn) {
9446           p -= (int)BlackPawn;
9447           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9448                   /* in Shogi restore piece to its original  first */
9449                   captured = (ChessSquare) (DEMOTED captured);
9450                   p = DEMOTED p;
9451           }
9452           p = PieceToNumber((ChessSquare)p);
9453           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9454           board[p][BOARD_WIDTH-2]++;
9455           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9456         } else {
9457           p -= (int)WhitePawn;
9458           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9459                   captured = (ChessSquare) (DEMOTED captured);
9460                   p = DEMOTED p;
9461           }
9462           p = PieceToNumber((ChessSquare)p);
9463           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9464           board[BOARD_HEIGHT-1-p][1]++;
9465           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9466         }
9467       }
9468     } else if (gameInfo.variant == VariantAtomic) {
9469       if (captured != EmptySquare) {
9470         int y, x;
9471         for (y = toY-1; y <= toY+1; y++) {
9472           for (x = toX-1; x <= toX+1; x++) {
9473             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9474                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9475               board[y][x] = EmptySquare;
9476             }
9477           }
9478         }
9479         board[toY][toX] = EmptySquare;
9480       }
9481     }
9482     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9483         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9484     } else
9485     if(promoChar == '+') {
9486         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9487         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9488     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9489         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9490     }
9491     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9492                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9493         // [HGM] superchess: take promotion piece out of holdings
9494         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9495         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9496             if(!--board[k][BOARD_WIDTH-2])
9497                 board[k][BOARD_WIDTH-1] = EmptySquare;
9498         } else {
9499             if(!--board[BOARD_HEIGHT-1-k][1])
9500                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9501         }
9502     }
9503
9504 }
9505
9506 /* Updates forwardMostMove */
9507 void
9508 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9509 {
9510 //    forwardMostMove++; // [HGM] bare: moved downstream
9511
9512     (void) CoordsToAlgebraic(boards[forwardMostMove],
9513                              PosFlags(forwardMostMove),
9514                              fromY, fromX, toY, toX, promoChar,
9515                              parseList[forwardMostMove]);
9516
9517     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9518         int timeLeft; static int lastLoadFlag=0; int king, piece;
9519         piece = boards[forwardMostMove][fromY][fromX];
9520         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9521         if(gameInfo.variant == VariantKnightmate)
9522             king += (int) WhiteUnicorn - (int) WhiteKing;
9523         if(forwardMostMove == 0) {
9524             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9525                 fprintf(serverMoves, "%s;", UserName());
9526             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9527                 fprintf(serverMoves, "%s;", second.tidy);
9528             fprintf(serverMoves, "%s;", first.tidy);
9529             if(gameMode == MachinePlaysWhite)
9530                 fprintf(serverMoves, "%s;", UserName());
9531             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9532                 fprintf(serverMoves, "%s;", second.tidy);
9533         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9534         lastLoadFlag = loadFlag;
9535         // print base move
9536         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9537         // print castling suffix
9538         if( toY == fromY && piece == king ) {
9539             if(toX-fromX > 1)
9540                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9541             if(fromX-toX >1)
9542                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9543         }
9544         // e.p. suffix
9545         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9546              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9547              boards[forwardMostMove][toY][toX] == EmptySquare
9548              && fromX != toX && fromY != toY)
9549                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9550         // promotion suffix
9551         if(promoChar != NULLCHAR)
9552                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9553         if(!loadFlag) {
9554                 char buf[MOVE_LEN*2], *p; int len;
9555             fprintf(serverMoves, "/%d/%d",
9556                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9557             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9558             else                      timeLeft = blackTimeRemaining/1000;
9559             fprintf(serverMoves, "/%d", timeLeft);
9560                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9561                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9562                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9563             fprintf(serverMoves, "/%s", buf);
9564         }
9565         fflush(serverMoves);
9566     }
9567
9568     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9569         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9570       return;
9571     }
9572     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9573     if (commentList[forwardMostMove+1] != NULL) {
9574         free(commentList[forwardMostMove+1]);
9575         commentList[forwardMostMove+1] = NULL;
9576     }
9577     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9578     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9579     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9580     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9581     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9582     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9583     adjustedClock = FALSE;
9584     gameInfo.result = GameUnfinished;
9585     if (gameInfo.resultDetails != NULL) {
9586         free(gameInfo.resultDetails);
9587         gameInfo.resultDetails = NULL;
9588     }
9589     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9590                               moveList[forwardMostMove - 1]);
9591     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9592       case MT_NONE:
9593       case MT_STALEMATE:
9594       default:
9595         break;
9596       case MT_CHECK:
9597         if(gameInfo.variant != VariantShogi)
9598             strcat(parseList[forwardMostMove - 1], "+");
9599         break;
9600       case MT_CHECKMATE:
9601       case MT_STAINMATE:
9602         strcat(parseList[forwardMostMove - 1], "#");
9603         break;
9604     }
9605
9606 }
9607
9608 /* Updates currentMove if not pausing */
9609 void
9610 ShowMove (int fromX, int fromY, int toX, int toY)
9611 {
9612     int instant = (gameMode == PlayFromGameFile) ?
9613         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9614     if(appData.noGUI) return;
9615     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9616         if (!instant) {
9617             if (forwardMostMove == currentMove + 1) {
9618                 AnimateMove(boards[forwardMostMove - 1],
9619                             fromX, fromY, toX, toY);
9620             }
9621             if (appData.highlightLastMove) {
9622                 SetHighlights(fromX, fromY, toX, toY);
9623             }
9624         }
9625         currentMove = forwardMostMove;
9626     }
9627
9628     if (instant) return;
9629
9630     DisplayMove(currentMove - 1);
9631     DrawPosition(FALSE, boards[currentMove]);
9632     DisplayBothClocks();
9633     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9634 }
9635
9636 void
9637 SendEgtPath (ChessProgramState *cps)
9638 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9639         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9640
9641         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9642
9643         while(*p) {
9644             char c, *q = name+1, *r, *s;
9645
9646             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9647             while(*p && *p != ',') *q++ = *p++;
9648             *q++ = ':'; *q = 0;
9649             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9650                 strcmp(name, ",nalimov:") == 0 ) {
9651                 // take nalimov path from the menu-changeable option first, if it is defined
9652               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9653                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9654             } else
9655             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9656                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9657                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9658                 s = r = StrStr(s, ":") + 1; // beginning of path info
9659                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9660                 c = *r; *r = 0;             // temporarily null-terminate path info
9661                     *--q = 0;               // strip of trailig ':' from name
9662                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9663                 *r = c;
9664                 SendToProgram(buf,cps);     // send egtbpath command for this format
9665             }
9666             if(*p == ',') p++; // read away comma to position for next format name
9667         }
9668 }
9669
9670 void
9671 InitChessProgram (ChessProgramState *cps, int setup)
9672 /* setup needed to setup FRC opening position */
9673 {
9674     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9675     if (appData.noChessProgram) return;
9676     hintRequested = FALSE;
9677     bookRequested = FALSE;
9678
9679     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9680     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9681     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9682     if(cps->memSize) { /* [HGM] memory */
9683       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9684         SendToProgram(buf, cps);
9685     }
9686     SendEgtPath(cps); /* [HGM] EGT */
9687     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9688       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9689         SendToProgram(buf, cps);
9690     }
9691
9692     SendToProgram(cps->initString, cps);
9693     if (gameInfo.variant != VariantNormal &&
9694         gameInfo.variant != VariantLoadable
9695         /* [HGM] also send variant if board size non-standard */
9696         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9697                                             ) {
9698       char *v = VariantName(gameInfo.variant);
9699       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9700         /* [HGM] in protocol 1 we have to assume all variants valid */
9701         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9702         DisplayFatalError(buf, 0, 1);
9703         return;
9704       }
9705
9706       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9707       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9708       if( gameInfo.variant == VariantXiangqi )
9709            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9710       if( gameInfo.variant == VariantShogi )
9711            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9712       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9713            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9714       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9715           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9716            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9717       if( gameInfo.variant == VariantCourier )
9718            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9719       if( gameInfo.variant == VariantSuper )
9720            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9721       if( gameInfo.variant == VariantGreat )
9722            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9723       if( gameInfo.variant == VariantSChess )
9724            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9725       if( gameInfo.variant == VariantGrand )
9726            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9727
9728       if(overruled) {
9729         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9730                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9731            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9732            if(StrStr(cps->variants, b) == NULL) {
9733                // specific sized variant not known, check if general sizing allowed
9734                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9735                    if(StrStr(cps->variants, "boardsize") == NULL) {
9736                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9737                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9738                        DisplayFatalError(buf, 0, 1);
9739                        return;
9740                    }
9741                    /* [HGM] here we really should compare with the maximum supported board size */
9742                }
9743            }
9744       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9745       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9746       SendToProgram(buf, cps);
9747     }
9748     currentlyInitializedVariant = gameInfo.variant;
9749
9750     /* [HGM] send opening position in FRC to first engine */
9751     if(setup) {
9752           SendToProgram("force\n", cps);
9753           SendBoard(cps, 0);
9754           /* engine is now in force mode! Set flag to wake it up after first move. */
9755           setboardSpoiledMachineBlack = 1;
9756     }
9757
9758     if (cps->sendICS) {
9759       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9760       SendToProgram(buf, cps);
9761     }
9762     cps->maybeThinking = FALSE;
9763     cps->offeredDraw = 0;
9764     if (!appData.icsActive) {
9765         SendTimeControl(cps, movesPerSession, timeControl,
9766                         timeIncrement, appData.searchDepth,
9767                         searchTime);
9768     }
9769     if (appData.showThinking
9770         // [HGM] thinking: four options require thinking output to be sent
9771         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9772                                 ) {
9773         SendToProgram("post\n", cps);
9774     }
9775     SendToProgram("hard\n", cps);
9776     if (!appData.ponderNextMove) {
9777         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9778            it without being sure what state we are in first.  "hard"
9779            is not a toggle, so that one is OK.
9780          */
9781         SendToProgram("easy\n", cps);
9782     }
9783     if (cps->usePing) {
9784       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9785       SendToProgram(buf, cps);
9786     }
9787     cps->initDone = TRUE;
9788     ClearEngineOutputPane(cps == &second);
9789 }
9790
9791
9792 void
9793 StartChessProgram (ChessProgramState *cps)
9794 {
9795     char buf[MSG_SIZ];
9796     int err;
9797
9798     if (appData.noChessProgram) return;
9799     cps->initDone = FALSE;
9800
9801     if (strcmp(cps->host, "localhost") == 0) {
9802         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9803     } else if (*appData.remoteShell == NULLCHAR) {
9804         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9805     } else {
9806         if (*appData.remoteUser == NULLCHAR) {
9807           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9808                     cps->program);
9809         } else {
9810           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9811                     cps->host, appData.remoteUser, cps->program);
9812         }
9813         err = StartChildProcess(buf, "", &cps->pr);
9814     }
9815
9816     if (err != 0) {
9817       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9818         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9819         if(cps != &first) return;
9820         appData.noChessProgram = TRUE;
9821         ThawUI();
9822         SetNCPMode();
9823 //      DisplayFatalError(buf, err, 1);
9824 //      cps->pr = NoProc;
9825 //      cps->isr = NULL;
9826         return;
9827     }
9828
9829     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9830     if (cps->protocolVersion > 1) {
9831       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9832       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9833       cps->comboCnt = 0;  //                and values of combo boxes
9834       SendToProgram(buf, cps);
9835     } else {
9836       SendToProgram("xboard\n", cps);
9837     }
9838 }
9839
9840 void
9841 TwoMachinesEventIfReady P((void))
9842 {
9843   static int curMess = 0;
9844   if (first.lastPing != first.lastPong) {
9845     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9846     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9847     return;
9848   }
9849   if (second.lastPing != second.lastPong) {
9850     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9851     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9852     return;
9853   }
9854   DisplayMessage("", ""); curMess = 0;
9855   ThawUI();
9856   TwoMachinesEvent();
9857 }
9858
9859 char *
9860 MakeName (char *template)
9861 {
9862     time_t clock;
9863     struct tm *tm;
9864     static char buf[MSG_SIZ];
9865     char *p = buf;
9866     int i;
9867
9868     clock = time((time_t *)NULL);
9869     tm = localtime(&clock);
9870
9871     while(*p++ = *template++) if(p[-1] == '%') {
9872         switch(*template++) {
9873           case 0:   *p = 0; return buf;
9874           case 'Y': i = tm->tm_year+1900; break;
9875           case 'y': i = tm->tm_year-100; break;
9876           case 'M': i = tm->tm_mon+1; break;
9877           case 'd': i = tm->tm_mday; break;
9878           case 'h': i = tm->tm_hour; break;
9879           case 'm': i = tm->tm_min; break;
9880           case 's': i = tm->tm_sec; break;
9881           default:  i = 0;
9882         }
9883         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9884     }
9885     return buf;
9886 }
9887
9888 int
9889 CountPlayers (char *p)
9890 {
9891     int n = 0;
9892     while(p = strchr(p, '\n')) p++, n++; // count participants
9893     return n;
9894 }
9895
9896 FILE *
9897 WriteTourneyFile (char *results, FILE *f)
9898 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9899     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9900     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9901         // create a file with tournament description
9902         fprintf(f, "-participants {%s}\n", appData.participants);
9903         fprintf(f, "-seedBase %d\n", appData.seedBase);
9904         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9905         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9906         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9907         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9908         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9909         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9910         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9911         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9912         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9913         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9914         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9915         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9916         if(searchTime > 0)
9917                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9918         else {
9919                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9920                 fprintf(f, "-tc %s\n", appData.timeControl);
9921                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9922         }
9923         fprintf(f, "-results \"%s\"\n", results);
9924     }
9925     return f;
9926 }
9927
9928 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9929
9930 void
9931 Substitute (char *participants, int expunge)
9932 {
9933     int i, changed, changes=0, nPlayers=0;
9934     char *p, *q, *r, buf[MSG_SIZ];
9935     if(participants == NULL) return;
9936     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9937     r = p = participants; q = appData.participants;
9938     while(*p && *p == *q) {
9939         if(*p == '\n') r = p+1, nPlayers++;
9940         p++; q++;
9941     }
9942     if(*p) { // difference
9943         while(*p && *p++ != '\n');
9944         while(*q && *q++ != '\n');
9945       changed = nPlayers;
9946         changes = 1 + (strcmp(p, q) != 0);
9947     }
9948     if(changes == 1) { // a single engine mnemonic was changed
9949         q = r; while(*q) nPlayers += (*q++ == '\n');
9950         p = buf; while(*r && (*p = *r++) != '\n') p++;
9951         *p = NULLCHAR;
9952         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9953         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9954         if(mnemonic[i]) { // The substitute is valid
9955             FILE *f;
9956             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9957                 flock(fileno(f), LOCK_EX);
9958                 ParseArgsFromFile(f);
9959                 fseek(f, 0, SEEK_SET);
9960                 FREE(appData.participants); appData.participants = participants;
9961                 if(expunge) { // erase results of replaced engine
9962                     int len = strlen(appData.results), w, b, dummy;
9963                     for(i=0; i<len; i++) {
9964                         Pairing(i, nPlayers, &w, &b, &dummy);
9965                         if((w == changed || b == changed) && appData.results[i] == '*') {
9966                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9967                             fclose(f);
9968                             return;
9969                         }
9970                     }
9971                     for(i=0; i<len; i++) {
9972                         Pairing(i, nPlayers, &w, &b, &dummy);
9973                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9974                     }
9975                 }
9976                 WriteTourneyFile(appData.results, f);
9977                 fclose(f); // release lock
9978                 return;
9979             }
9980         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9981     }
9982     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9983     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9984     free(participants);
9985     return;
9986 }
9987
9988 int
9989 CreateTourney (char *name)
9990 {
9991         FILE *f;
9992         if(matchMode && strcmp(name, appData.tourneyFile)) {
9993              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9994         }
9995         if(name[0] == NULLCHAR) {
9996             if(appData.participants[0])
9997                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9998             return 0;
9999         }
10000         f = fopen(name, "r");
10001         if(f) { // file exists
10002             ASSIGN(appData.tourneyFile, name);
10003             ParseArgsFromFile(f); // parse it
10004         } else {
10005             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10006             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10007                 DisplayError(_("Not enough participants"), 0);
10008                 return 0;
10009             }
10010             ASSIGN(appData.tourneyFile, name);
10011             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10012             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10013         }
10014         fclose(f);
10015         appData.noChessProgram = FALSE;
10016         appData.clockMode = TRUE;
10017         SetGNUMode();
10018         return 1;
10019 }
10020
10021 int
10022 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10023 {
10024     char buf[MSG_SIZ], *p, *q;
10025     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10026     skip = !all && group[0]; // if group requested, we start in skip mode
10027     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10028         p = names; q = buf; header = 0;
10029         while(*p && *p != '\n') *q++ = *p++;
10030         *q = 0;
10031         if(*p == '\n') p++;
10032         if(buf[0] == '#') {
10033             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
10034             depth++; // we must be entering a new group
10035             if(all) continue; // suppress printing group headers when complete list requested
10036             header = 1;
10037             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10038         }
10039         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10040         if(engineList[i]) free(engineList[i]);
10041         engineList[i] = strdup(buf);
10042         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10043         if(engineMnemonic[i]) free(engineMnemonic[i]);
10044         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10045             strcat(buf, " (");
10046             sscanf(q + 8, "%s", buf + strlen(buf));
10047             strcat(buf, ")");
10048         }
10049         engineMnemonic[i] = strdup(buf);
10050         i++;
10051     }
10052     engineList[i] = engineMnemonic[i] = NULL;
10053     return i;
10054 }
10055
10056 // following implemented as macro to avoid type limitations
10057 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10058
10059 void
10060 SwapEngines (int n)
10061 {   // swap settings for first engine and other engine (so far only some selected options)
10062     int h;
10063     char *p;
10064     if(n == 0) return;
10065     SWAP(directory, p)
10066     SWAP(chessProgram, p)
10067     SWAP(isUCI, h)
10068     SWAP(hasOwnBookUCI, h)
10069     SWAP(protocolVersion, h)
10070     SWAP(reuse, h)
10071     SWAP(scoreIsAbsolute, h)
10072     SWAP(timeOdds, h)
10073     SWAP(logo, p)
10074     SWAP(pgnName, p)
10075     SWAP(pvSAN, h)
10076     SWAP(engOptions, p)
10077 }
10078
10079 int
10080 SetPlayer (int player, char *p)
10081 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10082     int i;
10083     char buf[MSG_SIZ], *engineName;
10084     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10085     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10086     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10087     if(mnemonic[i]) {
10088         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10089         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10090         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10091         ParseArgsFromString(buf);
10092     }
10093     free(engineName);
10094     return i;
10095 }
10096
10097 char *recentEngines;
10098
10099 void
10100 RecentEngineEvent (int nr)
10101 {
10102     int n;
10103 //    SwapEngines(1); // bump first to second
10104 //    ReplaceEngine(&second, 1); // and load it there
10105     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10106     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10107     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10108         ReplaceEngine(&first, 0);
10109         FloatToFront(&appData.recentEngineList, command[n]);
10110     }
10111 }
10112
10113 int
10114 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10115 {   // determine players from game number
10116     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10117
10118     if(appData.tourneyType == 0) {
10119         roundsPerCycle = (nPlayers - 1) | 1;
10120         pairingsPerRound = nPlayers / 2;
10121     } else if(appData.tourneyType > 0) {
10122         roundsPerCycle = nPlayers - appData.tourneyType;
10123         pairingsPerRound = appData.tourneyType;
10124     }
10125     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10126     gamesPerCycle = gamesPerRound * roundsPerCycle;
10127     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10128     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10129     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10130     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10131     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10132     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10133
10134     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10135     if(appData.roundSync) *syncInterval = gamesPerRound;
10136
10137     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10138
10139     if(appData.tourneyType == 0) {
10140         if(curPairing == (nPlayers-1)/2 ) {
10141             *whitePlayer = curRound;
10142             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10143         } else {
10144             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10145             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10146             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10147             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10148         }
10149     } else if(appData.tourneyType > 1) {
10150         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10151         *whitePlayer = curRound + appData.tourneyType;
10152     } else if(appData.tourneyType > 0) {
10153         *whitePlayer = curPairing;
10154         *blackPlayer = curRound + appData.tourneyType;
10155     }
10156
10157     // take care of white/black alternation per round. 
10158     // For cycles and games this is already taken care of by default, derived from matchGame!
10159     return curRound & 1;
10160 }
10161
10162 int
10163 NextTourneyGame (int nr, int *swapColors)
10164 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10165     char *p, *q;
10166     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10167     FILE *tf;
10168     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10169     tf = fopen(appData.tourneyFile, "r");
10170     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10171     ParseArgsFromFile(tf); fclose(tf);
10172     InitTimeControls(); // TC might be altered from tourney file
10173
10174     nPlayers = CountPlayers(appData.participants); // count participants
10175     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10176     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10177
10178     if(syncInterval) {
10179         p = q = appData.results;
10180         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10181         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10182             DisplayMessage(_("Waiting for other game(s)"),"");
10183             waitingForGame = TRUE;
10184             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10185             return 0;
10186         }
10187         waitingForGame = FALSE;
10188     }
10189
10190     if(appData.tourneyType < 0) {
10191         if(nr>=0 && !pairingReceived) {
10192             char buf[1<<16];
10193             if(pairing.pr == NoProc) {
10194                 if(!appData.pairingEngine[0]) {
10195                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10196                     return 0;
10197                 }
10198                 StartChessProgram(&pairing); // starts the pairing engine
10199             }
10200             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10201             SendToProgram(buf, &pairing);
10202             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10203             SendToProgram(buf, &pairing);
10204             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10205         }
10206         pairingReceived = 0;                              // ... so we continue here 
10207         *swapColors = 0;
10208         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10209         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10210         matchGame = 1; roundNr = nr / syncInterval + 1;
10211     }
10212
10213     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10214
10215     // redefine engines, engine dir, etc.
10216     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10217     if(first.pr == NoProc) {
10218       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10219       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10220     }
10221     if(second.pr == NoProc) {
10222       SwapEngines(1);
10223       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10224       SwapEngines(1);         // and make that valid for second engine by swapping
10225       InitEngine(&second, 1);
10226     }
10227     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10228     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10229     return 1;
10230 }
10231
10232 void
10233 NextMatchGame ()
10234 {   // performs game initialization that does not invoke engines, and then tries to start the game
10235     int res, firstWhite, swapColors = 0;
10236     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10237     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
10238         char buf[MSG_SIZ];
10239         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10240         if(strcmp(buf, currentDebugFile)) { // name has changed
10241             FILE *f = fopen(buf, "w");
10242             if(f) { // if opening the new file failed, just keep using the old one
10243                 ASSIGN(currentDebugFile, buf);
10244                 fclose(debugFP);
10245                 debugFP = f;
10246             }
10247             if(appData.serverFileName) {
10248                 if(serverFP) fclose(serverFP);
10249                 serverFP = fopen(appData.serverFileName, "w");
10250                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10251                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10252             }
10253         }
10254     }
10255     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10256     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10257     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10258     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10259     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10260     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10261     Reset(FALSE, first.pr != NoProc);
10262     res = LoadGameOrPosition(matchGame); // setup game
10263     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10264     if(!res) return; // abort when bad game/pos file
10265     TwoMachinesEvent();
10266 }
10267
10268 void
10269 UserAdjudicationEvent (int result)
10270 {
10271     ChessMove gameResult = GameIsDrawn;
10272
10273     if( result > 0 ) {
10274         gameResult = WhiteWins;
10275     }
10276     else if( result < 0 ) {
10277         gameResult = BlackWins;
10278     }
10279
10280     if( gameMode == TwoMachinesPlay ) {
10281         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10282     }
10283 }
10284
10285
10286 // [HGM] save: calculate checksum of game to make games easily identifiable
10287 int
10288 StringCheckSum (char *s)
10289 {
10290         int i = 0;
10291         if(s==NULL) return 0;
10292         while(*s) i = i*259 + *s++;
10293         return i;
10294 }
10295
10296 int
10297 GameCheckSum ()
10298 {
10299         int i, sum=0;
10300         for(i=backwardMostMove; i<forwardMostMove; i++) {
10301                 sum += pvInfoList[i].depth;
10302                 sum += StringCheckSum(parseList[i]);
10303                 sum += StringCheckSum(commentList[i]);
10304                 sum *= 261;
10305         }
10306         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10307         return sum + StringCheckSum(commentList[i]);
10308 } // end of save patch
10309
10310 void
10311 GameEnds (ChessMove result, char *resultDetails, int whosays)
10312 {
10313     GameMode nextGameMode;
10314     int isIcsGame;
10315     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10316
10317     if(endingGame) return; /* [HGM] crash: forbid recursion */
10318     endingGame = 1;
10319     if(twoBoards) { // [HGM] dual: switch back to one board
10320         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10321         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10322     }
10323     if (appData.debugMode) {
10324       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10325               result, resultDetails ? resultDetails : "(null)", whosays);
10326     }
10327
10328     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10329
10330     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10331         /* If we are playing on ICS, the server decides when the
10332            game is over, but the engine can offer to draw, claim
10333            a draw, or resign.
10334          */
10335 #if ZIPPY
10336         if (appData.zippyPlay && first.initDone) {
10337             if (result == GameIsDrawn) {
10338                 /* In case draw still needs to be claimed */
10339                 SendToICS(ics_prefix);
10340                 SendToICS("draw\n");
10341             } else if (StrCaseStr(resultDetails, "resign")) {
10342                 SendToICS(ics_prefix);
10343                 SendToICS("resign\n");
10344             }
10345         }
10346 #endif
10347         endingGame = 0; /* [HGM] crash */
10348         return;
10349     }
10350
10351     /* If we're loading the game from a file, stop */
10352     if (whosays == GE_FILE) {
10353       (void) StopLoadGameTimer();
10354       gameFileFP = NULL;
10355     }
10356
10357     /* Cancel draw offers */
10358     first.offeredDraw = second.offeredDraw = 0;
10359
10360     /* If this is an ICS game, only ICS can really say it's done;
10361        if not, anyone can. */
10362     isIcsGame = (gameMode == IcsPlayingWhite ||
10363                  gameMode == IcsPlayingBlack ||
10364                  gameMode == IcsObserving    ||
10365                  gameMode == IcsExamining);
10366
10367     if (!isIcsGame || whosays == GE_ICS) {
10368         /* OK -- not an ICS game, or ICS said it was done */
10369         StopClocks();
10370         if (!isIcsGame && !appData.noChessProgram)
10371           SetUserThinkingEnables();
10372
10373         /* [HGM] if a machine claims the game end we verify this claim */
10374         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10375             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10376                 char claimer;
10377                 ChessMove trueResult = (ChessMove) -1;
10378
10379                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10380                                             first.twoMachinesColor[0] :
10381                                             second.twoMachinesColor[0] ;
10382
10383                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10384                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10385                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10386                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10387                 } else
10388                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10389                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10390                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10391                 } else
10392                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10393                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10394                 }
10395
10396                 // now verify win claims, but not in drop games, as we don't understand those yet
10397                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10398                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10399                     (result == WhiteWins && claimer == 'w' ||
10400                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10401                       if (appData.debugMode) {
10402                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10403                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10404                       }
10405                       if(result != trueResult) {
10406                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10407                               result = claimer == 'w' ? BlackWins : WhiteWins;
10408                               resultDetails = buf;
10409                       }
10410                 } else
10411                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10412                     && (forwardMostMove <= backwardMostMove ||
10413                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10414                         (claimer=='b')==(forwardMostMove&1))
10415                                                                                   ) {
10416                       /* [HGM] verify: draws that were not flagged are false claims */
10417                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10418                       result = claimer == 'w' ? BlackWins : WhiteWins;
10419                       resultDetails = buf;
10420                 }
10421                 /* (Claiming a loss is accepted no questions asked!) */
10422             }
10423             /* [HGM] bare: don't allow bare King to win */
10424             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10425                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10426                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10427                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10428                && result != GameIsDrawn)
10429             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10430                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10431                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10432                         if(p >= 0 && p <= (int)WhiteKing) k++;
10433                 }
10434                 if (appData.debugMode) {
10435                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10436                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10437                 }
10438                 if(k <= 1) {
10439                         result = GameIsDrawn;
10440                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10441                         resultDetails = buf;
10442                 }
10443             }
10444         }
10445
10446
10447         if(serverMoves != NULL && !loadFlag) { char c = '=';
10448             if(result==WhiteWins) c = '+';
10449             if(result==BlackWins) c = '-';
10450             if(resultDetails != NULL)
10451                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10452         }
10453         if (resultDetails != NULL) {
10454             gameInfo.result = result;
10455             gameInfo.resultDetails = StrSave(resultDetails);
10456
10457             /* display last move only if game was not loaded from file */
10458             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10459                 DisplayMove(currentMove - 1);
10460
10461             if (forwardMostMove != 0) {
10462                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10463                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10464                                                                 ) {
10465                     if (*appData.saveGameFile != NULLCHAR) {
10466                         SaveGameToFile(appData.saveGameFile, TRUE);
10467                     } else if (appData.autoSaveGames) {
10468                         AutoSaveGame();
10469                     }
10470                     if (*appData.savePositionFile != NULLCHAR) {
10471                         SavePositionToFile(appData.savePositionFile);
10472                     }
10473                 }
10474             }
10475
10476             /* Tell program how game ended in case it is learning */
10477             /* [HGM] Moved this to after saving the PGN, just in case */
10478             /* engine died and we got here through time loss. In that */
10479             /* case we will get a fatal error writing the pipe, which */
10480             /* would otherwise lose us the PGN.                       */
10481             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10482             /* output during GameEnds should never be fatal anymore   */
10483             if (gameMode == MachinePlaysWhite ||
10484                 gameMode == MachinePlaysBlack ||
10485                 gameMode == TwoMachinesPlay ||
10486                 gameMode == IcsPlayingWhite ||
10487                 gameMode == IcsPlayingBlack ||
10488                 gameMode == BeginningOfGame) {
10489                 char buf[MSG_SIZ];
10490                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10491                         resultDetails);
10492                 if (first.pr != NoProc) {
10493                     SendToProgram(buf, &first);
10494                 }
10495                 if (second.pr != NoProc &&
10496                     gameMode == TwoMachinesPlay) {
10497                     SendToProgram(buf, &second);
10498                 }
10499             }
10500         }
10501
10502         if (appData.icsActive) {
10503             if (appData.quietPlay &&
10504                 (gameMode == IcsPlayingWhite ||
10505                  gameMode == IcsPlayingBlack)) {
10506                 SendToICS(ics_prefix);
10507                 SendToICS("set shout 1\n");
10508             }
10509             nextGameMode = IcsIdle;
10510             ics_user_moved = FALSE;
10511             /* clean up premove.  It's ugly when the game has ended and the
10512              * premove highlights are still on the board.
10513              */
10514             if (gotPremove) {
10515               gotPremove = FALSE;
10516               ClearPremoveHighlights();
10517               DrawPosition(FALSE, boards[currentMove]);
10518             }
10519             if (whosays == GE_ICS) {
10520                 switch (result) {
10521                 case WhiteWins:
10522                     if (gameMode == IcsPlayingWhite)
10523                         PlayIcsWinSound();
10524                     else if(gameMode == IcsPlayingBlack)
10525                         PlayIcsLossSound();
10526                     break;
10527                 case BlackWins:
10528                     if (gameMode == IcsPlayingBlack)
10529                         PlayIcsWinSound();
10530                     else if(gameMode == IcsPlayingWhite)
10531                         PlayIcsLossSound();
10532                     break;
10533                 case GameIsDrawn:
10534                     PlayIcsDrawSound();
10535                     break;
10536                 default:
10537                     PlayIcsUnfinishedSound();
10538                 }
10539             }
10540         } else if (gameMode == EditGame ||
10541                    gameMode == PlayFromGameFile ||
10542                    gameMode == AnalyzeMode ||
10543                    gameMode == AnalyzeFile) {
10544             nextGameMode = gameMode;
10545         } else {
10546             nextGameMode = EndOfGame;
10547         }
10548         pausing = FALSE;
10549         ModeHighlight();
10550     } else {
10551         nextGameMode = gameMode;
10552     }
10553
10554     if (appData.noChessProgram) {
10555         gameMode = nextGameMode;
10556         ModeHighlight();
10557         endingGame = 0; /* [HGM] crash */
10558         return;
10559     }
10560
10561     if (first.reuse) {
10562         /* Put first chess program into idle state */
10563         if (first.pr != NoProc &&
10564             (gameMode == MachinePlaysWhite ||
10565              gameMode == MachinePlaysBlack ||
10566              gameMode == TwoMachinesPlay ||
10567              gameMode == IcsPlayingWhite ||
10568              gameMode == IcsPlayingBlack ||
10569              gameMode == BeginningOfGame)) {
10570             SendToProgram("force\n", &first);
10571             if (first.usePing) {
10572               char buf[MSG_SIZ];
10573               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10574               SendToProgram(buf, &first);
10575             }
10576         }
10577     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10578         /* Kill off first chess program */
10579         if (first.isr != NULL)
10580           RemoveInputSource(first.isr);
10581         first.isr = NULL;
10582
10583         if (first.pr != NoProc) {
10584             ExitAnalyzeMode();
10585             DoSleep( appData.delayBeforeQuit );
10586             SendToProgram("quit\n", &first);
10587             DoSleep( appData.delayAfterQuit );
10588             DestroyChildProcess(first.pr, first.useSigterm);
10589         }
10590         first.pr = NoProc;
10591     }
10592     if (second.reuse) {
10593         /* Put second chess program into idle state */
10594         if (second.pr != NoProc &&
10595             gameMode == TwoMachinesPlay) {
10596             SendToProgram("force\n", &second);
10597             if (second.usePing) {
10598               char buf[MSG_SIZ];
10599               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10600               SendToProgram(buf, &second);
10601             }
10602         }
10603     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10604         /* Kill off second chess program */
10605         if (second.isr != NULL)
10606           RemoveInputSource(second.isr);
10607         second.isr = NULL;
10608
10609         if (second.pr != NoProc) {
10610             DoSleep( appData.delayBeforeQuit );
10611             SendToProgram("quit\n", &second);
10612             DoSleep( appData.delayAfterQuit );
10613             DestroyChildProcess(second.pr, second.useSigterm);
10614         }
10615         second.pr = NoProc;
10616     }
10617
10618     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10619         char resChar = '=';
10620         switch (result) {
10621         case WhiteWins:
10622           resChar = '+';
10623           if (first.twoMachinesColor[0] == 'w') {
10624             first.matchWins++;
10625           } else {
10626             second.matchWins++;
10627           }
10628           break;
10629         case BlackWins:
10630           resChar = '-';
10631           if (first.twoMachinesColor[0] == 'b') {
10632             first.matchWins++;
10633           } else {
10634             second.matchWins++;
10635           }
10636           break;
10637         case GameUnfinished:
10638           resChar = ' ';
10639         default:
10640           break;
10641         }
10642
10643         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10644         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10645             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10646             ReserveGame(nextGame, resChar); // sets nextGame
10647             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10648             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10649         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10650
10651         if (nextGame <= appData.matchGames && !abortMatch) {
10652             gameMode = nextGameMode;
10653             matchGame = nextGame; // this will be overruled in tourney mode!
10654             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10655             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10656             endingGame = 0; /* [HGM] crash */
10657             return;
10658         } else {
10659             gameMode = nextGameMode;
10660             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10661                      first.tidy, second.tidy,
10662                      first.matchWins, second.matchWins,
10663                      appData.matchGames - (first.matchWins + second.matchWins));
10664             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10665             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10666             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10667             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10668                 first.twoMachinesColor = "black\n";
10669                 second.twoMachinesColor = "white\n";
10670             } else {
10671                 first.twoMachinesColor = "white\n";
10672                 second.twoMachinesColor = "black\n";
10673             }
10674         }
10675     }
10676     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10677         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10678       ExitAnalyzeMode();
10679     gameMode = nextGameMode;
10680     ModeHighlight();
10681     endingGame = 0;  /* [HGM] crash */
10682     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10683         if(matchMode == TRUE) { // match through command line: exit with or without popup
10684             if(ranking) {
10685                 ToNrEvent(forwardMostMove);
10686                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10687                 else ExitEvent(0);
10688             } else DisplayFatalError(buf, 0, 0);
10689         } else { // match through menu; just stop, with or without popup
10690             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10691             ModeHighlight();
10692             if(ranking){
10693                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10694             } else DisplayNote(buf);
10695       }
10696       if(ranking) free(ranking);
10697     }
10698 }
10699
10700 /* Assumes program was just initialized (initString sent).
10701    Leaves program in force mode. */
10702 void
10703 FeedMovesToProgram (ChessProgramState *cps, int upto)
10704 {
10705     int i;
10706
10707     if (appData.debugMode)
10708       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10709               startedFromSetupPosition ? "position and " : "",
10710               backwardMostMove, upto, cps->which);
10711     if(currentlyInitializedVariant != gameInfo.variant) {
10712       char buf[MSG_SIZ];
10713         // [HGM] variantswitch: make engine aware of new variant
10714         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10715                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10716         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10717         SendToProgram(buf, cps);
10718         currentlyInitializedVariant = gameInfo.variant;
10719     }
10720     SendToProgram("force\n", cps);
10721     if (startedFromSetupPosition) {
10722         SendBoard(cps, backwardMostMove);
10723     if (appData.debugMode) {
10724         fprintf(debugFP, "feedMoves\n");
10725     }
10726     }
10727     for (i = backwardMostMove; i < upto; i++) {
10728         SendMoveToProgram(i, cps);
10729     }
10730 }
10731
10732
10733 int
10734 ResurrectChessProgram ()
10735 {
10736      /* The chess program may have exited.
10737         If so, restart it and feed it all the moves made so far. */
10738     static int doInit = 0;
10739
10740     if (appData.noChessProgram) return 1;
10741
10742     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10743         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10744         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10745         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10746     } else {
10747         if (first.pr != NoProc) return 1;
10748         StartChessProgram(&first);
10749     }
10750     InitChessProgram(&first, FALSE);
10751     FeedMovesToProgram(&first, currentMove);
10752
10753     if (!first.sendTime) {
10754         /* can't tell gnuchess what its clock should read,
10755            so we bow to its notion. */
10756         ResetClocks();
10757         timeRemaining[0][currentMove] = whiteTimeRemaining;
10758         timeRemaining[1][currentMove] = blackTimeRemaining;
10759     }
10760
10761     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10762                 appData.icsEngineAnalyze) && first.analysisSupport) {
10763       SendToProgram("analyze\n", &first);
10764       first.analyzing = TRUE;
10765     }
10766     return 1;
10767 }
10768
10769 /*
10770  * Button procedures
10771  */
10772 void
10773 Reset (int redraw, int init)
10774 {
10775     int i;
10776
10777     if (appData.debugMode) {
10778         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10779                 redraw, init, gameMode);
10780     }
10781     CleanupTail(); // [HGM] vari: delete any stored variations
10782     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10783     pausing = pauseExamInvalid = FALSE;
10784     startedFromSetupPosition = blackPlaysFirst = FALSE;
10785     firstMove = TRUE;
10786     whiteFlag = blackFlag = FALSE;
10787     userOfferedDraw = FALSE;
10788     hintRequested = bookRequested = FALSE;
10789     first.maybeThinking = FALSE;
10790     second.maybeThinking = FALSE;
10791     first.bookSuspend = FALSE; // [HGM] book
10792     second.bookSuspend = FALSE;
10793     thinkOutput[0] = NULLCHAR;
10794     lastHint[0] = NULLCHAR;
10795     ClearGameInfo(&gameInfo);
10796     gameInfo.variant = StringToVariant(appData.variant);
10797     ics_user_moved = ics_clock_paused = FALSE;
10798     ics_getting_history = H_FALSE;
10799     ics_gamenum = -1;
10800     white_holding[0] = black_holding[0] = NULLCHAR;
10801     ClearProgramStats();
10802     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10803
10804     ResetFrontEnd();
10805     ClearHighlights();
10806     flipView = appData.flipView;
10807     ClearPremoveHighlights();
10808     gotPremove = FALSE;
10809     alarmSounded = FALSE;
10810
10811     GameEnds(EndOfFile, NULL, GE_PLAYER);
10812     if(appData.serverMovesName != NULL) {
10813         /* [HGM] prepare to make moves file for broadcasting */
10814         clock_t t = clock();
10815         if(serverMoves != NULL) fclose(serverMoves);
10816         serverMoves = fopen(appData.serverMovesName, "r");
10817         if(serverMoves != NULL) {
10818             fclose(serverMoves);
10819             /* delay 15 sec before overwriting, so all clients can see end */
10820             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10821         }
10822         serverMoves = fopen(appData.serverMovesName, "w");
10823     }
10824
10825     ExitAnalyzeMode();
10826     gameMode = BeginningOfGame;
10827     ModeHighlight();
10828     if(appData.icsActive) gameInfo.variant = VariantNormal;
10829     currentMove = forwardMostMove = backwardMostMove = 0;
10830     MarkTargetSquares(1);
10831     InitPosition(redraw);
10832     for (i = 0; i < MAX_MOVES; i++) {
10833         if (commentList[i] != NULL) {
10834             free(commentList[i]);
10835             commentList[i] = NULL;
10836         }
10837     }
10838     ResetClocks();
10839     timeRemaining[0][0] = whiteTimeRemaining;
10840     timeRemaining[1][0] = blackTimeRemaining;
10841
10842     if (first.pr == NoProc) {
10843         StartChessProgram(&first);
10844     }
10845     if (init) {
10846             InitChessProgram(&first, startedFromSetupPosition);
10847     }
10848     DisplayTitle("");
10849     DisplayMessage("", "");
10850     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10851     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10852     ClearMap();        // [HGM] exclude: invalidate map
10853 }
10854
10855 void
10856 AutoPlayGameLoop ()
10857 {
10858     for (;;) {
10859         if (!AutoPlayOneMove())
10860           return;
10861         if (matchMode || appData.timeDelay == 0)
10862           continue;
10863         if (appData.timeDelay < 0)
10864           return;
10865         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10866         break;
10867     }
10868 }
10869
10870
10871 int
10872 AutoPlayOneMove ()
10873 {
10874     int fromX, fromY, toX, toY;
10875
10876     if (appData.debugMode) {
10877       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10878     }
10879
10880     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10881       return FALSE;
10882
10883     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10884       pvInfoList[currentMove].depth = programStats.depth;
10885       pvInfoList[currentMove].score = programStats.score;
10886       pvInfoList[currentMove].time  = 0;
10887       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10888     }
10889
10890     if (currentMove >= forwardMostMove) {
10891       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10892 //      gameMode = EndOfGame;
10893 //      ModeHighlight();
10894
10895       /* [AS] Clear current move marker at the end of a game */
10896       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10897
10898       return FALSE;
10899     }
10900
10901     toX = moveList[currentMove][2] - AAA;
10902     toY = moveList[currentMove][3] - ONE;
10903
10904     if (moveList[currentMove][1] == '@') {
10905         if (appData.highlightLastMove) {
10906             SetHighlights(-1, -1, toX, toY);
10907         }
10908     } else {
10909         fromX = moveList[currentMove][0] - AAA;
10910         fromY = moveList[currentMove][1] - ONE;
10911
10912         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10913
10914         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10915
10916         if (appData.highlightLastMove) {
10917             SetHighlights(fromX, fromY, toX, toY);
10918         }
10919     }
10920     DisplayMove(currentMove);
10921     SendMoveToProgram(currentMove++, &first);
10922     DisplayBothClocks();
10923     DrawPosition(FALSE, boards[currentMove]);
10924     // [HGM] PV info: always display, routine tests if empty
10925     DisplayComment(currentMove - 1, commentList[currentMove]);
10926     return TRUE;
10927 }
10928
10929
10930 int
10931 LoadGameOneMove (ChessMove readAhead)
10932 {
10933     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10934     char promoChar = NULLCHAR;
10935     ChessMove moveType;
10936     char move[MSG_SIZ];
10937     char *p, *q;
10938
10939     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10940         gameMode != AnalyzeMode && gameMode != Training) {
10941         gameFileFP = NULL;
10942         return FALSE;
10943     }
10944
10945     yyboardindex = forwardMostMove;
10946     if (readAhead != EndOfFile) {
10947       moveType = readAhead;
10948     } else {
10949       if (gameFileFP == NULL)
10950           return FALSE;
10951       moveType = (ChessMove) Myylex();
10952     }
10953
10954     done = FALSE;
10955     switch (moveType) {
10956       case Comment:
10957         if (appData.debugMode)
10958           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10959         p = yy_text;
10960
10961         /* append the comment but don't display it */
10962         AppendComment(currentMove, p, FALSE);
10963         return TRUE;
10964
10965       case WhiteCapturesEnPassant:
10966       case BlackCapturesEnPassant:
10967       case WhitePromotion:
10968       case BlackPromotion:
10969       case WhiteNonPromotion:
10970       case BlackNonPromotion:
10971       case NormalMove:
10972       case WhiteKingSideCastle:
10973       case WhiteQueenSideCastle:
10974       case BlackKingSideCastle:
10975       case BlackQueenSideCastle:
10976       case WhiteKingSideCastleWild:
10977       case WhiteQueenSideCastleWild:
10978       case BlackKingSideCastleWild:
10979       case BlackQueenSideCastleWild:
10980       /* PUSH Fabien */
10981       case WhiteHSideCastleFR:
10982       case WhiteASideCastleFR:
10983       case BlackHSideCastleFR:
10984       case BlackASideCastleFR:
10985       /* POP Fabien */
10986         if (appData.debugMode)
10987           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10988         fromX = currentMoveString[0] - AAA;
10989         fromY = currentMoveString[1] - ONE;
10990         toX = currentMoveString[2] - AAA;
10991         toY = currentMoveString[3] - ONE;
10992         promoChar = currentMoveString[4];
10993         break;
10994
10995       case WhiteDrop:
10996       case BlackDrop:
10997         if (appData.debugMode)
10998           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10999         fromX = moveType == WhiteDrop ?
11000           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11001         (int) CharToPiece(ToLower(currentMoveString[0]));
11002         fromY = DROP_RANK;
11003         toX = currentMoveString[2] - AAA;
11004         toY = currentMoveString[3] - ONE;
11005         break;
11006
11007       case WhiteWins:
11008       case BlackWins:
11009       case GameIsDrawn:
11010       case GameUnfinished:
11011         if (appData.debugMode)
11012           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11013         p = strchr(yy_text, '{');
11014         if (p == NULL) p = strchr(yy_text, '(');
11015         if (p == NULL) {
11016             p = yy_text;
11017             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11018         } else {
11019             q = strchr(p, *p == '{' ? '}' : ')');
11020             if (q != NULL) *q = NULLCHAR;
11021             p++;
11022         }
11023         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11024         GameEnds(moveType, p, GE_FILE);
11025         done = TRUE;
11026         if (cmailMsgLoaded) {
11027             ClearHighlights();
11028             flipView = WhiteOnMove(currentMove);
11029             if (moveType == GameUnfinished) flipView = !flipView;
11030             if (appData.debugMode)
11031               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11032         }
11033         break;
11034
11035       case EndOfFile:
11036         if (appData.debugMode)
11037           fprintf(debugFP, "Parser hit end of file\n");
11038         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11039           case MT_NONE:
11040           case MT_CHECK:
11041             break;
11042           case MT_CHECKMATE:
11043           case MT_STAINMATE:
11044             if (WhiteOnMove(currentMove)) {
11045                 GameEnds(BlackWins, "Black mates", GE_FILE);
11046             } else {
11047                 GameEnds(WhiteWins, "White mates", GE_FILE);
11048             }
11049             break;
11050           case MT_STALEMATE:
11051             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11052             break;
11053         }
11054         done = TRUE;
11055         break;
11056
11057       case MoveNumberOne:
11058         if (lastLoadGameStart == GNUChessGame) {
11059             /* GNUChessGames have numbers, but they aren't move numbers */
11060             if (appData.debugMode)
11061               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11062                       yy_text, (int) moveType);
11063             return LoadGameOneMove(EndOfFile); /* tail recursion */
11064         }
11065         /* else fall thru */
11066
11067       case XBoardGame:
11068       case GNUChessGame:
11069       case PGNTag:
11070         /* Reached start of next game in file */
11071         if (appData.debugMode)
11072           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11073         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11074           case MT_NONE:
11075           case MT_CHECK:
11076             break;
11077           case MT_CHECKMATE:
11078           case MT_STAINMATE:
11079             if (WhiteOnMove(currentMove)) {
11080                 GameEnds(BlackWins, "Black mates", GE_FILE);
11081             } else {
11082                 GameEnds(WhiteWins, "White mates", GE_FILE);
11083             }
11084             break;
11085           case MT_STALEMATE:
11086             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11087             break;
11088         }
11089         done = TRUE;
11090         break;
11091
11092       case PositionDiagram:     /* should not happen; ignore */
11093       case ElapsedTime:         /* ignore */
11094       case NAG:                 /* ignore */
11095         if (appData.debugMode)
11096           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11097                   yy_text, (int) moveType);
11098         return LoadGameOneMove(EndOfFile); /* tail recursion */
11099
11100       case IllegalMove:
11101         if (appData.testLegality) {
11102             if (appData.debugMode)
11103               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11104             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11105                     (forwardMostMove / 2) + 1,
11106                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11107             DisplayError(move, 0);
11108             done = TRUE;
11109         } else {
11110             if (appData.debugMode)
11111               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11112                       yy_text, currentMoveString);
11113             fromX = currentMoveString[0] - AAA;
11114             fromY = currentMoveString[1] - ONE;
11115             toX = currentMoveString[2] - AAA;
11116             toY = currentMoveString[3] - ONE;
11117             promoChar = currentMoveString[4];
11118         }
11119         break;
11120
11121       case AmbiguousMove:
11122         if (appData.debugMode)
11123           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11124         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11125                 (forwardMostMove / 2) + 1,
11126                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11127         DisplayError(move, 0);
11128         done = TRUE;
11129         break;
11130
11131       default:
11132       case ImpossibleMove:
11133         if (appData.debugMode)
11134           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11135         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11136                 (forwardMostMove / 2) + 1,
11137                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11138         DisplayError(move, 0);
11139         done = TRUE;
11140         break;
11141     }
11142
11143     if (done) {
11144         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11145             DrawPosition(FALSE, boards[currentMove]);
11146             DisplayBothClocks();
11147             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11148               DisplayComment(currentMove - 1, commentList[currentMove]);
11149         }
11150         (void) StopLoadGameTimer();
11151         gameFileFP = NULL;
11152         cmailOldMove = forwardMostMove;
11153         return FALSE;
11154     } else {
11155         /* currentMoveString is set as a side-effect of yylex */
11156
11157         thinkOutput[0] = NULLCHAR;
11158         MakeMove(fromX, fromY, toX, toY, promoChar);
11159         currentMove = forwardMostMove;
11160         return TRUE;
11161     }
11162 }
11163
11164 /* Load the nth game from the given file */
11165 int
11166 LoadGameFromFile (char *filename, int n, char *title, int useList)
11167 {
11168     FILE *f;
11169     char buf[MSG_SIZ];
11170
11171     if (strcmp(filename, "-") == 0) {
11172         f = stdin;
11173         title = "stdin";
11174     } else {
11175         f = fopen(filename, "rb");
11176         if (f == NULL) {
11177           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11178             DisplayError(buf, errno);
11179             return FALSE;
11180         }
11181     }
11182     if (fseek(f, 0, 0) == -1) {
11183         /* f is not seekable; probably a pipe */
11184         useList = FALSE;
11185     }
11186     if (useList && n == 0) {
11187         int error = GameListBuild(f);
11188         if (error) {
11189             DisplayError(_("Cannot build game list"), error);
11190         } else if (!ListEmpty(&gameList) &&
11191                    ((ListGame *) gameList.tailPred)->number > 1) {
11192             GameListPopUp(f, title);
11193             return TRUE;
11194         }
11195         GameListDestroy();
11196         n = 1;
11197     }
11198     if (n == 0) n = 1;
11199     return LoadGame(f, n, title, FALSE);
11200 }
11201
11202
11203 void
11204 MakeRegisteredMove ()
11205 {
11206     int fromX, fromY, toX, toY;
11207     char promoChar;
11208     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11209         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11210           case CMAIL_MOVE:
11211           case CMAIL_DRAW:
11212             if (appData.debugMode)
11213               fprintf(debugFP, "Restoring %s for game %d\n",
11214                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11215
11216             thinkOutput[0] = NULLCHAR;
11217             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11218             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11219             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11220             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11221             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11222             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11223             MakeMove(fromX, fromY, toX, toY, promoChar);
11224             ShowMove(fromX, fromY, toX, toY);
11225
11226             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11227               case MT_NONE:
11228               case MT_CHECK:
11229                 break;
11230
11231               case MT_CHECKMATE:
11232               case MT_STAINMATE:
11233                 if (WhiteOnMove(currentMove)) {
11234                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11235                 } else {
11236                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11237                 }
11238                 break;
11239
11240               case MT_STALEMATE:
11241                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11242                 break;
11243             }
11244
11245             break;
11246
11247           case CMAIL_RESIGN:
11248             if (WhiteOnMove(currentMove)) {
11249                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11250             } else {
11251                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11252             }
11253             break;
11254
11255           case CMAIL_ACCEPT:
11256             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11257             break;
11258
11259           default:
11260             break;
11261         }
11262     }
11263
11264     return;
11265 }
11266
11267 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11268 int
11269 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11270 {
11271     int retVal;
11272
11273     if (gameNumber > nCmailGames) {
11274         DisplayError(_("No more games in this message"), 0);
11275         return FALSE;
11276     }
11277     if (f == lastLoadGameFP) {
11278         int offset = gameNumber - lastLoadGameNumber;
11279         if (offset == 0) {
11280             cmailMsg[0] = NULLCHAR;
11281             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11282                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11283                 nCmailMovesRegistered--;
11284             }
11285             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11286             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11287                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11288             }
11289         } else {
11290             if (! RegisterMove()) return FALSE;
11291         }
11292     }
11293
11294     retVal = LoadGame(f, gameNumber, title, useList);
11295
11296     /* Make move registered during previous look at this game, if any */
11297     MakeRegisteredMove();
11298
11299     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11300         commentList[currentMove]
11301           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11302         DisplayComment(currentMove - 1, commentList[currentMove]);
11303     }
11304
11305     return retVal;
11306 }
11307
11308 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11309 int
11310 ReloadGame (int offset)
11311 {
11312     int gameNumber = lastLoadGameNumber + offset;
11313     if (lastLoadGameFP == NULL) {
11314         DisplayError(_("No game has been loaded yet"), 0);
11315         return FALSE;
11316     }
11317     if (gameNumber <= 0) {
11318         DisplayError(_("Can't back up any further"), 0);
11319         return FALSE;
11320     }
11321     if (cmailMsgLoaded) {
11322         return CmailLoadGame(lastLoadGameFP, gameNumber,
11323                              lastLoadGameTitle, lastLoadGameUseList);
11324     } else {
11325         return LoadGame(lastLoadGameFP, gameNumber,
11326                         lastLoadGameTitle, lastLoadGameUseList);
11327     }
11328 }
11329
11330 int keys[EmptySquare+1];
11331
11332 int
11333 PositionMatches (Board b1, Board b2)
11334 {
11335     int r, f, sum=0;
11336     switch(appData.searchMode) {
11337         case 1: return CompareWithRights(b1, b2);
11338         case 2:
11339             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11340                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11341             }
11342             return TRUE;
11343         case 3:
11344             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11345               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11346                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11347             }
11348             return sum==0;
11349         case 4:
11350             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11351                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11352             }
11353             return sum==0;
11354     }
11355     return TRUE;
11356 }
11357
11358 #define Q_PROMO  4
11359 #define Q_EP     3
11360 #define Q_BCASTL 2
11361 #define Q_WCASTL 1
11362
11363 int pieceList[256], quickBoard[256];
11364 ChessSquare pieceType[256] = { EmptySquare };
11365 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11366 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11367 int soughtTotal, turn;
11368 Boolean epOK, flipSearch;
11369
11370 typedef struct {
11371     unsigned char piece, to;
11372 } Move;
11373
11374 #define DSIZE (250000)
11375
11376 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11377 Move *moveDatabase = initialSpace;
11378 unsigned int movePtr, dataSize = DSIZE;
11379
11380 int
11381 MakePieceList (Board board, int *counts)
11382 {
11383     int r, f, n=Q_PROMO, total=0;
11384     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11385     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11386         int sq = f + (r<<4);
11387         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11388             quickBoard[sq] = ++n;
11389             pieceList[n] = sq;
11390             pieceType[n] = board[r][f];
11391             counts[board[r][f]]++;
11392             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11393             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11394             total++;
11395         }
11396     }
11397     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11398     return total;
11399 }
11400
11401 void
11402 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11403 {
11404     int sq = fromX + (fromY<<4);
11405     int piece = quickBoard[sq];
11406     quickBoard[sq] = 0;
11407     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11408     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11409         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11410         moveDatabase[movePtr++].piece = Q_WCASTL;
11411         quickBoard[sq] = piece;
11412         piece = quickBoard[from]; quickBoard[from] = 0;
11413         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11414     } else
11415     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11416         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11417         moveDatabase[movePtr++].piece = Q_BCASTL;
11418         quickBoard[sq] = piece;
11419         piece = quickBoard[from]; quickBoard[from] = 0;
11420         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11421     } else
11422     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11423         quickBoard[(fromY<<4)+toX] = 0;
11424         moveDatabase[movePtr].piece = Q_EP;
11425         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11426         moveDatabase[movePtr].to = sq;
11427     } else
11428     if(promoPiece != pieceType[piece]) {
11429         moveDatabase[movePtr++].piece = Q_PROMO;
11430         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11431     }
11432     moveDatabase[movePtr].piece = piece;
11433     quickBoard[sq] = piece;
11434     movePtr++;
11435 }
11436
11437 int
11438 PackGame (Board board)
11439 {
11440     Move *newSpace = NULL;
11441     moveDatabase[movePtr].piece = 0; // terminate previous game
11442     if(movePtr > dataSize) {
11443         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11444         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11445         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11446         if(newSpace) {
11447             int i;
11448             Move *p = moveDatabase, *q = newSpace;
11449             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11450             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11451             moveDatabase = newSpace;
11452         } else { // calloc failed, we must be out of memory. Too bad...
11453             dataSize = 0; // prevent calloc events for all subsequent games
11454             return 0;     // and signal this one isn't cached
11455         }
11456     }
11457     movePtr++;
11458     MakePieceList(board, counts);
11459     return movePtr;
11460 }
11461
11462 int
11463 QuickCompare (Board board, int *minCounts, int *maxCounts)
11464 {   // compare according to search mode
11465     int r, f;
11466     switch(appData.searchMode)
11467     {
11468       case 1: // exact position match
11469         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11470         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11471             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11472         }
11473         break;
11474       case 2: // can have extra material on empty squares
11475         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11476             if(board[r][f] == EmptySquare) continue;
11477             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11478         }
11479         break;
11480       case 3: // material with exact Pawn structure
11481         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11482             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11483             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11484         } // fall through to material comparison
11485       case 4: // exact material
11486         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11487         break;
11488       case 6: // material range with given imbalance
11489         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11490         // fall through to range comparison
11491       case 5: // material range
11492         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11493     }
11494     return TRUE;
11495 }
11496
11497 int
11498 QuickScan (Board board, Move *move)
11499 {   // reconstruct game,and compare all positions in it
11500     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11501     do {
11502         int piece = move->piece;
11503         int to = move->to, from = pieceList[piece];
11504         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11505           if(!piece) return -1;
11506           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11507             piece = (++move)->piece;
11508             from = pieceList[piece];
11509             counts[pieceType[piece]]--;
11510             pieceType[piece] = (ChessSquare) move->to;
11511             counts[move->to]++;
11512           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11513             counts[pieceType[quickBoard[to]]]--;
11514             quickBoard[to] = 0; total--;
11515             move++;
11516             continue;
11517           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11518             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11519             from  = pieceList[piece]; // so this must be King
11520             quickBoard[from] = 0;
11521             quickBoard[to] = piece;
11522             pieceList[piece] = to;
11523             move++;
11524             continue;
11525           }
11526         }
11527         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11528         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11529         quickBoard[from] = 0;
11530         quickBoard[to] = piece;
11531         pieceList[piece] = to;
11532         cnt++; turn ^= 3;
11533         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11534            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11535            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11536                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11537           ) {
11538             static int lastCounts[EmptySquare+1];
11539             int i;
11540             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11541             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11542         } else stretch = 0;
11543         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11544         move++;
11545     } while(1);
11546 }
11547
11548 void
11549 InitSearch ()
11550 {
11551     int r, f;
11552     flipSearch = FALSE;
11553     CopyBoard(soughtBoard, boards[currentMove]);
11554     soughtTotal = MakePieceList(soughtBoard, maxSought);
11555     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11556     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11557     CopyBoard(reverseBoard, boards[currentMove]);
11558     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11559         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11560         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11561         reverseBoard[r][f] = piece;
11562     }
11563     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11564     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11565     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11566                  || (boards[currentMove][CASTLING][2] == NoRights || 
11567                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11568                  && (boards[currentMove][CASTLING][5] == NoRights || 
11569                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11570       ) {
11571         flipSearch = TRUE;
11572         CopyBoard(flipBoard, soughtBoard);
11573         CopyBoard(rotateBoard, reverseBoard);
11574         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11575             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11576             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11577         }
11578     }
11579     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11580     if(appData.searchMode >= 5) {
11581         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11582         MakePieceList(soughtBoard, minSought);
11583         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11584     }
11585     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11586         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11587 }
11588
11589 GameInfo dummyInfo;
11590
11591 int
11592 GameContainsPosition (FILE *f, ListGame *lg)
11593 {
11594     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11595     int fromX, fromY, toX, toY;
11596     char promoChar;
11597     static int initDone=FALSE;
11598
11599     // weed out games based on numerical tag comparison
11600     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11601     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11602     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11603     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11604     if(!initDone) {
11605         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11606         initDone = TRUE;
11607     }
11608     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11609     else CopyBoard(boards[scratch], initialPosition); // default start position
11610     if(lg->moves) {
11611         turn = btm + 1;
11612         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11613         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11614     }
11615     if(btm) plyNr++;
11616     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11617     fseek(f, lg->offset, 0);
11618     yynewfile(f);
11619     while(1) {
11620         yyboardindex = scratch;
11621         quickFlag = plyNr+1;
11622         next = Myylex();
11623         quickFlag = 0;
11624         switch(next) {
11625             case PGNTag:
11626                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11627             default:
11628                 continue;
11629
11630             case XBoardGame:
11631             case GNUChessGame:
11632                 if(plyNr) return -1; // after we have seen moves, this is for new game
11633               continue;
11634
11635             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11636             case ImpossibleMove:
11637             case WhiteWins: // game ends here with these four
11638             case BlackWins:
11639             case GameIsDrawn:
11640             case GameUnfinished:
11641                 return -1;
11642
11643             case IllegalMove:
11644                 if(appData.testLegality) return -1;
11645             case WhiteCapturesEnPassant:
11646             case BlackCapturesEnPassant:
11647             case WhitePromotion:
11648             case BlackPromotion:
11649             case WhiteNonPromotion:
11650             case BlackNonPromotion:
11651             case NormalMove:
11652             case WhiteKingSideCastle:
11653             case WhiteQueenSideCastle:
11654             case BlackKingSideCastle:
11655             case BlackQueenSideCastle:
11656             case WhiteKingSideCastleWild:
11657             case WhiteQueenSideCastleWild:
11658             case BlackKingSideCastleWild:
11659             case BlackQueenSideCastleWild:
11660             case WhiteHSideCastleFR:
11661             case WhiteASideCastleFR:
11662             case BlackHSideCastleFR:
11663             case BlackASideCastleFR:
11664                 fromX = currentMoveString[0] - AAA;
11665                 fromY = currentMoveString[1] - ONE;
11666                 toX = currentMoveString[2] - AAA;
11667                 toY = currentMoveString[3] - ONE;
11668                 promoChar = currentMoveString[4];
11669                 break;
11670             case WhiteDrop:
11671             case BlackDrop:
11672                 fromX = next == WhiteDrop ?
11673                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11674                   (int) CharToPiece(ToLower(currentMoveString[0]));
11675                 fromY = DROP_RANK;
11676                 toX = currentMoveString[2] - AAA;
11677                 toY = currentMoveString[3] - ONE;
11678                 promoChar = 0;
11679                 break;
11680         }
11681         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11682         plyNr++;
11683         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11684         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11685         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11686         if(appData.findMirror) {
11687             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11688             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11689         }
11690     }
11691 }
11692
11693 /* Load the nth game from open file f */
11694 int
11695 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11696 {
11697     ChessMove cm;
11698     char buf[MSG_SIZ];
11699     int gn = gameNumber;
11700     ListGame *lg = NULL;
11701     int numPGNTags = 0;
11702     int err, pos = -1;
11703     GameMode oldGameMode;
11704     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11705
11706     if (appData.debugMode)
11707         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11708
11709     if (gameMode == Training )
11710         SetTrainingModeOff();
11711
11712     oldGameMode = gameMode;
11713     if (gameMode != BeginningOfGame) {
11714       Reset(FALSE, TRUE);
11715     }
11716
11717     gameFileFP = f;
11718     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11719         fclose(lastLoadGameFP);
11720     }
11721
11722     if (useList) {
11723         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11724
11725         if (lg) {
11726             fseek(f, lg->offset, 0);
11727             GameListHighlight(gameNumber);
11728             pos = lg->position;
11729             gn = 1;
11730         }
11731         else {
11732             DisplayError(_("Game number out of range"), 0);
11733             return FALSE;
11734         }
11735     } else {
11736         GameListDestroy();
11737         if (fseek(f, 0, 0) == -1) {
11738             if (f == lastLoadGameFP ?
11739                 gameNumber == lastLoadGameNumber + 1 :
11740                 gameNumber == 1) {
11741                 gn = 1;
11742             } else {
11743                 DisplayError(_("Can't seek on game file"), 0);
11744                 return FALSE;
11745             }
11746         }
11747     }
11748     lastLoadGameFP = f;
11749     lastLoadGameNumber = gameNumber;
11750     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11751     lastLoadGameUseList = useList;
11752
11753     yynewfile(f);
11754
11755     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11756       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11757                 lg->gameInfo.black);
11758             DisplayTitle(buf);
11759     } else if (*title != NULLCHAR) {
11760         if (gameNumber > 1) {
11761           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11762             DisplayTitle(buf);
11763         } else {
11764             DisplayTitle(title);
11765         }
11766     }
11767
11768     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11769         gameMode = PlayFromGameFile;
11770         ModeHighlight();
11771     }
11772
11773     currentMove = forwardMostMove = backwardMostMove = 0;
11774     CopyBoard(boards[0], initialPosition);
11775     StopClocks();
11776
11777     /*
11778      * Skip the first gn-1 games in the file.
11779      * Also skip over anything that precedes an identifiable
11780      * start of game marker, to avoid being confused by
11781      * garbage at the start of the file.  Currently
11782      * recognized start of game markers are the move number "1",
11783      * the pattern "gnuchess .* game", the pattern
11784      * "^[#;%] [^ ]* game file", and a PGN tag block.
11785      * A game that starts with one of the latter two patterns
11786      * will also have a move number 1, possibly
11787      * following a position diagram.
11788      * 5-4-02: Let's try being more lenient and allowing a game to
11789      * start with an unnumbered move.  Does that break anything?
11790      */
11791     cm = lastLoadGameStart = EndOfFile;
11792     while (gn > 0) {
11793         yyboardindex = forwardMostMove;
11794         cm = (ChessMove) Myylex();
11795         switch (cm) {
11796           case EndOfFile:
11797             if (cmailMsgLoaded) {
11798                 nCmailGames = CMAIL_MAX_GAMES - gn;
11799             } else {
11800                 Reset(TRUE, TRUE);
11801                 DisplayError(_("Game not found in file"), 0);
11802             }
11803             return FALSE;
11804
11805           case GNUChessGame:
11806           case XBoardGame:
11807             gn--;
11808             lastLoadGameStart = cm;
11809             break;
11810
11811           case MoveNumberOne:
11812             switch (lastLoadGameStart) {
11813               case GNUChessGame:
11814               case XBoardGame:
11815               case PGNTag:
11816                 break;
11817               case MoveNumberOne:
11818               case EndOfFile:
11819                 gn--;           /* count this game */
11820                 lastLoadGameStart = cm;
11821                 break;
11822               default:
11823                 /* impossible */
11824                 break;
11825             }
11826             break;
11827
11828           case PGNTag:
11829             switch (lastLoadGameStart) {
11830               case GNUChessGame:
11831               case PGNTag:
11832               case MoveNumberOne:
11833               case EndOfFile:
11834                 gn--;           /* count this game */
11835                 lastLoadGameStart = cm;
11836                 break;
11837               case XBoardGame:
11838                 lastLoadGameStart = cm; /* game counted already */
11839                 break;
11840               default:
11841                 /* impossible */
11842                 break;
11843             }
11844             if (gn > 0) {
11845                 do {
11846                     yyboardindex = forwardMostMove;
11847                     cm = (ChessMove) Myylex();
11848                 } while (cm == PGNTag || cm == Comment);
11849             }
11850             break;
11851
11852           case WhiteWins:
11853           case BlackWins:
11854           case GameIsDrawn:
11855             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11856                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11857                     != CMAIL_OLD_RESULT) {
11858                     nCmailResults ++ ;
11859                     cmailResult[  CMAIL_MAX_GAMES
11860                                 - gn - 1] = CMAIL_OLD_RESULT;
11861                 }
11862             }
11863             break;
11864
11865           case NormalMove:
11866             /* Only a NormalMove can be at the start of a game
11867              * without a position diagram. */
11868             if (lastLoadGameStart == EndOfFile ) {
11869               gn--;
11870               lastLoadGameStart = MoveNumberOne;
11871             }
11872             break;
11873
11874           default:
11875             break;
11876         }
11877     }
11878
11879     if (appData.debugMode)
11880       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11881
11882     if (cm == XBoardGame) {
11883         /* Skip any header junk before position diagram and/or move 1 */
11884         for (;;) {
11885             yyboardindex = forwardMostMove;
11886             cm = (ChessMove) Myylex();
11887
11888             if (cm == EndOfFile ||
11889                 cm == GNUChessGame || cm == XBoardGame) {
11890                 /* Empty game; pretend end-of-file and handle later */
11891                 cm = EndOfFile;
11892                 break;
11893             }
11894
11895             if (cm == MoveNumberOne || cm == PositionDiagram ||
11896                 cm == PGNTag || cm == Comment)
11897               break;
11898         }
11899     } else if (cm == GNUChessGame) {
11900         if (gameInfo.event != NULL) {
11901             free(gameInfo.event);
11902         }
11903         gameInfo.event = StrSave(yy_text);
11904     }
11905
11906     startedFromSetupPosition = FALSE;
11907     while (cm == PGNTag) {
11908         if (appData.debugMode)
11909           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11910         err = ParsePGNTag(yy_text, &gameInfo);
11911         if (!err) numPGNTags++;
11912
11913         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11914         if(gameInfo.variant != oldVariant) {
11915             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11916             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11917             InitPosition(TRUE);
11918             oldVariant = gameInfo.variant;
11919             if (appData.debugMode)
11920               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11921         }
11922
11923
11924         if (gameInfo.fen != NULL) {
11925           Board initial_position;
11926           startedFromSetupPosition = TRUE;
11927           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11928             Reset(TRUE, TRUE);
11929             DisplayError(_("Bad FEN position in file"), 0);
11930             return FALSE;
11931           }
11932           CopyBoard(boards[0], initial_position);
11933           if (blackPlaysFirst) {
11934             currentMove = forwardMostMove = backwardMostMove = 1;
11935             CopyBoard(boards[1], initial_position);
11936             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11937             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11938             timeRemaining[0][1] = whiteTimeRemaining;
11939             timeRemaining[1][1] = blackTimeRemaining;
11940             if (commentList[0] != NULL) {
11941               commentList[1] = commentList[0];
11942               commentList[0] = NULL;
11943             }
11944           } else {
11945             currentMove = forwardMostMove = backwardMostMove = 0;
11946           }
11947           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11948           {   int i;
11949               initialRulePlies = FENrulePlies;
11950               for( i=0; i< nrCastlingRights; i++ )
11951                   initialRights[i] = initial_position[CASTLING][i];
11952           }
11953           yyboardindex = forwardMostMove;
11954           free(gameInfo.fen);
11955           gameInfo.fen = NULL;
11956         }
11957
11958         yyboardindex = forwardMostMove;
11959         cm = (ChessMove) Myylex();
11960
11961         /* Handle comments interspersed among the tags */
11962         while (cm == Comment) {
11963             char *p;
11964             if (appData.debugMode)
11965               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11966             p = yy_text;
11967             AppendComment(currentMove, p, FALSE);
11968             yyboardindex = forwardMostMove;
11969             cm = (ChessMove) Myylex();
11970         }
11971     }
11972
11973     /* don't rely on existence of Event tag since if game was
11974      * pasted from clipboard the Event tag may not exist
11975      */
11976     if (numPGNTags > 0){
11977         char *tags;
11978         if (gameInfo.variant == VariantNormal) {
11979           VariantClass v = StringToVariant(gameInfo.event);
11980           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11981           if(v < VariantShogi) gameInfo.variant = v;
11982         }
11983         if (!matchMode) {
11984           if( appData.autoDisplayTags ) {
11985             tags = PGNTags(&gameInfo);
11986             TagsPopUp(tags, CmailMsg());
11987             free(tags);
11988           }
11989         }
11990     } else {
11991         /* Make something up, but don't display it now */
11992         SetGameInfo();
11993         TagsPopDown();
11994     }
11995
11996     if (cm == PositionDiagram) {
11997         int i, j;
11998         char *p;
11999         Board initial_position;
12000
12001         if (appData.debugMode)
12002           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12003
12004         if (!startedFromSetupPosition) {
12005             p = yy_text;
12006             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12007               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12008                 switch (*p) {
12009                   case '{':
12010                   case '[':
12011                   case '-':
12012                   case ' ':
12013                   case '\t':
12014                   case '\n':
12015                   case '\r':
12016                     break;
12017                   default:
12018                     initial_position[i][j++] = CharToPiece(*p);
12019                     break;
12020                 }
12021             while (*p == ' ' || *p == '\t' ||
12022                    *p == '\n' || *p == '\r') p++;
12023
12024             if (strncmp(p, "black", strlen("black"))==0)
12025               blackPlaysFirst = TRUE;
12026             else
12027               blackPlaysFirst = FALSE;
12028             startedFromSetupPosition = TRUE;
12029
12030             CopyBoard(boards[0], initial_position);
12031             if (blackPlaysFirst) {
12032                 currentMove = forwardMostMove = backwardMostMove = 1;
12033                 CopyBoard(boards[1], initial_position);
12034                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12035                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12036                 timeRemaining[0][1] = whiteTimeRemaining;
12037                 timeRemaining[1][1] = blackTimeRemaining;
12038                 if (commentList[0] != NULL) {
12039                     commentList[1] = commentList[0];
12040                     commentList[0] = NULL;
12041                 }
12042             } else {
12043                 currentMove = forwardMostMove = backwardMostMove = 0;
12044             }
12045         }
12046         yyboardindex = forwardMostMove;
12047         cm = (ChessMove) Myylex();
12048     }
12049
12050     if (first.pr == NoProc) {
12051         StartChessProgram(&first);
12052     }
12053     InitChessProgram(&first, FALSE);
12054     SendToProgram("force\n", &first);
12055     if (startedFromSetupPosition) {
12056         SendBoard(&first, forwardMostMove);
12057     if (appData.debugMode) {
12058         fprintf(debugFP, "Load Game\n");
12059     }
12060         DisplayBothClocks();
12061     }
12062
12063     /* [HGM] server: flag to write setup moves in broadcast file as one */
12064     loadFlag = appData.suppressLoadMoves;
12065
12066     while (cm == Comment) {
12067         char *p;
12068         if (appData.debugMode)
12069           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12070         p = yy_text;
12071         AppendComment(currentMove, p, FALSE);
12072         yyboardindex = forwardMostMove;
12073         cm = (ChessMove) Myylex();
12074     }
12075
12076     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12077         cm == WhiteWins || cm == BlackWins ||
12078         cm == GameIsDrawn || cm == GameUnfinished) {
12079         DisplayMessage("", _("No moves in game"));
12080         if (cmailMsgLoaded) {
12081             if (appData.debugMode)
12082               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12083             ClearHighlights();
12084             flipView = FALSE;
12085         }
12086         DrawPosition(FALSE, boards[currentMove]);
12087         DisplayBothClocks();
12088         gameMode = EditGame;
12089         ModeHighlight();
12090         gameFileFP = NULL;
12091         cmailOldMove = 0;
12092         return TRUE;
12093     }
12094
12095     // [HGM] PV info: routine tests if comment empty
12096     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12097         DisplayComment(currentMove - 1, commentList[currentMove]);
12098     }
12099     if (!matchMode && appData.timeDelay != 0)
12100       DrawPosition(FALSE, boards[currentMove]);
12101
12102     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12103       programStats.ok_to_send = 1;
12104     }
12105
12106     /* if the first token after the PGN tags is a move
12107      * and not move number 1, retrieve it from the parser
12108      */
12109     if (cm != MoveNumberOne)
12110         LoadGameOneMove(cm);
12111
12112     /* load the remaining moves from the file */
12113     while (LoadGameOneMove(EndOfFile)) {
12114       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12115       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12116     }
12117
12118     /* rewind to the start of the game */
12119     currentMove = backwardMostMove;
12120
12121     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12122
12123     if (oldGameMode == AnalyzeFile ||
12124         oldGameMode == AnalyzeMode) {
12125       AnalyzeFileEvent();
12126     }
12127
12128     if (!matchMode && pos >= 0) {
12129         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12130     } else
12131     if (matchMode || appData.timeDelay == 0) {
12132       ToEndEvent();
12133     } else if (appData.timeDelay > 0) {
12134       AutoPlayGameLoop();
12135     }
12136
12137     if (appData.debugMode)
12138         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12139
12140     loadFlag = 0; /* [HGM] true game starts */
12141     return TRUE;
12142 }
12143
12144 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12145 int
12146 ReloadPosition (int offset)
12147 {
12148     int positionNumber = lastLoadPositionNumber + offset;
12149     if (lastLoadPositionFP == NULL) {
12150         DisplayError(_("No position has been loaded yet"), 0);
12151         return FALSE;
12152     }
12153     if (positionNumber <= 0) {
12154         DisplayError(_("Can't back up any further"), 0);
12155         return FALSE;
12156     }
12157     return LoadPosition(lastLoadPositionFP, positionNumber,
12158                         lastLoadPositionTitle);
12159 }
12160
12161 /* Load the nth position from the given file */
12162 int
12163 LoadPositionFromFile (char *filename, int n, char *title)
12164 {
12165     FILE *f;
12166     char buf[MSG_SIZ];
12167
12168     if (strcmp(filename, "-") == 0) {
12169         return LoadPosition(stdin, n, "stdin");
12170     } else {
12171         f = fopen(filename, "rb");
12172         if (f == NULL) {
12173             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12174             DisplayError(buf, errno);
12175             return FALSE;
12176         } else {
12177             return LoadPosition(f, n, title);
12178         }
12179     }
12180 }
12181
12182 /* Load the nth position from the given open file, and close it */
12183 int
12184 LoadPosition (FILE *f, int positionNumber, char *title)
12185 {
12186     char *p, line[MSG_SIZ];
12187     Board initial_position;
12188     int i, j, fenMode, pn;
12189
12190     if (gameMode == Training )
12191         SetTrainingModeOff();
12192
12193     if (gameMode != BeginningOfGame) {
12194         Reset(FALSE, TRUE);
12195     }
12196     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12197         fclose(lastLoadPositionFP);
12198     }
12199     if (positionNumber == 0) positionNumber = 1;
12200     lastLoadPositionFP = f;
12201     lastLoadPositionNumber = positionNumber;
12202     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12203     if (first.pr == NoProc && !appData.noChessProgram) {
12204       StartChessProgram(&first);
12205       InitChessProgram(&first, FALSE);
12206     }
12207     pn = positionNumber;
12208     if (positionNumber < 0) {
12209         /* Negative position number means to seek to that byte offset */
12210         if (fseek(f, -positionNumber, 0) == -1) {
12211             DisplayError(_("Can't seek on position file"), 0);
12212             return FALSE;
12213         };
12214         pn = 1;
12215     } else {
12216         if (fseek(f, 0, 0) == -1) {
12217             if (f == lastLoadPositionFP ?
12218                 positionNumber == lastLoadPositionNumber + 1 :
12219                 positionNumber == 1) {
12220                 pn = 1;
12221             } else {
12222                 DisplayError(_("Can't seek on position file"), 0);
12223                 return FALSE;
12224             }
12225         }
12226     }
12227     /* See if this file is FEN or old-style xboard */
12228     if (fgets(line, MSG_SIZ, f) == NULL) {
12229         DisplayError(_("Position not found in file"), 0);
12230         return FALSE;
12231     }
12232     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12233     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12234
12235     if (pn >= 2) {
12236         if (fenMode || line[0] == '#') pn--;
12237         while (pn > 0) {
12238             /* skip positions before number pn */
12239             if (fgets(line, MSG_SIZ, f) == NULL) {
12240                 Reset(TRUE, TRUE);
12241                 DisplayError(_("Position not found in file"), 0);
12242                 return FALSE;
12243             }
12244             if (fenMode || line[0] == '#') pn--;
12245         }
12246     }
12247
12248     if (fenMode) {
12249         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12250             DisplayError(_("Bad FEN position in file"), 0);
12251             return FALSE;
12252         }
12253     } else {
12254         (void) fgets(line, MSG_SIZ, f);
12255         (void) fgets(line, MSG_SIZ, f);
12256
12257         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12258             (void) fgets(line, MSG_SIZ, f);
12259             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12260                 if (*p == ' ')
12261                   continue;
12262                 initial_position[i][j++] = CharToPiece(*p);
12263             }
12264         }
12265
12266         blackPlaysFirst = FALSE;
12267         if (!feof(f)) {
12268             (void) fgets(line, MSG_SIZ, f);
12269             if (strncmp(line, "black", strlen("black"))==0)
12270               blackPlaysFirst = TRUE;
12271         }
12272     }
12273     startedFromSetupPosition = TRUE;
12274
12275     CopyBoard(boards[0], initial_position);
12276     if (blackPlaysFirst) {
12277         currentMove = forwardMostMove = backwardMostMove = 1;
12278         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12279         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12280         CopyBoard(boards[1], initial_position);
12281         DisplayMessage("", _("Black to play"));
12282     } else {
12283         currentMove = forwardMostMove = backwardMostMove = 0;
12284         DisplayMessage("", _("White to play"));
12285     }
12286     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12287     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12288         SendToProgram("force\n", &first);
12289         SendBoard(&first, forwardMostMove);
12290     }
12291     if (appData.debugMode) {
12292 int i, j;
12293   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12294   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12295         fprintf(debugFP, "Load Position\n");
12296     }
12297
12298     if (positionNumber > 1) {
12299       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12300         DisplayTitle(line);
12301     } else {
12302         DisplayTitle(title);
12303     }
12304     gameMode = EditGame;
12305     ModeHighlight();
12306     ResetClocks();
12307     timeRemaining[0][1] = whiteTimeRemaining;
12308     timeRemaining[1][1] = blackTimeRemaining;
12309     DrawPosition(FALSE, boards[currentMove]);
12310
12311     return TRUE;
12312 }
12313
12314
12315 void
12316 CopyPlayerNameIntoFileName (char **dest, char *src)
12317 {
12318     while (*src != NULLCHAR && *src != ',') {
12319         if (*src == ' ') {
12320             *(*dest)++ = '_';
12321             src++;
12322         } else {
12323             *(*dest)++ = *src++;
12324         }
12325     }
12326 }
12327
12328 char *
12329 DefaultFileName (char *ext)
12330 {
12331     static char def[MSG_SIZ];
12332     char *p;
12333
12334     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12335         p = def;
12336         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12337         *p++ = '-';
12338         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12339         *p++ = '.';
12340         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12341     } else {
12342         def[0] = NULLCHAR;
12343     }
12344     return def;
12345 }
12346
12347 /* Save the current game to the given file */
12348 int
12349 SaveGameToFile (char *filename, int append)
12350 {
12351     FILE *f;
12352     char buf[MSG_SIZ];
12353     int result, i, t,tot=0;
12354
12355     if (strcmp(filename, "-") == 0) {
12356         return SaveGame(stdout, 0, NULL);
12357     } else {
12358         for(i=0; i<10; i++) { // upto 10 tries
12359              f = fopen(filename, append ? "a" : "w");
12360              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12361              if(f || errno != 13) break;
12362              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12363              tot += t;
12364         }
12365         if (f == NULL) {
12366             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12367             DisplayError(buf, errno);
12368             return FALSE;
12369         } else {
12370             safeStrCpy(buf, lastMsg, MSG_SIZ);
12371             DisplayMessage(_("Waiting for access to save file"), "");
12372             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12373             DisplayMessage(_("Saving game"), "");
12374             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12375             result = SaveGame(f, 0, NULL);
12376             DisplayMessage(buf, "");
12377             return result;
12378         }
12379     }
12380 }
12381
12382 char *
12383 SavePart (char *str)
12384 {
12385     static char buf[MSG_SIZ];
12386     char *p;
12387
12388     p = strchr(str, ' ');
12389     if (p == NULL) return str;
12390     strncpy(buf, str, p - str);
12391     buf[p - str] = NULLCHAR;
12392     return buf;
12393 }
12394
12395 #define PGN_MAX_LINE 75
12396
12397 #define PGN_SIDE_WHITE  0
12398 #define PGN_SIDE_BLACK  1
12399
12400 static int
12401 FindFirstMoveOutOfBook (int side)
12402 {
12403     int result = -1;
12404
12405     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12406         int index = backwardMostMove;
12407         int has_book_hit = 0;
12408
12409         if( (index % 2) != side ) {
12410             index++;
12411         }
12412
12413         while( index < forwardMostMove ) {
12414             /* Check to see if engine is in book */
12415             int depth = pvInfoList[index].depth;
12416             int score = pvInfoList[index].score;
12417             int in_book = 0;
12418
12419             if( depth <= 2 ) {
12420                 in_book = 1;
12421             }
12422             else if( score == 0 && depth == 63 ) {
12423                 in_book = 1; /* Zappa */
12424             }
12425             else if( score == 2 && depth == 99 ) {
12426                 in_book = 1; /* Abrok */
12427             }
12428
12429             has_book_hit += in_book;
12430
12431             if( ! in_book ) {
12432                 result = index;
12433
12434                 break;
12435             }
12436
12437             index += 2;
12438         }
12439     }
12440
12441     return result;
12442 }
12443
12444 void
12445 GetOutOfBookInfo (char * buf)
12446 {
12447     int oob[2];
12448     int i;
12449     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12450
12451     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12452     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12453
12454     *buf = '\0';
12455
12456     if( oob[0] >= 0 || oob[1] >= 0 ) {
12457         for( i=0; i<2; i++ ) {
12458             int idx = oob[i];
12459
12460             if( idx >= 0 ) {
12461                 if( i > 0 && oob[0] >= 0 ) {
12462                     strcat( buf, "   " );
12463                 }
12464
12465                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12466                 sprintf( buf+strlen(buf), "%s%.2f",
12467                     pvInfoList[idx].score >= 0 ? "+" : "",
12468                     pvInfoList[idx].score / 100.0 );
12469             }
12470         }
12471     }
12472 }
12473
12474 /* Save game in PGN style and close the file */
12475 int
12476 SaveGamePGN (FILE *f)
12477 {
12478     int i, offset, linelen, newblock;
12479     time_t tm;
12480 //    char *movetext;
12481     char numtext[32];
12482     int movelen, numlen, blank;
12483     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12484
12485     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12486
12487     tm = time((time_t *) NULL);
12488
12489     PrintPGNTags(f, &gameInfo);
12490
12491     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12492
12493     if (backwardMostMove > 0 || startedFromSetupPosition) {
12494         char *fen = PositionToFEN(backwardMostMove, NULL);
12495         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12496         fprintf(f, "\n{--------------\n");
12497         PrintPosition(f, backwardMostMove);
12498         fprintf(f, "--------------}\n");
12499         free(fen);
12500     }
12501     else {
12502         /* [AS] Out of book annotation */
12503         if( appData.saveOutOfBookInfo ) {
12504             char buf[64];
12505
12506             GetOutOfBookInfo( buf );
12507
12508             if( buf[0] != '\0' ) {
12509                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12510             }
12511         }
12512
12513         fprintf(f, "\n");
12514     }
12515
12516     i = backwardMostMove;
12517     linelen = 0;
12518     newblock = TRUE;
12519
12520     while (i < forwardMostMove) {
12521         /* Print comments preceding this move */
12522         if (commentList[i] != NULL) {
12523             if (linelen > 0) fprintf(f, "\n");
12524             fprintf(f, "%s", commentList[i]);
12525             linelen = 0;
12526             newblock = TRUE;
12527         }
12528
12529         /* Format move number */
12530         if ((i % 2) == 0)
12531           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12532         else
12533           if (newblock)
12534             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12535           else
12536             numtext[0] = NULLCHAR;
12537
12538         numlen = strlen(numtext);
12539         newblock = FALSE;
12540
12541         /* Print move number */
12542         blank = linelen > 0 && numlen > 0;
12543         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12544             fprintf(f, "\n");
12545             linelen = 0;
12546             blank = 0;
12547         }
12548         if (blank) {
12549             fprintf(f, " ");
12550             linelen++;
12551         }
12552         fprintf(f, "%s", numtext);
12553         linelen += numlen;
12554
12555         /* Get move */
12556         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12557         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12558
12559         /* Print move */
12560         blank = linelen > 0 && movelen > 0;
12561         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12562             fprintf(f, "\n");
12563             linelen = 0;
12564             blank = 0;
12565         }
12566         if (blank) {
12567             fprintf(f, " ");
12568             linelen++;
12569         }
12570         fprintf(f, "%s", move_buffer);
12571         linelen += movelen;
12572
12573         /* [AS] Add PV info if present */
12574         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12575             /* [HGM] add time */
12576             char buf[MSG_SIZ]; int seconds;
12577
12578             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12579
12580             if( seconds <= 0)
12581               buf[0] = 0;
12582             else
12583               if( seconds < 30 )
12584                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12585               else
12586                 {
12587                   seconds = (seconds + 4)/10; // round to full seconds
12588                   if( seconds < 60 )
12589                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12590                   else
12591                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12592                 }
12593
12594             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12595                       pvInfoList[i].score >= 0 ? "+" : "",
12596                       pvInfoList[i].score / 100.0,
12597                       pvInfoList[i].depth,
12598                       buf );
12599
12600             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12601
12602             /* Print score/depth */
12603             blank = linelen > 0 && movelen > 0;
12604             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12605                 fprintf(f, "\n");
12606                 linelen = 0;
12607                 blank = 0;
12608             }
12609             if (blank) {
12610                 fprintf(f, " ");
12611                 linelen++;
12612             }
12613             fprintf(f, "%s", move_buffer);
12614             linelen += movelen;
12615         }
12616
12617         i++;
12618     }
12619
12620     /* Start a new line */
12621     if (linelen > 0) fprintf(f, "\n");
12622
12623     /* Print comments after last move */
12624     if (commentList[i] != NULL) {
12625         fprintf(f, "%s\n", commentList[i]);
12626     }
12627
12628     /* Print result */
12629     if (gameInfo.resultDetails != NULL &&
12630         gameInfo.resultDetails[0] != NULLCHAR) {
12631         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12632                 PGNResult(gameInfo.result));
12633     } else {
12634         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12635     }
12636
12637     fclose(f);
12638     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12639     return TRUE;
12640 }
12641
12642 /* Save game in old style and close the file */
12643 int
12644 SaveGameOldStyle (FILE *f)
12645 {
12646     int i, offset;
12647     time_t tm;
12648
12649     tm = time((time_t *) NULL);
12650
12651     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12652     PrintOpponents(f);
12653
12654     if (backwardMostMove > 0 || startedFromSetupPosition) {
12655         fprintf(f, "\n[--------------\n");
12656         PrintPosition(f, backwardMostMove);
12657         fprintf(f, "--------------]\n");
12658     } else {
12659         fprintf(f, "\n");
12660     }
12661
12662     i = backwardMostMove;
12663     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12664
12665     while (i < forwardMostMove) {
12666         if (commentList[i] != NULL) {
12667             fprintf(f, "[%s]\n", commentList[i]);
12668         }
12669
12670         if ((i % 2) == 1) {
12671             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12672             i++;
12673         } else {
12674             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12675             i++;
12676             if (commentList[i] != NULL) {
12677                 fprintf(f, "\n");
12678                 continue;
12679             }
12680             if (i >= forwardMostMove) {
12681                 fprintf(f, "\n");
12682                 break;
12683             }
12684             fprintf(f, "%s\n", parseList[i]);
12685             i++;
12686         }
12687     }
12688
12689     if (commentList[i] != NULL) {
12690         fprintf(f, "[%s]\n", commentList[i]);
12691     }
12692
12693     /* This isn't really the old style, but it's close enough */
12694     if (gameInfo.resultDetails != NULL &&
12695         gameInfo.resultDetails[0] != NULLCHAR) {
12696         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12697                 gameInfo.resultDetails);
12698     } else {
12699         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12700     }
12701
12702     fclose(f);
12703     return TRUE;
12704 }
12705
12706 /* Save the current game to open file f and close the file */
12707 int
12708 SaveGame (FILE *f, int dummy, char *dummy2)
12709 {
12710     if (gameMode == EditPosition) EditPositionDone(TRUE);
12711     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12712     if (appData.oldSaveStyle)
12713       return SaveGameOldStyle(f);
12714     else
12715       return SaveGamePGN(f);
12716 }
12717
12718 /* Save the current position to the given file */
12719 int
12720 SavePositionToFile (char *filename)
12721 {
12722     FILE *f;
12723     char buf[MSG_SIZ];
12724
12725     if (strcmp(filename, "-") == 0) {
12726         return SavePosition(stdout, 0, NULL);
12727     } else {
12728         f = fopen(filename, "a");
12729         if (f == NULL) {
12730             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12731             DisplayError(buf, errno);
12732             return FALSE;
12733         } else {
12734             safeStrCpy(buf, lastMsg, MSG_SIZ);
12735             DisplayMessage(_("Waiting for access to save file"), "");
12736             flock(fileno(f), LOCK_EX); // [HGM] lock
12737             DisplayMessage(_("Saving position"), "");
12738             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12739             SavePosition(f, 0, NULL);
12740             DisplayMessage(buf, "");
12741             return TRUE;
12742         }
12743     }
12744 }
12745
12746 /* Save the current position to the given open file and close the file */
12747 int
12748 SavePosition (FILE *f, int dummy, char *dummy2)
12749 {
12750     time_t tm;
12751     char *fen;
12752
12753     if (gameMode == EditPosition) EditPositionDone(TRUE);
12754     if (appData.oldSaveStyle) {
12755         tm = time((time_t *) NULL);
12756
12757         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12758         PrintOpponents(f);
12759         fprintf(f, "[--------------\n");
12760         PrintPosition(f, currentMove);
12761         fprintf(f, "--------------]\n");
12762     } else {
12763         fen = PositionToFEN(currentMove, NULL);
12764         fprintf(f, "%s\n", fen);
12765         free(fen);
12766     }
12767     fclose(f);
12768     return TRUE;
12769 }
12770
12771 void
12772 ReloadCmailMsgEvent (int unregister)
12773 {
12774 #if !WIN32
12775     static char *inFilename = NULL;
12776     static char *outFilename;
12777     int i;
12778     struct stat inbuf, outbuf;
12779     int status;
12780
12781     /* Any registered moves are unregistered if unregister is set, */
12782     /* i.e. invoked by the signal handler */
12783     if (unregister) {
12784         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12785             cmailMoveRegistered[i] = FALSE;
12786             if (cmailCommentList[i] != NULL) {
12787                 free(cmailCommentList[i]);
12788                 cmailCommentList[i] = NULL;
12789             }
12790         }
12791         nCmailMovesRegistered = 0;
12792     }
12793
12794     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12795         cmailResult[i] = CMAIL_NOT_RESULT;
12796     }
12797     nCmailResults = 0;
12798
12799     if (inFilename == NULL) {
12800         /* Because the filenames are static they only get malloced once  */
12801         /* and they never get freed                                      */
12802         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12803         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12804
12805         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12806         sprintf(outFilename, "%s.out", appData.cmailGameName);
12807     }
12808
12809     status = stat(outFilename, &outbuf);
12810     if (status < 0) {
12811         cmailMailedMove = FALSE;
12812     } else {
12813         status = stat(inFilename, &inbuf);
12814         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12815     }
12816
12817     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12818        counts the games, notes how each one terminated, etc.
12819
12820        It would be nice to remove this kludge and instead gather all
12821        the information while building the game list.  (And to keep it
12822        in the game list nodes instead of having a bunch of fixed-size
12823        parallel arrays.)  Note this will require getting each game's
12824        termination from the PGN tags, as the game list builder does
12825        not process the game moves.  --mann
12826        */
12827     cmailMsgLoaded = TRUE;
12828     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12829
12830     /* Load first game in the file or popup game menu */
12831     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12832
12833 #endif /* !WIN32 */
12834     return;
12835 }
12836
12837 int
12838 RegisterMove ()
12839 {
12840     FILE *f;
12841     char string[MSG_SIZ];
12842
12843     if (   cmailMailedMove
12844         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12845         return TRUE;            /* Allow free viewing  */
12846     }
12847
12848     /* Unregister move to ensure that we don't leave RegisterMove        */
12849     /* with the move registered when the conditions for registering no   */
12850     /* longer hold                                                       */
12851     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12852         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12853         nCmailMovesRegistered --;
12854
12855         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12856           {
12857               free(cmailCommentList[lastLoadGameNumber - 1]);
12858               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12859           }
12860     }
12861
12862     if (cmailOldMove == -1) {
12863         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12864         return FALSE;
12865     }
12866
12867     if (currentMove > cmailOldMove + 1) {
12868         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12869         return FALSE;
12870     }
12871
12872     if (currentMove < cmailOldMove) {
12873         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12874         return FALSE;
12875     }
12876
12877     if (forwardMostMove > currentMove) {
12878         /* Silently truncate extra moves */
12879         TruncateGame();
12880     }
12881
12882     if (   (currentMove == cmailOldMove + 1)
12883         || (   (currentMove == cmailOldMove)
12884             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12885                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12886         if (gameInfo.result != GameUnfinished) {
12887             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12888         }
12889
12890         if (commentList[currentMove] != NULL) {
12891             cmailCommentList[lastLoadGameNumber - 1]
12892               = StrSave(commentList[currentMove]);
12893         }
12894         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12895
12896         if (appData.debugMode)
12897           fprintf(debugFP, "Saving %s for game %d\n",
12898                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12899
12900         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12901
12902         f = fopen(string, "w");
12903         if (appData.oldSaveStyle) {
12904             SaveGameOldStyle(f); /* also closes the file */
12905
12906             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12907             f = fopen(string, "w");
12908             SavePosition(f, 0, NULL); /* also closes the file */
12909         } else {
12910             fprintf(f, "{--------------\n");
12911             PrintPosition(f, currentMove);
12912             fprintf(f, "--------------}\n\n");
12913
12914             SaveGame(f, 0, NULL); /* also closes the file*/
12915         }
12916
12917         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12918         nCmailMovesRegistered ++;
12919     } else if (nCmailGames == 1) {
12920         DisplayError(_("You have not made a move yet"), 0);
12921         return FALSE;
12922     }
12923
12924     return TRUE;
12925 }
12926
12927 void
12928 MailMoveEvent ()
12929 {
12930 #if !WIN32
12931     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12932     FILE *commandOutput;
12933     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12934     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12935     int nBuffers;
12936     int i;
12937     int archived;
12938     char *arcDir;
12939
12940     if (! cmailMsgLoaded) {
12941         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12942         return;
12943     }
12944
12945     if (nCmailGames == nCmailResults) {
12946         DisplayError(_("No unfinished games"), 0);
12947         return;
12948     }
12949
12950 #if CMAIL_PROHIBIT_REMAIL
12951     if (cmailMailedMove) {
12952       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);
12953         DisplayError(msg, 0);
12954         return;
12955     }
12956 #endif
12957
12958     if (! (cmailMailedMove || RegisterMove())) return;
12959
12960     if (   cmailMailedMove
12961         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12962       snprintf(string, MSG_SIZ, partCommandString,
12963                appData.debugMode ? " -v" : "", appData.cmailGameName);
12964         commandOutput = popen(string, "r");
12965
12966         if (commandOutput == NULL) {
12967             DisplayError(_("Failed to invoke cmail"), 0);
12968         } else {
12969             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12970                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12971             }
12972             if (nBuffers > 1) {
12973                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12974                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12975                 nBytes = MSG_SIZ - 1;
12976             } else {
12977                 (void) memcpy(msg, buffer, nBytes);
12978             }
12979             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12980
12981             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12982                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12983
12984                 archived = TRUE;
12985                 for (i = 0; i < nCmailGames; i ++) {
12986                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12987                         archived = FALSE;
12988                     }
12989                 }
12990                 if (   archived
12991                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12992                         != NULL)) {
12993                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12994                            arcDir,
12995                            appData.cmailGameName,
12996                            gameInfo.date);
12997                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12998                     cmailMsgLoaded = FALSE;
12999                 }
13000             }
13001
13002             DisplayInformation(msg);
13003             pclose(commandOutput);
13004         }
13005     } else {
13006         if ((*cmailMsg) != '\0') {
13007             DisplayInformation(cmailMsg);
13008         }
13009     }
13010
13011     return;
13012 #endif /* !WIN32 */
13013 }
13014
13015 char *
13016 CmailMsg ()
13017 {
13018 #if WIN32
13019     return NULL;
13020 #else
13021     int  prependComma = 0;
13022     char number[5];
13023     char string[MSG_SIZ];       /* Space for game-list */
13024     int  i;
13025
13026     if (!cmailMsgLoaded) return "";
13027
13028     if (cmailMailedMove) {
13029       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13030     } else {
13031         /* Create a list of games left */
13032       snprintf(string, MSG_SIZ, "[");
13033         for (i = 0; i < nCmailGames; i ++) {
13034             if (! (   cmailMoveRegistered[i]
13035                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13036                 if (prependComma) {
13037                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13038                 } else {
13039                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13040                     prependComma = 1;
13041                 }
13042
13043                 strcat(string, number);
13044             }
13045         }
13046         strcat(string, "]");
13047
13048         if (nCmailMovesRegistered + nCmailResults == 0) {
13049             switch (nCmailGames) {
13050               case 1:
13051                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13052                 break;
13053
13054               case 2:
13055                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13056                 break;
13057
13058               default:
13059                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13060                          nCmailGames);
13061                 break;
13062             }
13063         } else {
13064             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13065               case 1:
13066                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13067                          string);
13068                 break;
13069
13070               case 0:
13071                 if (nCmailResults == nCmailGames) {
13072                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13073                 } else {
13074                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13075                 }
13076                 break;
13077
13078               default:
13079                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13080                          string);
13081             }
13082         }
13083     }
13084     return cmailMsg;
13085 #endif /* WIN32 */
13086 }
13087
13088 void
13089 ResetGameEvent ()
13090 {
13091     if (gameMode == Training)
13092       SetTrainingModeOff();
13093
13094     Reset(TRUE, TRUE);
13095     cmailMsgLoaded = FALSE;
13096     if (appData.icsActive) {
13097       SendToICS(ics_prefix);
13098       SendToICS("refresh\n");
13099     }
13100 }
13101
13102 void
13103 ExitEvent (int status)
13104 {
13105     exiting++;
13106     if (exiting > 2) {
13107       /* Give up on clean exit */
13108       exit(status);
13109     }
13110     if (exiting > 1) {
13111       /* Keep trying for clean exit */
13112       return;
13113     }
13114
13115     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13116
13117     if (telnetISR != NULL) {
13118       RemoveInputSource(telnetISR);
13119     }
13120     if (icsPR != NoProc) {
13121       DestroyChildProcess(icsPR, TRUE);
13122     }
13123
13124     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13125     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13126
13127     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13128     /* make sure this other one finishes before killing it!                  */
13129     if(endingGame) { int count = 0;
13130         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13131         while(endingGame && count++ < 10) DoSleep(1);
13132         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13133     }
13134
13135     /* Kill off chess programs */
13136     if (first.pr != NoProc) {
13137         ExitAnalyzeMode();
13138
13139         DoSleep( appData.delayBeforeQuit );
13140         SendToProgram("quit\n", &first);
13141         DoSleep( appData.delayAfterQuit );
13142         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13143     }
13144     if (second.pr != NoProc) {
13145         DoSleep( appData.delayBeforeQuit );
13146         SendToProgram("quit\n", &second);
13147         DoSleep( appData.delayAfterQuit );
13148         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13149     }
13150     if (first.isr != NULL) {
13151         RemoveInputSource(first.isr);
13152     }
13153     if (second.isr != NULL) {
13154         RemoveInputSource(second.isr);
13155     }
13156
13157     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13158     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13159
13160     ShutDownFrontEnd();
13161     exit(status);
13162 }
13163
13164 void
13165 PauseEvent ()
13166 {
13167     if (appData.debugMode)
13168         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13169     if (pausing) {
13170         pausing = FALSE;
13171         ModeHighlight();
13172         if (gameMode == MachinePlaysWhite ||
13173             gameMode == MachinePlaysBlack) {
13174             StartClocks();
13175         } else {
13176             DisplayBothClocks();
13177         }
13178         if (gameMode == PlayFromGameFile) {
13179             if (appData.timeDelay >= 0)
13180                 AutoPlayGameLoop();
13181         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13182             Reset(FALSE, TRUE);
13183             SendToICS(ics_prefix);
13184             SendToICS("refresh\n");
13185         } else if (currentMove < forwardMostMove) {
13186             ForwardInner(forwardMostMove);
13187         }
13188         pauseExamInvalid = FALSE;
13189     } else {
13190         switch (gameMode) {
13191           default:
13192             return;
13193           case IcsExamining:
13194             pauseExamForwardMostMove = forwardMostMove;
13195             pauseExamInvalid = FALSE;
13196             /* fall through */
13197           case IcsObserving:
13198           case IcsPlayingWhite:
13199           case IcsPlayingBlack:
13200             pausing = TRUE;
13201             ModeHighlight();
13202             return;
13203           case PlayFromGameFile:
13204             (void) StopLoadGameTimer();
13205             pausing = TRUE;
13206             ModeHighlight();
13207             break;
13208           case BeginningOfGame:
13209             if (appData.icsActive) return;
13210             /* else fall through */
13211           case MachinePlaysWhite:
13212           case MachinePlaysBlack:
13213           case TwoMachinesPlay:
13214             if (forwardMostMove == 0)
13215               return;           /* don't pause if no one has moved */
13216             if ((gameMode == MachinePlaysWhite &&
13217                  !WhiteOnMove(forwardMostMove)) ||
13218                 (gameMode == MachinePlaysBlack &&
13219                  WhiteOnMove(forwardMostMove))) {
13220                 StopClocks();
13221             }
13222             pausing = TRUE;
13223             ModeHighlight();
13224             break;
13225         }
13226     }
13227 }
13228
13229 void
13230 EditCommentEvent ()
13231 {
13232     char title[MSG_SIZ];
13233
13234     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13235       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13236     } else {
13237       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13238                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13239                parseList[currentMove - 1]);
13240     }
13241
13242     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13243 }
13244
13245
13246 void
13247 EditTagsEvent ()
13248 {
13249     char *tags = PGNTags(&gameInfo);
13250     bookUp = FALSE;
13251     EditTagsPopUp(tags, NULL);
13252     free(tags);
13253 }
13254
13255 void
13256 AnalyzeModeEvent ()
13257 {
13258     if (appData.noChessProgram || gameMode == AnalyzeMode)
13259       return;
13260
13261     if (gameMode != AnalyzeFile) {
13262         if (!appData.icsEngineAnalyze) {
13263                EditGameEvent();
13264                if (gameMode != EditGame) return;
13265         }
13266         ResurrectChessProgram();
13267         SendToProgram("analyze\n", &first);
13268         first.analyzing = TRUE;
13269         /*first.maybeThinking = TRUE;*/
13270         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13271         EngineOutputPopUp();
13272     }
13273     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13274     pausing = FALSE;
13275     ModeHighlight();
13276     SetGameInfo();
13277
13278     StartAnalysisClock();
13279     GetTimeMark(&lastNodeCountTime);
13280     lastNodeCount = 0;
13281 }
13282
13283 void
13284 AnalyzeFileEvent ()
13285 {
13286     if (appData.noChessProgram || gameMode == AnalyzeFile)
13287       return;
13288
13289     if (gameMode != AnalyzeMode) {
13290         EditGameEvent();
13291         if (gameMode != EditGame) return;
13292         ResurrectChessProgram();
13293         SendToProgram("analyze\n", &first);
13294         first.analyzing = TRUE;
13295         /*first.maybeThinking = TRUE;*/
13296         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13297         EngineOutputPopUp();
13298     }
13299     gameMode = AnalyzeFile;
13300     pausing = FALSE;
13301     ModeHighlight();
13302     SetGameInfo();
13303
13304     StartAnalysisClock();
13305     GetTimeMark(&lastNodeCountTime);
13306     lastNodeCount = 0;
13307     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13308 }
13309
13310 void
13311 MachineWhiteEvent ()
13312 {
13313     char buf[MSG_SIZ];
13314     char *bookHit = NULL;
13315
13316     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13317       return;
13318
13319
13320     if (gameMode == PlayFromGameFile ||
13321         gameMode == TwoMachinesPlay  ||
13322         gameMode == Training         ||
13323         gameMode == AnalyzeMode      ||
13324         gameMode == EndOfGame)
13325         EditGameEvent();
13326
13327     if (gameMode == EditPosition)
13328         EditPositionDone(TRUE);
13329
13330     if (!WhiteOnMove(currentMove)) {
13331         DisplayError(_("It is not White's turn"), 0);
13332         return;
13333     }
13334
13335     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13336       ExitAnalyzeMode();
13337
13338     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13339         gameMode == AnalyzeFile)
13340         TruncateGame();
13341
13342     ResurrectChessProgram();    /* in case it isn't running */
13343     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13344         gameMode = MachinePlaysWhite;
13345         ResetClocks();
13346     } else
13347     gameMode = MachinePlaysWhite;
13348     pausing = FALSE;
13349     ModeHighlight();
13350     SetGameInfo();
13351     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13352     DisplayTitle(buf);
13353     if (first.sendName) {
13354       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13355       SendToProgram(buf, &first);
13356     }
13357     if (first.sendTime) {
13358       if (first.useColors) {
13359         SendToProgram("black\n", &first); /*gnu kludge*/
13360       }
13361       SendTimeRemaining(&first, TRUE);
13362     }
13363     if (first.useColors) {
13364       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13365     }
13366     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13367     SetMachineThinkingEnables();
13368     first.maybeThinking = TRUE;
13369     StartClocks();
13370     firstMove = FALSE;
13371
13372     if (appData.autoFlipView && !flipView) {
13373       flipView = !flipView;
13374       DrawPosition(FALSE, NULL);
13375       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13376     }
13377
13378     if(bookHit) { // [HGM] book: simulate book reply
13379         static char bookMove[MSG_SIZ]; // a bit generous?
13380
13381         programStats.nodes = programStats.depth = programStats.time =
13382         programStats.score = programStats.got_only_move = 0;
13383         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13384
13385         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13386         strcat(bookMove, bookHit);
13387         HandleMachineMove(bookMove, &first);
13388     }
13389 }
13390
13391 void
13392 MachineBlackEvent ()
13393 {
13394   char buf[MSG_SIZ];
13395   char *bookHit = NULL;
13396
13397     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13398         return;
13399
13400
13401     if (gameMode == PlayFromGameFile ||
13402         gameMode == TwoMachinesPlay  ||
13403         gameMode == Training         ||
13404         gameMode == AnalyzeMode      ||
13405         gameMode == EndOfGame)
13406         EditGameEvent();
13407
13408     if (gameMode == EditPosition)
13409         EditPositionDone(TRUE);
13410
13411     if (WhiteOnMove(currentMove)) {
13412         DisplayError(_("It is not Black's turn"), 0);
13413         return;
13414     }
13415
13416     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13417       ExitAnalyzeMode();
13418
13419     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13420         gameMode == AnalyzeFile)
13421         TruncateGame();
13422
13423     ResurrectChessProgram();    /* in case it isn't running */
13424     gameMode = MachinePlaysBlack;
13425     pausing = FALSE;
13426     ModeHighlight();
13427     SetGameInfo();
13428     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13429     DisplayTitle(buf);
13430     if (first.sendName) {
13431       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13432       SendToProgram(buf, &first);
13433     }
13434     if (first.sendTime) {
13435       if (first.useColors) {
13436         SendToProgram("white\n", &first); /*gnu kludge*/
13437       }
13438       SendTimeRemaining(&first, FALSE);
13439     }
13440     if (first.useColors) {
13441       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13442     }
13443     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13444     SetMachineThinkingEnables();
13445     first.maybeThinking = TRUE;
13446     StartClocks();
13447
13448     if (appData.autoFlipView && flipView) {
13449       flipView = !flipView;
13450       DrawPosition(FALSE, NULL);
13451       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13452     }
13453     if(bookHit) { // [HGM] book: simulate book reply
13454         static char bookMove[MSG_SIZ]; // a bit generous?
13455
13456         programStats.nodes = programStats.depth = programStats.time =
13457         programStats.score = programStats.got_only_move = 0;
13458         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13459
13460         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13461         strcat(bookMove, bookHit);
13462         HandleMachineMove(bookMove, &first);
13463     }
13464 }
13465
13466
13467 void
13468 DisplayTwoMachinesTitle ()
13469 {
13470     char buf[MSG_SIZ];
13471     if (appData.matchGames > 0) {
13472         if(appData.tourneyFile[0]) {
13473           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13474                    gameInfo.white, _("vs."), gameInfo.black,
13475                    nextGame+1, appData.matchGames+1,
13476                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13477         } else 
13478         if (first.twoMachinesColor[0] == 'w') {
13479           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13480                    gameInfo.white, _("vs."),  gameInfo.black,
13481                    first.matchWins, second.matchWins,
13482                    matchGame - 1 - (first.matchWins + second.matchWins));
13483         } else {
13484           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13485                    gameInfo.white, _("vs."), gameInfo.black,
13486                    second.matchWins, first.matchWins,
13487                    matchGame - 1 - (first.matchWins + second.matchWins));
13488         }
13489     } else {
13490       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13491     }
13492     DisplayTitle(buf);
13493 }
13494
13495 void
13496 SettingsMenuIfReady ()
13497 {
13498   if (second.lastPing != second.lastPong) {
13499     DisplayMessage("", _("Waiting for second chess program"));
13500     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13501     return;
13502   }
13503   ThawUI();
13504   DisplayMessage("", "");
13505   SettingsPopUp(&second);
13506 }
13507
13508 int
13509 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13510 {
13511     char buf[MSG_SIZ];
13512     if (cps->pr == NoProc) {
13513         StartChessProgram(cps);
13514         if (cps->protocolVersion == 1) {
13515           retry();
13516         } else {
13517           /* kludge: allow timeout for initial "feature" command */
13518           FreezeUI();
13519           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13520           DisplayMessage("", buf);
13521           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13522         }
13523         return 1;
13524     }
13525     return 0;
13526 }
13527
13528 void
13529 TwoMachinesEvent P((void))
13530 {
13531     int i;
13532     char buf[MSG_SIZ];
13533     ChessProgramState *onmove;
13534     char *bookHit = NULL;
13535     static int stalling = 0;
13536     TimeMark now;
13537     long wait;
13538
13539     if (appData.noChessProgram) return;
13540
13541     switch (gameMode) {
13542       case TwoMachinesPlay:
13543         return;
13544       case MachinePlaysWhite:
13545       case MachinePlaysBlack:
13546         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13547             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13548             return;
13549         }
13550         /* fall through */
13551       case BeginningOfGame:
13552       case PlayFromGameFile:
13553       case EndOfGame:
13554         EditGameEvent();
13555         if (gameMode != EditGame) return;
13556         break;
13557       case EditPosition:
13558         EditPositionDone(TRUE);
13559         break;
13560       case AnalyzeMode:
13561       case AnalyzeFile:
13562         ExitAnalyzeMode();
13563         break;
13564       case EditGame:
13565       default:
13566         break;
13567     }
13568
13569 //    forwardMostMove = currentMove;
13570     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13571
13572     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13573
13574     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13575     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13576       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13577       return;
13578     }
13579     if(!stalling) {
13580       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13581       SendToProgram("force\n", &second);
13582       stalling = 1;
13583       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13584       return;
13585     }
13586     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13587     if(appData.matchPause>10000 || appData.matchPause<10)
13588                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13589     wait = SubtractTimeMarks(&now, &pauseStart);
13590     if(wait < appData.matchPause) {
13591         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13592         return;
13593     }
13594     // we are now committed to starting the game
13595     stalling = 0;
13596     DisplayMessage("", "");
13597     if (startedFromSetupPosition) {
13598         SendBoard(&second, backwardMostMove);
13599     if (appData.debugMode) {
13600         fprintf(debugFP, "Two Machines\n");
13601     }
13602     }
13603     for (i = backwardMostMove; i < forwardMostMove; i++) {
13604         SendMoveToProgram(i, &second);
13605     }
13606
13607     gameMode = TwoMachinesPlay;
13608     pausing = FALSE;
13609     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13610     SetGameInfo();
13611     DisplayTwoMachinesTitle();
13612     firstMove = TRUE;
13613     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13614         onmove = &first;
13615     } else {
13616         onmove = &second;
13617     }
13618     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13619     SendToProgram(first.computerString, &first);
13620     if (first.sendName) {
13621       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13622       SendToProgram(buf, &first);
13623     }
13624     SendToProgram(second.computerString, &second);
13625     if (second.sendName) {
13626       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13627       SendToProgram(buf, &second);
13628     }
13629
13630     ResetClocks();
13631     if (!first.sendTime || !second.sendTime) {
13632         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13633         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13634     }
13635     if (onmove->sendTime) {
13636       if (onmove->useColors) {
13637         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13638       }
13639       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13640     }
13641     if (onmove->useColors) {
13642       SendToProgram(onmove->twoMachinesColor, onmove);
13643     }
13644     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13645 //    SendToProgram("go\n", onmove);
13646     onmove->maybeThinking = TRUE;
13647     SetMachineThinkingEnables();
13648
13649     StartClocks();
13650
13651     if(bookHit) { // [HGM] book: simulate book reply
13652         static char bookMove[MSG_SIZ]; // a bit generous?
13653
13654         programStats.nodes = programStats.depth = programStats.time =
13655         programStats.score = programStats.got_only_move = 0;
13656         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13657
13658         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13659         strcat(bookMove, bookHit);
13660         savedMessage = bookMove; // args for deferred call
13661         savedState = onmove;
13662         ScheduleDelayedEvent(DeferredBookMove, 1);
13663     }
13664 }
13665
13666 void
13667 TrainingEvent ()
13668 {
13669     if (gameMode == Training) {
13670       SetTrainingModeOff();
13671       gameMode = PlayFromGameFile;
13672       DisplayMessage("", _("Training mode off"));
13673     } else {
13674       gameMode = Training;
13675       animateTraining = appData.animate;
13676
13677       /* make sure we are not already at the end of the game */
13678       if (currentMove < forwardMostMove) {
13679         SetTrainingModeOn();
13680         DisplayMessage("", _("Training mode on"));
13681       } else {
13682         gameMode = PlayFromGameFile;
13683         DisplayError(_("Already at end of game"), 0);
13684       }
13685     }
13686     ModeHighlight();
13687 }
13688
13689 void
13690 IcsClientEvent ()
13691 {
13692     if (!appData.icsActive) return;
13693     switch (gameMode) {
13694       case IcsPlayingWhite:
13695       case IcsPlayingBlack:
13696       case IcsObserving:
13697       case IcsIdle:
13698       case BeginningOfGame:
13699       case IcsExamining:
13700         return;
13701
13702       case EditGame:
13703         break;
13704
13705       case EditPosition:
13706         EditPositionDone(TRUE);
13707         break;
13708
13709       case AnalyzeMode:
13710       case AnalyzeFile:
13711         ExitAnalyzeMode();
13712         break;
13713
13714       default:
13715         EditGameEvent();
13716         break;
13717     }
13718
13719     gameMode = IcsIdle;
13720     ModeHighlight();
13721     return;
13722 }
13723
13724 void
13725 EditGameEvent ()
13726 {
13727     int i;
13728
13729     switch (gameMode) {
13730       case Training:
13731         SetTrainingModeOff();
13732         break;
13733       case MachinePlaysWhite:
13734       case MachinePlaysBlack:
13735       case BeginningOfGame:
13736         SendToProgram("force\n", &first);
13737         SetUserThinkingEnables();
13738         break;
13739       case PlayFromGameFile:
13740         (void) StopLoadGameTimer();
13741         if (gameFileFP != NULL) {
13742             gameFileFP = NULL;
13743         }
13744         break;
13745       case EditPosition:
13746         EditPositionDone(TRUE);
13747         break;
13748       case AnalyzeMode:
13749       case AnalyzeFile:
13750         ExitAnalyzeMode();
13751         SendToProgram("force\n", &first);
13752         break;
13753       case TwoMachinesPlay:
13754         GameEnds(EndOfFile, NULL, GE_PLAYER);
13755         ResurrectChessProgram();
13756         SetUserThinkingEnables();
13757         break;
13758       case EndOfGame:
13759         ResurrectChessProgram();
13760         break;
13761       case IcsPlayingBlack:
13762       case IcsPlayingWhite:
13763         DisplayError(_("Warning: You are still playing a game"), 0);
13764         break;
13765       case IcsObserving:
13766         DisplayError(_("Warning: You are still observing a game"), 0);
13767         break;
13768       case IcsExamining:
13769         DisplayError(_("Warning: You are still examining a game"), 0);
13770         break;
13771       case IcsIdle:
13772         break;
13773       case EditGame:
13774       default:
13775         return;
13776     }
13777
13778     pausing = FALSE;
13779     StopClocks();
13780     first.offeredDraw = second.offeredDraw = 0;
13781
13782     if (gameMode == PlayFromGameFile) {
13783         whiteTimeRemaining = timeRemaining[0][currentMove];
13784         blackTimeRemaining = timeRemaining[1][currentMove];
13785         DisplayTitle("");
13786     }
13787
13788     if (gameMode == MachinePlaysWhite ||
13789         gameMode == MachinePlaysBlack ||
13790         gameMode == TwoMachinesPlay ||
13791         gameMode == EndOfGame) {
13792         i = forwardMostMove;
13793         while (i > currentMove) {
13794             SendToProgram("undo\n", &first);
13795             i--;
13796         }
13797         if(!adjustedClock) {
13798         whiteTimeRemaining = timeRemaining[0][currentMove];
13799         blackTimeRemaining = timeRemaining[1][currentMove];
13800         DisplayBothClocks();
13801         }
13802         if (whiteFlag || blackFlag) {
13803             whiteFlag = blackFlag = 0;
13804         }
13805         DisplayTitle("");
13806     }
13807
13808     gameMode = EditGame;
13809     ModeHighlight();
13810     SetGameInfo();
13811 }
13812
13813
13814 void
13815 EditPositionEvent ()
13816 {
13817     if (gameMode == EditPosition) {
13818         EditGameEvent();
13819         return;
13820     }
13821
13822     EditGameEvent();
13823     if (gameMode != EditGame) return;
13824
13825     gameMode = EditPosition;
13826     ModeHighlight();
13827     SetGameInfo();
13828     if (currentMove > 0)
13829       CopyBoard(boards[0], boards[currentMove]);
13830
13831     blackPlaysFirst = !WhiteOnMove(currentMove);
13832     ResetClocks();
13833     currentMove = forwardMostMove = backwardMostMove = 0;
13834     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13835     DisplayMove(-1);
13836     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13837 }
13838
13839 void
13840 ExitAnalyzeMode ()
13841 {
13842     /* [DM] icsEngineAnalyze - possible call from other functions */
13843     if (appData.icsEngineAnalyze) {
13844         appData.icsEngineAnalyze = FALSE;
13845
13846         DisplayMessage("",_("Close ICS engine analyze..."));
13847     }
13848     if (first.analysisSupport && first.analyzing) {
13849       SendToProgram("exit\n", &first);
13850       first.analyzing = FALSE;
13851     }
13852     thinkOutput[0] = NULLCHAR;
13853 }
13854
13855 void
13856 EditPositionDone (Boolean fakeRights)
13857 {
13858     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13859
13860     startedFromSetupPosition = TRUE;
13861     InitChessProgram(&first, FALSE);
13862     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13863       boards[0][EP_STATUS] = EP_NONE;
13864       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13865     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13866         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13867         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13868       } else boards[0][CASTLING][2] = NoRights;
13869     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13870         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13871         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13872       } else boards[0][CASTLING][5] = NoRights;
13873     }
13874     SendToProgram("force\n", &first);
13875     if (blackPlaysFirst) {
13876         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13877         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13878         currentMove = forwardMostMove = backwardMostMove = 1;
13879         CopyBoard(boards[1], boards[0]);
13880     } else {
13881         currentMove = forwardMostMove = backwardMostMove = 0;
13882     }
13883     SendBoard(&first, forwardMostMove);
13884     if (appData.debugMode) {
13885         fprintf(debugFP, "EditPosDone\n");
13886     }
13887     DisplayTitle("");
13888     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13889     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13890     gameMode = EditGame;
13891     ModeHighlight();
13892     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13893     ClearHighlights(); /* [AS] */
13894 }
13895
13896 /* Pause for `ms' milliseconds */
13897 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13898 void
13899 TimeDelay (long ms)
13900 {
13901     TimeMark m1, m2;
13902
13903     GetTimeMark(&m1);
13904     do {
13905         GetTimeMark(&m2);
13906     } while (SubtractTimeMarks(&m2, &m1) < ms);
13907 }
13908
13909 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13910 void
13911 SendMultiLineToICS (char *buf)
13912 {
13913     char temp[MSG_SIZ+1], *p;
13914     int len;
13915
13916     len = strlen(buf);
13917     if (len > MSG_SIZ)
13918       len = MSG_SIZ;
13919
13920     strncpy(temp, buf, len);
13921     temp[len] = 0;
13922
13923     p = temp;
13924     while (*p) {
13925         if (*p == '\n' || *p == '\r')
13926           *p = ' ';
13927         ++p;
13928     }
13929
13930     strcat(temp, "\n");
13931     SendToICS(temp);
13932     SendToPlayer(temp, strlen(temp));
13933 }
13934
13935 void
13936 SetWhiteToPlayEvent ()
13937 {
13938     if (gameMode == EditPosition) {
13939         blackPlaysFirst = FALSE;
13940         DisplayBothClocks();    /* works because currentMove is 0 */
13941     } else if (gameMode == IcsExamining) {
13942         SendToICS(ics_prefix);
13943         SendToICS("tomove white\n");
13944     }
13945 }
13946
13947 void
13948 SetBlackToPlayEvent ()
13949 {
13950     if (gameMode == EditPosition) {
13951         blackPlaysFirst = TRUE;
13952         currentMove = 1;        /* kludge */
13953         DisplayBothClocks();
13954         currentMove = 0;
13955     } else if (gameMode == IcsExamining) {
13956         SendToICS(ics_prefix);
13957         SendToICS("tomove black\n");
13958     }
13959 }
13960
13961 void
13962 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13963 {
13964     char buf[MSG_SIZ];
13965     ChessSquare piece = boards[0][y][x];
13966
13967     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13968
13969     switch (selection) {
13970       case ClearBoard:
13971         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13972             SendToICS(ics_prefix);
13973             SendToICS("bsetup clear\n");
13974         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13975             SendToICS(ics_prefix);
13976             SendToICS("clearboard\n");
13977         } else {
13978             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13979                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13980                 for (y = 0; y < BOARD_HEIGHT; y++) {
13981                     if (gameMode == IcsExamining) {
13982                         if (boards[currentMove][y][x] != EmptySquare) {
13983                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13984                                     AAA + x, ONE + y);
13985                             SendToICS(buf);
13986                         }
13987                     } else {
13988                         boards[0][y][x] = p;
13989                     }
13990                 }
13991             }
13992         }
13993         if (gameMode == EditPosition) {
13994             DrawPosition(FALSE, boards[0]);
13995         }
13996         break;
13997
13998       case WhitePlay:
13999         SetWhiteToPlayEvent();
14000         break;
14001
14002       case BlackPlay:
14003         SetBlackToPlayEvent();
14004         break;
14005
14006       case EmptySquare:
14007         if (gameMode == IcsExamining) {
14008             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14009             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14010             SendToICS(buf);
14011         } else {
14012             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14013                 if(x == BOARD_LEFT-2) {
14014                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14015                     boards[0][y][1] = 0;
14016                 } else
14017                 if(x == BOARD_RGHT+1) {
14018                     if(y >= gameInfo.holdingsSize) break;
14019                     boards[0][y][BOARD_WIDTH-2] = 0;
14020                 } else break;
14021             }
14022             boards[0][y][x] = EmptySquare;
14023             DrawPosition(FALSE, boards[0]);
14024         }
14025         break;
14026
14027       case PromotePiece:
14028         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14029            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14030             selection = (ChessSquare) (PROMOTED piece);
14031         } else if(piece == EmptySquare) selection = WhiteSilver;
14032         else selection = (ChessSquare)((int)piece - 1);
14033         goto defaultlabel;
14034
14035       case DemotePiece:
14036         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14037            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14038             selection = (ChessSquare) (DEMOTED piece);
14039         } else if(piece == EmptySquare) selection = BlackSilver;
14040         else selection = (ChessSquare)((int)piece + 1);
14041         goto defaultlabel;
14042
14043       case WhiteQueen:
14044       case BlackQueen:
14045         if(gameInfo.variant == VariantShatranj ||
14046            gameInfo.variant == VariantXiangqi  ||
14047            gameInfo.variant == VariantCourier  ||
14048            gameInfo.variant == VariantMakruk     )
14049             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14050         goto defaultlabel;
14051
14052       case WhiteKing:
14053       case BlackKing:
14054         if(gameInfo.variant == VariantXiangqi)
14055             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14056         if(gameInfo.variant == VariantKnightmate)
14057             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14058       default:
14059         defaultlabel:
14060         if (gameMode == IcsExamining) {
14061             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14062             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14063                      PieceToChar(selection), AAA + x, ONE + y);
14064             SendToICS(buf);
14065         } else {
14066             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14067                 int n;
14068                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14069                     n = PieceToNumber(selection - BlackPawn);
14070                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14071                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14072                     boards[0][BOARD_HEIGHT-1-n][1]++;
14073                 } else
14074                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14075                     n = PieceToNumber(selection);
14076                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14077                     boards[0][n][BOARD_WIDTH-1] = selection;
14078                     boards[0][n][BOARD_WIDTH-2]++;
14079                 }
14080             } else
14081             boards[0][y][x] = selection;
14082             DrawPosition(TRUE, boards[0]);
14083             ClearHighlights();
14084             fromX = fromY = -1;
14085         }
14086         break;
14087     }
14088 }
14089
14090
14091 void
14092 DropMenuEvent (ChessSquare selection, int x, int y)
14093 {
14094     ChessMove moveType;
14095
14096     switch (gameMode) {
14097       case IcsPlayingWhite:
14098       case MachinePlaysBlack:
14099         if (!WhiteOnMove(currentMove)) {
14100             DisplayMoveError(_("It is Black's turn"));
14101             return;
14102         }
14103         moveType = WhiteDrop;
14104         break;
14105       case IcsPlayingBlack:
14106       case MachinePlaysWhite:
14107         if (WhiteOnMove(currentMove)) {
14108             DisplayMoveError(_("It is White's turn"));
14109             return;
14110         }
14111         moveType = BlackDrop;
14112         break;
14113       case EditGame:
14114         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14115         break;
14116       default:
14117         return;
14118     }
14119
14120     if (moveType == BlackDrop && selection < BlackPawn) {
14121       selection = (ChessSquare) ((int) selection
14122                                  + (int) BlackPawn - (int) WhitePawn);
14123     }
14124     if (boards[currentMove][y][x] != EmptySquare) {
14125         DisplayMoveError(_("That square is occupied"));
14126         return;
14127     }
14128
14129     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14130 }
14131
14132 void
14133 AcceptEvent ()
14134 {
14135     /* Accept a pending offer of any kind from opponent */
14136
14137     if (appData.icsActive) {
14138         SendToICS(ics_prefix);
14139         SendToICS("accept\n");
14140     } else if (cmailMsgLoaded) {
14141         if (currentMove == cmailOldMove &&
14142             commentList[cmailOldMove] != NULL &&
14143             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14144                    "Black offers a draw" : "White offers a draw")) {
14145             TruncateGame();
14146             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14147             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14148         } else {
14149             DisplayError(_("There is no pending offer on this move"), 0);
14150             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14151         }
14152     } else {
14153         /* Not used for offers from chess program */
14154     }
14155 }
14156
14157 void
14158 DeclineEvent ()
14159 {
14160     /* Decline a pending offer of any kind from opponent */
14161
14162     if (appData.icsActive) {
14163         SendToICS(ics_prefix);
14164         SendToICS("decline\n");
14165     } else if (cmailMsgLoaded) {
14166         if (currentMove == cmailOldMove &&
14167             commentList[cmailOldMove] != NULL &&
14168             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14169                    "Black offers a draw" : "White offers a draw")) {
14170 #ifdef NOTDEF
14171             AppendComment(cmailOldMove, "Draw declined", TRUE);
14172             DisplayComment(cmailOldMove - 1, "Draw declined");
14173 #endif /*NOTDEF*/
14174         } else {
14175             DisplayError(_("There is no pending offer on this move"), 0);
14176         }
14177     } else {
14178         /* Not used for offers from chess program */
14179     }
14180 }
14181
14182 void
14183 RematchEvent ()
14184 {
14185     /* Issue ICS rematch command */
14186     if (appData.icsActive) {
14187         SendToICS(ics_prefix);
14188         SendToICS("rematch\n");
14189     }
14190 }
14191
14192 void
14193 CallFlagEvent ()
14194 {
14195     /* Call your opponent's flag (claim a win on time) */
14196     if (appData.icsActive) {
14197         SendToICS(ics_prefix);
14198         SendToICS("flag\n");
14199     } else {
14200         switch (gameMode) {
14201           default:
14202             return;
14203           case MachinePlaysWhite:
14204             if (whiteFlag) {
14205                 if (blackFlag)
14206                   GameEnds(GameIsDrawn, "Both players ran out of time",
14207                            GE_PLAYER);
14208                 else
14209                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14210             } else {
14211                 DisplayError(_("Your opponent is not out of time"), 0);
14212             }
14213             break;
14214           case MachinePlaysBlack:
14215             if (blackFlag) {
14216                 if (whiteFlag)
14217                   GameEnds(GameIsDrawn, "Both players ran out of time",
14218                            GE_PLAYER);
14219                 else
14220                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14221             } else {
14222                 DisplayError(_("Your opponent is not out of time"), 0);
14223             }
14224             break;
14225         }
14226     }
14227 }
14228
14229 void
14230 ClockClick (int which)
14231 {       // [HGM] code moved to back-end from winboard.c
14232         if(which) { // black clock
14233           if (gameMode == EditPosition || gameMode == IcsExamining) {
14234             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14235             SetBlackToPlayEvent();
14236           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14237           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14238           } else if (shiftKey) {
14239             AdjustClock(which, -1);
14240           } else if (gameMode == IcsPlayingWhite ||
14241                      gameMode == MachinePlaysBlack) {
14242             CallFlagEvent();
14243           }
14244         } else { // white clock
14245           if (gameMode == EditPosition || gameMode == IcsExamining) {
14246             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14247             SetWhiteToPlayEvent();
14248           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14249           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14250           } else if (shiftKey) {
14251             AdjustClock(which, -1);
14252           } else if (gameMode == IcsPlayingBlack ||
14253                    gameMode == MachinePlaysWhite) {
14254             CallFlagEvent();
14255           }
14256         }
14257 }
14258
14259 void
14260 DrawEvent ()
14261 {
14262     /* Offer draw or accept pending draw offer from opponent */
14263
14264     if (appData.icsActive) {
14265         /* Note: tournament rules require draw offers to be
14266            made after you make your move but before you punch
14267            your clock.  Currently ICS doesn't let you do that;
14268            instead, you immediately punch your clock after making
14269            a move, but you can offer a draw at any time. */
14270
14271         SendToICS(ics_prefix);
14272         SendToICS("draw\n");
14273         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14274     } else if (cmailMsgLoaded) {
14275         if (currentMove == cmailOldMove &&
14276             commentList[cmailOldMove] != NULL &&
14277             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14278                    "Black offers a draw" : "White offers a draw")) {
14279             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14280             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14281         } else if (currentMove == cmailOldMove + 1) {
14282             char *offer = WhiteOnMove(cmailOldMove) ?
14283               "White offers a draw" : "Black offers a draw";
14284             AppendComment(currentMove, offer, TRUE);
14285             DisplayComment(currentMove - 1, offer);
14286             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14287         } else {
14288             DisplayError(_("You must make your move before offering a draw"), 0);
14289             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14290         }
14291     } else if (first.offeredDraw) {
14292         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14293     } else {
14294         if (first.sendDrawOffers) {
14295             SendToProgram("draw\n", &first);
14296             userOfferedDraw = TRUE;
14297         }
14298     }
14299 }
14300
14301 void
14302 AdjournEvent ()
14303 {
14304     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14305
14306     if (appData.icsActive) {
14307         SendToICS(ics_prefix);
14308         SendToICS("adjourn\n");
14309     } else {
14310         /* Currently GNU Chess doesn't offer or accept Adjourns */
14311     }
14312 }
14313
14314
14315 void
14316 AbortEvent ()
14317 {
14318     /* Offer Abort or accept pending Abort offer from opponent */
14319
14320     if (appData.icsActive) {
14321         SendToICS(ics_prefix);
14322         SendToICS("abort\n");
14323     } else {
14324         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14325     }
14326 }
14327
14328 void
14329 ResignEvent ()
14330 {
14331     /* Resign.  You can do this even if it's not your turn. */
14332
14333     if (appData.icsActive) {
14334         SendToICS(ics_prefix);
14335         SendToICS("resign\n");
14336     } else {
14337         switch (gameMode) {
14338           case MachinePlaysWhite:
14339             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14340             break;
14341           case MachinePlaysBlack:
14342             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14343             break;
14344           case EditGame:
14345             if (cmailMsgLoaded) {
14346                 TruncateGame();
14347                 if (WhiteOnMove(cmailOldMove)) {
14348                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14349                 } else {
14350                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14351                 }
14352                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14353             }
14354             break;
14355           default:
14356             break;
14357         }
14358     }
14359 }
14360
14361
14362 void
14363 StopObservingEvent ()
14364 {
14365     /* Stop observing current games */
14366     SendToICS(ics_prefix);
14367     SendToICS("unobserve\n");
14368 }
14369
14370 void
14371 StopExaminingEvent ()
14372 {
14373     /* Stop observing current game */
14374     SendToICS(ics_prefix);
14375     SendToICS("unexamine\n");
14376 }
14377
14378 void
14379 ForwardInner (int target)
14380 {
14381     int limit; int oldSeekGraphUp = seekGraphUp;
14382
14383     if (appData.debugMode)
14384         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14385                 target, currentMove, forwardMostMove);
14386
14387     if (gameMode == EditPosition)
14388       return;
14389
14390     seekGraphUp = FALSE;
14391     MarkTargetSquares(1);
14392
14393     if (gameMode == PlayFromGameFile && !pausing)
14394       PauseEvent();
14395
14396     if (gameMode == IcsExamining && pausing)
14397       limit = pauseExamForwardMostMove;
14398     else
14399       limit = forwardMostMove;
14400
14401     if (target > limit) target = limit;
14402
14403     if (target > 0 && moveList[target - 1][0]) {
14404         int fromX, fromY, toX, toY;
14405         toX = moveList[target - 1][2] - AAA;
14406         toY = moveList[target - 1][3] - ONE;
14407         if (moveList[target - 1][1] == '@') {
14408             if (appData.highlightLastMove) {
14409                 SetHighlights(-1, -1, toX, toY);
14410             }
14411         } else {
14412             fromX = moveList[target - 1][0] - AAA;
14413             fromY = moveList[target - 1][1] - ONE;
14414             if (target == currentMove + 1) {
14415                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14416             }
14417             if (appData.highlightLastMove) {
14418                 SetHighlights(fromX, fromY, toX, toY);
14419             }
14420         }
14421     }
14422     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14423         gameMode == Training || gameMode == PlayFromGameFile ||
14424         gameMode == AnalyzeFile) {
14425         while (currentMove < target) {
14426             SendMoveToProgram(currentMove++, &first);
14427         }
14428     } else {
14429         currentMove = target;
14430     }
14431
14432     if (gameMode == EditGame || gameMode == EndOfGame) {
14433         whiteTimeRemaining = timeRemaining[0][currentMove];
14434         blackTimeRemaining = timeRemaining[1][currentMove];
14435     }
14436     DisplayBothClocks();
14437     DisplayMove(currentMove - 1);
14438     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14439     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14440     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14441         DisplayComment(currentMove - 1, commentList[currentMove]);
14442     }
14443     ClearMap(); // [HGM] exclude: invalidate map
14444 }
14445
14446
14447 void
14448 ForwardEvent ()
14449 {
14450     if (gameMode == IcsExamining && !pausing) {
14451         SendToICS(ics_prefix);
14452         SendToICS("forward\n");
14453     } else {
14454         ForwardInner(currentMove + 1);
14455     }
14456 }
14457
14458 void
14459 ToEndEvent ()
14460 {
14461     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14462         /* to optimze, we temporarily turn off analysis mode while we feed
14463          * the remaining moves to the engine. Otherwise we get analysis output
14464          * after each move.
14465          */
14466         if (first.analysisSupport) {
14467           SendToProgram("exit\nforce\n", &first);
14468           first.analyzing = FALSE;
14469         }
14470     }
14471
14472     if (gameMode == IcsExamining && !pausing) {
14473         SendToICS(ics_prefix);
14474         SendToICS("forward 999999\n");
14475     } else {
14476         ForwardInner(forwardMostMove);
14477     }
14478
14479     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14480         /* we have fed all the moves, so reactivate analysis mode */
14481         SendToProgram("analyze\n", &first);
14482         first.analyzing = TRUE;
14483         /*first.maybeThinking = TRUE;*/
14484         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14485     }
14486 }
14487
14488 void
14489 BackwardInner (int target)
14490 {
14491     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14492
14493     if (appData.debugMode)
14494         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14495                 target, currentMove, forwardMostMove);
14496
14497     if (gameMode == EditPosition) return;
14498     seekGraphUp = FALSE;
14499     MarkTargetSquares(1);
14500     if (currentMove <= backwardMostMove) {
14501         ClearHighlights();
14502         DrawPosition(full_redraw, boards[currentMove]);
14503         return;
14504     }
14505     if (gameMode == PlayFromGameFile && !pausing)
14506       PauseEvent();
14507
14508     if (moveList[target][0]) {
14509         int fromX, fromY, toX, toY;
14510         toX = moveList[target][2] - AAA;
14511         toY = moveList[target][3] - ONE;
14512         if (moveList[target][1] == '@') {
14513             if (appData.highlightLastMove) {
14514                 SetHighlights(-1, -1, toX, toY);
14515             }
14516         } else {
14517             fromX = moveList[target][0] - AAA;
14518             fromY = moveList[target][1] - ONE;
14519             if (target == currentMove - 1) {
14520                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14521             }
14522             if (appData.highlightLastMove) {
14523                 SetHighlights(fromX, fromY, toX, toY);
14524             }
14525         }
14526     }
14527     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14528         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14529         while (currentMove > target) {
14530             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14531                 // null move cannot be undone. Reload program with move history before it.
14532                 int i;
14533                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14534                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14535                 }
14536                 SendBoard(&first, i); 
14537                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14538                 break;
14539             }
14540             SendToProgram("undo\n", &first);
14541             currentMove--;
14542         }
14543     } else {
14544         currentMove = target;
14545     }
14546
14547     if (gameMode == EditGame || gameMode == EndOfGame) {
14548         whiteTimeRemaining = timeRemaining[0][currentMove];
14549         blackTimeRemaining = timeRemaining[1][currentMove];
14550     }
14551     DisplayBothClocks();
14552     DisplayMove(currentMove - 1);
14553     DrawPosition(full_redraw, boards[currentMove]);
14554     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14555     // [HGM] PV info: routine tests if comment empty
14556     DisplayComment(currentMove - 1, commentList[currentMove]);
14557     ClearMap(); // [HGM] exclude: invalidate map
14558 }
14559
14560 void
14561 BackwardEvent ()
14562 {
14563     if (gameMode == IcsExamining && !pausing) {
14564         SendToICS(ics_prefix);
14565         SendToICS("backward\n");
14566     } else {
14567         BackwardInner(currentMove - 1);
14568     }
14569 }
14570
14571 void
14572 ToStartEvent ()
14573 {
14574     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14575         /* to optimize, we temporarily turn off analysis mode while we undo
14576          * all the moves. Otherwise we get analysis output after each undo.
14577          */
14578         if (first.analysisSupport) {
14579           SendToProgram("exit\nforce\n", &first);
14580           first.analyzing = FALSE;
14581         }
14582     }
14583
14584     if (gameMode == IcsExamining && !pausing) {
14585         SendToICS(ics_prefix);
14586         SendToICS("backward 999999\n");
14587     } else {
14588         BackwardInner(backwardMostMove);
14589     }
14590
14591     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14592         /* we have fed all the moves, so reactivate analysis mode */
14593         SendToProgram("analyze\n", &first);
14594         first.analyzing = TRUE;
14595         /*first.maybeThinking = TRUE;*/
14596         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14597     }
14598 }
14599
14600 void
14601 ToNrEvent (int to)
14602 {
14603   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14604   if (to >= forwardMostMove) to = forwardMostMove;
14605   if (to <= backwardMostMove) to = backwardMostMove;
14606   if (to < currentMove) {
14607     BackwardInner(to);
14608   } else {
14609     ForwardInner(to);
14610   }
14611 }
14612
14613 void
14614 RevertEvent (Boolean annotate)
14615 {
14616     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14617         return;
14618     }
14619     if (gameMode != IcsExamining) {
14620         DisplayError(_("You are not examining a game"), 0);
14621         return;
14622     }
14623     if (pausing) {
14624         DisplayError(_("You can't revert while pausing"), 0);
14625         return;
14626     }
14627     SendToICS(ics_prefix);
14628     SendToICS("revert\n");
14629 }
14630
14631 void
14632 RetractMoveEvent ()
14633 {
14634     switch (gameMode) {
14635       case MachinePlaysWhite:
14636       case MachinePlaysBlack:
14637         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14638             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14639             return;
14640         }
14641         if (forwardMostMove < 2) return;
14642         currentMove = forwardMostMove = forwardMostMove - 2;
14643         whiteTimeRemaining = timeRemaining[0][currentMove];
14644         blackTimeRemaining = timeRemaining[1][currentMove];
14645         DisplayBothClocks();
14646         DisplayMove(currentMove - 1);
14647         ClearHighlights();/*!! could figure this out*/
14648         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14649         SendToProgram("remove\n", &first);
14650         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14651         break;
14652
14653       case BeginningOfGame:
14654       default:
14655         break;
14656
14657       case IcsPlayingWhite:
14658       case IcsPlayingBlack:
14659         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14660             SendToICS(ics_prefix);
14661             SendToICS("takeback 2\n");
14662         } else {
14663             SendToICS(ics_prefix);
14664             SendToICS("takeback 1\n");
14665         }
14666         break;
14667     }
14668 }
14669
14670 void
14671 MoveNowEvent ()
14672 {
14673     ChessProgramState *cps;
14674
14675     switch (gameMode) {
14676       case MachinePlaysWhite:
14677         if (!WhiteOnMove(forwardMostMove)) {
14678             DisplayError(_("It is your turn"), 0);
14679             return;
14680         }
14681         cps = &first;
14682         break;
14683       case MachinePlaysBlack:
14684         if (WhiteOnMove(forwardMostMove)) {
14685             DisplayError(_("It is your turn"), 0);
14686             return;
14687         }
14688         cps = &first;
14689         break;
14690       case TwoMachinesPlay:
14691         if (WhiteOnMove(forwardMostMove) ==
14692             (first.twoMachinesColor[0] == 'w')) {
14693             cps = &first;
14694         } else {
14695             cps = &second;
14696         }
14697         break;
14698       case BeginningOfGame:
14699       default:
14700         return;
14701     }
14702     SendToProgram("?\n", cps);
14703 }
14704
14705 void
14706 TruncateGameEvent ()
14707 {
14708     EditGameEvent();
14709     if (gameMode != EditGame) return;
14710     TruncateGame();
14711 }
14712
14713 void
14714 TruncateGame ()
14715 {
14716     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14717     if (forwardMostMove > currentMove) {
14718         if (gameInfo.resultDetails != NULL) {
14719             free(gameInfo.resultDetails);
14720             gameInfo.resultDetails = NULL;
14721             gameInfo.result = GameUnfinished;
14722         }
14723         forwardMostMove = currentMove;
14724         HistorySet(parseList, backwardMostMove, forwardMostMove,
14725                    currentMove-1);
14726     }
14727 }
14728
14729 void
14730 HintEvent ()
14731 {
14732     if (appData.noChessProgram) return;
14733     switch (gameMode) {
14734       case MachinePlaysWhite:
14735         if (WhiteOnMove(forwardMostMove)) {
14736             DisplayError(_("Wait until your turn"), 0);
14737             return;
14738         }
14739         break;
14740       case BeginningOfGame:
14741       case MachinePlaysBlack:
14742         if (!WhiteOnMove(forwardMostMove)) {
14743             DisplayError(_("Wait until your turn"), 0);
14744             return;
14745         }
14746         break;
14747       default:
14748         DisplayError(_("No hint available"), 0);
14749         return;
14750     }
14751     SendToProgram("hint\n", &first);
14752     hintRequested = TRUE;
14753 }
14754
14755 void
14756 BookEvent ()
14757 {
14758     if (appData.noChessProgram) return;
14759     switch (gameMode) {
14760       case MachinePlaysWhite:
14761         if (WhiteOnMove(forwardMostMove)) {
14762             DisplayError(_("Wait until your turn"), 0);
14763             return;
14764         }
14765         break;
14766       case BeginningOfGame:
14767       case MachinePlaysBlack:
14768         if (!WhiteOnMove(forwardMostMove)) {
14769             DisplayError(_("Wait until your turn"), 0);
14770             return;
14771         }
14772         break;
14773       case EditPosition:
14774         EditPositionDone(TRUE);
14775         break;
14776       case TwoMachinesPlay:
14777         return;
14778       default:
14779         break;
14780     }
14781     SendToProgram("bk\n", &first);
14782     bookOutput[0] = NULLCHAR;
14783     bookRequested = TRUE;
14784 }
14785
14786 void
14787 AboutGameEvent ()
14788 {
14789     char *tags = PGNTags(&gameInfo);
14790     TagsPopUp(tags, CmailMsg());
14791     free(tags);
14792 }
14793
14794 /* end button procedures */
14795
14796 void
14797 PrintPosition (FILE *fp, int move)
14798 {
14799     int i, j;
14800
14801     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14802         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14803             char c = PieceToChar(boards[move][i][j]);
14804             fputc(c == 'x' ? '.' : c, fp);
14805             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14806         }
14807     }
14808     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14809       fprintf(fp, "white to play\n");
14810     else
14811       fprintf(fp, "black to play\n");
14812 }
14813
14814 void
14815 PrintOpponents (FILE *fp)
14816 {
14817     if (gameInfo.white != NULL) {
14818         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14819     } else {
14820         fprintf(fp, "\n");
14821     }
14822 }
14823
14824 /* Find last component of program's own name, using some heuristics */
14825 void
14826 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14827 {
14828     char *p, *q, c;
14829     int local = (strcmp(host, "localhost") == 0);
14830     while (!local && (p = strchr(prog, ';')) != NULL) {
14831         p++;
14832         while (*p == ' ') p++;
14833         prog = p;
14834     }
14835     if (*prog == '"' || *prog == '\'') {
14836         q = strchr(prog + 1, *prog);
14837     } else {
14838         q = strchr(prog, ' ');
14839     }
14840     if (q == NULL) q = prog + strlen(prog);
14841     p = q;
14842     while (p >= prog && *p != '/' && *p != '\\') p--;
14843     p++;
14844     if(p == prog && *p == '"') p++;
14845     c = *q; *q = 0;
14846     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14847     memcpy(buf, p, q - p);
14848     buf[q - p] = NULLCHAR;
14849     if (!local) {
14850         strcat(buf, "@");
14851         strcat(buf, host);
14852     }
14853 }
14854
14855 char *
14856 TimeControlTagValue ()
14857 {
14858     char buf[MSG_SIZ];
14859     if (!appData.clockMode) {
14860       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14861     } else if (movesPerSession > 0) {
14862       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14863     } else if (timeIncrement == 0) {
14864       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14865     } else {
14866       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14867     }
14868     return StrSave(buf);
14869 }
14870
14871 void
14872 SetGameInfo ()
14873 {
14874     /* This routine is used only for certain modes */
14875     VariantClass v = gameInfo.variant;
14876     ChessMove r = GameUnfinished;
14877     char *p = NULL;
14878
14879     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14880         r = gameInfo.result;
14881         p = gameInfo.resultDetails;
14882         gameInfo.resultDetails = NULL;
14883     }
14884     ClearGameInfo(&gameInfo);
14885     gameInfo.variant = v;
14886
14887     switch (gameMode) {
14888       case MachinePlaysWhite:
14889         gameInfo.event = StrSave( appData.pgnEventHeader );
14890         gameInfo.site = StrSave(HostName());
14891         gameInfo.date = PGNDate();
14892         gameInfo.round = StrSave("-");
14893         gameInfo.white = StrSave(first.tidy);
14894         gameInfo.black = StrSave(UserName());
14895         gameInfo.timeControl = TimeControlTagValue();
14896         break;
14897
14898       case MachinePlaysBlack:
14899         gameInfo.event = StrSave( appData.pgnEventHeader );
14900         gameInfo.site = StrSave(HostName());
14901         gameInfo.date = PGNDate();
14902         gameInfo.round = StrSave("-");
14903         gameInfo.white = StrSave(UserName());
14904         gameInfo.black = StrSave(first.tidy);
14905         gameInfo.timeControl = TimeControlTagValue();
14906         break;
14907
14908       case TwoMachinesPlay:
14909         gameInfo.event = StrSave( appData.pgnEventHeader );
14910         gameInfo.site = StrSave(HostName());
14911         gameInfo.date = PGNDate();
14912         if (roundNr > 0) {
14913             char buf[MSG_SIZ];
14914             snprintf(buf, MSG_SIZ, "%d", roundNr);
14915             gameInfo.round = StrSave(buf);
14916         } else {
14917             gameInfo.round = StrSave("-");
14918         }
14919         if (first.twoMachinesColor[0] == 'w') {
14920             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14921             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14922         } else {
14923             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14924             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14925         }
14926         gameInfo.timeControl = TimeControlTagValue();
14927         break;
14928
14929       case EditGame:
14930         gameInfo.event = StrSave("Edited game");
14931         gameInfo.site = StrSave(HostName());
14932         gameInfo.date = PGNDate();
14933         gameInfo.round = StrSave("-");
14934         gameInfo.white = StrSave("-");
14935         gameInfo.black = StrSave("-");
14936         gameInfo.result = r;
14937         gameInfo.resultDetails = p;
14938         break;
14939
14940       case EditPosition:
14941         gameInfo.event = StrSave("Edited position");
14942         gameInfo.site = StrSave(HostName());
14943         gameInfo.date = PGNDate();
14944         gameInfo.round = StrSave("-");
14945         gameInfo.white = StrSave("-");
14946         gameInfo.black = StrSave("-");
14947         break;
14948
14949       case IcsPlayingWhite:
14950       case IcsPlayingBlack:
14951       case IcsObserving:
14952       case IcsExamining:
14953         break;
14954
14955       case PlayFromGameFile:
14956         gameInfo.event = StrSave("Game from non-PGN file");
14957         gameInfo.site = StrSave(HostName());
14958         gameInfo.date = PGNDate();
14959         gameInfo.round = StrSave("-");
14960         gameInfo.white = StrSave("?");
14961         gameInfo.black = StrSave("?");
14962         break;
14963
14964       default:
14965         break;
14966     }
14967 }
14968
14969 void
14970 ReplaceComment (int index, char *text)
14971 {
14972     int len;
14973     char *p;
14974     float score;
14975
14976     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14977        pvInfoList[index-1].depth == len &&
14978        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14979        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14980     while (*text == '\n') text++;
14981     len = strlen(text);
14982     while (len > 0 && text[len - 1] == '\n') len--;
14983
14984     if (commentList[index] != NULL)
14985       free(commentList[index]);
14986
14987     if (len == 0) {
14988         commentList[index] = NULL;
14989         return;
14990     }
14991   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14992       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14993       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14994     commentList[index] = (char *) malloc(len + 2);
14995     strncpy(commentList[index], text, len);
14996     commentList[index][len] = '\n';
14997     commentList[index][len + 1] = NULLCHAR;
14998   } else {
14999     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15000     char *p;
15001     commentList[index] = (char *) malloc(len + 7);
15002     safeStrCpy(commentList[index], "{\n", 3);
15003     safeStrCpy(commentList[index]+2, text, len+1);
15004     commentList[index][len+2] = NULLCHAR;
15005     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15006     strcat(commentList[index], "\n}\n");
15007   }
15008 }
15009
15010 void
15011 CrushCRs (char *text)
15012 {
15013   char *p = text;
15014   char *q = text;
15015   char ch;
15016
15017   do {
15018     ch = *p++;
15019     if (ch == '\r') continue;
15020     *q++ = ch;
15021   } while (ch != '\0');
15022 }
15023
15024 void
15025 AppendComment (int index, char *text, Boolean addBraces)
15026 /* addBraces  tells if we should add {} */
15027 {
15028     int oldlen, len;
15029     char *old;
15030
15031 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15032     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15033
15034     CrushCRs(text);
15035     while (*text == '\n') text++;
15036     len = strlen(text);
15037     while (len > 0 && text[len - 1] == '\n') len--;
15038     text[len] = NULLCHAR;
15039
15040     if (len == 0) return;
15041
15042     if (commentList[index] != NULL) {
15043       Boolean addClosingBrace = addBraces;
15044         old = commentList[index];
15045         oldlen = strlen(old);
15046         while(commentList[index][oldlen-1] ==  '\n')
15047           commentList[index][--oldlen] = NULLCHAR;
15048         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15049         safeStrCpy(commentList[index], old, oldlen + len + 6);
15050         free(old);
15051         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15052         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15053           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15054           while (*text == '\n') { text++; len--; }
15055           commentList[index][--oldlen] = NULLCHAR;
15056       }
15057         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15058         else          strcat(commentList[index], "\n");
15059         strcat(commentList[index], text);
15060         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15061         else          strcat(commentList[index], "\n");
15062     } else {
15063         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15064         if(addBraces)
15065           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15066         else commentList[index][0] = NULLCHAR;
15067         strcat(commentList[index], text);
15068         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15069         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15070     }
15071 }
15072
15073 static char *
15074 FindStr (char * text, char * sub_text)
15075 {
15076     char * result = strstr( text, sub_text );
15077
15078     if( result != NULL ) {
15079         result += strlen( sub_text );
15080     }
15081
15082     return result;
15083 }
15084
15085 /* [AS] Try to extract PV info from PGN comment */
15086 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15087 char *
15088 GetInfoFromComment (int index, char * text)
15089 {
15090     char * sep = text, *p;
15091
15092     if( text != NULL && index > 0 ) {
15093         int score = 0;
15094         int depth = 0;
15095         int time = -1, sec = 0, deci;
15096         char * s_eval = FindStr( text, "[%eval " );
15097         char * s_emt = FindStr( text, "[%emt " );
15098
15099         if( s_eval != NULL || s_emt != NULL ) {
15100             /* New style */
15101             char delim;
15102
15103             if( s_eval != NULL ) {
15104                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15105                     return text;
15106                 }
15107
15108                 if( delim != ']' ) {
15109                     return text;
15110                 }
15111             }
15112
15113             if( s_emt != NULL ) {
15114             }
15115                 return text;
15116         }
15117         else {
15118             /* We expect something like: [+|-]nnn.nn/dd */
15119             int score_lo = 0;
15120
15121             if(*text != '{') return text; // [HGM] braces: must be normal comment
15122
15123             sep = strchr( text, '/' );
15124             if( sep == NULL || sep < (text+4) ) {
15125                 return text;
15126             }
15127
15128             p = text;
15129             if(p[1] == '(') { // comment starts with PV
15130                p = strchr(p, ')'); // locate end of PV
15131                if(p == NULL || sep < p+5) return text;
15132                // at this point we have something like "{(.*) +0.23/6 ..."
15133                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15134                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15135                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15136             }
15137             time = -1; sec = -1; deci = -1;
15138             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15139                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15140                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15141                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15142                 return text;
15143             }
15144
15145             if( score_lo < 0 || score_lo >= 100 ) {
15146                 return text;
15147             }
15148
15149             if(sec >= 0) time = 600*time + 10*sec; else
15150             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15151
15152             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15153
15154             /* [HGM] PV time: now locate end of PV info */
15155             while( *++sep >= '0' && *sep <= '9'); // strip depth
15156             if(time >= 0)
15157             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15158             if(sec >= 0)
15159             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15160             if(deci >= 0)
15161             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15162             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15163         }
15164
15165         if( depth <= 0 ) {
15166             return text;
15167         }
15168
15169         if( time < 0 ) {
15170             time = -1;
15171         }
15172
15173         pvInfoList[index-1].depth = depth;
15174         pvInfoList[index-1].score = score;
15175         pvInfoList[index-1].time  = 10*time; // centi-sec
15176         if(*sep == '}') *sep = 0; else *--sep = '{';
15177         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15178     }
15179     return sep;
15180 }
15181
15182 void
15183 SendToProgram (char *message, ChessProgramState *cps)
15184 {
15185     int count, outCount, error;
15186     char buf[MSG_SIZ];
15187
15188     if (cps->pr == NoProc) return;
15189     Attention(cps);
15190
15191     if (appData.debugMode) {
15192         TimeMark now;
15193         GetTimeMark(&now);
15194         fprintf(debugFP, "%ld >%-6s: %s",
15195                 SubtractTimeMarks(&now, &programStartTime),
15196                 cps->which, message);
15197         if(serverFP)
15198             fprintf(serverFP, "%ld >%-6s: %s",
15199                 SubtractTimeMarks(&now, &programStartTime),
15200                 cps->which, message), fflush(serverFP);
15201     }
15202
15203     count = strlen(message);
15204     outCount = OutputToProcess(cps->pr, message, count, &error);
15205     if (outCount < count && !exiting
15206                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15207       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15208       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15209         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15210             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15211                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15212                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15213                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15214             } else {
15215                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15216                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15217                 gameInfo.result = res;
15218             }
15219             gameInfo.resultDetails = StrSave(buf);
15220         }
15221         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15222         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15223     }
15224 }
15225
15226 void
15227 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15228 {
15229     char *end_str;
15230     char buf[MSG_SIZ];
15231     ChessProgramState *cps = (ChessProgramState *)closure;
15232
15233     if (isr != cps->isr) return; /* Killed intentionally */
15234     if (count <= 0) {
15235         if (count == 0) {
15236             RemoveInputSource(cps->isr);
15237             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15238             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15239                     _(cps->which), cps->program);
15240         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15241                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15242                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15243                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15244                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15245                 } else {
15246                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15247                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15248                     gameInfo.result = res;
15249                 }
15250                 gameInfo.resultDetails = StrSave(buf);
15251             }
15252             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15253             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15254         } else {
15255             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15256                     _(cps->which), cps->program);
15257             RemoveInputSource(cps->isr);
15258
15259             /* [AS] Program is misbehaving badly... kill it */
15260             if( count == -2 ) {
15261                 DestroyChildProcess( cps->pr, 9 );
15262                 cps->pr = NoProc;
15263             }
15264
15265             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15266         }
15267         return;
15268     }
15269
15270     if ((end_str = strchr(message, '\r')) != NULL)
15271       *end_str = NULLCHAR;
15272     if ((end_str = strchr(message, '\n')) != NULL)
15273       *end_str = NULLCHAR;
15274
15275     if (appData.debugMode) {
15276         TimeMark now; int print = 1;
15277         char *quote = ""; char c; int i;
15278
15279         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15280                 char start = message[0];
15281                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15282                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15283                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15284                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15285                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15286                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15287                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15288                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15289                    sscanf(message, "hint: %c", &c)!=1 && 
15290                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15291                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15292                     print = (appData.engineComments >= 2);
15293                 }
15294                 message[0] = start; // restore original message
15295         }
15296         if(print) {
15297                 GetTimeMark(&now);
15298                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15299                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15300                         quote,
15301                         message);
15302                 if(serverFP)
15303                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15304                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15305                         quote,
15306                         message), fflush(serverFP);
15307         }
15308     }
15309
15310     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15311     if (appData.icsEngineAnalyze) {
15312         if (strstr(message, "whisper") != NULL ||
15313              strstr(message, "kibitz") != NULL ||
15314             strstr(message, "tellics") != NULL) return;
15315     }
15316
15317     HandleMachineMove(message, cps);
15318 }
15319
15320
15321 void
15322 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15323 {
15324     char buf[MSG_SIZ];
15325     int seconds;
15326
15327     if( timeControl_2 > 0 ) {
15328         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15329             tc = timeControl_2;
15330         }
15331     }
15332     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15333     inc /= cps->timeOdds;
15334     st  /= cps->timeOdds;
15335
15336     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15337
15338     if (st > 0) {
15339       /* Set exact time per move, normally using st command */
15340       if (cps->stKludge) {
15341         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15342         seconds = st % 60;
15343         if (seconds == 0) {
15344           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15345         } else {
15346           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15347         }
15348       } else {
15349         snprintf(buf, MSG_SIZ, "st %d\n", st);
15350       }
15351     } else {
15352       /* Set conventional or incremental time control, using level command */
15353       if (seconds == 0) {
15354         /* Note old gnuchess bug -- minutes:seconds used to not work.
15355            Fixed in later versions, but still avoid :seconds
15356            when seconds is 0. */
15357         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15358       } else {
15359         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15360                  seconds, inc/1000.);
15361       }
15362     }
15363     SendToProgram(buf, cps);
15364
15365     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15366     /* Orthogonally, limit search to given depth */
15367     if (sd > 0) {
15368       if (cps->sdKludge) {
15369         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15370       } else {
15371         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15372       }
15373       SendToProgram(buf, cps);
15374     }
15375
15376     if(cps->nps >= 0) { /* [HGM] nps */
15377         if(cps->supportsNPS == FALSE)
15378           cps->nps = -1; // don't use if engine explicitly says not supported!
15379         else {
15380           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15381           SendToProgram(buf, cps);
15382         }
15383     }
15384 }
15385
15386 ChessProgramState *
15387 WhitePlayer ()
15388 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15389 {
15390     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15391        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15392         return &second;
15393     return &first;
15394 }
15395
15396 void
15397 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15398 {
15399     char message[MSG_SIZ];
15400     long time, otime;
15401
15402     /* Note: this routine must be called when the clocks are stopped
15403        or when they have *just* been set or switched; otherwise
15404        it will be off by the time since the current tick started.
15405     */
15406     if (machineWhite) {
15407         time = whiteTimeRemaining / 10;
15408         otime = blackTimeRemaining / 10;
15409     } else {
15410         time = blackTimeRemaining / 10;
15411         otime = whiteTimeRemaining / 10;
15412     }
15413     /* [HGM] translate opponent's time by time-odds factor */
15414     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15415
15416     if (time <= 0) time = 1;
15417     if (otime <= 0) otime = 1;
15418
15419     snprintf(message, MSG_SIZ, "time %ld\n", time);
15420     SendToProgram(message, cps);
15421
15422     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15423     SendToProgram(message, cps);
15424 }
15425
15426 int
15427 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15428 {
15429   char buf[MSG_SIZ];
15430   int len = strlen(name);
15431   int val;
15432
15433   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15434     (*p) += len + 1;
15435     sscanf(*p, "%d", &val);
15436     *loc = (val != 0);
15437     while (**p && **p != ' ')
15438       (*p)++;
15439     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15440     SendToProgram(buf, cps);
15441     return TRUE;
15442   }
15443   return FALSE;
15444 }
15445
15446 int
15447 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15448 {
15449   char buf[MSG_SIZ];
15450   int len = strlen(name);
15451   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15452     (*p) += len + 1;
15453     sscanf(*p, "%d", loc);
15454     while (**p && **p != ' ') (*p)++;
15455     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15456     SendToProgram(buf, cps);
15457     return TRUE;
15458   }
15459   return FALSE;
15460 }
15461
15462 int
15463 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15464 {
15465   char buf[MSG_SIZ];
15466   int len = strlen(name);
15467   if (strncmp((*p), name, len) == 0
15468       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15469     (*p) += len + 2;
15470     sscanf(*p, "%[^\"]", loc);
15471     while (**p && **p != '\"') (*p)++;
15472     if (**p == '\"') (*p)++;
15473     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15474     SendToProgram(buf, cps);
15475     return TRUE;
15476   }
15477   return FALSE;
15478 }
15479
15480 int
15481 ParseOption (Option *opt, ChessProgramState *cps)
15482 // [HGM] options: process the string that defines an engine option, and determine
15483 // name, type, default value, and allowed value range
15484 {
15485         char *p, *q, buf[MSG_SIZ];
15486         int n, min = (-1)<<31, max = 1<<31, def;
15487
15488         if(p = strstr(opt->name, " -spin ")) {
15489             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15490             if(max < min) max = min; // enforce consistency
15491             if(def < min) def = min;
15492             if(def > max) def = max;
15493             opt->value = def;
15494             opt->min = min;
15495             opt->max = max;
15496             opt->type = Spin;
15497         } else if((p = strstr(opt->name, " -slider "))) {
15498             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15499             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15500             if(max < min) max = min; // enforce consistency
15501             if(def < min) def = min;
15502             if(def > max) def = max;
15503             opt->value = def;
15504             opt->min = min;
15505             opt->max = max;
15506             opt->type = Spin; // Slider;
15507         } else if((p = strstr(opt->name, " -string "))) {
15508             opt->textValue = p+9;
15509             opt->type = TextBox;
15510         } else if((p = strstr(opt->name, " -file "))) {
15511             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15512             opt->textValue = p+7;
15513             opt->type = FileName; // FileName;
15514         } else if((p = strstr(opt->name, " -path "))) {
15515             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15516             opt->textValue = p+7;
15517             opt->type = PathName; // PathName;
15518         } else if(p = strstr(opt->name, " -check ")) {
15519             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15520             opt->value = (def != 0);
15521             opt->type = CheckBox;
15522         } else if(p = strstr(opt->name, " -combo ")) {
15523             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15524             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15525             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15526             opt->value = n = 0;
15527             while(q = StrStr(q, " /// ")) {
15528                 n++; *q = 0;    // count choices, and null-terminate each of them
15529                 q += 5;
15530                 if(*q == '*') { // remember default, which is marked with * prefix
15531                     q++;
15532                     opt->value = n;
15533                 }
15534                 cps->comboList[cps->comboCnt++] = q;
15535             }
15536             cps->comboList[cps->comboCnt++] = NULL;
15537             opt->max = n + 1;
15538             opt->type = ComboBox;
15539         } else if(p = strstr(opt->name, " -button")) {
15540             opt->type = Button;
15541         } else if(p = strstr(opt->name, " -save")) {
15542             opt->type = SaveButton;
15543         } else return FALSE;
15544         *p = 0; // terminate option name
15545         // now look if the command-line options define a setting for this engine option.
15546         if(cps->optionSettings && cps->optionSettings[0])
15547             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15548         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15549           snprintf(buf, MSG_SIZ, "option %s", p);
15550                 if(p = strstr(buf, ",")) *p = 0;
15551                 if(q = strchr(buf, '=')) switch(opt->type) {
15552                     case ComboBox:
15553                         for(n=0; n<opt->max; n++)
15554                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15555                         break;
15556                     case TextBox:
15557                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15558                         break;
15559                     case Spin:
15560                     case CheckBox:
15561                         opt->value = atoi(q+1);
15562                     default:
15563                         break;
15564                 }
15565                 strcat(buf, "\n");
15566                 SendToProgram(buf, cps);
15567         }
15568         return TRUE;
15569 }
15570
15571 void
15572 FeatureDone (ChessProgramState *cps, int val)
15573 {
15574   DelayedEventCallback cb = GetDelayedEvent();
15575   if ((cb == InitBackEnd3 && cps == &first) ||
15576       (cb == SettingsMenuIfReady && cps == &second) ||
15577       (cb == LoadEngine) ||
15578       (cb == TwoMachinesEventIfReady)) {
15579     CancelDelayedEvent();
15580     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15581   }
15582   cps->initDone = val;
15583 }
15584
15585 /* Parse feature command from engine */
15586 void
15587 ParseFeatures (char *args, ChessProgramState *cps)
15588 {
15589   char *p = args;
15590   char *q;
15591   int val;
15592   char buf[MSG_SIZ];
15593
15594   for (;;) {
15595     while (*p == ' ') p++;
15596     if (*p == NULLCHAR) return;
15597
15598     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15599     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15600     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15601     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15602     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15603     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15604     if (BoolFeature(&p, "reuse", &val, cps)) {
15605       /* Engine can disable reuse, but can't enable it if user said no */
15606       if (!val) cps->reuse = FALSE;
15607       continue;
15608     }
15609     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15610     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15611       if (gameMode == TwoMachinesPlay) {
15612         DisplayTwoMachinesTitle();
15613       } else {
15614         DisplayTitle("");
15615       }
15616       continue;
15617     }
15618     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15619     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15620     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15621     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15622     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15623     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15624     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15625     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15626     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15627     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15628     if (IntFeature(&p, "done", &val, cps)) {
15629       FeatureDone(cps, val);
15630       continue;
15631     }
15632     /* Added by Tord: */
15633     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15634     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15635     /* End of additions by Tord */
15636
15637     /* [HGM] added features: */
15638     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15639     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15640     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15641     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15642     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15643     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15644     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15645         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15646           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15647             SendToProgram(buf, cps);
15648             continue;
15649         }
15650         if(cps->nrOptions >= MAX_OPTIONS) {
15651             cps->nrOptions--;
15652             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15653             DisplayError(buf, 0);
15654         }
15655         continue;
15656     }
15657     /* End of additions by HGM */
15658
15659     /* unknown feature: complain and skip */
15660     q = p;
15661     while (*q && *q != '=') q++;
15662     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15663     SendToProgram(buf, cps);
15664     p = q;
15665     if (*p == '=') {
15666       p++;
15667       if (*p == '\"') {
15668         p++;
15669         while (*p && *p != '\"') p++;
15670         if (*p == '\"') p++;
15671       } else {
15672         while (*p && *p != ' ') p++;
15673       }
15674     }
15675   }
15676
15677 }
15678
15679 void
15680 PeriodicUpdatesEvent (int newState)
15681 {
15682     if (newState == appData.periodicUpdates)
15683       return;
15684
15685     appData.periodicUpdates=newState;
15686
15687     /* Display type changes, so update it now */
15688 //    DisplayAnalysis();
15689
15690     /* Get the ball rolling again... */
15691     if (newState) {
15692         AnalysisPeriodicEvent(1);
15693         StartAnalysisClock();
15694     }
15695 }
15696
15697 void
15698 PonderNextMoveEvent (int newState)
15699 {
15700     if (newState == appData.ponderNextMove) return;
15701     if (gameMode == EditPosition) EditPositionDone(TRUE);
15702     if (newState) {
15703         SendToProgram("hard\n", &first);
15704         if (gameMode == TwoMachinesPlay) {
15705             SendToProgram("hard\n", &second);
15706         }
15707     } else {
15708         SendToProgram("easy\n", &first);
15709         thinkOutput[0] = NULLCHAR;
15710         if (gameMode == TwoMachinesPlay) {
15711             SendToProgram("easy\n", &second);
15712         }
15713     }
15714     appData.ponderNextMove = newState;
15715 }
15716
15717 void
15718 NewSettingEvent (int option, int *feature, char *command, int value)
15719 {
15720     char buf[MSG_SIZ];
15721
15722     if (gameMode == EditPosition) EditPositionDone(TRUE);
15723     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15724     if(feature == NULL || *feature) SendToProgram(buf, &first);
15725     if (gameMode == TwoMachinesPlay) {
15726         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15727     }
15728 }
15729
15730 void
15731 ShowThinkingEvent ()
15732 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15733 {
15734     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15735     int newState = appData.showThinking
15736         // [HGM] thinking: other features now need thinking output as well
15737         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15738
15739     if (oldState == newState) return;
15740     oldState = newState;
15741     if (gameMode == EditPosition) EditPositionDone(TRUE);
15742     if (oldState) {
15743         SendToProgram("post\n", &first);
15744         if (gameMode == TwoMachinesPlay) {
15745             SendToProgram("post\n", &second);
15746         }
15747     } else {
15748         SendToProgram("nopost\n", &first);
15749         thinkOutput[0] = NULLCHAR;
15750         if (gameMode == TwoMachinesPlay) {
15751             SendToProgram("nopost\n", &second);
15752         }
15753     }
15754 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15755 }
15756
15757 void
15758 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15759 {
15760   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15761   if (pr == NoProc) return;
15762   AskQuestion(title, question, replyPrefix, pr);
15763 }
15764
15765 void
15766 TypeInEvent (char firstChar)
15767 {
15768     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15769         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15770         gameMode == AnalyzeMode || gameMode == EditGame || 
15771         gameMode == EditPosition || gameMode == IcsExamining ||
15772         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15773         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15774                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15775                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15776         gameMode == Training) PopUpMoveDialog(firstChar);
15777 }
15778
15779 void
15780 TypeInDoneEvent (char *move)
15781 {
15782         Board board;
15783         int n, fromX, fromY, toX, toY;
15784         char promoChar;
15785         ChessMove moveType;
15786
15787         // [HGM] FENedit
15788         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15789                 EditPositionPasteFEN(move);
15790                 return;
15791         }
15792         // [HGM] movenum: allow move number to be typed in any mode
15793         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15794           ToNrEvent(2*n-1);
15795           return;
15796         }
15797         // undocumented kludge: allow command-line option to be typed in!
15798         // (potentially fatal, and does not implement the effect of the option.)
15799         // should only be used for options that are values on which future decisions will be made,
15800         // and definitely not on options that would be used during initialization.
15801         if(strstr(move, "!!! -") == move) {
15802             ParseArgsFromString(move+4);
15803             return;
15804         }
15805
15806       if (gameMode != EditGame && currentMove != forwardMostMove && 
15807         gameMode != Training) {
15808         DisplayMoveError(_("Displayed move is not current"));
15809       } else {
15810         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15811           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15812         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15813         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15814           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15815           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15816         } else {
15817           DisplayMoveError(_("Could not parse move"));
15818         }
15819       }
15820 }
15821
15822 void
15823 DisplayMove (int moveNumber)
15824 {
15825     char message[MSG_SIZ];
15826     char res[MSG_SIZ];
15827     char cpThinkOutput[MSG_SIZ];
15828
15829     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15830
15831     if (moveNumber == forwardMostMove - 1 ||
15832         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15833
15834         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15835
15836         if (strchr(cpThinkOutput, '\n')) {
15837             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15838         }
15839     } else {
15840         *cpThinkOutput = NULLCHAR;
15841     }
15842
15843     /* [AS] Hide thinking from human user */
15844     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15845         *cpThinkOutput = NULLCHAR;
15846         if( thinkOutput[0] != NULLCHAR ) {
15847             int i;
15848
15849             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15850                 cpThinkOutput[i] = '.';
15851             }
15852             cpThinkOutput[i] = NULLCHAR;
15853             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15854         }
15855     }
15856
15857     if (moveNumber == forwardMostMove - 1 &&
15858         gameInfo.resultDetails != NULL) {
15859         if (gameInfo.resultDetails[0] == NULLCHAR) {
15860           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15861         } else {
15862           snprintf(res, MSG_SIZ, " {%s} %s",
15863                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15864         }
15865     } else {
15866         res[0] = NULLCHAR;
15867     }
15868
15869     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15870         DisplayMessage(res, cpThinkOutput);
15871     } else {
15872       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15873                 WhiteOnMove(moveNumber) ? " " : ".. ",
15874                 parseList[moveNumber], res);
15875         DisplayMessage(message, cpThinkOutput);
15876     }
15877 }
15878
15879 void
15880 DisplayComment (int moveNumber, char *text)
15881 {
15882     char title[MSG_SIZ];
15883
15884     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15885       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15886     } else {
15887       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15888               WhiteOnMove(moveNumber) ? " " : ".. ",
15889               parseList[moveNumber]);
15890     }
15891     if (text != NULL && (appData.autoDisplayComment || commentUp))
15892         CommentPopUp(title, text);
15893 }
15894
15895 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15896  * might be busy thinking or pondering.  It can be omitted if your
15897  * gnuchess is configured to stop thinking immediately on any user
15898  * input.  However, that gnuchess feature depends on the FIONREAD
15899  * ioctl, which does not work properly on some flavors of Unix.
15900  */
15901 void
15902 Attention (ChessProgramState *cps)
15903 {
15904 #if ATTENTION
15905     if (!cps->useSigint) return;
15906     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15907     switch (gameMode) {
15908       case MachinePlaysWhite:
15909       case MachinePlaysBlack:
15910       case TwoMachinesPlay:
15911       case IcsPlayingWhite:
15912       case IcsPlayingBlack:
15913       case AnalyzeMode:
15914       case AnalyzeFile:
15915         /* Skip if we know it isn't thinking */
15916         if (!cps->maybeThinking) return;
15917         if (appData.debugMode)
15918           fprintf(debugFP, "Interrupting %s\n", cps->which);
15919         InterruptChildProcess(cps->pr);
15920         cps->maybeThinking = FALSE;
15921         break;
15922       default:
15923         break;
15924     }
15925 #endif /*ATTENTION*/
15926 }
15927
15928 int
15929 CheckFlags ()
15930 {
15931     if (whiteTimeRemaining <= 0) {
15932         if (!whiteFlag) {
15933             whiteFlag = TRUE;
15934             if (appData.icsActive) {
15935                 if (appData.autoCallFlag &&
15936                     gameMode == IcsPlayingBlack && !blackFlag) {
15937                   SendToICS(ics_prefix);
15938                   SendToICS("flag\n");
15939                 }
15940             } else {
15941                 if (blackFlag) {
15942                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15943                 } else {
15944                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15945                     if (appData.autoCallFlag) {
15946                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15947                         return TRUE;
15948                     }
15949                 }
15950             }
15951         }
15952     }
15953     if (blackTimeRemaining <= 0) {
15954         if (!blackFlag) {
15955             blackFlag = TRUE;
15956             if (appData.icsActive) {
15957                 if (appData.autoCallFlag &&
15958                     gameMode == IcsPlayingWhite && !whiteFlag) {
15959                   SendToICS(ics_prefix);
15960                   SendToICS("flag\n");
15961                 }
15962             } else {
15963                 if (whiteFlag) {
15964                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15965                 } else {
15966                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15967                     if (appData.autoCallFlag) {
15968                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15969                         return TRUE;
15970                     }
15971                 }
15972             }
15973         }
15974     }
15975     return FALSE;
15976 }
15977
15978 void
15979 CheckTimeControl ()
15980 {
15981     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15982         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15983
15984     /*
15985      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15986      */
15987     if ( !WhiteOnMove(forwardMostMove) ) {
15988         /* White made time control */
15989         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15990         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15991         /* [HGM] time odds: correct new time quota for time odds! */
15992                                             / WhitePlayer()->timeOdds;
15993         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15994     } else {
15995         lastBlack -= blackTimeRemaining;
15996         /* Black made time control */
15997         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15998                                             / WhitePlayer()->other->timeOdds;
15999         lastWhite = whiteTimeRemaining;
16000     }
16001 }
16002
16003 void
16004 DisplayBothClocks ()
16005 {
16006     int wom = gameMode == EditPosition ?
16007       !blackPlaysFirst : WhiteOnMove(currentMove);
16008     DisplayWhiteClock(whiteTimeRemaining, wom);
16009     DisplayBlackClock(blackTimeRemaining, !wom);
16010 }
16011
16012
16013 /* Timekeeping seems to be a portability nightmare.  I think everyone
16014    has ftime(), but I'm really not sure, so I'm including some ifdefs
16015    to use other calls if you don't.  Clocks will be less accurate if
16016    you have neither ftime nor gettimeofday.
16017 */
16018
16019 /* VS 2008 requires the #include outside of the function */
16020 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16021 #include <sys/timeb.h>
16022 #endif
16023
16024 /* Get the current time as a TimeMark */
16025 void
16026 GetTimeMark (TimeMark *tm)
16027 {
16028 #if HAVE_GETTIMEOFDAY
16029
16030     struct timeval timeVal;
16031     struct timezone timeZone;
16032
16033     gettimeofday(&timeVal, &timeZone);
16034     tm->sec = (long) timeVal.tv_sec;
16035     tm->ms = (int) (timeVal.tv_usec / 1000L);
16036
16037 #else /*!HAVE_GETTIMEOFDAY*/
16038 #if HAVE_FTIME
16039
16040 // include <sys/timeb.h> / moved to just above start of function
16041     struct timeb timeB;
16042
16043     ftime(&timeB);
16044     tm->sec = (long) timeB.time;
16045     tm->ms = (int) timeB.millitm;
16046
16047 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16048     tm->sec = (long) time(NULL);
16049     tm->ms = 0;
16050 #endif
16051 #endif
16052 }
16053
16054 /* Return the difference in milliseconds between two
16055    time marks.  We assume the difference will fit in a long!
16056 */
16057 long
16058 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16059 {
16060     return 1000L*(tm2->sec - tm1->sec) +
16061            (long) (tm2->ms - tm1->ms);
16062 }
16063
16064
16065 /*
16066  * Code to manage the game clocks.
16067  *
16068  * In tournament play, black starts the clock and then white makes a move.
16069  * We give the human user a slight advantage if he is playing white---the
16070  * clocks don't run until he makes his first move, so it takes zero time.
16071  * Also, we don't account for network lag, so we could get out of sync
16072  * with GNU Chess's clock -- but then, referees are always right.
16073  */
16074
16075 static TimeMark tickStartTM;
16076 static long intendedTickLength;
16077
16078 long
16079 NextTickLength (long timeRemaining)
16080 {
16081     long nominalTickLength, nextTickLength;
16082
16083     if (timeRemaining > 0L && timeRemaining <= 10000L)
16084       nominalTickLength = 100L;
16085     else
16086       nominalTickLength = 1000L;
16087     nextTickLength = timeRemaining % nominalTickLength;
16088     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16089
16090     return nextTickLength;
16091 }
16092
16093 /* Adjust clock one minute up or down */
16094 void
16095 AdjustClock (Boolean which, int dir)
16096 {
16097     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16098     if(which) blackTimeRemaining += 60000*dir;
16099     else      whiteTimeRemaining += 60000*dir;
16100     DisplayBothClocks();
16101     adjustedClock = TRUE;
16102 }
16103
16104 /* Stop clocks and reset to a fresh time control */
16105 void
16106 ResetClocks ()
16107 {
16108     (void) StopClockTimer();
16109     if (appData.icsActive) {
16110         whiteTimeRemaining = blackTimeRemaining = 0;
16111     } else if (searchTime) {
16112         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16113         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16114     } else { /* [HGM] correct new time quote for time odds */
16115         whiteTC = blackTC = fullTimeControlString;
16116         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16117         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16118     }
16119     if (whiteFlag || blackFlag) {
16120         DisplayTitle("");
16121         whiteFlag = blackFlag = FALSE;
16122     }
16123     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16124     DisplayBothClocks();
16125     adjustedClock = FALSE;
16126 }
16127
16128 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16129
16130 /* Decrement running clock by amount of time that has passed */
16131 void
16132 DecrementClocks ()
16133 {
16134     long timeRemaining;
16135     long lastTickLength, fudge;
16136     TimeMark now;
16137
16138     if (!appData.clockMode) return;
16139     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16140
16141     GetTimeMark(&now);
16142
16143     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16144
16145     /* Fudge if we woke up a little too soon */
16146     fudge = intendedTickLength - lastTickLength;
16147     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16148
16149     if (WhiteOnMove(forwardMostMove)) {
16150         if(whiteNPS >= 0) lastTickLength = 0;
16151         timeRemaining = whiteTimeRemaining -= lastTickLength;
16152         if(timeRemaining < 0 && !appData.icsActive) {
16153             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16154             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16155                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16156                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16157             }
16158         }
16159         DisplayWhiteClock(whiteTimeRemaining - fudge,
16160                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16161     } else {
16162         if(blackNPS >= 0) lastTickLength = 0;
16163         timeRemaining = blackTimeRemaining -= lastTickLength;
16164         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16165             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16166             if(suddenDeath) {
16167                 blackStartMove = forwardMostMove;
16168                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16169             }
16170         }
16171         DisplayBlackClock(blackTimeRemaining - fudge,
16172                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16173     }
16174     if (CheckFlags()) return;
16175
16176     tickStartTM = now;
16177     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16178     StartClockTimer(intendedTickLength);
16179
16180     /* if the time remaining has fallen below the alarm threshold, sound the
16181      * alarm. if the alarm has sounded and (due to a takeback or time control
16182      * with increment) the time remaining has increased to a level above the
16183      * threshold, reset the alarm so it can sound again.
16184      */
16185
16186     if (appData.icsActive && appData.icsAlarm) {
16187
16188         /* make sure we are dealing with the user's clock */
16189         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16190                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16191            )) return;
16192
16193         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16194             alarmSounded = FALSE;
16195         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16196             PlayAlarmSound();
16197             alarmSounded = TRUE;
16198         }
16199     }
16200 }
16201
16202
16203 /* A player has just moved, so stop the previously running
16204    clock and (if in clock mode) start the other one.
16205    We redisplay both clocks in case we're in ICS mode, because
16206    ICS gives us an update to both clocks after every move.
16207    Note that this routine is called *after* forwardMostMove
16208    is updated, so the last fractional tick must be subtracted
16209    from the color that is *not* on move now.
16210 */
16211 void
16212 SwitchClocks (int newMoveNr)
16213 {
16214     long lastTickLength;
16215     TimeMark now;
16216     int flagged = FALSE;
16217
16218     GetTimeMark(&now);
16219
16220     if (StopClockTimer() && appData.clockMode) {
16221         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16222         if (!WhiteOnMove(forwardMostMove)) {
16223             if(blackNPS >= 0) lastTickLength = 0;
16224             blackTimeRemaining -= lastTickLength;
16225            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16226 //         if(pvInfoList[forwardMostMove].time == -1)
16227                  pvInfoList[forwardMostMove].time =               // use GUI time
16228                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16229         } else {
16230            if(whiteNPS >= 0) lastTickLength = 0;
16231            whiteTimeRemaining -= lastTickLength;
16232            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16233 //         if(pvInfoList[forwardMostMove].time == -1)
16234                  pvInfoList[forwardMostMove].time =
16235                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16236         }
16237         flagged = CheckFlags();
16238     }
16239     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16240     CheckTimeControl();
16241
16242     if (flagged || !appData.clockMode) return;
16243
16244     switch (gameMode) {
16245       case MachinePlaysBlack:
16246       case MachinePlaysWhite:
16247       case BeginningOfGame:
16248         if (pausing) return;
16249         break;
16250
16251       case EditGame:
16252       case PlayFromGameFile:
16253       case IcsExamining:
16254         return;
16255
16256       default:
16257         break;
16258     }
16259
16260     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16261         if(WhiteOnMove(forwardMostMove))
16262              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16263         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16264     }
16265
16266     tickStartTM = now;
16267     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16268       whiteTimeRemaining : blackTimeRemaining);
16269     StartClockTimer(intendedTickLength);
16270 }
16271
16272
16273 /* Stop both clocks */
16274 void
16275 StopClocks ()
16276 {
16277     long lastTickLength;
16278     TimeMark now;
16279
16280     if (!StopClockTimer()) return;
16281     if (!appData.clockMode) return;
16282
16283     GetTimeMark(&now);
16284
16285     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16286     if (WhiteOnMove(forwardMostMove)) {
16287         if(whiteNPS >= 0) lastTickLength = 0;
16288         whiteTimeRemaining -= lastTickLength;
16289         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16290     } else {
16291         if(blackNPS >= 0) lastTickLength = 0;
16292         blackTimeRemaining -= lastTickLength;
16293         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16294     }
16295     CheckFlags();
16296 }
16297
16298 /* Start clock of player on move.  Time may have been reset, so
16299    if clock is already running, stop and restart it. */
16300 void
16301 StartClocks ()
16302 {
16303     (void) StopClockTimer(); /* in case it was running already */
16304     DisplayBothClocks();
16305     if (CheckFlags()) return;
16306
16307     if (!appData.clockMode) return;
16308     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16309
16310     GetTimeMark(&tickStartTM);
16311     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16312       whiteTimeRemaining : blackTimeRemaining);
16313
16314    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16315     whiteNPS = blackNPS = -1;
16316     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16317        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16318         whiteNPS = first.nps;
16319     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16320        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16321         blackNPS = first.nps;
16322     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16323         whiteNPS = second.nps;
16324     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16325         blackNPS = second.nps;
16326     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16327
16328     StartClockTimer(intendedTickLength);
16329 }
16330
16331 char *
16332 TimeString (long ms)
16333 {
16334     long second, minute, hour, day;
16335     char *sign = "";
16336     static char buf[32];
16337
16338     if (ms > 0 && ms <= 9900) {
16339       /* convert milliseconds to tenths, rounding up */
16340       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16341
16342       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16343       return buf;
16344     }
16345
16346     /* convert milliseconds to seconds, rounding up */
16347     /* use floating point to avoid strangeness of integer division
16348        with negative dividends on many machines */
16349     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16350
16351     if (second < 0) {
16352         sign = "-";
16353         second = -second;
16354     }
16355
16356     day = second / (60 * 60 * 24);
16357     second = second % (60 * 60 * 24);
16358     hour = second / (60 * 60);
16359     second = second % (60 * 60);
16360     minute = second / 60;
16361     second = second % 60;
16362
16363     if (day > 0)
16364       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16365               sign, day, hour, minute, second);
16366     else if (hour > 0)
16367       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16368     else
16369       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16370
16371     return buf;
16372 }
16373
16374
16375 /*
16376  * This is necessary because some C libraries aren't ANSI C compliant yet.
16377  */
16378 char *
16379 StrStr (char *string, char *match)
16380 {
16381     int i, length;
16382
16383     length = strlen(match);
16384
16385     for (i = strlen(string) - length; i >= 0; i--, string++)
16386       if (!strncmp(match, string, length))
16387         return string;
16388
16389     return NULL;
16390 }
16391
16392 char *
16393 StrCaseStr (char *string, char *match)
16394 {
16395     int i, j, length;
16396
16397     length = strlen(match);
16398
16399     for (i = strlen(string) - length; i >= 0; i--, string++) {
16400         for (j = 0; j < length; j++) {
16401             if (ToLower(match[j]) != ToLower(string[j]))
16402               break;
16403         }
16404         if (j == length) return string;
16405     }
16406
16407     return NULL;
16408 }
16409
16410 #ifndef _amigados
16411 int
16412 StrCaseCmp (char *s1, char *s2)
16413 {
16414     char c1, c2;
16415
16416     for (;;) {
16417         c1 = ToLower(*s1++);
16418         c2 = ToLower(*s2++);
16419         if (c1 > c2) return 1;
16420         if (c1 < c2) return -1;
16421         if (c1 == NULLCHAR) return 0;
16422     }
16423 }
16424
16425
16426 int
16427 ToLower (int c)
16428 {
16429     return isupper(c) ? tolower(c) : c;
16430 }
16431
16432
16433 int
16434 ToUpper (int c)
16435 {
16436     return islower(c) ? toupper(c) : c;
16437 }
16438 #endif /* !_amigados    */
16439
16440 char *
16441 StrSave (char *s)
16442 {
16443   char *ret;
16444
16445   if ((ret = (char *) malloc(strlen(s) + 1)))
16446     {
16447       safeStrCpy(ret, s, strlen(s)+1);
16448     }
16449   return ret;
16450 }
16451
16452 char *
16453 StrSavePtr (char *s, char **savePtr)
16454 {
16455     if (*savePtr) {
16456         free(*savePtr);
16457     }
16458     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16459       safeStrCpy(*savePtr, s, strlen(s)+1);
16460     }
16461     return(*savePtr);
16462 }
16463
16464 char *
16465 PGNDate ()
16466 {
16467     time_t clock;
16468     struct tm *tm;
16469     char buf[MSG_SIZ];
16470
16471     clock = time((time_t *)NULL);
16472     tm = localtime(&clock);
16473     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16474             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16475     return StrSave(buf);
16476 }
16477
16478
16479 char *
16480 PositionToFEN (int move, char *overrideCastling)
16481 {
16482     int i, j, fromX, fromY, toX, toY;
16483     int whiteToPlay;
16484     char buf[MSG_SIZ];
16485     char *p, *q;
16486     int emptycount;
16487     ChessSquare piece;
16488
16489     whiteToPlay = (gameMode == EditPosition) ?
16490       !blackPlaysFirst : (move % 2 == 0);
16491     p = buf;
16492
16493     /* Piece placement data */
16494     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16495         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16496         emptycount = 0;
16497         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16498             if (boards[move][i][j] == EmptySquare) {
16499                 emptycount++;
16500             } else { ChessSquare piece = boards[move][i][j];
16501                 if (emptycount > 0) {
16502                     if(emptycount<10) /* [HGM] can be >= 10 */
16503                         *p++ = '0' + emptycount;
16504                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16505                     emptycount = 0;
16506                 }
16507                 if(PieceToChar(piece) == '+') {
16508                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16509                     *p++ = '+';
16510                     piece = (ChessSquare)(DEMOTED piece);
16511                 }
16512                 *p++ = PieceToChar(piece);
16513                 if(p[-1] == '~') {
16514                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16515                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16516                     *p++ = '~';
16517                 }
16518             }
16519         }
16520         if (emptycount > 0) {
16521             if(emptycount<10) /* [HGM] can be >= 10 */
16522                 *p++ = '0' + emptycount;
16523             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16524             emptycount = 0;
16525         }
16526         *p++ = '/';
16527     }
16528     *(p - 1) = ' ';
16529
16530     /* [HGM] print Crazyhouse or Shogi holdings */
16531     if( gameInfo.holdingsWidth ) {
16532         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16533         q = p;
16534         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16535             piece = boards[move][i][BOARD_WIDTH-1];
16536             if( piece != EmptySquare )
16537               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16538                   *p++ = PieceToChar(piece);
16539         }
16540         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16541             piece = boards[move][BOARD_HEIGHT-i-1][0];
16542             if( piece != EmptySquare )
16543               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16544                   *p++ = PieceToChar(piece);
16545         }
16546
16547         if( q == p ) *p++ = '-';
16548         *p++ = ']';
16549         *p++ = ' ';
16550     }
16551
16552     /* Active color */
16553     *p++ = whiteToPlay ? 'w' : 'b';
16554     *p++ = ' ';
16555
16556   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16557     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16558   } else {
16559   if(nrCastlingRights) {
16560      q = p;
16561      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16562        /* [HGM] write directly from rights */
16563            if(boards[move][CASTLING][2] != NoRights &&
16564               boards[move][CASTLING][0] != NoRights   )
16565                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16566            if(boards[move][CASTLING][2] != NoRights &&
16567               boards[move][CASTLING][1] != NoRights   )
16568                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16569            if(boards[move][CASTLING][5] != NoRights &&
16570               boards[move][CASTLING][3] != NoRights   )
16571                 *p++ = boards[move][CASTLING][3] + AAA;
16572            if(boards[move][CASTLING][5] != NoRights &&
16573               boards[move][CASTLING][4] != NoRights   )
16574                 *p++ = boards[move][CASTLING][4] + AAA;
16575      } else {
16576
16577         /* [HGM] write true castling rights */
16578         if( nrCastlingRights == 6 ) {
16579             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16580                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16581             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16582                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16583             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16584                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16585             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16586                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16587         }
16588      }
16589      if (q == p) *p++ = '-'; /* No castling rights */
16590      *p++ = ' ';
16591   }
16592
16593   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16594      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16595     /* En passant target square */
16596     if (move > backwardMostMove) {
16597         fromX = moveList[move - 1][0] - AAA;
16598         fromY = moveList[move - 1][1] - ONE;
16599         toX = moveList[move - 1][2] - AAA;
16600         toY = moveList[move - 1][3] - ONE;
16601         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16602             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16603             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16604             fromX == toX) {
16605             /* 2-square pawn move just happened */
16606             *p++ = toX + AAA;
16607             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16608         } else {
16609             *p++ = '-';
16610         }
16611     } else if(move == backwardMostMove) {
16612         // [HGM] perhaps we should always do it like this, and forget the above?
16613         if((signed char)boards[move][EP_STATUS] >= 0) {
16614             *p++ = boards[move][EP_STATUS] + AAA;
16615             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16616         } else {
16617             *p++ = '-';
16618         }
16619     } else {
16620         *p++ = '-';
16621     }
16622     *p++ = ' ';
16623   }
16624   }
16625
16626     /* [HGM] find reversible plies */
16627     {   int i = 0, j=move;
16628
16629         if (appData.debugMode) { int k;
16630             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16631             for(k=backwardMostMove; k<=forwardMostMove; k++)
16632                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16633
16634         }
16635
16636         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16637         if( j == backwardMostMove ) i += initialRulePlies;
16638         sprintf(p, "%d ", i);
16639         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16640     }
16641     /* Fullmove number */
16642     sprintf(p, "%d", (move / 2) + 1);
16643
16644     return StrSave(buf);
16645 }
16646
16647 Boolean
16648 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16649 {
16650     int i, j;
16651     char *p, c;
16652     int emptycount;
16653     ChessSquare piece;
16654
16655     p = fen;
16656
16657     /* [HGM] by default clear Crazyhouse holdings, if present */
16658     if(gameInfo.holdingsWidth) {
16659        for(i=0; i<BOARD_HEIGHT; i++) {
16660            board[i][0]             = EmptySquare; /* black holdings */
16661            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16662            board[i][1]             = (ChessSquare) 0; /* black counts */
16663            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16664        }
16665     }
16666
16667     /* Piece placement data */
16668     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16669         j = 0;
16670         for (;;) {
16671             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16672                 if (*p == '/') p++;
16673                 emptycount = gameInfo.boardWidth - j;
16674                 while (emptycount--)
16675                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16676                 break;
16677 #if(BOARD_FILES >= 10)
16678             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16679                 p++; emptycount=10;
16680                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16681                 while (emptycount--)
16682                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16683 #endif
16684             } else if (isdigit(*p)) {
16685                 emptycount = *p++ - '0';
16686                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16687                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16688                 while (emptycount--)
16689                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16690             } else if (*p == '+' || isalpha(*p)) {
16691                 if (j >= gameInfo.boardWidth) return FALSE;
16692                 if(*p=='+') {
16693                     piece = CharToPiece(*++p);
16694                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16695                     piece = (ChessSquare) (PROMOTED piece ); p++;
16696                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16697                 } else piece = CharToPiece(*p++);
16698
16699                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16700                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16701                     piece = (ChessSquare) (PROMOTED piece);
16702                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16703                     p++;
16704                 }
16705                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16706             } else {
16707                 return FALSE;
16708             }
16709         }
16710     }
16711     while (*p == '/' || *p == ' ') p++;
16712
16713     /* [HGM] look for Crazyhouse holdings here */
16714     while(*p==' ') p++;
16715     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16716         if(*p == '[') p++;
16717         if(*p == '-' ) p++; /* empty holdings */ else {
16718             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16719             /* if we would allow FEN reading to set board size, we would   */
16720             /* have to add holdings and shift the board read so far here   */
16721             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16722                 p++;
16723                 if((int) piece >= (int) BlackPawn ) {
16724                     i = (int)piece - (int)BlackPawn;
16725                     i = PieceToNumber((ChessSquare)i);
16726                     if( i >= gameInfo.holdingsSize ) return FALSE;
16727                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16728                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16729                 } else {
16730                     i = (int)piece - (int)WhitePawn;
16731                     i = PieceToNumber((ChessSquare)i);
16732                     if( i >= gameInfo.holdingsSize ) return FALSE;
16733                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16734                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16735                 }
16736             }
16737         }
16738         if(*p == ']') p++;
16739     }
16740
16741     while(*p == ' ') p++;
16742
16743     /* Active color */
16744     c = *p++;
16745     if(appData.colorNickNames) {
16746       if( c == appData.colorNickNames[0] ) c = 'w'; else
16747       if( c == appData.colorNickNames[1] ) c = 'b';
16748     }
16749     switch (c) {
16750       case 'w':
16751         *blackPlaysFirst = FALSE;
16752         break;
16753       case 'b':
16754         *blackPlaysFirst = TRUE;
16755         break;
16756       default:
16757         return FALSE;
16758     }
16759
16760     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16761     /* return the extra info in global variiables             */
16762
16763     /* set defaults in case FEN is incomplete */
16764     board[EP_STATUS] = EP_UNKNOWN;
16765     for(i=0; i<nrCastlingRights; i++ ) {
16766         board[CASTLING][i] =
16767             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16768     }   /* assume possible unless obviously impossible */
16769     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16770     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16771     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16772                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16773     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16774     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16775     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16776                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16777     FENrulePlies = 0;
16778
16779     while(*p==' ') p++;
16780     if(nrCastlingRights) {
16781       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16782           /* castling indicator present, so default becomes no castlings */
16783           for(i=0; i<nrCastlingRights; i++ ) {
16784                  board[CASTLING][i] = NoRights;
16785           }
16786       }
16787       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16788              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16789              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16790              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16791         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16792
16793         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16794             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16795             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16796         }
16797         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16798             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16799         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16800                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16801         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16802                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16803         switch(c) {
16804           case'K':
16805               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16806               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16807               board[CASTLING][2] = whiteKingFile;
16808               break;
16809           case'Q':
16810               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16811               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16812               board[CASTLING][2] = whiteKingFile;
16813               break;
16814           case'k':
16815               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16816               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16817               board[CASTLING][5] = blackKingFile;
16818               break;
16819           case'q':
16820               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16821               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16822               board[CASTLING][5] = blackKingFile;
16823           case '-':
16824               break;
16825           default: /* FRC castlings */
16826               if(c >= 'a') { /* black rights */
16827                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16828                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16829                   if(i == BOARD_RGHT) break;
16830                   board[CASTLING][5] = i;
16831                   c -= AAA;
16832                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16833                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16834                   if(c > i)
16835                       board[CASTLING][3] = c;
16836                   else
16837                       board[CASTLING][4] = c;
16838               } else { /* white rights */
16839                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16840                     if(board[0][i] == WhiteKing) break;
16841                   if(i == BOARD_RGHT) break;
16842                   board[CASTLING][2] = i;
16843                   c -= AAA - 'a' + 'A';
16844                   if(board[0][c] >= WhiteKing) break;
16845                   if(c > i)
16846                       board[CASTLING][0] = c;
16847                   else
16848                       board[CASTLING][1] = c;
16849               }
16850         }
16851       }
16852       for(i=0; i<nrCastlingRights; i++)
16853         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16854     if (appData.debugMode) {
16855         fprintf(debugFP, "FEN castling rights:");
16856         for(i=0; i<nrCastlingRights; i++)
16857         fprintf(debugFP, " %d", board[CASTLING][i]);
16858         fprintf(debugFP, "\n");
16859     }
16860
16861       while(*p==' ') p++;
16862     }
16863
16864     /* read e.p. field in games that know e.p. capture */
16865     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16866        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16867       if(*p=='-') {
16868         p++; board[EP_STATUS] = EP_NONE;
16869       } else {
16870          char c = *p++ - AAA;
16871
16872          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16873          if(*p >= '0' && *p <='9') p++;
16874          board[EP_STATUS] = c;
16875       }
16876     }
16877
16878
16879     if(sscanf(p, "%d", &i) == 1) {
16880         FENrulePlies = i; /* 50-move ply counter */
16881         /* (The move number is still ignored)    */
16882     }
16883
16884     return TRUE;
16885 }
16886
16887 void
16888 EditPositionPasteFEN (char *fen)
16889 {
16890   if (fen != NULL) {
16891     Board initial_position;
16892
16893     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16894       DisplayError(_("Bad FEN position in clipboard"), 0);
16895       return ;
16896     } else {
16897       int savedBlackPlaysFirst = blackPlaysFirst;
16898       EditPositionEvent();
16899       blackPlaysFirst = savedBlackPlaysFirst;
16900       CopyBoard(boards[0], initial_position);
16901       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16902       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16903       DisplayBothClocks();
16904       DrawPosition(FALSE, boards[currentMove]);
16905     }
16906   }
16907 }
16908
16909 static char cseq[12] = "\\   ";
16910
16911 Boolean
16912 set_cont_sequence (char *new_seq)
16913 {
16914     int len;
16915     Boolean ret;
16916
16917     // handle bad attempts to set the sequence
16918         if (!new_seq)
16919                 return 0; // acceptable error - no debug
16920
16921     len = strlen(new_seq);
16922     ret = (len > 0) && (len < sizeof(cseq));
16923     if (ret)
16924       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16925     else if (appData.debugMode)
16926       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16927     return ret;
16928 }
16929
16930 /*
16931     reformat a source message so words don't cross the width boundary.  internal
16932     newlines are not removed.  returns the wrapped size (no null character unless
16933     included in source message).  If dest is NULL, only calculate the size required
16934     for the dest buffer.  lp argument indicats line position upon entry, and it's
16935     passed back upon exit.
16936 */
16937 int
16938 wrap (char *dest, char *src, int count, int width, int *lp)
16939 {
16940     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16941
16942     cseq_len = strlen(cseq);
16943     old_line = line = *lp;
16944     ansi = len = clen = 0;
16945
16946     for (i=0; i < count; i++)
16947     {
16948         if (src[i] == '\033')
16949             ansi = 1;
16950
16951         // if we hit the width, back up
16952         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16953         {
16954             // store i & len in case the word is too long
16955             old_i = i, old_len = len;
16956
16957             // find the end of the last word
16958             while (i && src[i] != ' ' && src[i] != '\n')
16959             {
16960                 i--;
16961                 len--;
16962             }
16963
16964             // word too long?  restore i & len before splitting it
16965             if ((old_i-i+clen) >= width)
16966             {
16967                 i = old_i;
16968                 len = old_len;
16969             }
16970
16971             // extra space?
16972             if (i && src[i-1] == ' ')
16973                 len--;
16974
16975             if (src[i] != ' ' && src[i] != '\n')
16976             {
16977                 i--;
16978                 if (len)
16979                     len--;
16980             }
16981
16982             // now append the newline and continuation sequence
16983             if (dest)
16984                 dest[len] = '\n';
16985             len++;
16986             if (dest)
16987                 strncpy(dest+len, cseq, cseq_len);
16988             len += cseq_len;
16989             line = cseq_len;
16990             clen = cseq_len;
16991             continue;
16992         }
16993
16994         if (dest)
16995             dest[len] = src[i];
16996         len++;
16997         if (!ansi)
16998             line++;
16999         if (src[i] == '\n')
17000             line = 0;
17001         if (src[i] == 'm')
17002             ansi = 0;
17003     }
17004     if (dest && appData.debugMode)
17005     {
17006         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17007             count, width, line, len, *lp);
17008         show_bytes(debugFP, src, count);
17009         fprintf(debugFP, "\ndest: ");
17010         show_bytes(debugFP, dest, len);
17011         fprintf(debugFP, "\n");
17012     }
17013     *lp = dest ? line : old_line;
17014
17015     return len;
17016 }
17017
17018 // [HGM] vari: routines for shelving variations
17019 Boolean modeRestore = FALSE;
17020
17021 void
17022 PushInner (int firstMove, int lastMove)
17023 {
17024         int i, j, nrMoves = lastMove - firstMove;
17025
17026         // push current tail of game on stack
17027         savedResult[storedGames] = gameInfo.result;
17028         savedDetails[storedGames] = gameInfo.resultDetails;
17029         gameInfo.resultDetails = NULL;
17030         savedFirst[storedGames] = firstMove;
17031         savedLast [storedGames] = lastMove;
17032         savedFramePtr[storedGames] = framePtr;
17033         framePtr -= nrMoves; // reserve space for the boards
17034         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17035             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17036             for(j=0; j<MOVE_LEN; j++)
17037                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17038             for(j=0; j<2*MOVE_LEN; j++)
17039                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17040             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17041             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17042             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17043             pvInfoList[firstMove+i-1].depth = 0;
17044             commentList[framePtr+i] = commentList[firstMove+i];
17045             commentList[firstMove+i] = NULL;
17046         }
17047
17048         storedGames++;
17049         forwardMostMove = firstMove; // truncate game so we can start variation
17050 }
17051
17052 void
17053 PushTail (int firstMove, int lastMove)
17054 {
17055         if(appData.icsActive) { // only in local mode
17056                 forwardMostMove = currentMove; // mimic old ICS behavior
17057                 return;
17058         }
17059         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17060
17061         PushInner(firstMove, lastMove);
17062         if(storedGames == 1) GreyRevert(FALSE);
17063         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17064 }
17065
17066 void
17067 PopInner (Boolean annotate)
17068 {
17069         int i, j, nrMoves;
17070         char buf[8000], moveBuf[20];
17071
17072         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17073         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17074         nrMoves = savedLast[storedGames] - currentMove;
17075         if(annotate) {
17076                 int cnt = 10;
17077                 if(!WhiteOnMove(currentMove))
17078                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17079                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17080                 for(i=currentMove; i<forwardMostMove; i++) {
17081                         if(WhiteOnMove(i))
17082                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17083                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17084                         strcat(buf, moveBuf);
17085                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17086                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17087                 }
17088                 strcat(buf, ")");
17089         }
17090         for(i=1; i<=nrMoves; i++) { // copy last variation back
17091             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17092             for(j=0; j<MOVE_LEN; j++)
17093                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17094             for(j=0; j<2*MOVE_LEN; j++)
17095                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17096             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17097             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17098             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17099             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17100             commentList[currentMove+i] = commentList[framePtr+i];
17101             commentList[framePtr+i] = NULL;
17102         }
17103         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17104         framePtr = savedFramePtr[storedGames];
17105         gameInfo.result = savedResult[storedGames];
17106         if(gameInfo.resultDetails != NULL) {
17107             free(gameInfo.resultDetails);
17108       }
17109         gameInfo.resultDetails = savedDetails[storedGames];
17110         forwardMostMove = currentMove + nrMoves;
17111 }
17112
17113 Boolean
17114 PopTail (Boolean annotate)
17115 {
17116         if(appData.icsActive) return FALSE; // only in local mode
17117         if(!storedGames) return FALSE; // sanity
17118         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17119
17120         PopInner(annotate);
17121         if(currentMove < forwardMostMove) ForwardEvent(); else
17122         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17123
17124         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17125         return TRUE;
17126 }
17127
17128 void
17129 CleanupTail ()
17130 {       // remove all shelved variations
17131         int i;
17132         for(i=0; i<storedGames; i++) {
17133             if(savedDetails[i])
17134                 free(savedDetails[i]);
17135             savedDetails[i] = NULL;
17136         }
17137         for(i=framePtr; i<MAX_MOVES; i++) {
17138                 if(commentList[i]) free(commentList[i]);
17139                 commentList[i] = NULL;
17140         }
17141         framePtr = MAX_MOVES-1;
17142         storedGames = 0;
17143 }
17144
17145 void
17146 LoadVariation (int index, char *text)
17147 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17148         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17149         int level = 0, move;
17150
17151         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17152         // first find outermost bracketing variation
17153         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17154             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17155                 if(*p == '{') wait = '}'; else
17156                 if(*p == '[') wait = ']'; else
17157                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17158                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17159             }
17160             if(*p == wait) wait = NULLCHAR; // closing ]} found
17161             p++;
17162         }
17163         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17164         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17165         end[1] = NULLCHAR; // clip off comment beyond variation
17166         ToNrEvent(currentMove-1);
17167         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17168         // kludge: use ParsePV() to append variation to game
17169         move = currentMove;
17170         ParsePV(start, TRUE, TRUE);
17171         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17172         ClearPremoveHighlights();
17173         CommentPopDown();
17174         ToNrEvent(currentMove+1);
17175 }
17176